From 287e3abbabdfc16f37fdcfb8bad937fc435e0cae Mon Sep 17 00:00:00 2001
From: HorridoJoho <HorridoJoho@l2jserver.com>
Date: Sat, 5 Dec 2020 12:25:28 +0100
Subject: [PATCH] TeleporterService

Add TeleporterService. Needs game update for configs. Please enable in
teleporterservice.properties in game.

This also needs custom npc data to be enabled in general.properties.

The default NPC is 60002. So when the teleporter is loaded you first
need to spawn it with the //spawn 60002 command.

Thanks to: @Marduk13
---
 .../model/entity/CustomServiceProduct.java    |   1 +
 .../service/teleporter/TeleporterService.java | 557 ++++++++++++++++++
 .../TeleporterServiceBypassHandler.java       |  63 ++
 .../TeleporterServiceItemHandler.java         |  53 ++
 ...TeleporterServiceVoicedCommandHandler.java |  56 ++
 .../teleporter/model/GlobalConfig.java        |  73 +++
 .../teleporter/model/TeleporterConfig.java    | 108 ++++
 .../entity/AbstractTeleportCategory.java      |  41 ++
 .../model/entity/AbstractTeleporter.java      | 149 +++++
 .../model/entity/GroupTeleport.java           |  59 ++
 .../model/entity/GroupTeleportCategory.java   |  62 ++
 .../model/entity/NpcTeleporter.java           |  68 +++
 .../teleporter/model/entity/SoloTeleport.java |  74 +++
 .../model/entity/SoloTeleportCategory.java    |  62 ++
 .../model/entity/VoicedTeleporter.java        |  49 ++
 src/main/resources/data/scripts.cfg           |   1 +
 .../teleporter/html/npc/cc_category_list.html |   6 +
 .../teleporter/html/npc/cc_teleport_list.html |   7 +
 .../service/teleporter/html/npc/main.html     |   5 +
 .../teleporter/html/npc/p_category_list.html  |   6 +
 .../teleporter/html/npc/p_teleport_list.html  |   7 +
 .../html/npc/solo_category_list.html          |   6 +
 .../html/npc/solo_teleport_list.html          |   7 +
 .../service/teleporter/json/documentation.txt | 128 ++++
 .../data/service/teleporter/json/global.json  | 425 +++++++++++++
 .../service/teleporter/json/npcs/60002.json   |  14 +
 .../data/service/teleporter/json/voiced.json  |  12 +
 .../npcs/custom/custom_teleporterservice.xml  |   9 +
 28 files changed, 2108 insertions(+)
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterService.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceBypassHandler.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceItemHandler.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceVoicedCommandHandler.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/GlobalConfig.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/TeleporterConfig.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/AbstractTeleportCategory.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/AbstractTeleporter.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/GroupTeleport.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/GroupTeleportCategory.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/NpcTeleporter.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/SoloTeleport.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/SoloTeleportCategory.java
 create mode 100644 src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/VoicedTeleporter.java
 create mode 100644 src/main/resources/data/service/teleporter/html/npc/cc_category_list.html
 create mode 100644 src/main/resources/data/service/teleporter/html/npc/cc_teleport_list.html
 create mode 100644 src/main/resources/data/service/teleporter/html/npc/main.html
 create mode 100644 src/main/resources/data/service/teleporter/html/npc/p_category_list.html
 create mode 100644 src/main/resources/data/service/teleporter/html/npc/p_teleport_list.html
 create mode 100644 src/main/resources/data/service/teleporter/html/npc/solo_category_list.html
 create mode 100644 src/main/resources/data/service/teleporter/html/npc/solo_teleport_list.html
 create mode 100644 src/main/resources/data/service/teleporter/json/documentation.txt
 create mode 100644 src/main/resources/data/service/teleporter/json/global.json
 create mode 100644 src/main/resources/data/service/teleporter/json/npcs/60002.json
 create mode 100644 src/main/resources/data/service/teleporter/json/voiced.json
 create mode 100644 src/main/resources/data/stats/npcs/custom/custom_teleporterservice.xml

diff --git a/src/main/java/com/l2jserver/datapack/custom/service/base/model/entity/CustomServiceProduct.java b/src/main/java/com/l2jserver/datapack/custom/service/base/model/entity/CustomServiceProduct.java
index ff2aacf97a..b09f476206 100644
--- a/src/main/java/com/l2jserver/datapack/custom/service/base/model/entity/CustomServiceProduct.java
+++ b/src/main/java/com/l2jserver/datapack/custom/service/base/model/entity/CustomServiceProduct.java
@@ -40,6 +40,7 @@ public abstract class CustomServiceProduct extends Refable {
 		if (!items.isEmpty()) {
 			HTMLTemplatePlaceholder itemsPlaceholder = getPlaceholder().addChild("items", null).getChild("items");
 			for (ItemRequirement item : items) {
+				item.afterDeserialize();
 				itemsPlaceholder.addAliasChild(String.valueOf(itemsPlaceholder.getChildrenSize()), item.getPlaceholder());
 			}
 		}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterService.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterService.java
new file mode 100644
index 0000000000..221eea3ef0
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterService.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.l2jserver.datapack.custom.service.base.CustomServiceScript;
+import com.l2jserver.datapack.custom.service.base.util.CommandProcessor;
+import com.l2jserver.datapack.custom.service.base.util.htmltmpls.HTMLTemplatePlaceholder;
+import com.l2jserver.datapack.custom.service.teleporter.model.TeleporterConfig;
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.AbstractTeleporter;
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.GroupTeleport;
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.GroupTeleportCategory;
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.SoloTeleport;
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.SoloTeleportCategory;
+import com.l2jserver.gameserver.config.Configuration;
+import com.l2jserver.gameserver.handler.BypassHandler;
+import com.l2jserver.gameserver.handler.ItemHandler;
+import com.l2jserver.gameserver.handler.VoicedCommandHandler;
+import com.l2jserver.gameserver.instancemanager.InstanceManager;
+import com.l2jserver.gameserver.model.AbstractPlayerGroup;
+import com.l2jserver.gameserver.model.L2CommandChannel;
+import com.l2jserver.gameserver.model.L2Party;
+import com.l2jserver.gameserver.model.actor.L2Npc;
+import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
+import com.l2jserver.gameserver.model.entity.TvTEvent;
+import com.l2jserver.gameserver.model.instancezone.InstanceWorld;
+import com.l2jserver.gameserver.network.SystemMessageId;
+import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
+import com.l2jserver.gameserver.taskmanager.AttackStanceTaskManager;
+
+/**
+ * Teleporter service.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public final class TeleporterService extends CustomServiceScript {
+	private static final Logger LOG = LoggerFactory.getLogger(TeleporterService.class.getName());
+	
+	public static final String SCRIPT_NAME = "teleporter";
+	public static final Path SCRIPT_PATH = Paths.get(SCRIPT_COLLECTION, SCRIPT_NAME);
+	
+	private static TeleporterConfig config;
+	private static TeleporterService script;
+	
+	public static TeleporterConfig getConfig() {
+		return config;
+	}
+	
+	public static TeleporterService getService() {
+		return script;
+	}
+	
+	public static void main(String[] args) {
+		if (!Configuration.teleporterService().enable()) {
+			LOG.info("Disabled.");
+			return;
+		}
+		
+		LOG.info("Loading...");
+		try {
+			config = new TeleporterConfig();
+			script = new TeleporterService();
+		} catch (Exception e) {
+			LOG.error("Failed to load!", e);
+			return;
+		}
+		
+		getConfig().registerNpcs(script);
+	}
+	
+	public TeleporterService() {
+		super(SCRIPT_NAME);
+		
+		BypassHandler.getInstance().registerHandler(TeleporterServiceBypassHandler.getInstance());
+		
+		if (Configuration.teleporterService().getVoicedEnable()) {
+			VoicedCommandHandler.getInstance().registerHandler(TeleporterServiceVoicedCommandHandler.getInstance());
+			ItemHandler.getInstance().registerHandler(TeleporterServiceItemHandler.getInstance());
+		}
+	}
+	
+	@Override
+	public boolean unload() {
+		BypassHandler.getInstance().removeHandler(TeleporterServiceBypassHandler.getInstance());
+		if (Configuration.teleporterService().getVoicedEnable()) {
+			VoicedCommandHandler.getInstance().removeHandler(TeleporterServiceVoicedCommandHandler.getInstance());
+			ItemHandler.getInstance().removeHandler(TeleporterServiceItemHandler.getInstance());
+		}
+		return super.unload();
+	}
+	
+	// ////////////////////////////////////
+	// UTILITY METHODS
+	// ////////////////////////////////////
+	private void _showAdvancedHtml(L2PcInstance player, AbstractTeleporter teleporter, L2Npc npc, String htmlPath, Map<String, HTMLTemplatePlaceholder> placeholders) {
+		showAdvancedHtml(player, teleporter, npc, htmlPath, placeholders);
+	}
+	
+	private void _showTeleportHtml(L2PcInstance player, AbstractTeleporter teleporter, SoloTeleport teleport, L2Npc npc, String htmlPath) {
+		Map<String, HTMLTemplatePlaceholder> placeholders = new HashMap<>();
+		placeholders.put("teleport", teleport.getPlaceholder());
+		
+		_showAdvancedHtml(player, teleporter, npc, htmlPath, placeholders);
+	}
+	
+	private boolean _takeTeleportItems(SoloTeleport teleport, L2PcInstance initiator) {
+		if (!teleport.getItems().isEmpty()) {
+			HashMap<Integer, Long> items = new HashMap<>();
+			fillItemAmountMap(items, teleport);
+			
+			for (Entry<Integer, Long> item : items.entrySet()) {
+				if (initiator.getInventory().getInventoryItemCount(item.getKey(), 0, true) < item.getValue()) {
+					initiator.sendMessage("Not enough items!");
+					return false;
+				}
+			}
+			
+			for (Entry<Integer, Long> item : items.entrySet()) {
+				initiator.destroyItemByItemId("TeleporterService", item.getKey(), item.getValue(), initiator, true);
+			}
+		}
+		
+		return true;
+	}
+	
+	private InstanceWorld _createInstance(String instanceDefinition) {
+		int instanceId = InstanceManager.getInstance().createDynamicInstance(instanceDefinition);
+		InstanceWorld world = new InstanceWorld();
+		world.setInstanceId(instanceId);
+		// TODO: world.setTemplateId(???);
+		InstanceManager.getInstance().addWorld(world);
+		return world;
+	}
+	
+	// ////////////////////////////////////
+	// HTML COMMANDS
+	// ////////////////////////////////////
+	private boolean _showMainHtml(L2PcInstance player, AbstractTeleporter teleporter, L2Npc npc) {
+		_showAdvancedHtml(player, teleporter, npc, "main.html", new HashMap<String, HTMLTemplatePlaceholder>());
+		return true;
+	}
+	
+	private boolean _showSoloCategoryList(L2PcInstance player, AbstractTeleporter teleporter, L2Npc npc) {
+		_showAdvancedHtml(player, teleporter, npc, "solo_category_list.html", new HashMap<String, HTMLTemplatePlaceholder>());
+		return true;
+	}
+	
+	private boolean _showPartyCategoryList(L2PcInstance player, AbstractTeleporter teleporter, L2Npc npc) {
+		_showAdvancedHtml(player, teleporter, npc, "p_category_list.html", new HashMap<String, HTMLTemplatePlaceholder>());
+		return true;
+	}
+	
+	private boolean _showCommandChannelCategoryList(L2PcInstance player, AbstractTeleporter teleporter, L2Npc npc) {
+		_showAdvancedHtml(player, teleporter, npc, "cc_category_list.html", new HashMap<String, HTMLTemplatePlaceholder>());
+		return true;
+	}
+	
+	private boolean _showSoloListHtml(L2PcInstance player, AbstractTeleporter teleporter, L2Npc npc, String soloListIdent) {
+		Map<String, HTMLTemplatePlaceholder> placeholders = new HashMap<>();
+		
+		if (soloListIdent != null && !soloListIdent.isEmpty()) {
+			SoloTeleportCategory category = teleporter.getSoloTeleportCategories().get(soloListIdent);
+			placeholders.put("category", category.getPlaceholder());
+		}
+		
+		_showAdvancedHtml(player, teleporter, npc, "solo_teleport_list.html", placeholders);
+		return true;
+	}
+	
+	private boolean _showPartyListHtml(L2PcInstance player, AbstractTeleporter teleporter, L2Npc npc, String partyListIdent) {
+		Map<String, HTMLTemplatePlaceholder> placeholders = new HashMap<>();
+		
+		if (partyListIdent != null && !partyListIdent.isEmpty()) {
+			GroupTeleportCategory category = teleporter.getPartyTeleportCategories().get(partyListIdent);
+			placeholders.put("category", category.getPlaceholder());
+		}
+		
+		_showAdvancedHtml(player, teleporter, npc, "p_teleport_list.html", placeholders);
+		return true;
+	}
+	
+	private boolean _showCommandChannelListHtml(L2PcInstance player, AbstractTeleporter teleporter, L2Npc npc, String commandChannelListIdent) {
+		Map<String, HTMLTemplatePlaceholder> placeholders = new HashMap<>();
+		
+		if (commandChannelListIdent != null && !commandChannelListIdent.isEmpty()) {
+			GroupTeleportCategory category = teleporter.getCommandChannelTeleportCategories().get(commandChannelListIdent);
+			placeholders.put("category", category.getPlaceholder());
+		}
+		
+		_showAdvancedHtml(player, teleporter, npc, "cc_teleport_list.html", placeholders);
+		return true;
+	}
+	
+	private boolean _showSoloTeleportHtml(L2PcInstance player, AbstractTeleporter teleporter, L2Npc npc, String teleIdent, String catIdent) {
+		SoloTeleport teleport = null;
+		if (catIdent != null && !catIdent.isEmpty()) {
+			SoloTeleportCategory category = teleporter.getSoloTeleportCategories().get(catIdent);
+			if (category == null) {
+				debug(player, "Invalid category ident: " + catIdent);
+				return false;
+			}
+			
+			teleport = category.getSoloTeleports().get(teleIdent);
+		} else {
+			teleport = teleporter.getSoloTeleports().get(teleIdent);
+		}
+		
+		if (teleport == null) {
+			debug(player, "Invalid teleport ident: " + teleIdent);
+			return false;
+		}
+		
+		_showTeleportHtml(player, teleporter, teleport, npc, "solo_teleport.html");
+		return true;
+	}
+	
+	private boolean _showPartyTeleportHtml(L2PcInstance player, AbstractTeleporter teleporter, L2Npc npc, String teleIdent, String catIdent) {
+		GroupTeleport teleport = null;
+		if (catIdent != null && !catIdent.isEmpty()) {
+			GroupTeleportCategory category = teleporter.getPartyTeleportCategories().get(catIdent);
+			if (category == null) {
+				debug(player, "Invalid category ident: " + catIdent);
+				return false;
+			}
+			
+			teleport = category.getGroupTeleports().get(teleIdent);
+		} else {
+			teleport = teleporter.getPartyTeleports().get(teleIdent);
+		}
+		
+		if (teleport == null) {
+			debug(player, "Invalid teleport ident: " + teleIdent);
+			return false;
+		}
+		
+		_showTeleportHtml(player, teleporter, teleport, npc, "p_teleport.html");
+		return true;
+	}
+	
+	private boolean _showCommandChannelTeleportHtml(L2PcInstance player, AbstractTeleporter teleporter, L2Npc npc, String teleIdent, String catIdent) {
+		GroupTeleport teleport = null;
+		if (catIdent != null && !catIdent.isEmpty()) {
+			GroupTeleportCategory category = teleporter.getCommandChannelTeleportCategories().get(catIdent);
+			if (category == null) {
+				debug(player, "Invalid category ident: " + catIdent);
+				return false;
+			}
+			
+			teleport = category.getGroupTeleports().get(teleIdent);
+		} else {
+			teleport = teleporter.getCommandChannelTeleports().get(teleIdent);
+		}
+		
+		if (teleport == null) {
+			debug(player, "Invalid teleport ident: " + teleIdent);
+			return false;
+		}
+		
+		_showTeleportHtml(player, teleporter, teleport, npc, "cc_teleport.html");
+		return true;
+	}
+	
+	// ////////////////////////////////////
+	// TELEPORT COMMANDS
+	// ////////////////////////////////////
+	private void _makeTeleport(SoloTeleport teleport, L2PcInstance initiator) {
+		if (!_takeTeleportItems(teleport, initiator)) {
+			return;
+		}
+		
+		InstanceWorld world = null;
+		if (!teleport.getInstance().isEmpty()) {
+			world = _createInstance(teleport.getInstance());
+			world.addAllowed(initiator.getObjectId());
+		}
+		
+		initiator.teleToLocation(teleport.getX(), teleport.getY(), teleport.getZ(), teleport.getHeading(), world != null ? world.getInstanceId() : initiator.getInstanceId(), teleport.getRandomOffset());
+	}
+	
+	private void _makeGroupTeleport(GroupTeleport teleport, L2PcInstance initiator, AbstractPlayerGroup group) {
+		final L2PcInstance leader = group.getLeader();
+		if (leader != initiator) {
+			initiator.sendMessage("You are not the leader!");
+			return;
+		}
+		
+		final int memberCount = group.getMemberCount();
+		if (group.getMemberCount() < teleport.getMinMembers()) {
+			group.broadcastString("Not enough members!");
+			return;
+		}
+		
+		final int leaderInstanceId = leader.getInstanceId();
+		final ArrayList<L2PcInstance> membersInRange = new ArrayList<>(memberCount);
+		membersInRange.add(leader);
+		
+		for (L2PcInstance member : group.getMembers()) {
+			if ((member != leader) && ((member.getInstanceId() != leaderInstanceId) || (member.calculateDistance(leader, false, false) > teleport.getMaxDistance()))) {
+				continue;
+			}
+			
+			membersInRange.add(member);
+		}
+		
+		if (membersInRange.size() < memberCount) {
+			if (!teleport.getAllowIncomplete()) {
+				group.broadcastString("Your group is not together!");
+				return;
+			} else if (membersInRange.size() < teleport.getMinMembers()) {
+				group.broadcastString("Not enough members around!");
+				return;
+			}
+		}
+		
+		if (membersInRange.size() > teleport.getMaxMembers()) {
+			group.broadcastString("Too many members!");
+			return;
+		}
+		
+		if (!_takeTeleportItems(teleport, initiator)) {
+			return;
+		}
+		
+		InstanceWorld world = null;
+		int instanceId = initiator.getInstanceId();
+		if (!teleport.getInstance().isEmpty()) {
+			world = _createInstance(teleport.getInstance());
+			instanceId = world.getInstanceId();
+		}
+		
+		for (L2PcInstance member : membersInRange) {
+			if (world != null) {
+				world.addAllowed(member.getObjectId());
+			}
+			member.teleToLocation(teleport.getX(), teleport.getY(), teleport.getZ(), teleport.getHeading(), instanceId, teleport.getRandomOffset());
+		}
+	}
+	
+	private void _teleportSolo(L2PcInstance player, AbstractTeleporter teleporter, String teleId, String catId) {
+		SoloTeleport teleport = null;
+		if (catId != null && !catId.isEmpty()) {
+			SoloTeleportCategory category = teleporter.getSoloTeleportCategories().get(catId);
+			if (category == null) {
+				debug(player, "Invalid category ident: " + catId);
+				return;
+			}
+			
+			teleport = category.getSoloTeleports().get(teleId);
+		} else {
+			teleport = teleporter.getSoloTeleports().get(teleId);
+		}
+		
+		if (teleport == null) {
+			debug(player, "Invalid solo teleport id: " + teleId);
+			return;
+		}
+		
+		_makeTeleport(teleport, player);
+	}
+	
+	private void _teleportParty(L2PcInstance player, AbstractTeleporter teleporter, String teleId, String catId) {
+		GroupTeleport teleport = null;
+		if (catId != null && !catId.isEmpty()) {
+			GroupTeleportCategory category = teleporter.getPartyTeleportCategories().get(catId);
+			if (category == null) {
+				debug(player, "Invalid category ident: " + catId);
+				return;
+			}
+			
+			teleport = category.getGroupTeleports().get(teleId);
+		} else {
+			teleport = teleporter.getPartyTeleports().get(teleId);
+		}
+		
+		if (teleport == null) {
+			debug(player, "Invalid party teleport id: " + teleId);
+			return;
+		}
+		
+		final L2Party party = player.getParty();
+		if (party == null) {
+			player.sendMessage("You are not in a party!");
+			return;
+		}
+		
+		_makeGroupTeleport(teleport, player, party);
+	}
+	
+	private void _teleportCommandChannel(L2PcInstance player, AbstractTeleporter teleporter, String teleId, String catId) {
+		GroupTeleport teleport = null;
+		if (catId != null && !catId.isEmpty()) {
+			GroupTeleportCategory category = teleporter.getCommandChannelTeleportCategories().get(catId);
+			if (category == null) {
+				debug(player, "Invalid category ident: " + catId);
+				return;
+			}
+			
+			teleport = category.getGroupTeleports().get(teleId);
+		} else {
+			teleport = teleporter.getCommandChannelTeleports().get(teleId);
+		}
+		
+		if (teleport == null) {
+			debug(player, "Invalid command channel teleport id: " + teleId);
+			return;
+		}
+		
+		final L2Party party = player.getParty();
+		if (party == null) {
+			player.sendMessage("You are not in a party!");
+			return;
+		}
+		
+		final L2CommandChannel commandChannel = party.getCommandChannel();
+		if (commandChannel == null) {
+			player.sendMessage("Your party is not in a command channel!");
+			return;
+		}
+		
+		_makeGroupTeleport(teleport, player, commandChannel);
+	}
+	
+	private void _executeTeleportCommand(L2PcInstance player, AbstractTeleporter teleporter, CommandProcessor command) {
+		if (command.matchAndRemove("solo ", "s ")) {
+			String[] args = command.splitRemaining(" ");
+			_teleportSolo(player, teleporter, args[0], args.length > 1 ? args[1] : null);
+		} else if (command.matchAndRemove("party ", "p ")) {
+			String[] args = command.splitRemaining(" ");
+			_teleportParty(player, teleporter, args[0], args.length > 1 ? args[1] : null);
+		} else if (command.matchAndRemove("command_channel ", "cc ")) {
+			String[] args = command.splitRemaining(" ");
+			_teleportCommandChannel(player, teleporter, args[0], args.length > 1 ? args[1] : null);
+		}
+	}
+	
+	//
+	// //////////////////////////////
+	
+	@Override
+	protected boolean executeHtmlCommand(L2PcInstance player, L2Npc npc, CommandProcessor command) {
+		AbstractTeleporter teleporter = getConfig().determineTeleporter(npc, player);
+		if (teleporter == null) {
+			player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.S1_NOT_FOUND).addString("Teleporter"));
+			return false;
+		}
+		
+		if (command.matchAndRemove("main", "m")) {
+			return _showMainHtml(player, teleporter, npc);
+		} else if (command.matchAndRemove("solo_category_list", "scl")) {
+			return _showSoloCategoryList(player, teleporter, npc);
+		} else if (command.matchAndRemove("party_category_list", "pcl")) {
+			return _showPartyCategoryList(player, teleporter, npc);
+		} else if (command.matchAndRemove("command_channel_category_list", "cccl")) {
+			return _showCommandChannelCategoryList(player, teleporter, npc);
+		} else if (command.matchAndRemove("solo_list ", "sl ")) {
+			return _showSoloListHtml(player, teleporter, npc, command.getRemaining());
+		} else if (command.matchAndRemove("party_list ", "pl ")) {
+			return _showPartyListHtml(player, teleporter, npc, command.getRemaining());
+		} else if (command.matchAndRemove("command_channel_list ", "ccl ")) {
+			return _showCommandChannelListHtml(player, teleporter, npc, command.getRemaining());
+		} else if (command.matchAndRemove("solo_list", "sl")) {
+			return _showSoloListHtml(player, teleporter, npc, null);
+		} else if (command.matchAndRemove("party_list", "pl")) {
+			return _showPartyListHtml(player, teleporter, npc, null);
+		} else if (command.matchAndRemove("command_channel_list", "ccl")) {
+			return _showCommandChannelListHtml(player, teleporter, npc, null);
+		} else if (command.matchAndRemove("solo_teleport ", "st ")) {
+			String[] args = command.splitRemaining(" ");
+			return _showSoloTeleportHtml(player, teleporter, npc, args[0], args.length > 1 ? args[1] : null);
+		} else if (command.matchAndRemove("party_teleport ", "pt ")) {
+			String[] args = command.splitRemaining(" ");
+			return _showPartyTeleportHtml(player, teleporter, npc, args[0], args.length > 1 ? args[1] : null);
+		} else if (command.matchAndRemove("command_channel_teleport ", "cct ")) {
+			String[] args = command.splitRemaining(" ");
+			return _showCommandChannelTeleportHtml(player, teleporter, npc, args[0], args.length > 1 ? args[1] : null);
+		}
+		
+		return false;
+	}
+	
+	@Override
+	protected boolean executeActionCommand(L2PcInstance player, L2Npc npc, CommandProcessor command) {
+		SystemMessage abortSysMsg = null;
+		AbstractTeleporter teleporter = null;
+		
+		if (player.isDead() || player.isAlikeDead()) {
+			abortSysMsg = SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED);
+			abortSysMsg.addString("Teleporter");
+		} else if (isInsideAnyZoneOf(player, Configuration.teleporterService().getForbidInZones())) {
+			abortSysMsg = SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED);
+			abortSysMsg.addString("Teleporter");
+		} else if (Configuration.teleporterService().getForbidInEvents() && ((player.getEventStatus() != null) || (player.getBlockCheckerArena() != -1) || player.isOnEvent() || player.isInOlympiadMode() || TvTEvent.isPlayerParticipant(player.getObjectId()))) {
+			abortSysMsg = SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED);
+			abortSysMsg.addString("Teleporter");
+		} else if (Configuration.teleporterService().getForbidInDuell() && player.isInDuel()) {
+			abortSysMsg = SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED);
+			abortSysMsg.addString("Teleporter");
+		} else if (Configuration.teleporterService().getForbidInFight() && AttackStanceTaskManager.getInstance().hasAttackStanceTask(player)) {
+			abortSysMsg = SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED);
+			abortSysMsg.addString("Teleporter");
+		} else if (Configuration.teleporterService().getForbidInPvp() && (player.getPvpFlag() == 1)) {
+			abortSysMsg = SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED);
+			abortSysMsg.addString("Teleporter");
+		} else if (Configuration.teleporterService().getForbidForChaoticPlayers() && player.getKarma() > 0) {
+			abortSysMsg = SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED);
+			abortSysMsg.addString("Teleporter");
+		} else {
+			teleporter = getConfig().determineTeleporter(npc, player);
+			if (teleporter == null) {
+				abortSysMsg = SystemMessage.getSystemMessage(SystemMessageId.S1_NOT_FOUND);
+				abortSysMsg.addString("Teleporter");
+			}
+		}
+		
+		if (abortSysMsg != null) {
+			player.sendPacket(abortSysMsg);
+			return false;
+		}
+		
+		if (command.matchAndRemove("teleport ", "t ")) {
+			_executeTeleportCommand(player, teleporter, command);
+			return false;
+		}
+		
+		return true;
+	}
+	
+	@Override
+	protected boolean isDebugEnabled() {
+		return Configuration.teleporterService().getDebug();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceBypassHandler.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceBypassHandler.java
new file mode 100644
index 0000000000..373ed34640
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceBypassHandler.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter;
+
+import com.l2jserver.gameserver.handler.IBypassHandler;
+import com.l2jserver.gameserver.model.actor.L2Character;
+import com.l2jserver.gameserver.model.actor.L2Npc;
+import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
+
+/**
+ * Teleporter service bypass handler.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public class TeleporterServiceBypassHandler implements IBypassHandler {
+	
+	public static final String BYPASS = "TeleporterService";
+	private static final String[] BYPASS_LIST = new String[] {
+		BYPASS
+	};
+	
+	private TeleporterServiceBypassHandler() {
+	}
+	
+	@Override
+	public boolean useBypass(String command, L2PcInstance activeChar, L2Character target) {
+		if ((target == null) || !target.isNpc()) {
+			return false;
+		}
+		
+		TeleporterService.getService().executeCommand(activeChar, (L2Npc) target, command.substring(BYPASS.length()).trim());
+		return true;
+	}
+	
+	@Override
+	public String[] getBypassList() {
+		return BYPASS_LIST;
+	}
+	
+	static TeleporterServiceBypassHandler getInstance() {
+		return SingletonHolder.INSTANCE;
+	}
+	
+	private static final class SingletonHolder {
+		protected static final TeleporterServiceBypassHandler INSTANCE = new TeleporterServiceBypassHandler();
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceItemHandler.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceItemHandler.java
new file mode 100644
index 0000000000..d3e297ac9f
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceItemHandler.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter;
+
+import com.l2jserver.gameserver.handler.IItemHandler;
+import com.l2jserver.gameserver.model.actor.L2Playable;
+import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
+import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
+
+/**
+ * Teleporter service item handler.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public final class TeleporterServiceItemHandler implements IItemHandler {
+	
+	private TeleporterServiceItemHandler() {
+	}
+	
+	@Override
+	public boolean useItem(L2Playable playable, L2ItemInstance item, boolean forceUse) {
+		if (!playable.isPlayer()) {
+			return false;
+		}
+		
+		TeleporterService.getService().executeCommand((L2PcInstance) playable, null, null);
+		return true;
+	}
+	
+	static TeleporterServiceItemHandler getInstance() {
+		return SingletonHolder.INSTANCE;
+	}
+	
+	private static final class SingletonHolder {
+		protected static final TeleporterServiceItemHandler INSTANCE = new TeleporterServiceItemHandler();
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceVoicedCommandHandler.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceVoicedCommandHandler.java
new file mode 100644
index 0000000000..ddfd18c786
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/TeleporterServiceVoicedCommandHandler.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter;
+
+import com.l2jserver.gameserver.config.Configuration;
+import com.l2jserver.gameserver.handler.IVoicedCommandHandler;
+import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
+
+/**
+ * Teleporter service voiced command handler.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public final class TeleporterServiceVoicedCommandHandler implements IVoicedCommandHandler {
+	private static final String[] COMMANDS = new String[] {
+		Configuration.teleporterService().getVoicedCommand()
+	};
+	
+	private TeleporterServiceVoicedCommandHandler() {
+	}
+	
+	@Override
+	public boolean useVoicedCommand(String command, L2PcInstance activeChar, String params) {
+		TeleporterService.getService().executeCommand(activeChar, null, params);
+		return true;
+	}
+	
+	@Override
+	public String[] getVoicedCommandList() {
+		return COMMANDS;
+	}
+	
+	static TeleporterServiceVoicedCommandHandler getInstance() {
+		return SingletonHolder.INSTANCE;
+	}
+	
+	private static final class SingletonHolder {
+		protected static final TeleporterServiceVoicedCommandHandler INSTANCE = new TeleporterServiceVoicedCommandHandler();
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/GlobalConfig.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/GlobalConfig.java
new file mode 100644
index 0000000000..9f404dcbf5
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/GlobalConfig.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter.model;
+
+import java.util.Map;
+
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.GroupTeleport;
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.GroupTeleportCategory;
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.SoloTeleport;
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.SoloTeleportCategory;
+
+/**
+ * Global configuration.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public class GlobalConfig {
+	private Map<String, SoloTeleport> soloTeleports;
+	private Map<String, GroupTeleport> groupTeleports;
+	
+	private Map<String, SoloTeleportCategory> soloTeleportCategories;
+	private Map<String, GroupTeleportCategory> groupTeleportCategories;
+	
+	public void afterDeserialize(TeleporterConfig config) {
+		for (SoloTeleport teleport : soloTeleports.values()) {
+			teleport.afterDeserialize(config);
+		}
+		
+		for (GroupTeleport teleport : groupTeleports.values()) {
+			teleport.afterDeserialize(config);
+		}
+		
+		for (SoloTeleportCategory soloCat : soloTeleportCategories.values()) {
+			soloCat.afterDeserialize(config);
+		}
+		
+		for (GroupTeleportCategory groupCat : groupTeleportCategories.values()) {
+			groupCat.afterDeserialize(config);
+		}
+	}
+	
+	public Map<String, SoloTeleport> getSoloTeleports() {
+		return soloTeleports;
+	}
+	
+	public Map<String, GroupTeleport> getGroupTeleports() {
+		return groupTeleports;
+	}
+	
+	public Map<String, SoloTeleportCategory> getSoloTeleportCategories() {
+		return soloTeleportCategories;
+	}
+	
+	public Map<String, GroupTeleportCategory> getGroupTeleportCategories() {
+		return groupTeleportCategories;
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/TeleporterConfig.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/TeleporterConfig.java
new file mode 100644
index 0000000000..0973bf986b
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/TeleporterConfig.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter.model;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonSyntaxException;
+import com.l2jserver.datapack.custom.service.teleporter.TeleporterService;
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.AbstractTeleporter;
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.NpcTeleporter;
+import com.l2jserver.datapack.custom.service.teleporter.model.entity.VoicedTeleporter;
+import com.l2jserver.gameserver.config.Configuration;
+import com.l2jserver.gameserver.model.actor.L2Npc;
+import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
+
+/**
+ * Teleporter config.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public class TeleporterConfig {
+	private final GlobalConfig global;
+	private final VoicedTeleporter voiced;
+	private final Map<Integer, NpcTeleporter> npcs;
+	
+	public TeleporterConfig() throws JsonSyntaxException, JsonIOException, IOException {
+		Gson gson = new Gson();
+		
+		Path jsonPath = Paths.get(Configuration.server().getDatapackRoot().getAbsolutePath(), "data", TeleporterService.SCRIPT_PATH.toString(), "json");
+		
+		global = gson.fromJson(Files.newBufferedReader(jsonPath.resolve("global.json")), GlobalConfig.class);
+		voiced = gson.fromJson(Files.newBufferedReader(jsonPath.resolve("voiced.json")), VoicedTeleporter.class);
+		npcs = new HashMap<>();
+		
+		Path npcsDir = Paths.get(jsonPath.toString(), "npcs");
+		try (var dirStream = Files.newDirectoryStream(npcsDir, "*.json")) {
+			for (Path entry : dirStream) {
+				if (!Files.isRegularFile(entry)) {
+					continue;
+				}
+				
+				NpcTeleporter npc = gson.fromJson(Files.newBufferedReader(entry), NpcTeleporter.class);
+				npcs.put(npc.getId(), npc);
+			}
+		}
+		
+		global.afterDeserialize(this);
+		voiced.afterDeserialize(this);
+		for (NpcTeleporter npc : npcs.values()) {
+			npc.afterDeserialize(this);
+		}
+	}
+	
+	public AbstractTeleporter determineTeleporter(L2Npc npc, L2PcInstance player) {
+		if (npc == null) {
+			if (!Configuration.teleporterService().getVoicedEnable() || ((Configuration.teleporterService().getVoicedRequiredItem() > 0) && (player.getInventory().getAllItemsByItemId(Configuration.teleporterService().getVoicedRequiredItem()) == null))) {
+				return null;
+			}
+			return voiced;
+		}
+		return npcs.get(npc.getId());
+	}
+	
+	public void registerNpcs(TeleporterService scriptInstance) {
+		for (NpcTeleporter npc : npcs.values()) {
+			if (npc.getDirectFirstTalk()) {
+				scriptInstance.addFirstTalkId(npc.getId());
+			}
+			scriptInstance.addStartNpc(npc.getId());
+			scriptInstance.addTalkId(npc.getId());
+		}
+	}
+	
+	public GlobalConfig getGlobal() {
+		return global;
+	}
+	
+	public VoicedTeleporter getVoiced() {
+		return voiced;
+	}
+	
+	public Map<Integer, NpcTeleporter> getNpcs() {
+		return npcs;
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/AbstractTeleportCategory.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/AbstractTeleportCategory.java
new file mode 100644
index 0000000000..a34b4e2c4b
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/AbstractTeleportCategory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter.model.entity;
+
+import com.l2jserver.datapack.custom.service.base.model.entity.Refable;
+import com.l2jserver.datapack.custom.service.teleporter.model.TeleporterConfig;
+
+/**
+ * Base class for teleporter categories.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public abstract class AbstractTeleportCategory extends Refable {
+	private String name;
+	
+	public void afterDeserialize(TeleporterConfig config) {
+		super.afterDeserialize();
+		
+		getPlaceholder().addChild("name", name);
+	}
+	
+	public final String getName() {
+		return name;
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/AbstractTeleporter.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/AbstractTeleporter.java
new file mode 100644
index 0000000000..b7360c0c60
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/AbstractTeleporter.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter.model.entity;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.l2jserver.datapack.custom.service.base.model.entity.CustomServiceServer;
+import com.l2jserver.datapack.custom.service.base.util.htmltmpls.HTMLTemplatePlaceholder;
+import com.l2jserver.datapack.custom.service.teleporter.model.TeleporterConfig;
+
+/**
+ * Base class for teleporter service types.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public abstract class AbstractTeleporter extends CustomServiceServer {
+	private List<String> soloTeleports;
+	private List<String> partyTeleports;
+	private List<String> commandChannelTeleports;
+	
+	private List<String> soloTeleportCategories;
+	private List<String> partyTeleportCategories;
+	private List<String> commandChannelTeleportCategories;
+	
+	public transient Map<String, SoloTeleport> soloTeleportsMap;
+	public transient Map<String, GroupTeleport> partyTeleportsMap;
+	public transient Map<String, GroupTeleport> commandChannelTeleportsMap;
+	
+	private transient Map<String, SoloTeleportCategory> soloTeleportCategoriesMap;
+	private transient Map<String, GroupTeleportCategory> partyTeleportCategoriesMap;
+	private transient Map<String, GroupTeleportCategory> commandChannelTeleportCategoriesMap;
+	
+	public AbstractTeleporter(String bypassPrefix) {
+		super(bypassPrefix, "teleporter");
+		
+		soloTeleportsMap = new LinkedHashMap<>();
+		partyTeleportsMap = new LinkedHashMap<>();
+		commandChannelTeleportsMap = new LinkedHashMap<>();
+		
+		soloTeleportCategoriesMap = new LinkedHashMap<>();
+		partyTeleportCategoriesMap = new LinkedHashMap<>();
+		commandChannelTeleportCategoriesMap = new LinkedHashMap<>();
+	}
+	
+	public void afterDeserialize(TeleporterConfig config) {
+		super.afterDeserialize();
+		
+		for (String id : soloTeleports) {
+			soloTeleportsMap.put(id, config.getGlobal().getSoloTeleports().get(id));
+		}
+		for (String id : partyTeleports) {
+			partyTeleportsMap.put(id, config.getGlobal().getGroupTeleports().get(id));
+		}
+		for (String id : commandChannelTeleports) {
+			commandChannelTeleportsMap.put(id, config.getGlobal().getGroupTeleports().get(id));
+		}
+		
+		for (String id : soloTeleportCategories) {
+			soloTeleportCategoriesMap.put(id, config.getGlobal().getSoloTeleportCategories().get(id));
+		}
+		for (String id : partyTeleportCategories) {
+			partyTeleportCategoriesMap.put(id, config.getGlobal().getGroupTeleportCategories().get(id));
+		}
+		for (String id : commandChannelTeleportCategories) {
+			commandChannelTeleportCategoriesMap.put(id, config.getGlobal().getGroupTeleportCategories().get(id));
+		}
+		
+		if (!soloTeleports.isEmpty()) {
+			HTMLTemplatePlaceholder telePlaceholder = getPlaceholder().addChild("solo_teleports", null).getChild("solo_teleports");
+			for (Entry<String, SoloTeleport> soloTeleport : soloTeleportsMap.entrySet()) {
+				telePlaceholder.addAliasChild(String.valueOf(telePlaceholder.getChildrenSize()), soloTeleport.getValue().getPlaceholder());
+			}
+		}
+		if (!partyTeleports.isEmpty()) {
+			HTMLTemplatePlaceholder telePlaceholder = getPlaceholder().addChild("party_teleports", null).getChild("party_teleports");
+			for (Entry<String, GroupTeleport> partyTeleport : partyTeleportsMap.entrySet()) {
+				telePlaceholder.addAliasChild(String.valueOf(telePlaceholder.getChildrenSize()), partyTeleport.getValue().getPlaceholder());
+			}
+		}
+		if (!commandChannelTeleports.isEmpty()) {
+			HTMLTemplatePlaceholder telePlaceholder = getPlaceholder().addChild("command_channel_teleports", null).getChild("command_channel_teleports");
+			for (Entry<String, GroupTeleport> commandChannelTeleport : commandChannelTeleportsMap.entrySet()) {
+				telePlaceholder.addAliasChild(String.valueOf(telePlaceholder.getChildrenSize()), commandChannelTeleport.getValue().getPlaceholder());
+			}
+		}
+		
+		if (!soloTeleportCategories.isEmpty()) {
+			HTMLTemplatePlaceholder catsPlaceholder = getPlaceholder().addChild("solo_categories", null).getChild("solo_categories");
+			for (Entry<String, SoloTeleportCategory> soloTeleportCategories : soloTeleportCategoriesMap.entrySet()) {
+				catsPlaceholder.addAliasChild(String.valueOf(catsPlaceholder.getChildrenSize()), soloTeleportCategories.getValue().getPlaceholder());
+			}
+		}
+		if (!partyTeleportCategories.isEmpty()) {
+			HTMLTemplatePlaceholder catsPlaceholder = getPlaceholder().addChild("party_categories", null).getChild("party_categories");
+			for (Entry<String, GroupTeleportCategory> partyTeleportCategories : partyTeleportCategoriesMap.entrySet()) {
+				catsPlaceholder.addAliasChild(String.valueOf(catsPlaceholder.getChildrenSize()), partyTeleportCategories.getValue().getPlaceholder());
+			}
+		}
+		if (!commandChannelTeleportCategories.isEmpty()) {
+			HTMLTemplatePlaceholder catsPlaceholder = getPlaceholder().addChild("command_channel_categories", null).getChild("command_channel_categories");
+			for (Entry<String, GroupTeleportCategory> commandChannelTeleportCategories : commandChannelTeleportCategoriesMap.entrySet()) {
+				catsPlaceholder.addAliasChild(String.valueOf(catsPlaceholder.getChildrenSize()), commandChannelTeleportCategories.getValue().getPlaceholder());
+			}
+		}
+	}
+	
+	public final Map<String, SoloTeleport> getSoloTeleports() {
+		return soloTeleportsMap;
+	}
+	
+	public final Map<String, GroupTeleport> getPartyTeleports() {
+		return partyTeleportsMap;
+	}
+	
+	public final Map<String, GroupTeleport> getCommandChannelTeleports() {
+		return commandChannelTeleportsMap;
+	}
+	
+	public final Map<String, SoloTeleportCategory> getSoloTeleportCategories() {
+		return soloTeleportCategoriesMap;
+	}
+	
+	public final Map<String, GroupTeleportCategory> getPartyTeleportCategories() {
+		return partyTeleportCategoriesMap;
+	}
+	
+	public final Map<String, GroupTeleportCategory> getCommandChannelTeleportCategories() {
+		return commandChannelTeleportCategoriesMap;
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/GroupTeleport.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/GroupTeleport.java
new file mode 100644
index 0000000000..c1e76a3068
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/GroupTeleport.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter.model.entity;
+
+import com.l2jserver.datapack.custom.service.teleporter.model.TeleporterConfig;
+
+/**
+ * Group teleport.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public class GroupTeleport extends SoloTeleport {
+	private int minMembers;
+	private int maxMembers;
+	private int maxDistance;
+	private boolean allowIncomplete;
+	
+	public GroupTeleport() {
+	}
+	
+	@Override
+	public void afterDeserialize(TeleporterConfig config) {
+		super.afterDeserialize(config);
+		
+		getPlaceholder().addChild("min_members", String.valueOf(minMembers)).addChild("max_members", String.valueOf(maxMembers)).addChild("max_distance", String.valueOf(maxDistance));
+	}
+	
+	public int getMinMembers() {
+		return minMembers;
+	}
+	
+	public int getMaxMembers() {
+		return maxMembers;
+	}
+	
+	public int getMaxDistance() {
+		return maxDistance;
+	}
+	
+	public boolean getAllowIncomplete() {
+		return allowIncomplete;
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/GroupTeleportCategory.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/GroupTeleportCategory.java
new file mode 100644
index 0000000000..27cbb5b65a
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/GroupTeleportCategory.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter.model.entity;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.l2jserver.datapack.custom.service.base.util.htmltmpls.HTMLTemplatePlaceholder;
+import com.l2jserver.datapack.custom.service.teleporter.model.TeleporterConfig;
+
+/**
+ * Group teleport category.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public class GroupTeleportCategory extends AbstractTeleportCategory {
+	private List<String> groupTeleports;
+	
+	private transient Map<String, GroupTeleport> groupTeleportsMap;
+	
+	public GroupTeleportCategory() {
+		groupTeleportsMap = new LinkedHashMap<>();
+	}
+	
+	@Override
+	public void afterDeserialize(TeleporterConfig config) {
+		super.afterDeserialize(config);
+		
+		for (String id : groupTeleports) {
+			groupTeleportsMap.put(id, config.getGlobal().getGroupTeleports().get(id));
+		}
+		
+		if (!groupTeleports.isEmpty()) {
+			HTMLTemplatePlaceholder telePlaceholder = getPlaceholder().addChild("teleports", null).getChild("teleports");
+			for (Entry<String, GroupTeleport> groupTeleport : groupTeleportsMap.entrySet()) {
+				telePlaceholder.addAliasChild(String.valueOf(telePlaceholder.getChildrenSize()), groupTeleport.getValue().getPlaceholder());
+			}
+		}
+	}
+	
+	public Map<String, GroupTeleport> getGroupTeleports() {
+		return groupTeleportsMap;
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/NpcTeleporter.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/NpcTeleporter.java
new file mode 100644
index 0000000000..b09a58d069
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/NpcTeleporter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter.model.entity;
+
+import com.l2jserver.datapack.custom.service.base.model.entity.IRefable;
+import com.l2jserver.datapack.custom.service.teleporter.TeleporterServiceBypassHandler;
+import com.l2jserver.datapack.custom.service.teleporter.model.TeleporterConfig;
+import com.l2jserver.gameserver.data.xml.impl.NpcData;
+import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate;
+
+/**
+ * Npc teleporter.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public final class NpcTeleporter extends AbstractTeleporter implements IRefable<Integer> {
+	private int npcId;
+	private boolean directFirstTalk;
+	
+	public NpcTeleporter() {
+		super(TeleporterServiceBypassHandler.BYPASS);
+	}
+	
+	@Override
+	public void afterDeserialize(TeleporterConfig config) {
+		super.afterDeserialize(config);
+		
+		getPlaceholder().addChild("ident", String.valueOf(npcId));
+	}
+	
+	public L2NpcTemplate getNpc() {
+		return NpcData.getInstance().getTemplate(npcId);
+	}
+	
+	public int getNpcId() {
+		return npcId;
+	}
+	
+	public boolean getDirectFirstTalk() {
+		return directFirstTalk;
+	}
+	
+	@Override
+	public final String getName() {
+		return getNpc().getName();
+	}
+	
+	@Override
+	public final Integer getId() {
+		return npcId;
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/SoloTeleport.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/SoloTeleport.java
new file mode 100644
index 0000000000..2dd0e2b4d9
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/SoloTeleport.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter.model.entity;
+
+import com.l2jserver.datapack.custom.service.base.model.entity.CustomServiceProduct;
+import com.l2jserver.datapack.custom.service.teleporter.model.TeleporterConfig;
+
+/**
+ * Solo teleport.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public class SoloTeleport extends CustomServiceProduct {
+	private String name;
+	private int x;
+	private int y;
+	private int z;
+	private int heading;
+	private int randomOffset;
+	private String instance;
+	
+	public SoloTeleport() {
+	}
+	
+	public void afterDeserialize(TeleporterConfig config) {
+		super.afterDeserialize();
+		
+		getPlaceholder().addChild("name", name);
+	}
+	
+	public String getName() {
+		return name;
+	}
+	
+	public int getRandomOffset() {
+		return randomOffset;
+	}
+	
+	public int getHeading() {
+		return heading;
+	}
+	
+	public String getInstance() {
+		return instance;
+	}
+	
+	public int getZ() {
+		return z;
+	}
+	
+	public int getY() {
+		return y;
+	}
+	
+	public int getX() {
+		return x;
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/SoloTeleportCategory.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/SoloTeleportCategory.java
new file mode 100644
index 0000000000..a98212b23c
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/SoloTeleportCategory.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter.model.entity;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.l2jserver.datapack.custom.service.base.util.htmltmpls.HTMLTemplatePlaceholder;
+import com.l2jserver.datapack.custom.service.teleporter.model.TeleporterConfig;
+
+/**
+ * Solo teleport category.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public class SoloTeleportCategory extends AbstractTeleportCategory {
+	private List<String> soloTeleports;
+	
+	private transient Map<String, SoloTeleport> soloTeleportsMap;
+	
+	public SoloTeleportCategory() {
+		soloTeleportsMap = new LinkedHashMap<>();
+	}
+	
+	@Override
+	public void afterDeserialize(TeleporterConfig config) {
+		super.afterDeserialize(config);
+		
+		for (String id : soloTeleports) {
+			soloTeleportsMap.put(id, config.getGlobal().getSoloTeleports().get(id));
+		}
+		
+		if (!soloTeleports.isEmpty()) {
+			HTMLTemplatePlaceholder telePlaceholder = getPlaceholder().addChild("teleports", null).getChild("teleports");
+			for (Entry<String, SoloTeleport> soloTeleport : soloTeleportsMap.entrySet()) {
+				telePlaceholder.addAliasChild(String.valueOf(telePlaceholder.getChildrenSize()), soloTeleport.getValue().getPlaceholder());
+			}
+		}
+	}
+	
+	public Map<String, SoloTeleport> getSoloTeleports() {
+		return soloTeleportsMap;
+	}
+}
diff --git a/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/VoicedTeleporter.java b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/VoicedTeleporter.java
new file mode 100644
index 0000000000..81e4b1bd21
--- /dev/null
+++ b/src/main/java/com/l2jserver/datapack/custom/service/teleporter/model/entity/VoicedTeleporter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2004-2021 L2J DataPack
+ * 
+ * This file is part of L2J DataPack.
+ * 
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.datapack.custom.service.teleporter.model.entity;
+
+import com.l2jserver.datapack.custom.service.teleporter.model.TeleporterConfig;
+import com.l2jserver.gameserver.config.Configuration;
+import com.l2jserver.gameserver.datatables.ItemTable;
+import com.l2jserver.gameserver.model.items.L2Item;
+
+/**
+ * Voiced teleporter.
+ * @author HorridoJoho
+ * @version 2.6.2.0
+ */
+public final class VoicedTeleporter extends AbstractTeleporter {
+	public VoicedTeleporter() {
+		super("voice ." + Configuration.teleporterService().getVoicedCommand());
+	}
+	
+	@Override
+	public void afterDeserialize(TeleporterConfig config) {
+		super.afterDeserialize(config);
+	}
+	
+	public L2Item getRequiredItem() {
+		return ItemTable.getInstance().getTemplate(Configuration.teleporterService().getVoicedRequiredItem());
+	}
+	
+	@Override
+	public String getName() {
+		return Configuration.teleporterService().getVoicedName();
+	}
+}
diff --git a/src/main/resources/data/scripts.cfg b/src/main/resources/data/scripts.cfg
index 1e10f9add1..03779dd64f 100644
--- a/src/main/resources/data/scripts.cfg
+++ b/src/main/resources/data/scripts.cfg
@@ -9,6 +9,7 @@ com/l2jserver/datapack/features/SkillTransfer/SkillTransfer.java
 # Custom
 com/l2jserver/datapack/custom/Validators/SubClassSkills.java
 com/l2jserver/datapack/custom/service/buffer/BufferService.java
+com/l2jserver/datapack/custom/service/teleporter/TeleporterService.java
 com/l2jserver/datapack/custom/service/discord/DiscordBot.java
 
 # Custom Events
diff --git a/src/main/resources/data/service/teleporter/html/npc/cc_category_list.html b/src/main/resources/data/service/teleporter/html/npc/cc_category_list.html
new file mode 100644
index 0000000000..bf1347f037
--- /dev/null
+++ b/src/main/resources/data/service/teleporter/html/npc/cc_category_list.html
@@ -0,0 +1,6 @@
+<html><title>%teleporter.name%</title><body>
+<a action="%teleporter.bypass_prefix%">Home</a>&nbsp;> <font color=LEVEL>Command Channel Teleports</font><br>
+[FOREACH(teleport IN teleporter.command_channel_teleports DO <a action="%teleporter.bypass_prefix% t cc %teleport.ident%" msg="811;%teleport.name%">%teleport.name% (%teleport.min_members% - %teleport.max_members%, [FOREACH(item IN teleport.items DO %item.amount% %item.name%)ENDEACH])</a><br1>)ENDEACH]
+<br1><center>
+[FOREACH(category IN teleporter.command_channel_categories DO <button width=200 height=34 fore=L2UI_CT1.Button_DF back=L2UI_CT1.Button_DF_Down value="%category.name%" action="%teleporter.bypass_prefix% h ccl %category.ident%"><br1>)ENDEACH]
+</center></body></html>
\ No newline at end of file
diff --git a/src/main/resources/data/service/teleporter/html/npc/cc_teleport_list.html b/src/main/resources/data/service/teleporter/html/npc/cc_teleport_list.html
new file mode 100644
index 0000000000..3d9dfc3899
--- /dev/null
+++ b/src/main/resources/data/service/teleporter/html/npc/cc_teleport_list.html
@@ -0,0 +1,7 @@
+<html><title>%teleporter.name%</title><body>
+<a action="%teleporter.bypass_prefix%">Home</a>&nbsp;>&nbsp;<a action="%teleporter.bypass_prefix% h cccl">Command Channel Teleports</a>[EXISTS(category,&nbsp;> <font color=LEVEL>%category.name%</font>)]<br>
+[EXISTS(category,
+    [FOREACH(teleport IN teleporter.command_channel_teleports DO <a action="%teleporter.bypass_prefix% t cc %teleport.ident%" msg="811;%teleport.name%">%teleport.name% (%teleport.min_members% - %teleport.max_members%, [FOREACH(item IN teleport.items DO %item.amount% %item.name%)ENDEACH])</a><br1>)ENDEACH]
+)ENDEXISTS]
+[FOREACH(teleport IN category.teleports DO <a action="%teleporter.bypass_prefix% t cc %teleport.ident% %category.ident%" msg="811;%teleport.name%" msg="811;%teleport.name%">%teleport.name% (%teleport.min_members% - %teleport.max_members%, [FOREACH(item IN teleport.items DO %item.amount% %item.name%)ENDEACH])</a><br1>)ENDEACH]
+</body></html>
\ No newline at end of file
diff --git a/src/main/resources/data/service/teleporter/html/npc/main.html b/src/main/resources/data/service/teleporter/html/npc/main.html
new file mode 100644
index 0000000000..bb99021e48
--- /dev/null
+++ b/src/main/resources/data/service/teleporter/html/npc/main.html
@@ -0,0 +1,5 @@
+<html noscrollbar><title>%teleporter.name%</title><body><center>
+<button width=200 height=34 fore=L2UI_CT1.Button_DF back=L2UI_CT1.Button_DF_Down value="Solo Teleports" action="%teleporter.bypass_prefix% h scl"><br>
+<button width=200 height=34 fore=L2UI_CT1.Button_DF back=L2UI_CT1.Button_DF_Down value="Party Teleports" action="%teleporter.bypass_prefix% h pcl"><br>
+<button width=200 height=34 fore=L2UI_CT1.Button_DF back=L2UI_CT1.Button_DF_Down value="Command Channel Teleports" action="%teleporter.bypass_prefix% h cccl">
+</center></body></html>
\ No newline at end of file
diff --git a/src/main/resources/data/service/teleporter/html/npc/p_category_list.html b/src/main/resources/data/service/teleporter/html/npc/p_category_list.html
new file mode 100644
index 0000000000..6e4400e637
--- /dev/null
+++ b/src/main/resources/data/service/teleporter/html/npc/p_category_list.html
@@ -0,0 +1,6 @@
+<html><title>%teleporter.name%</title><body>
+<a action="%teleporter.bypass_prefix%">Home</a>&nbsp;> <font color=LEVEL>Party Teleports</font><br>
+[FOREACH(teleport IN teleporter.party_teleports DO <a action="%teleporter.bypass_prefix% t p %teleport.ident%" msg="811;%teleport.name%">%teleport.name% (%teleport.min_members% - %teleport.max_members%, [FOREACH(item IN teleport.items DO %item.amount% %item.name%)ENDEACH])</a><br1>)ENDEACH]
+<br1><center>
+[FOREACH(category IN teleporter.party_categories DO <button width=200 height=34 fore=L2UI_CT1.Button_DF back=L2UI_CT1.Button_DF_Down value="%category.name%" action="%teleporter.bypass_prefix% h pl %category.ident%"><br1>)ENDEACH]
+</center></body></html>
diff --git a/src/main/resources/data/service/teleporter/html/npc/p_teleport_list.html b/src/main/resources/data/service/teleporter/html/npc/p_teleport_list.html
new file mode 100644
index 0000000000..2271959422
--- /dev/null
+++ b/src/main/resources/data/service/teleporter/html/npc/p_teleport_list.html
@@ -0,0 +1,7 @@
+<html><title>%teleporter.name%</title><body>
+<a action="%teleporter.bypass_prefix%">Home</a>&nbsp;>&nbsp;<a action="%teleporter.bypass_prefix% h pcl">Party Teleports</a>[EXISTS(category,&nbsp;> <font color=LEVEL>%category.name%</font>)]<br>
+[EXISTS(!category,
+    [FOREACH(teleport IN teleporter.party_teleports DO <a action="%teleporter.bypass_prefix% t p %teleport.ident%">%teleport.name% (%teleport.min_members% - %teleport.max_members%, [FOREACH(item IN teleport.items DO %item.amount% %item.name%)ENDEACH])</a><br1>)ENDEACH]
+)ENDEXISTS]
+[FOREACH(teleport IN category.teleports DO <a action="%teleporter.bypass_prefix% t p %teleport.ident% %category.ident%" msg="811;%teleport.name%">%teleport.name% (%teleport.min_members% - %teleport.max_members%, [FOREACH(item IN teleport.items DO %item.amount% %item.name%)ENDEACH])</a><br1>)ENDEACH]
+</body></html>
\ No newline at end of file
diff --git a/src/main/resources/data/service/teleporter/html/npc/solo_category_list.html b/src/main/resources/data/service/teleporter/html/npc/solo_category_list.html
new file mode 100644
index 0000000000..3ffecfd35c
--- /dev/null
+++ b/src/main/resources/data/service/teleporter/html/npc/solo_category_list.html
@@ -0,0 +1,6 @@
+<html><title>%teleporter.name%</title><body>
+<a action="%teleporter.bypass_prefix%">Home</a>&nbsp;> <font color=LEVEL>Solo Teleports</font><br>
+[FOREACH(teleport IN teleporter.solo_teleports DO <a action="%teleporter.bypass_prefix% t s %teleport.ident%" msg="811;%teleport.name%">%teleport.name% ([FOREACH(item IN teleport.items DO %item.amount% %item.name%)ENDEACH])</a><br1>)ENDEACH]
+<br1><center>
+[FOREACH(category IN teleporter.solo_categories DO <button width=200 height=34 fore=L2UI_CT1.Button_DF back=L2UI_CT1.Button_DF_Down value="%category.name%" action="%teleporter.bypass_prefix% h sl %category.ident%"><br1>)ENDEACH]
+</center></body></html>
\ No newline at end of file
diff --git a/src/main/resources/data/service/teleporter/html/npc/solo_teleport_list.html b/src/main/resources/data/service/teleporter/html/npc/solo_teleport_list.html
new file mode 100644
index 0000000000..b097eeca00
--- /dev/null
+++ b/src/main/resources/data/service/teleporter/html/npc/solo_teleport_list.html
@@ -0,0 +1,7 @@
+<html><title>%teleporter.name%</title><body>
+<a action="%teleporter.bypass_prefix%">Home</a>&nbsp;>&nbsp;<a action="%teleporter.bypass_prefix% h scl">Solo Teleports</a>[EXISTS(category,&nbsp;> <font color=LEVEL>%category.name%</font>)ENDEXISTS]<br>
+[EXISTS(!category,
+	[FOREACH(teleport IN teleporter.solo_teleports DO<a action="%teleporter.bypass_prefix% t s %teleport.ident%" msg="811;%teleport.name%">%teleport.name% ([FOREACH(item IN teleport.items DO %item.amount% %item.name%)ENDEACH])</a><br1>)ENDEACH]
+)ENDEXISTS]
+[FOREACH(teleport IN category.teleports DO <a action="%teleporter.bypass_prefix% t s %teleport.ident% %category.ident%" msg="811;%teleport.name%">%teleport.name% ([FOREACH(item IN teleport.items DO %item.amount% %item.name%)ENDEACH])</a><br1>)ENDEACH]
+</body></html>
\ No newline at end of file
diff --git a/src/main/resources/data/service/teleporter/json/documentation.txt b/src/main/resources/data/service/teleporter/json/documentation.txt
new file mode 100644
index 0000000000..f25688c7ff
--- /dev/null
+++ b/src/main/resources/data/service/teleporter/json/documentation.txt
@@ -0,0 +1,128 @@
+################################################################################
+# TeleporterService - JSON Docs                                                #
+################################################################################
+
+###############
+# global.json #
+###############
+
+    {
+        "soloTeleports": {
+            "<soloTeleportId>": {
+                "id": "<soloTeleportId>",
+                "name": (string),
+                "x": (number),
+                "y": (number),
+                "z"; (number),
+                "heading": (number),
+                "randomOffset": (number),
+                "instance": (string),
+                "items" : [
+                    { "id":(number), "amount":(number) },
+                    ....
+                ]
+            },
+            ....
+        },
+
+        "groupTeleports": {
+            "<groupTeleportId>": {
+                "id": "<groupTeleportId>",
+                "name": (string),
+                "x": (number),
+                "y": (number),
+                "z"; (number),
+                "heading": (number),
+                "randomOffset": (number),
+                "instance": (string),
+                "minMembers": (number),
+                "maxMembers": (number),
+                "maxDistance": (number),
+                "allowIncomplete": (boolean),
+                "items" : [
+                    { "id":(number), "amount":(number) },
+                    ....
+                ]
+            },
+            ....
+        },
+        
+        "soloTeleportCategories": {
+            "<soloTeleportCategoryId>": {
+                "id": "<soloTeleportCategoryId>",
+                "name": (string),
+                "soloTeleports": ["<soloTeleportId>",...]
+            },
+            ...
+        },
+        
+        "groupTeleportCategories": {
+            "<groupTeleportCategoryId>": {
+                "id": "<groupTeleportCategoryId>",
+                "name": (string),
+                "groupTeleports": ["<groupTeleportId>",...]
+            },
+            ...
+        }
+    }
+
+    Notes:
+    • solo-/groupTeleports: An object where each property represents a solo/group teleport (the "id" property must match the key in "solo-/groupTeleports").
+    • solo-/groupTeleports.name: the display name
+    • solo-/groupTeleports.randomOffset: a random offset is applied to the specified teleport location for each group member but the leader
+    • solo-/groupTeleports.<solo-/groupTeleportId>.instance: an instance is created from the specified instance definition xml and the players are teleported into that instance
+    • groupTeleports.<groupTeleportId>.minMembers: minimum members in the group, including leader
+    • groupTeleports.<groupTeleportId>.maxMembers: maximum members in the group, including leader
+    • groupTeleports.<groupTeleportId>.maxDistance: max distance of party members to the leader
+    • groupTeleports.<groupTeleportId>.allowIncomplete: allow an incomplete group to teleport; when not all group members are in maxDistance range to the party leader, only teleport the members which are in range, if the amount of members is at least minMembers
+    • solo-/groupTeleports.<solo-/groupTeleportId>.items: An array of item objects
+    • solo-/groupTeleportCategories.<solo-/groupTeleportCategoryId>: An object where each property represents a solo/group teleport category (the "id" property must match the key in solo-/groupTeleportCategories.<solo-/groupTeleportCategoryId>)
+    • solo-/groupTeleportCategories.<solo-/groupTeleportCategoryId>.name: Display name of the category 
+    • solo-/groupTeleportCategories.<solo-/groupTeleportCategoryId>.groupTeleports: Array of solo-/groupTeleport id's
+
+
+###############
+# voiced.json #
+###############
+
+    {
+        "dialogType": "NPC" or "COMMUNITY",
+        "htmlFolder": (string),
+        
+        "soloTeleports": ["<soloTeleportId>", ...],
+        "partyTeleports": ["<groupTeleportId>", ...],
+        "commandChannelTeleports": ["<groupTeleportId>", ...],
+    
+        "soloTeleportCategories": ["<soloTeleportCategoryId>",...],
+        "partyTeleportCategories": ["<groupTeleportCategoryId>",...],
+        "commandChannelTeleportCategories": ["<groupTeleportCategoryId>",...]
+    }
+
+    Notes:
+    • dialogType: NPC opens npc html window, COMMUNITY opens community board
+    • htmlFolder: where to load htmls from
+    • solo-/party-/commandChannelTeleports: array of solo-/groupTeleportId
+    • solo-/party-/commandChannelTeleportCategories: array of solo-/groupTeleportCategoryId
+
+
+#####################
+# npcs/<npcId>.json #
+#####################
+
+    {
+        "npcId": "<npcId>",
+        "directFirstTalk": (boolean),
+        "dialogType": "NPC" or "COMMUNITY",
+        "htmlFolder": (string),
+        
+        "soloTeleports": ["<soloTeleportId>", ...],
+        "partyTeleports": ["<groupTeleportId>", ...],
+        "commandChannelTeleports": ["<groupTeleportId>", ...],
+    
+        "soloTeleportCategories": ["<soloTeleportCategoryId>",...],
+        "partyTeleportCategories": ["<groupTeleportCategoryId>",...],
+        "commandChannelTeleportCategories": ["<groupTeleportCategoryId>",...]
+    }
+
+    Notes:
+    • see notes from voiced.json
\ No newline at end of file
diff --git a/src/main/resources/data/service/teleporter/json/global.json b/src/main/resources/data/service/teleporter/json/global.json
new file mode 100644
index 0000000000..6dba438eaf
--- /dev/null
+++ b/src/main/resources/data/service/teleporter/json/global.json
@@ -0,0 +1,425 @@
+{
+    "soloTeleports": {
+		"S_GIRAN": {"id":"S_GIRAN", "name":"Giran", "x":82698, "y":148638, "z":-3473, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GODDARD": {"id":"S_GODDARD", "name":"Goddard", "x":147725, "y":-56517, "z":-2780, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DION": {"id":"S_DION", "name":"Dion", "x":18748, "y":145437, "z":-3132, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GLUDIO": {"id":"S_GLUDIO", "name":"Gludio", "x":-14225, "y":123540, "z":-3121, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SCHUTTGART": {"id":"S_SCHUTTGART", "name":"Schutgart", "x":87358, "y":-141982, "z":-1341, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HUNTERS": {"id":"S_HUNTERS", "name":"Hunters", "x":116589, "y":76268, "z":-2734, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ADEN": {"id":"S_ADEN", "name":"Aden", "x":147450, "y":27064, "z":-2208, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_RUNE": {"id":"S_RUNE", "name":"Rune", "x":44070, "y":-50243, "z":-796, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_OREN": {"id":"S_OREN", "name":"Oren", "x":82321, "y":55139, "z":-1529, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GLUDIN": {"id":"S_GLUDIN", "name":"Gludin", "x":-83063, "y":150791, "z":-3133, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HEINE": {"id":"S_HEINE", "name":"Heine", "x":111115, "y":219017, "z":-3547, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FLORAN": {"id":"S_FLORAN", "name":"Floran", "x":17144, "y":170156, "z":-3502, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HUMAN": {"id":"S_HUMAN", "name":"Human", "x":-82687, "y":243157, "z":-3734, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ORC": {"id":"S_ORC", "name":"Orc", "x":-44133, "y":-113911, "z":-244, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ELVEN": {"id":"S_ELVEN", "name":"Elven", "x":45873, "y":49288, "z":-3064, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DWARVEN": {"id":"S_DWARVEN", "name":"Dwarven", "x":116551, "y":-182493, "z":-1525, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DARK_ELVEN": {"id":"S_DARK_ELVEN", "name":"Dark Elven", "x":12428, "y":16551, "z":-4588, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_KAMAEL": {"id":"S_KAMAEL", "name":"Kamael", "x":-116934, "y":46616, "z":368, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_ADEN_CASTLE": {"id":"S_ADEN_CASTLE", "name":"Aden Castle", "x":147457, "y":10843, "z":-736, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GODDARD_CASTLE": {"id":"S_GODDARD_CASTLE", "name":"Goddard Castle", "x":147482, "y":-45026, "z":-2084, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DION_CASTLE": {"id":"S_DION_CASTLE", "name":"Dion Castle", "x":22306, "y":156027, "z":-2953, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_RUNE_CASTLE": {"id":"S_RUNE_CASTLE", "name":"Rune Castle", "x":19118, "y":-49136, "z":-1266, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_INNADRIL_CASTLE": {"id":"S_INNADRIL_CASTLE", "name":"Innadril Castle", "x":244631, "y":27064, "z":-1057, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GIRAN_CASTLE": {"id":"S_GIRAN_CASTLE", "name":"Giran Castle", "x":112122, "y":144855, "z":-2751, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_OREN_CASTLE": {"id":"S_OREN_CASTLE", "name":"Oren Castle", "x":78116, "y":36961, "z":-2458, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GLUDIO_CASTLE": {"id":"S_GLUDIO_CASTLE", "name":"Gludio Castle", "x":-18361, "y":113887, "z":-2767, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SCHUTTGART_CASTLE": {"id":"S_SCHUTTGART_CASTLE", "name":"Schuttgart Castle", "x":77540, "y":-149114, "z":-352, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_SAINTS": {"id":"S_SAINTS", "name":"Saint's", "x":82623, "y":209258, "z":-5439, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DISCIPLES": {"id":"S_DISCIPLES", "name":"Disciple's", "x":171880, "y":-17657, "z":-4901, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_MARTYRDOM": {"id":"S_MARTYRDOM", "name":"Martyrdom", "x":117788, "y":132785, "z":-4830, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DEVOTION": {"id":"S_DEVOTION", "name":"Devotion", "x":-52850, "y":79044, "z":-4741, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_PATRIOTS": {"id":"S_PATRIOTS", "name":"Patriot's", "x":-22219, "y":77315, "z":-5173, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WORSHIP": {"id":"S_WORSHIP", "name":"Worship", "x":110663, "y":174008, "z":-5444, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_PILGRIMS": {"id":"S_PILGRIMS", "name":"Pilgrim", "x":45221, "y":124407, "z":-5412, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SACRAFICE": {"id":"S_SACRAFICE", "name":"Sacrifice", "x":-41562, "y":209253, "z":-5086, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_DARK_OMENS": {"id":"S_DARK_OMENS", "name":"Dark Omens", "x":-19979, "y":13528, "z":-4900, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FORBIDDEN_PATH": {"id":"S_FORBIDDEN_PATH", "name":"Forbidden Path", "x":113434, "y":84523, "z":-6540, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WITCH": {"id":"S_WITCH", "name":"Witch", "x":140011, "y":79709, "z":-5428, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_APOSTATE": {"id":"S_APOSTATE", "name":"Apostate", "x":77199, "y":78370, "z":-5114, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_BRANDED": {"id":"S_BRANDED", "name":"Branded", "x":45605, "y":170296, "z":-4986, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HERETIC": {"id":"S_HERETIC", "name":"Heretic", "x":42504, "y":143934, "z":-5381, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_FORTRESS_OF_RESISTANCE": {"id":"S_FORTRESS_OF_RESISTANCE", "name":"Fortress of Resistance", "x":45556, "y":109807, "z":-1990, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_BANDIT_STRONGHOLD": {"id":"S_BANDIT_STRONGHOLD", "name":"Bandit Stronghold", "x":86575, "y":-19745, "z":-1931, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DEVASTATED_CASTLE": {"id":"S_DEVASTATED_CASTLE", "name":"Devastated Castle", "x":178342, "y":-14003, "z":-2255, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WILD_BEAST_RESERVE": {"id":"S_WILD_BEAST_RESERVE", "name":"Wild Beast Reserve", "x":53556, "y":-94063, "z":-1630, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_RAINDBOW_SPRINGS_CHATEAU": {"id":"S_RAINDBOW_SPRINGS_CHATEAU", "name":"Rainbow Springs Chateau", "x":140997, "y":-123379, "z":-1906, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FORTRESS_OF_THE_DEAD": {"id":"S_FORTRESS_OF_THE_DEAD", "name":"Fortress of the Dead", "x":57960, "y":-30188, "z":-501, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_EXECUTION_GROUNDS": {"id":"S_EXECUTION_GROUNDS", "name":"Execution Grounds", "x":38291, "y":148029, "z":-3696, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_PARTISANS_HIDEAWAY": {"id":"S_PARTISANS_HIDEAWAY", "name":"Partisan's Hideaway", "x":50081, "y":116859, "z":-2176, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CRUMA_MARSHLANDS": {"id":"S_CRUMA_MARSHLANDS", "name":"Cruma Marshlands", "x":5106, "y":126916, "z":-3664, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_TANOR_CANYON": {"id":"S_TANOR_CANYON", "name":"Tanor Canyon", "x":58316, "y":163851, "z":-2816, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_BEE_HIVE": {"id":"S_BEE_HIVE", "name":"Bee Hive", "x":34475, "y":188095, "z":-2976, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DION_HILLS": {"id":"S_DION_HILLS", "name":"Dion Hills", "x":29928, "y":151415, "z":2392, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FLORAN_AGRICULTURAL_AREA": {"id":"S_FLORAN_AGRICULTURAL_AREA", "name":"Floran Agricultural Area", "x":10610, "y":156322, "z":-2472, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_PLAINS_OF_DION": {"id":"S_PLAINS_OF_DION", "name":"Plains of Dion", "x":630, "y":179184, "z":-3720, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CRUMA_TOWER": {"id":"S_CRUMA_TOWER", "name":"Cruma Tower", "x":17225, "y":114173, "z":-3440, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CRUMA_TOWER_1ST": {"id":"S_CRUMA_TOWER_1ST", "name":"Cruma Tower 1st Floor", "x":17724, "y":114004, "z":-11672, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CRUMA_TOWER_2ND": {"id":"S_CRUMA_TOWER_2ND", "name":"Cruma Tower 2nd Floor", "x":17730, "y":108301, "z":-9057, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CRUMA_TOWER_3RD": {"id":"S_CRUMA_TOWER_3RD", "name":"Cruma Tower 3rd Floor", "x":17719, "y":115430, "z":-6582, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_RUINS_OF_DESPAIR": {"id":"S_RUINS_OF_DESPAIR", "name":"Ruins of Despair", "x":-19120, "y":136816, "z":-3762, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_RUINS_OF_AGONY": {"id":"S_RUINS_OF_AGONY", "name":"Ruins of Agony", "x":-42628, "y":119766, "z":-3528, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WASTELAND": {"id":"S_WASTELAND", "name":"Wasteland", "x":-16526, "y":208032, "z":-3664, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ABANDONED_CAMP": {"id":"S_ABANDONED_CAMP", "name":"Abandoned Camp", "x":-49853, "y":147089, "z":-2784, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ORC_BARRACKS": {"id":"S_ORC_BARRACKS", "name":"Orc Barracks", "x":-89763, "y":105359, "z":-3576, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WINDY_HILL": {"id":"S_WINDY_HILL", "name":"Windy Hill", "x":-88539, "y":83389, "z":-2864, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_RED_ROCK_RIDGE": {"id":"S_RED_ROCK_RIDGE", "name":"Red Rock Ridge", "x":-44829, "y":188171, "z":-3256, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_LANGK_LIZARDMEN_DWELLINGS": {"id":"S_LANGK_LIZARDMEN_DWELLINGS", "name":"Langk Lizardmen Dwellings", "x":-44763, "y":203497, "z":-3592, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_MAILLE_LIZARDMEN_BARRACKS": {"id":"S_MAILLE_LIZARDMEN_BARRACKS", "name":"Maille Lizardmen Barracks", "x":-25283, "y":106820, "z":-3416, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_TALKING_ISLAND_NORTHERN_TERRITORY": {"id":"S_TALKING_ISLAND_NORTHERN_TERRITORY", "name":"Talking Island, Northern Territory", "x":-104344, "y":226217, "z":-3616, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_TALKING_ISLAND_EASTERN_TERRITORY": {"id":"S_TALKING_ISLAND_EASTERN_TERRITORY", "name":"Talking Island, Eastern Territory", "x":-95336, "y":240478, "z":-3264, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FELLMERE_HARVESTING_GROUNDS": {"id":"S_FELLMERE_HARVESTING_GROUNDS", "name":"Fellmere Harvesting Grounds", "x":-63736, "y":101522, "z":-3552, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WINDMILL_HILL": {"id":"S_WINDMILL_HILL", "name":"Windmill Hill", "x":-75437, "y":168800, "z":-3632, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_EVIL_HUNTING_GROUNDS": {"id":"S_EVIL_HUNTING_GROUNDS", "name":"Evil Hunting Grounds", "x":-6989, "y":109503, "z":-3040, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WINDAWOOD_MANOR": {"id":"S_WINDAWOOD_MANOR", "name":"Windawood Manor", "x":-28327, "y":155125, "z":-3496, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_OL_MAHUM_CHECKPOINT": {"id":"S_OL_MAHUM_CHECKPOINT", "name":"Ol Mahum Checkpoint", "x":-6661, "y":201880, "z":-3632, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_THE_NEUTRAL_ZONE": {"id":"S_THE_NEUTRAL_ZONE", "name":"The Neutral Zone", "x":-10612, "y":75881, "z":-3592, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_THE_ANT_NEST": {"id":"S_THE_ANT_NEST", "name":"The Ant Nest", "x":-9959, "y":176184, "z":-4160, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FORGOTTEN_TEMPLE": {"id":"S_FORGOTTEN_TEMPLE", "name":"Forgotten Temple", "x":-53001, "y":191425, "z":-3568, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ELVEN_RUINS": {"id":"S_ELVEN_RUINS", "name":"Elven Ruins", "x":-112367, "y":234703, "z":-3688, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ANT_INCUBATOR": {"id":"S_ANT_INCUBATOR", "name":"Ant Incubator", "x":-26489, "y":195307, "z":-3928, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GLUDIN_HARBOR": {"id":"S_GLUDIN_HARBOR", "name":"Gludin Harbor", "x":-91101, "y":150344, "z":-3624, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_TALKING_ISLAND_HARBOR": {"id":"S_TALKING_ISLAND_HARBOR", "name":"Talking Island Harbor", "x":-96811, "y":259153, "z":-3616, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GLUDIN_ARENA": {"id":"S_GLUDIN_ARENA", "name":"Gludin Arena", "x":-87328, "y":142266, "z":-3640, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FELLMERE_LAKE": {"id":"S_FELLMERE_LAKE", "name":"Fellmere Lake", "x":-57798, "y":127629, "z":-2928, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_OBELISK_OF_VICTORY": {"id":"S_OBELISK_OF_VICTORY", "name":"Obelisk of Victory", "x":-99586, "y":237637, "z":-3568, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SINGING_WATERFALL": {"id":"S_SINGING_WATERFALL", "name":"Singing Waterfall", "x":-111728, "y":244330, "z":-3448, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HELLBOUND_ENTRANCE": {"id":"S_HELLBOUND_ENTRANCE", "name":"Hellbound Entrance", "x":-11802, "y":236360, "z":-3271, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GLUDIO_AIRSHIP_FIELD": {"id":"S_GLUDIO_AIRSHIP_FIELD", "name":"Gludio AirShip Field", "x":-149365, "y":255309, "z":-86, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_BLAZING_SWAMP": {"id":"S_BLAZING_SWAMP", "name":"Blazing Swamp", "x":155310, "y":-16339, "z":-3320, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_THE_FORBIDDEN_GATEWAY": {"id":"S_THE_FORBIDDEN_GATEWAY", "name":"The Forbidden Gateway", "x":188611, "y":20588, "z":-3696, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_THE_ENCHANTED_VALLEY": {"id":"S_THE_ENCHANTED_VALLEY", "name":"The Enchanted Valley", "x":124904, "y":61992, "z":-3973, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_THE_CEMETERY": {"id":"S_THE_CEMETERY", "name":"The Cemetery", "x":167047, "y":20304, "z":-3328, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_THE_FOREST_OF_MIRRORS": {"id":"S_THE_FOREST_OF_MIRRORS", "name":"The Forest of Mirrors", "x":142065, "y":81300, "z":-3000, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ANCIENT_BATTLEGROUND": {"id":"S_ANCIENT_BATTLEGROUND", "name":"Ancient Battleground", "x":106517, "y":-2871, "z":-3454, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FORSAKEN_PLAINS": {"id":"S_FORSAKEN_PLAINS", "name":"Forsaken Plains", "x":168217, "y":37990, "z":-4072, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SILENT_VALLEY": {"id":"S_SILENT_VALLEY", "name":"Silent Valley", "x":170838, "y":55776, "z":-5280, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HUNTERS_VALLEY": {"id":"S_HUNTERS_VALLEY", "name":"Hunters Valley", "x":114306, "y":86573, "z":-3112, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_PLAINS_OF_GLORY": {"id":"S_PLAINS_OF_GLORY", "name":"Plains of Glory", "x":135580, "y":19467, "z":-3424, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FIELDS_OF_MASSACRE": {"id":"S_FIELDS_OF_MASSACRE", "name":"Fields of Massacre", "x":183543, "y":-14974, "z":-2768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WAR-TORN_PLAINS": {"id":"S_WAR-TORN_PLAINS", "name":"War-Torn Plains", "x":156898, "y":11217, "z":-4032, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ISLE_OF_SOULS": {"id":"S_ISLE_OF_SOULS", "name":"Isle of Souls", "x":-121436, "y":56288, "z":-1586, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_MIMIRS_FOREST": {"id":"S_MIMIRS_FOREST", "name":"Mimir's Forest", "x":-103032, "y":46457, "z":-1136, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HILLS_OF_GOLD": {"id":"S_HILLS_OF_GOLD", "name":"Hills of Gold", "x":-116114, "y":87005, "z":-3544, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_TOWER_OF_INSOLENCE": {"id":"S_TOWER_OF_INSOLENCE", "name":"Tower of Insolence", "x":114649, "y":11115, "z":-5120, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_THE_GIANTS_CAVE": {"id":"S_THE_GIANTS_CAVE", "name":"The Giant's Cave", "x":181737, "y":46469, "z":-4276, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_NORNILS_GARDEN": {"id":"S_NORNILS_GARDEN", "name":"Nornil's Garden", "x":-84728, "y":60089, "z":-2576, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_NORNILS_CAVE": {"id":"S_NORNILS_CAVE", "name":"Nornil's Cave", "x":-86961, "y":43356, "z":-2680, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_COLISEUM": {"id":"S_COLISEUM", "name":"Coliseum", "x":146440, "y":46723, "z":-3400, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ANGHEL_WATERFALL": {"id":"S_ANGHEL_WATERFALL", "name":"Anghel Waterfall", "x":166182, "y":91560, "z":-3168, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_EASTERN_BORDER_OUTPOST": {"id":"S_EASTERN_BORDER_OUTPOST", "name":"Eastern Border Outpost", "x":158141, "y":-24543, "z":-1288, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_NARSELL_LAKE": {"id":"S_NARSELL_LAKE", "name":"Narsell Lake", "x":146440, "y":46723, "z":-3400, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WESTERN_BORDER_OUTPOST": {"id":"S_WESTERN_BORDER_OUTPOST", "name":"Western Border Outpost", "x":112405, "y":-16607, "z":-1864, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CAVE_OF_SOULS": {"id":"S_CAVE_OF_SOULS", "name":"Cave of Souls", "x":-123842, "y":38117, "z":1176, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_VALLEY_OF_HEROES": {"id":"S_VALLEY_OF_HEROES", "name":"Valley of Heroes", "x":-39347, "y":-107274, "z":-2072, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_IMMORTAL_PLATEAU_NORTHERN_REGION": {"id":"S_IMMORTAL_PLATEAU_NORTHERN_REGION", "name":"Immortal Plateau, Northern Region", "x":-10983, "y":-117484, "z":-2464, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_IMMORTAL_PLATEAU_SOUTHERN_REGION": {"id":"S_IMMORTAL_PLATEAU_SOUTHERN_REGION", "name":"Immortal Plateau, Southern Region", "x":-4190, "y":-80040, "z":-2696, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FROZEN_VALLEY": {"id":"S_FROZEN_VALLEY", "name":"Frozen Valley", "x":112971, "y":-174924, "z":-608, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WESTERN_MINING_ZONE": {"id":"S_WESTERN_MINING_ZONE", "name":"Western Mining Zone", "x":128527, "y":-204036, "z":-3408, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_EASTERN_MINING_ZONE": {"id":"S_EASTERN_MINING_ZONE", "name":"Eastern Mining Zone", "x":175836, "y":-205837, "z":-3384, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_PLUNDEROUS_PLAINS": {"id":"S_PLUNDEROUS_PLAINS", "name":"Plunderous Plains", "x":111965, "y":-154172, "z":-1528, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FROZEN_LABYRINTH": {"id":"S_FROZEN_LABYRINTH", "name":"Frozen Labyrinth", "x":113903, "y":-108752, "z":-884, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FREYAS_GARDEN": {"id":"S_FREYAS_GARDEN", "name":"Freya's Garden", "x":102728, "y":-126242, "z":-2840, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_PAVEL_RUINS": {"id":"S_PAVEL_RUINS", "name":"Pavel Ruins", "x":91280, "y":-117152, "z":-3952, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DEN_OF_EVIL": {"id":"S_DEN_OF_EVIL", "name":"Den of Evil", "x":68693, "y":-110438, "z":-1946, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CRYPTS_OF_DISGRACE": {"id":"S_CRYPTS_OF_DISGRACE", "name":"Crypts of Disgrace", "x":47692, "y":-115745, "z":-3744, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SKY_WAGON_RELIC": {"id":"S_SKY_WAGON_RELIC", "name":"Sky Wagon Relic", "x":121618, "y":-141554, "z":-1496, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CAVE_OF_TRIALS": {"id":"S_CAVE_OF_TRIALS", "name":"Cave of Trials", "x":9340, "y":-112509, "z":-2536, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ABANDONED_COAL_MINES": {"id":"S_ABANDONED_COAL_MINES", "name":"Abandoned Coal Mines", "x":139714, "y":-177456, "z":-1536, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_MITHRIL_MINES": {"id":"S_MITHRIL_MINES", "name":"Mithril Mines", "x":171946, "y":-173352, "z":3440, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ARCHAIC_LABORATORY": {"id":"S_ARCHAIC_LABORATORY", "name":"Archaic Laboratory", "x":90418, "y":-107317, "z":-3328, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FROZEN_WATERFALLS": {"id":"S_FROZEN_WATERFALLS", "name":"Frozen Waterfalls", "x":8652, "y":-139941, "z":-1144, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_STRIP_MINE": {"id":"S_STRIP_MINE", "name":"Strip Mine", "x":106561, "y":-173949, "z":-400, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SPINE_MOUNTAINS": {"id":"S_SPINE_MOUNTAINS", "name":"Spine Mountains", "x":147493, "y":-200840, "z":192, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_MINING_ZONE_PASSAGE": {"id":"S_MINING_ZONE_PASSAGE", "name":"Mining Zone Passage", "x":113826, "y":-171150, "z":-160, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CARONS_DUNGEON": {"id":"S_CARONS_DUNGEON", "name":"Caron's Dungeon", "x":76021, "y":-110477, "z":-1704, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_VALLEY_OF_THE_LORDS": {"id":"S_VALLEY_OF_THE_LORDS", "name":"Valley of the Lords", "x":32173, "y":-122954, "z":-792, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FROST_LAKE": {"id":"S_FROST_LAKE", "name":"Frost Lake", "x":107577, "y":-122392, "z":-3632, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ICE_MERCHANT_CABIN": {"id":"S_ICE_MERCHANT_CABIN", "name":"Ice Merchant Cabin", "x":113750, "y":-109163, "z":-832, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_BRIGAND_STRONGHOLD": {"id":"S_BRIGAND_STRONGHOLD", "name":"Brigand Stronghold", "x":126272, "y":-159336, "z":-1232, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GRAVEROBBER_HIDEOUT": {"id":"S_GRAVEROBBER_HIDEOUT", "name":"Graverobber Hideout", "x":48336, "y":-107734, "z":-1577, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_GARDEN_OF_BEASTS": {"id":"S_GARDEN_OF_BEASTS", "name":"Garden of Beasts", "x":132997, "y":-60608, "z":-2960, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HOT_SPRINGS": {"id":"S_HOT_SPRINGS", "name":"Hot Springs", "x":144880, "y":-113468, "z":-2560, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_KETRA_ORC_OUTPOST": {"id":"S_KETRA_ORC_OUTPOST", "name":"Ketra Orc Outpost", "x":146990, "y":-67128, "z":-3640, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WALL_OF_ARGOS": {"id":"S_WALL_OF_ARGOS", "name":"Wall of Argos", "x":165054, "y":-47861, "z":-3560, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SHRINE_OF_LOYALTY": {"id":"S_SHRINE_OF_LOYALTY", "name":"Shrine of Loyalty", "x":190112, "y":61776, "z":-2944, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_VARKA_SILENOS_BARRACKS": {"id":"S_VARKA_SILENOS_BARRACKS", "name":"Varka Silenos Barracks", "x":125740, "y":-40864, "z":-3736, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DEVILS_PASS": {"id":"S_DEVILS_PASS", "name":"Devil's Pass", "x":42504, "y":-61870, "z":-2904, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FORGE_OF_THE_GODS": {"id":"S_FORGE_OF_THE_GODS", "name":"Forge of the Gods", "x":168902, "y":-116703, "z":-2417, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HALL_OF_FLAMES": {"id":"S_HALL_OF_FLAMES", "name":"Hall of Flames", "x":189964, "y":-116820, "z":-1624, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_IMPERIAL_TOMB": {"id":"S_IMPERIAL_TOMB", "name":"Imperial Tomb", "x":186699, "y":-75915, "z":-2826, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FOUR_SEPULCHERS": {"id":"S_FOUR_SEPULCHERS", "name":"Four Sepulchers", "x":178127, "y":-84435, "z":-7215, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_KETRA_ORC_VILLAGE": {"id":"S_KETRA_ORC_VILLAGE", "name":"Ketra Orc Village", "x":149548, "y":-82014, "z":-5592, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_VARKA_SILENOS_VILLAGE": {"id":"S_VARKA_SILENOS_VILLAGE", "name":"Varka Silenos Village", "x":108155, "y":-53670, "z":-2472, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HOT_SPRINGS_ARENA": {"id":"S_HOT_SPRINGS_ARENA", "name":"Hot Springs Arena", "x":152180, "y":-126093, "z":-2282, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_DRAGON_VALLEY": {"id":"S_DRAGON_VALLEY", "name":"Dragon Valley", "x":73024, "y":118485, "z":-3720, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DEATH_PASS": {"id":"S_DEATH_PASS", "name":"Death Pass", "x":67933, "y":117045, "z":-3544, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_BREKAS_STRONGHOLD": {"id":"S_BREKAS_STRONGHOLD", "name":"Breka's Stronghold", "x":85546, "y":131328, "z":-3672, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GORGON_FLOWER_GARDEN": {"id":"S_GORGON_FLOWER_GARDEN", "name":"Gorgon Flower Garden", "x":113553, "y":134813, "z":-3540, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ANTHARAS_LAIR": {"id":"S_ANTHARAS_LAIR", "name":"Antharas' Lair", "x":131557, "y":114509, "z":-3712, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DEVILS_ISLE": {"id":"S_DEVILS_ISLE", "name":"Devil's Isle", "x":43408, "y":206881, "z":-3752, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HARDINS_ACADEMY": {"id":"S_HARDINS_ACADEMY", "name":"Hardin's Academy", "x":105918, "y":109759, "z":-3170, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GIRAN_HARBOR": {"id":"S_GIRAN_HARBOR", "name":"Giran Harbor", "x":47938, "y":186864, "z":-3420, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GIRAN_ARENA": {"id":"S_GIRAN_ARENA", "name":"Giran Arena", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_PIRATE_TUNNEL": {"id":"S_PIRATE_TUNNEL", "name":"Pirate Tunnel", "x":41528, "y":198358, "z":-4648, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ANTHARAS_HEART": {"id":"S_ANTHARAS_HEART", "name":"Antharas' Heart", "x":154623, "y":121134, "z":-3809, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_ELVEN_FOREST": {"id":"S_ELVEN_FOREST", "name":"Elven Forest", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SHADOW_OF_THE_MOTHER_TREE": {"id":"S_SHADOW_OF_THE_MOTHER_TREE", "name":"Shadow of the Mother Tree", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_THE_DARK_FOREST": {"id":"S_THE_DARK_FOREST", "name":"The Dark Forest", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SWAMPLAND": {"id":"S_SWAMPLAND", "name":"Swampland", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SEA_OF_SPORES": {"id":"S_SEA_OF_SPORES", "name":"Sea of Spores", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_PLAINS_OF_THE_LIZARDMEN": {"id":"S_PLAINS_OF_THE_LIZARDMEN", "name":"Palins of the Lizardmen", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SEL_MAHUM_TRAINING_GROUNDS": {"id":"S_SEL_MAHUM_TRAINING_GROUNDS", "name":"Sel Mahum Training Grounds", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SHILENS_GARDEN": {"id":"S_SHILENS_GARDEN", "name":"Shilens Garden", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_BLACK_ROCK_HILL": {"id":"S_BLACK_ROCK_HILL", "name":"Black Rock Hill", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SPIDER_NEST": {"id":"S_SPIDER_NEST", "name":"Spider Nest", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_TIMAK_OUTPOST": {"id":"S_TIMAK_OUTPOST", "name":"Timak Outpost", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_IVORY_TOWER_CRATER": {"id":"S_IVORY_TOWER_CRATER", "name":"Ivory Tower Crater", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FOREST_OF_EVIL": {"id":"S_FOREST_OF_EVIL", "name":"Forest of Evil", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_OUTLAW_FOREST": {"id":"S_OUTLAW_FOREST", "name":"Outlaw Forest", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ELVEN_FORTRESS": {"id":"S_ELVEN_FORTRESS", "name":"Elven Fortress", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SCHOOL_OF_DARK_ARTS": {"id":"S_SCHOOL_OF_DARK_ARTS", "name":"School of Dark Arts", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_IVORY_TOWER": {"id":"S_IVORY_TOWER", "name":"Ivory Tower", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_IRIS_LAKE": {"id":"S_IRIS_LAKE", "name":"Iris Lake", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ALTAR_OF_RITES": {"id":"S_ALTAR_OF_RITES", "name":"Altar of Rites", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_MISTY_MOUNTAINS": {"id":"S_MISTY_MOUNTAINS", "name":"Misty Mountains", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_STARLIGHT_WATERFALL": {"id":"S_STARLIGHT_WATERFALL", "name":"Starlight Waterfall", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_UNDINE_WATERFALL": {"id":"S_UNDINE_WATERFALL", "name":"Undine Waterfall", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_THE_GODS_FALLS": {"id":"S_THE_GODS_FALLS", "name":"The Gods Falls", "x":73579, "y":142709, "z":-3768, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_FIELD_OF_SILENCE": {"id":"S_FIELD_OF_SILENCE", "name":"Field of Silence", "x":91088, "y":182384, "z":-3192, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FIELD_OF_WHISPERS": {"id":"S_FIELD_OF_WHISPERS", "name":"Field of Whispers", "x":74592, "y":207656, "z":-3032, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ALLIGATOR_ISLAND": {"id":"S_ALLIGATOR_ISLAND", "name":"Alligator Island", "x":115583, "y":192261, "z":-3488, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ALLIGATOR_BEACH": {"id":"S_ALLIGATOR_BEACH", "name":"Alligator Beach", "x":116267, "y":201177, "z":-3432, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ISLE_OF_PRAYER": {"id":"S_ISLE_OF_PRAYER", "name":"Isle of Prayer", "x":145159, "y":189247, "z":-3756, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_PARNASSUS": {"id":"S_PARNASSUS", "name":"Parnassus", "x":149363, "y":172341, "z":-946, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CHROMATIC_HIGHLANDS": {"id":"S_CHROMATIC_HIGHLANDS", "name":"Chromatic Highlands", "x":148444, "y":160914, "z":-3102, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GARDEN_OF_EVA": {"id":"S_GARDEN_OF_EVA", "name":"Garden of Eva", "x":84413, "y":234334, "z":-3656, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GARDEN_OF_EVA_1ST_FLOOR": {"id":"S_GARDEN_OF_EVA_1ST_FLOOR", "name":"Garden of Eva 1st Floor", "x":80688, "y":245566, "z":-8926, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GARDEN_OF_EVA_2ND_FLOOR": {"id":"S_GARDEN_OF_EVA_2ND_FLOOR", "name":"Garden of Eva 2nd Floor", "x":80629, "y":246420, "z":-9331, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GARDEN_OF_EVA_3RD_FLOOR": {"id":"S_GARDEN_OF_EVA_3RD_FLOOR", "name":"Garden of Eva 3rd Floor", "x":87750, "y":252422, "z":-9851, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GARDEN_OF_EVA_4TH_FLOOR": {"id":"S_GARDEN_OF_EVA_4TH_FLOOR", "name":"Garden of Eva 4th Floor", "x":82506, "y":255978, "z":-10363, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_GARDEN_OF_EVA_5TH_FLOOR": {"id":"S_GARDEN_OF_EVA_5TH_FLOOR", "name":"Garden of Eva 5th Floor", "x":82158, "y":252376, "z":-10592, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_TOUR_BOAT_DOCK": {"id":"S_TOUR_BOAT_DOCK", "name":"Tour Boat Dock", "x":111418, "y":225960, "z":-3624, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CORAL_REEF": {"id":"S_CORAL_REEF", "name":"Coral Reef", "x":160074, "y":167893, "z":-3538, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_BEAST_FARM": {"id":"S_BEAST_FARM", "name":"Beast Farm", "x":43805, "y":-88010, "z":-2780, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_VALLEY_OF_SAINTS": {"id":"S_VALLEY_OF_SAINTS", "name":"Valley of Saints", "x":65797, "y":-71510, "z":-3744, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FOREST_OF_THE_DEAD": {"id":"S_FOREST_OF_THE_DEAD", "name":"Forest of the Dead", "x":52107, "y":-54328, "z":-3158, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SWAMP_OF_SCREAMS": {"id":"S_SWAMP_OF_SCREAMS", "name":"Swamp of Screams", "x":69340, "y":-50203, "z":-3314, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_MONASTERY_OF_SILENCE": {"id":"S_MONASTERY_OF_SILENCE", "name":"Monastery of Silence", "x":106414, "y":-87799, "z":-2949, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_THE_PAGAN_TEMPLE": {"id":"S_THE_PAGAN_TEMPLE", "name":"The Pagan Temple", "x":35630, "y":-49748, "z":-760, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_STAKATO_NEST": {"id":"S_STAKATO_NEST", "name":"Stakato Nest", "x":88969, "y":-45307, "z":-2112, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CURSED_VILLAGE": {"id":"S_CURSED_VILLAGE", "name":"Cursed Village", "x":57670, "y":-41672, "z":-3154, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_RUNE_HARBOR": {"id":"S_RUNE_HARBOR", "name":"Rune Harbor", "x":36839, "y":-38435, "z":-3640, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_PRIMEVAL_ISLE_WHARF": {"id":"S_PRIMEVAL_ISLE_WHARF", "name":"Primeval Isle Wharf", "x":10448, "y":-24960, "z":-3664, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WINDTAIL_WATERFALL": {"id":"S_WINDTAIL_WATERFALL", "name":"Windtail Waterfall", "x":40723, "y":-94881, "z":-2096, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_AARU_FORT": {"id":"S_AARU_FORT", "name":"Aaru", "x":72889, "y":188048, "z":-2580, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ANTHARAS_FORT": {"id":"S_ANTHARAS_FORT", "name":"Antharas'", "x":77910, "y":89232, "z":-2883, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_ARCHAIC_FORT": {"id":"S_ARCHAIC_FORT", "name":"Archaic ", "x":111798, "y":-141743, "z":-2927, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_BAYOU_FORT": {"id":"S_BAYOU_FORT", "name":"Bayou", "x":189897, "y":36705, "z":-3407, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_BORDERLAND_FORT": {"id":"S_BORDERLAND_FORT", "name":"Borderland", "x":157008, "y":-68935, "z":-2861, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_CLOUD_MOUNTAIN_FORT": {"id":"S_CLOUD_MOUNTAIN_FORT", "name":"Cloud Mountain", "x":-54316, "y":89187, "z":-2819, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DEMON_FORT": {"id":"S_DEMON_FORT", "name":"Demon", "x":98822, "y":-56465, "z":-649, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DRAGONSPINE_FORT": {"id":"S_DRAGONSPINE_FORT", "name":"Dragonspine", "x":10527, "y":96849, "z":-3424, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_FLORAN_FORT": {"id":"S_FLORAN_FORT", "name":"Floran", "x":7690, "y":150721, "z":-2887, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HIVE_FORT": {"id":"S_HIVE_FORT", "name":"Hive", "x":15447, "y":186169, "z":-2921, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_HUNTERS_FORT": {"id":"S_HUNTERS_FORT", "name":"Hunter's", "x":124188, "y":93295, "z":-2142, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_IVORY_FORT": {"id":"S_IVORY_FORT", "name":"Ivory", "x":71419, "y":6187, "z":-3036, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_MONASTIC_FORT": {"id":"S_MONASTIC_FORT", "name":"Monastic", "x":71894, "y":-92615, "z":-1420, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_NARSELL_FORT": {"id":"S_NARSELL_FORT", "name":"Narsell", "x":156707, "y":53966, "z":-3251, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SHANTY_FORT": {"id":"S_SHANTY_FORT", "name":"Shanty", "x":-55004, "y":157132, "z":-2050, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SOUTHERN_FORT": {"id":"S_SOUTHERN_FORT", "name":"Southern", "x":-25348, "y":219856, "z":-3249, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SWAMP_FORT": {"id":"S_SWAMP_FORT", "name":"Swamp", "x":68731, "y":-63848, "z":-2783, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_TANOR_FORT": {"id":"S_TANOR_FORT", "name":"Tanor", "x":58948, "y":137927, "z":-1752, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_VALLEY_FORT": {"id":"S_VALLEY_FORT", "name":"Valley", "x":126064, "y":120508, "z":-2583, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WESTERN_FORT": {"id":"S_WESTERN_FORT", "name":"Western", "x":112350, "y":-17183, "z":-992, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_WHITE_SANDS_FORT": {"id":"S_WHITE_SANDS_FORT", "name":"White Sands", "x":116400, "y":203804, "z":-3331, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_SEED_OF_DESTRUCTION": {"id":"S_SEED_OF_DESTRUCTION", "name":"Seed of Destruction", "x":-248140, "y":250678, "z":4331, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SEED_OF_INFINITY": {"id":"S_SEED_OF_INFINITY", "name":"Seed of Infinity", "x":-212843, "y":209695, "z":4280, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_SEED_OF_ANNIHILATION": {"id":"S_SEED_OF_ANNIHILATION", "name":"Seed of Annihilation", "x":-178480, "y":153561, "z":2471, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+		"S_DARKCLOUD_MANSION": {"id":"S_DARKCLOUD_MANSION", "name":"Darkcloud Mansion", "x":140007, "y":150467, "z":-3112, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]},
+
+		"S_FANTASY_ISLAND": {"id":"S_FANTASY_ISLAND", "name":"Fantasy Island", "x":-59103, "y":-56895, "z":-2038, "heading":0, "randomOffset":0, "instance":"", "items":[{"id":57, "amount":100}]}
+	},
+
+    "groupTeleports": {
+	},
+
+	"soloTeleportCategories": {
+		"STC_TOWN_AREAS": {
+			"id":"STC_TOWN_AREAS",
+			"name":"Town Areas",
+			"soloTeleports": [
+				"S_GIRAN", "S_GODDARD", "S_DION", "S_GLUDIO", "S_SCHUTTGART", "S_HUNTERS", "S_ADEN", "S_RUNE", "S_OREN",
+				"S_GLUDIN", "S_HEINE", "S_FLORAN", "S_HUMAN", "S_ORC", "S_ELVEN", "S_DWARVEN", "S_DARK_ELVEN",
+				"S_KAMAEL"
+			]
+		},
+		"STC_CASTLE_AREAS": {
+			"id":"STC_CASTLE_AREAS",
+			"name":"Castle Areas",
+			"soloTeleports": [
+				"S_ADEN_CASTLE", "S_GODDARD_CASTLE", "S_DION_CASTLE", "S_RUNE_CASTLE", "S_INNADRIL_CASTLE",
+				"S_GIRAN_CASTLE", "S_OREN_CASTLE", "S_GLUDIO_CASTLE", "S_SCHUTTGART_CASTLE"
+			]
+		},
+		"STC_NECROPOLIS": {
+			"id":"STC_NECROPOLIS",
+			"name":"Necropolis",
+			"soloTeleports": [
+				"S_SAINTS", "S_DISCIPLES", "S_MARTYRDOM", "S_DEVOTION", "S_PATRIOTS", "S_WORSHIP", "S_PILGRIMS",
+				"S_SACRAFICE"
+			]
+		},
+		"STC_CATACOMBS": {
+			"id":"STC_CATACOMBS",
+			"name":"Catacombs",
+			"soloTeleports": ["S_DARK_OMENS", "S_FORBIDDEN_PATH", "S_WITCH", "S_APOSTATE", "S_BRANDED", "S_HERETIC"]
+		},
+		"STC_CONQUERABLE_CLAN_HALLS": {
+			"id":"STC_CONQUERABLE_CLAN_HALLS",
+			"name":"Conquerable Clan Halls",
+			"soloTeleports": [
+				"S_FORTRESS_OF_RESISTANCE", "S_BANDIT_STRONGHOLD", "S_DEVASTATED_CASTLE", "S_WILD_BEAST_RESERVE",
+				"S_RAINDBOW_SPRINGS_CHATEAU", "S_FORTRESS_OF_THE_DEAD"
+			]
+		},
+		"STC_WORLD_AREAS_DION": {
+			"id":"STC_WORLD_AREAS_DION",
+			"name":"Dion Territory",
+			"soloTeleports": [
+				"S_EXECUTION_GROUNDS", "S_PARTISANS_HIDEAWAY", "S_CRUMA_MARSHLANDS", "S_TANOR_CANYON", "S_BEE_HIVE",
+				"S_DION_HILLS", "S_FLORAN_AGRICULTURAL_AREA", "S_PLAINS_OF_DION", "S_CRUMA_TOWER", "S_CRUMA_TOWER_1ST",
+				"S_CRUMA_TOWER_2ND", "S_CRUMA_TOWER_3RD"
+			]
+		},
+		"STC_WORLD_AREAS_GLUDIO": {
+			"id":"STC_WORLD_AREAS_GLUDIO",
+			"name":"Gludio  Territory",
+			"soloTeleports": [
+				"S_RUINS_OF_DESPAIR", "S_RUINS_OF_AGONY", "S_WASTELAND", "S_ABANDONED_CAMP", "S_ORC_BARRACKS",
+				"S_WINDY_HILL", "S_RED_ROCK_RIDGE", "S_LANGK_LIZARDMEN_DWELLINGS", "S_MAILLE_LIZARDMEN_BARRACKS",
+				"S_TALKING_ISLAND_NORTHERN_TERRITORY", "S_TALKING_ISLAND_EASTERN_TERRITORY",
+				"S_FELLMERE_HARVESTING_GROUNDS", "S_WINDMILL_HILL", "S_EVIL_HUNTING_GROUNDS", "S_WINDAWOOD_MANOR",
+				"S_OL_MAHUM_CHECKPOINT", "S_THE_NEUTRAL_ZONE", "S_THE_ANT_NEST", "S_FORGOTTEN_TEMPLE", "S_ELVEN_RUINS",
+				"S_ANT_INCUBATOR", "S_GLUDIN_HARBOR", "S_TALKING_ISLAND_HARBOR", "S_GLUDIN_ARENA", "S_FELLMERE_LAKE",
+				"S_OBELISK_OF_VICTORY", "S_SINGING_WATERFALL", "S_HELLBOUND_ENTRANCE", "S_GLUDIO_AIRSHIP_FIELD"
+			]
+		},
+		"STC_WORLD_AREAS_ADEN": {
+			"id":"STC_WORLD_AREAS_ADEN",
+			"name":"Aden Territory",
+			"soloTeleports": [
+				"S_BLAZING_SWAMP", "S_THE_FORBIDDEN_GATEWAY", "S_THE_ENCHANTED_VALLEY", "S_THE_CEMETERY",
+				"S_THE_FOREST_OF_MIRRORS", "S_ANCIENT_BATTLEGROUND", "S_FORSAKEN_PLAINS", "S_SILENT_VALLEY",
+				"S_HUNTERS_VALLEY", "S_PLAINS_OF_GLORY", "S_FIELDS_OF_MASSACRE", "S_WAR-TORN_PLAINS", "S_ISLE_OF_SOULS",
+				"S_MIMIRS_FOREST", "S_HILLS_OF_GOLD", "S_TOWER_OF_INSOLENCE", "S_THE_GIANTS_CAVE",
+				"S_NORNILS_GARDEN", "S_NORNILS_CAVE", "S_COLISEUM", "S_ANGHEL_WATERFALL", "S_EASTERN_BORDER_OUTPOST",
+				"S_NARSELL_LAKE", "S_WESTERN_BORDER_OUTPOST", "S_CAVE_OF_SOULS"
+			]
+		},
+		"STC_WORLD_AREAS_SCHUTTGART": {
+			"id":"STC_WORLD_AREAS_SCHUTTGART",
+			"name":"Schuttgart Territory",
+			"soloTeleports": [
+				"S_VALLEY_OF_HEROES", "S_IMMORTAL_PLATEAU_NORTHERN_REGION", "S_IMMORTAL_PLATEAU_SOUTHERN_REGION",
+				"S_FROZEN_VALLEY", "S_WESTERN_MINING_ZONE", "S_EASTERN_MINING_ZONE", "S_PLUNDEROUS_PLAINS",
+				"S_FROZEN_LABYRINTH", "S_FREYAS_GARDEN", "S_PAVEL_RUINS", "S_DEN_OF_EVIL", "S_CRYPTS_OF_DISGRACE",
+				"S_SKY_WAGON_RELIC", "S_CAVE_OF_TRIALS", "S_ABANDONED_COAL_MINES", "S_MITHRIL_MINES",
+				"S_ARCHAIC_LABORATORY", "S_FROZEN_WATERFALLS", "S_STRIP_MINE", "S_SPINE_MOUNTAINS",
+				"S_MINING_ZONE_PASSAGE", "S_CARONS_DUNGEON", "S_VALLEY_OF_THE_LORDS", "S_FROST_LAKE",
+				"S_ICE_MERCHANT_CABIN", "S_BRIGAND_STRONGHOLD", "S_GRAVEROBBER_HIDEOUT"
+			]
+		},
+		"STC_WORLD_AREAS_GODDARD": {
+			"id":"STC_WORLD_AREAS_GODDARD",
+			"name":"Goddard Territory",
+			"soloTeleports": [
+				"S_GARDEN_OF_BEASTS", "S_HOT_SPRINGS", "S_KETRA_ORC_OUTPOST", "S_WALL_OF_ARGOS", "S_SHRINE_OF_LOYALTY",
+				"S_VARKA_SILENOS_BARRACKS", "S_DEVILS_PASS", "S_FORGE_OF_THE_GODS", "S_HALL_OF_FLAMES",
+				"S_IMPERIAL_TOMB", "S_FOUR_SEPULCHERS", "S_KETRA_ORC_VILLAGE", "S_VARKA_SILENOS_VILLAGE",
+				"S_HOT_SPRINGS_ARENA"
+			]
+		},
+		"STC_WORLD_AREAS_GIRAN": {
+			"id":"STC_WORLD_AREAS_GIRAN",
+			"name":"Giran Territory",
+			"soloTeleports": [
+				"S_DRAGON_VALLEY", "S_DEATH_PASS", "S_BREKAS_STRONGHOLD", "S_GORGON_FLOWER_GARDEN", "S_ANTHARAS_LAIR",
+				"S_DEVILS_ISLE", "S_HARDINS_ACADEMY", "S_GIRAN_HARBOR", "S_GIRAN_ARENA", "S_PIRATE_TUNNEL",
+				"S_ANTHARAS_HEART"
+			]
+		},
+		"STC_WORLD_AREAS_OREN": {
+			"id":"STC_WORLD_AREAS_OREN",
+			"name":"Oren Territory",
+			"soloTeleports": [
+				"S_ELVEN_FOREST", "S_SHADOW_OF_THE_MOTHER_TREE", "S_THE_DARK_FOREST", "S_SWAMPLAND", "S_SEA_OF_SPORES",
+				"S_PLAINS_OF_THE_LIZARDMEN", "S_SEL_MAHUM_TRAINING_GROUNDS", "S_SHILENS_GARDEN", "S_BLACK_ROCK_HILL",
+				"S_SPIDER_NEST", "S_TIMAK_OUTPOST", "S_IVORY_TOWER_CRATER", "S_FOREST_OF_EVIL", "S_OUTLAW_FOREST",
+				"S_ELVEN_FORTRESS", "S_SCHOOL_OF_DARK_ARTS", "S_IVORY_TOWER", "S_IRIS_LAKE", "S_ALTAR_OF_RITES",
+				"S_MISTY_MOUNTAINS", "S_STARLIGHT_WATERFALL", "S_UNDINE_WATERFALL", "S_THE_GODS_FALLS"
+			]
+		},
+		"STC_WORLD_AREAS_INNADRIL": {
+			"id":"STC_WORLD_AREAS_INNADRIL",
+			"name":"Innadril Territory",
+			"soloTeleports": [
+				"S_FIELD_OF_SILENCE", "S_FIELD_OF_WHISPERS", "S_ALLIGATOR_ISLAND", "S_ALLIGATOR_BEACH", "S_ISLE_OF_PRAYER",
+				"S_PARNASSUS", "S_CHROMATIC_HIGHLANDS", "S_GARDEN_OF_EVA", "S_GARDEN_OF_EVA_1ST_FLOOR",
+				"S_GARDEN_OF_EVA_2ND_FLOOR", "S_GARDEN_OF_EVA_3RD_FLOOR", "S_GARDEN_OF_EVA_4TH_FLOOR",
+				"S_GARDEN_OF_EVA_5TH_FLOOR", "S_TOUR_BOAT_DOCK", "S_CORAL_REEF"
+			]
+		},
+		"STC_WORLD_AREAS_RUNE": {
+			"id":"STC_WORLD_AREAS_RUNE",
+			"name":"Rune Territory",
+			"soloTeleports": [
+				"S_BEAST_FARM", "S_VALLEY_OF_SAINTS", "S_FOREST_OF_THE_DEAD", "S_SWAMP_OF_SCREAMS", "S_MONASTERY_OF_SILENCE",
+				"S_THE_PAGAN_TEMPLE", "S_STAKATO_NEST", "S_CURSED_VILLAGE", "S_RUNE_HARBOR", "S_PRIMEVAL_ISLE_WHARF",
+				"S_WINDTAIL_WATERFALL"
+			]
+		},		
+		"STC_FORTRESS": {
+			"id":"STC_FORTRESS",
+			"name":"Fortress",
+			"soloTeleports": [
+				"S_AARU_FORT", "S_ANTHARAS_FORT", "S_ARCHAIC_FORT", "S_BAYOU_FORT", "S_BORDERLAND_FORT", "S_CLOUD_MOUNTAIN_FORT",
+				"S_DEMON_FORT", "S_DRAGONSPINE_FORT", "S_FLORAN_FORT", "S_HIVE_FORT", "S_HUNTERS_FORT", "S_IVORY_FORT",
+				"S_MONASTIC_FORT", "S_NARSELL_FORT", "S_SHANTY_FORT", "S_SOUTHERN_FORT", "S_SWAMP_FORT", "S_TANOR_FORT",
+				"S_VALLEY_FORT", "S_WESTERN_FORT", "S_WHITE_SANDS_FORT"
+			]
+		},
+		"STC_INSTANCES": {
+			"id":"STC_INSTANCES",
+			"name":"Instances",
+			"soloTeleports": [
+				"S_SEED_OF_DESTRUCTION", "S_SEED_OF_INFINITY", "S_SEED_OF_ANNIHILATION", "S_DARKCLOUD_MANSION"
+			]
+		},
+		"STC_OTHER_LOCATIONS": {
+			"id":"STC_OTHER_LOCATIONS",
+			"name":"Other Locations",
+			"soloTeleports": ["S_FANTASY_ISLAND"]
+		}
+	},
+
+	"groupTeleportCategories": {
+	}
+}
\ No newline at end of file
diff --git a/src/main/resources/data/service/teleporter/json/npcs/60002.json b/src/main/resources/data/service/teleporter/json/npcs/60002.json
new file mode 100644
index 0000000000..70efaec188
--- /dev/null
+++ b/src/main/resources/data/service/teleporter/json/npcs/60002.json
@@ -0,0 +1,14 @@
+{
+    "npcId":60002,
+    "directFirstTalk":true,
+    "dialogType":"NPC",
+    "htmlFolder":"npc",
+    
+    "soloTeleports": [],
+    "partyTeleports": [],
+    "commandChannelTeleports": [],
+    
+    "soloTeleportCategories": ["STC_TOWN_AREAS", "STC_CASTLE_AREAS", "STC_NECROPOLIS", "STC_CATACOMBS", "STC_CONQUERABLE_CLAN_HALLS", "STC_WORLD_AREAS_DION", "STC_WORLD_AREAS_GLUDIO", "STC_WORLD_AREAS_ADEN", "STC_WORLD_AREAS_SCHUTTGART", "STC_WORLD_AREAS_GODDARD", "STC_WORLD_AREAS_GIRAN", "STC_WORLD_AREAS_OREN", "STC_WORLD_AREAS_INNADRIL", "STC_WORLD_AREAS_RUNE", "STC_FORTRESS", "STC_INSTANCES", "STC_OTHER_LOCATIONS"],
+    "partyTeleportCategories": [],
+    "commandChannelTeleportCategories": []
+}
\ No newline at end of file
diff --git a/src/main/resources/data/service/teleporter/json/voiced.json b/src/main/resources/data/service/teleporter/json/voiced.json
new file mode 100644
index 0000000000..fcdc7ff364
--- /dev/null
+++ b/src/main/resources/data/service/teleporter/json/voiced.json
@@ -0,0 +1,12 @@
+{
+    "dialogType":"NPC",
+    "htmlFolder":"npc",
+    
+    "soloTeleports": [],
+    "partyTeleports": [],
+    "commandChannelTeleports": [],
+    
+    "soloTeleportCategories": ["STC_TOWN_AREAS", "STC_CASTLE_AREAS", "STC_NECROPOLIS", "STC_CATACOMBS", "STC_CONQUERABLE_CLAN_HALLS", "STC_WORLD_AREAS_DION", "STC_WORLD_AREAS_GLUDIO", "STC_WORLD_AREAS_ADEN", "STC_WORLD_AREAS_SCHUTTGART", "STC_WORLD_AREAS_GODDARD", "STC_WORLD_AREAS_GIRAN", "STC_WORLD_AREAS_OREN", "STC_WORLD_AREAS_INNADRIL", "STC_WORLD_AREAS_RUNE", "STC_FORTRESS", "STC_INSTANCES", "STC_OTHER_LOCATIONS"],
+    "partyTeleportCategories": [],
+    "commandChannelTeleportCategories": []
+}
\ No newline at end of file
diff --git a/src/main/resources/data/stats/npcs/custom/custom_teleporterservice.xml b/src/main/resources/data/stats/npcs/custom/custom_teleporterservice.xml
new file mode 100644
index 0000000000..b5d2a8f284
--- /dev/null
+++ b/src/main/resources/data/stats/npcs/custom/custom_teleporterservice.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../xsd/npcs.xsd">
+    <npc id="60002" displayId="32226" name="Selanta" usingServerSideName="true" title="Teleporter" usingServerSideTitle="true" type="L2Npc">
+        <collision>
+            <radius normal="11" />
+            <height normal="22.25" />
+        </collision>
+    </npc>
+</list>
\ No newline at end of file
-- 
GitLab