public class NemesisConfig {

	private static final String CATEGORY = "NemesisSystem Settings";
	private static Configuration config;
	private static final String[] DEFAULT_MOB_LIST = {
			"minecraft:zombie",
			"minecraft:zombie_pigman",
			"minecraft:zombie_villager",
			"minecraft:stray",
			"minecraft:husk",
			"minecraft:skeleton"
	};

	public static int NEMESIS_LIMIT = 16;
	public static int BOOK_DROP_CHANCE_BODY_GUARD = 10;
	public static int BOOK_DROP_CHANCE_MOB = 40;
	public static boolean DISCOVERY_ENABLED = true;
	public static String[] MOB_WHITELIST = DEFAULT_MOB_LIST;
	public static int NEMESIS_SETTLE_RADIUS = 5000;

	public static void init(File configFile) {
		if (config == null) {
			config = new Configuration(configFile);
			loadConfiguration();
		}
	}

	private static void loadConfiguration() {
		try {
			DISCOVERY_ENABLED =
					config.getBoolean("DISCOVERY_ENABLED", CATEGORY, true,
							"Enable the Nemesis Discovery System");

			NEMESIS_LIMIT =
					config.getInt("NEMESIS_LIMIT", CATEGORY, 16, 4, 256,
							"Maximum number of nemeses in each dimension");

			BOOK_DROP_CHANCE_BODY_GUARD =
					config.getInt("BOOK_DROP_CHANCE_BODY_GUARD", CATEGORY, 10, -1, 1000,
							"Chance a body guard will drop a discovery book (1 out of n)");

			BOOK_DROP_CHANCE_MOB =
					config.getInt("BOOK_DROP_CHANCE_MOB", CATEGORY, 40, -1, 1000,
							"Chance a mob will drop a discovery book (1 out of n)");

			MOB_WHITELIST =
					config.getStringList("MOB_WHITELIST", CATEGORY, DEFAULT_MOB_LIST,
							"Mobs that will be used to create nemeses. (Must extend EntityCreature)");

			NEMESIS_SETTLE_RADIUS =
					config.getInt("NEMESIS_SETTLE_RADIUS", CATEGORY, 5000, 500, 100000,
							"The distance a nemesis will settle within from the origin when there are not enough nemeses");

			config.save();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@SubscribeEvent
	public void onConfigurationChangedEvent(ConfigChangedEvent.OnConfigChangedEvent event) {
		if (event.getModID().equalsIgnoreCase(NemesisSystem.MODID)) {
			loadConfiguration();
		}
	}
} @Mod(modid = NemesisSystem.MODID, version = NemesisSystem.VERSION, name = NemesisSystem.MODNAME)
public class NemesisSystem {

	public static final String MODID = "nemesissystem";
	public static final String VERSION = "1.12.1-2";
	public static final String MODNAME = "NemesisSystem";

	public static final String NBT_NEMESIS_ID = "torocraft_nemesis_id";
	public static final String TAG_NEMESIS = "torocraft_nemesis";
	public static final String TAG_BODY_GUARD = "nemesis_body_guard";
	public static final String TAG_SPAWNING = "nemesis_is_spawning";
	public static final String TAG_BUFFED_MOB = "nemesis_buffed_mob";
	public static final int MIN_TOROTRAIT_VERSION = 1;
	public static final int COMPAT_VERSION = 1;
	public static final String TAG_BUFF_MOB_REINFORCEMENT = "nemesis_buffed_mob_reinforcement";
	//public static final String TAG_SUMMONED_MOB = "nemesis_summoned_mob";

	@Mod.Instance(MODID)
	public static NemesisSystem INSTANCE;

	public static MinecraftServer SERVER;

	public static SimpleNetworkWrapper NETWORK = NetworkRegistry.INSTANCE.newSimpleChannel(MODID);

	@SidedProxy(clientSide = "net.torocraft.nemesissystem.proxy.ClientProxy", serverSide = "net.torocraft.nemesissystem.proxy.ServerProxy")
	public static CommonProxy PROXY;

	@SideOnly(Side.CLIENT)
	public static List<NemesisEntry> NEMESES;

	@SideOnly(Side.CLIENT)
	public static PlayerKnowledgeBase KNOWLEDGE_BASE;

	@SideOnly(Side.CLIENT)
	public static NemesisEntry NEMESIS;

	@SideOnly(Side.CLIENT)
	public static NemesisKnowledge KNOWLEDGE;

	@EventHandler
	public void preInit(FMLPreInitializationEvent e) {
		PROXY.preInit(e);
	}

	@EventHandler
	public void init(FMLInitializationEvent e) {
		PROXY.init(e);
	}

	@EventHandler
	public void postInit(FMLPostInitializationEvent e) {
		PROXY.postInit(e);
	}

	@EventHandler
	public void serverLoad(FMLServerStartingEvent e) {
		SERVER = e.getServer();
		e.registerServerCommand(new NemesisSystemCommand());
	}

} public class Entities {

	public static void init() {
		int entityId = 0;
		EntityZombieNemesis.init(entityId++);
		EntityPigZombieNemesis.init(entityId++);
		EntityZombieVillagerNemesis.init(entityId++);
		EntityHuskNemesis.init(entityId++);
		EntitySkeletonNemesis.init(entityId++);
		EntityStrayNemesis.init(entityId++);
	}

	public static void registerRenders() {
		RenderZombieNemesis.init();
		RenderPigZombieNemesis.init();
		RenderZombieVillagerNemesis.init();
		RenderHuskNemesis.init();
		RenderSkeletonNemesis.init();
		RenderStrayNemesis.init();
	}
} public class AttackHandler {

	public static void init() {
		MinecraftForge.EVENT_BUS.register(new AttackHandler());
	}

	@SubscribeEvent
	public void onAttacked(LivingHurtEvent event) {
		World world = event.getEntity().getEntityWorld();

		if (world.isRemote || !(event.getEntity() instanceof EntityCreature)) {
			return;
		}

		if (event.getEntity().getTags().contains(NemesisSystem.TAG_NEMESIS)) {
			orderGuardsToAttackAggressor((EntityCreature) event.getEntity(), event.getSource().getTrueSource());
		}
	}


	@SubscribeEvent
	public void onTeleportEntityHarm(LivingHurtEvent event) {
		if (!(event.getEntityLiving() instanceof EntityLiving) || !event.getEntityLiving().getTags().contains(NemesisSystem.TAG_NEMESIS)) {
			return;
		}

		EntityLiving entity = (EntityLiving) event.getEntityLiving();
		NemesisEntry nemesis = NemesisUtil.loadNemesisFromEntity(entity);
		if (nemesis == null) {
			return;
		}

		if (!nemesis.hasTrait(Type.TELEPORT)) {
			return;
		}

		World world = entity.getEntityWorld();
		if (world.rand.nextInt(2) != 0) {
			return;
		}

		List<EntityCreature> guards = NemesisUtil.findNemesisBodyGuards(world, nemesis.getId(), entity.getPosition());
		if (guards.size() < 1) {
			return;
		}
		EntityCreature teleportTarget = guards.get(world.rand.nextInt(guards.size()));

		NemesisActions.throwPearl(entity, teleportTarget);
	}

	private void orderGuardsToAttackAggressor(EntityCreature boss, Entity attacker) {
		if (attacker == null || !(attacker instanceof EntityLivingBase)) {
			return;
		}
		UUID id = boss.getEntityData().getUniqueId(NemesisSystem.NBT_NEMESIS_ID);
		NemesisUtil.findNemesisBodyGuards(boss.world, id, boss.getPosition()).forEach((EntityCreature guard) -> {
			if (boss.getRNG().nextInt(7) == 0) {
				guard.setAttackTarget((EntityLivingBase) attacker);
			}
		});
	}

} public class DeathHandler {

	public static void init() {
		MinecraftForge.EVENT_BUS.register(new DeathHandler());
	}

	public static final String TAG_RONIN = "nemesissystem_ronin";

	@SubscribeEvent
	public void onDeath(PlayerEvent.Clone event) {
		if (!event.getEntityPlayer().getEntityWorld().isRemote) {
			PlayerKnowledgeBase.get(event.getOriginal()).save(event.getEntityPlayer());
		}
	}

	@SubscribeEvent
	public void onDrops(LivingDropsEvent event) {
		World world = event.getEntity().getEntityWorld();

		if (world.isRemote || !(event.getEntity() instanceof EntityCreature)) {
			return;
		}

		if (event.getSource() == null || event.getSource().getTrueSource() == null){
			return;
		}

		if (!(event.getSource().getTrueSource() instanceof EntityPlayer)) {
			return;
		}

		if (event.getEntity().getTags().contains(NemesisSystem.TAG_NEMESIS)) {
			handleNemesisDrops(event.getDrops(), (EntityCreature) event.getEntity());
		}
	}

	@SubscribeEvent
	public void onDeath(LivingDeathEvent event) {

		World world = event.getEntity().getEntityWorld();

		if (world.isRemote) {
			return;
		}

		Entity slayer = event.getSource().getTrueSource();

		if (event.getEntity() instanceof EntityPlayer && slayer instanceof EntityCreature) {
			handlePlayerDeath((EntityPlayer) event.getEntity(), (EntityCreature) slayer);
			return;
		}

		if (!(event.getEntity() instanceof EntityCreature)) {
			return;
		}

		if (event.getEntity().getTags().contains(NemesisSystem.TAG_NEMESIS)) {
			handleNemesisDeath((EntityCreature) event.getEntity(), slayer);
		}
	}

	@SubscribeEvent
	public void dropExperience(LivingExperienceDropEvent event) {
		if (!event.getEntity().getTags().contains(NemesisSystem.TAG_NEMESIS)) {
			return;
		}
		NemesisEntry nemesis = NemesisUtil.loadNemesisFromEntity(event.getEntity());
		if (nemesis == null) {
			return;
		}
		event.setDroppedExperience(10 + (5 * (nemesis.getLevel() - 1)));
	}

	private void handlePlayerDeath(EntityPlayer player, EntityCreature slayer) {
		if (slayer == null) {
			return;
		}
		NemesisEntry nemesis = null;

		if (slayer.getTags().contains(NemesisSystem.TAG_NEMESIS)) {
			nemesis = NemesisUtil.loadNemesisFromEntity(slayer);
		}

		if (nemesis == null) {
			if (NemesisUtil.isNemesisClassEntity(slayer)) {
				BlockPos pos = NemesisUtil.getRandomNemesisLocation(slayer.world, slayer.getPosition());
				NemesisEntry newNemesis = NemesisActions.createAndRegisterNemesis(slayer, pos);
				nemesisDuelIfCrowed(slayer.world, newNemesis);
			}
		} else {
			NemesisActions.promote(player.world, nemesis);
		}
	}

	private void nemesisDuelIfCrowed(World world, NemesisEntry exclude) {
		NemesisActions.duelIfCrowded(world, exclude, true);
	}

	private static int safeNextInt(Random rand, int bound) {
		try {
			return rand.nextInt(bound);
		}catch(Exception e){
			return 0;
		}
	}

	private void handleNemesisDrops(List<EntityItem> drops, EntityCreature nemesisEntity) {
		NemesisEntry nemesis = NemesisUtil.loadNemesisFromEntity(nemesisEntity);
		Random rand = nemesisEntity.getRNG();

		if (nemesis == null) {
			return;
		}

		int diamondDropCount = safeNextInt(rand, 1 + (2 * nemesis.getLevel()));
		int emeraldDropCount = safeNextInt(rand, nemesis.getLevel() - 1);

		drops.add(drop(nemesisEntity, new ItemStack(Items.DIAMOND, diamondDropCount)));
		if (emeraldDropCount > 0) {
			drops.add(drop(nemesisEntity, new ItemStack(Items.EMERALD, emeraldDropCount)));
		}

		for (ItemStack stack : nemesis.getArmorInventory()) {
			drops.add(damageAndDrop(nemesisEntity, stack));
		}

		for (ItemStack stack : nemesis.getHandInventory()) {
			drops.add(damageAndDrop(nemesisEntity, stack));
		}
	}

	public static EntityItem damageAndDrop(EntityCreature entity, ItemStack stack) {
		if (stack.isItemStackDamageable()) {
			stack.setItemDamage(stack.getMaxDamage() - entity.getRNG().nextInt(1 + entity.getRNG().nextInt(Math.max(stack.getMaxDamage() - 3, 1))));
		}
		return drop(entity, stack);
	}

	public static EntityItem drop(EntityCreature entity, ItemStack stack) {
		return new EntityItem(entity.getEntityWorld(), entity.posX, entity.posY, entity.posZ, stack);
	}

	private static void handleNemesisDeath(EntityCreature nemesisEntity, Entity attacker) {
		NemesisEntry nemesis = NemesisUtil.loadNemesisFromEntity(nemesisEntity);

		if (nemesis == null) {
			return;
		}

		if (attacker == null || !(attacker instanceof EntityLivingBase)) {
			return;
		}

		NemesisActions.demote(nemesisEntity.world, nemesis, attacker.getName());

		NemesisUtil.findNemesisBodyGuards(nemesisEntity.world, nemesis.getId(), nemesisEntity.getPosition())
				.forEach((EntityCreature guard) -> {
					guard.setAttackTarget(null);
					guard.getTags().add(TAG_RONIN);
				});
	}

} public class DiscoveryHandler {

	public static void init() {
		MinecraftForge.EVENT_BUS.register(new DiscoveryHandler());
	}

	@SubscribeEvent
	public void readBook(RightClickItem event) {

		if (event.getWorld().isRemote) {
			return;
		}

		ItemStack item = event.getItemStack();

		if (!DiscoveryUtil.isDiscoveryBook(item)) {
			return;
		}

		event.setCanceled(true);

		if (!NemesisConfig.DISCOVERY_ENABLED) {
			return;
		}

		NemesisDiscovery discovery = DiscoveryUtil.readBook(event.getWorld(), item);

		if (discovery == null) {
			event.getEntityPlayer().sendMessage(new TextComponentString("Nemesis no longer exists"));
			return;
		}

		DiscoveryUtil.newDiscovery(event.getEntityPlayer(), discovery);
		NemesisEntry nemesis = NemesisRegistryProvider.get(event.getWorld()).getById(discovery.nemesisId);

		if (nemesis == null) {
			event.getEntityPlayer().sendMessage(new TextComponentString("Nemesis no longer exists"));
			return;
		}

		NemesisKnowledge knowledge = DiscoveryUtil.getGetPlayerKnowledgeOfNemesis(event.getEntityPlayer(), nemesis.getId());

		if (knowledge == null) {
			knowledge = new NemesisKnowledge();
		}

		MinecraftForge.EVENT_BUS.post(new DiscoveryEvent(nemesis, discovery, event.getEntityPlayer()));
		NemesisSystem.NETWORK.sendTo(new MessageOpenNemesisDetailsGui(nemesis, knowledge), (EntityPlayerMP) event.getEntityPlayer());
	}

} public class DropHandler {

	public static void init() {
		MinecraftForge.EVENT_BUS.register(new DropHandler());
	}

	@SubscribeEvent
	public void dropBook(LivingDropsEvent event) {
		if (!NemesisConfig.DISCOVERY_ENABLED) {
			return;
		}

		if (!(event.getEntity() instanceof EntityMob)) {
			return;
		}

		if (event.getEntity().getTags().contains(TAG_NEMESIS)) {
			return;
		}

		if (!(event.getSource().getTrueSource() instanceof EntityPlayer)) {
			return;
		}

		World world = event.getEntity().getEntityWorld();

		int dropChance = dropChance(event.getEntity());

		if (dropChance != 0 && (dropChance < 0 || world.rand.nextInt(dropChance) != 0)) {
			return;
		}

		BlockPos position = event.getEntity().getPosition();
		event.getDrops().add(new EntityItem(world, position.getX(), position.getY(), position.getZ(), DiscoveryUtil.createUnreadBook()));
	}

	private static int dropChance(Entity entity) {
		if (entity.getTags().contains(TAG_BODY_GUARD)) {
			return NemesisConfig.BOOK_DROP_CHANCE_BODY_GUARD;
		}
		if (entity instanceof EntityMob) {
			return NemesisConfig.BOOK_DROP_CHANCE_MOB;
		}
		return -1;
	}
} public class LootHandler {

	public static final ResourceLocation LOOT_TABLE = new ResourceLocation(NemesisSystem.MODID, "nemesis_loot");

	public static void init() {
		MinecraftForge.EVENT_BUS.register(new LootHandler());
	}

	@SubscribeEvent
	public void lootTableLoad(final LootTableLoadEvent event) {
		if (NemesisConfig.DISCOVERY_ENABLED && isLootTarget(event)) {

			// TODO improve roll settings

			String name = LOOT_TABLE.toString();
			LootEntry entry = new LootEntryTable(LOOT_TABLE, 1, 0, new LootCondition[0], name);

			RandomValueRange rolls = new RandomValueRange(0, 1);
			LootPool pool = new LootPool(new LootEntry[] { entry }, new LootCondition[0], rolls, rolls, name);
			event.getTable().addPool(pool);
		}
	}

	private boolean isLootTarget(LootTableLoadEvent event) {
		return event.getName().equals(LootTableList.CHESTS_END_CITY_TREASURE)
				|| event.getName().equals(LootTableList.CHESTS_SIMPLE_DUNGEON)
				|| event.getName().equals(LootTableList.CHESTS_VILLAGE_BLACKSMITH)
				|| event.getName().equals(LootTableList.CHESTS_ABANDONED_MINESHAFT)
				|| event.getName().equals(LootTableList.CHESTS_NETHER_BRIDGE)
				|| event.getName().equals(LootTableList.CHESTS_STRONGHOLD_LIBRARY)
				|| event.getName().equals(LootTableList.CHESTS_STRONGHOLD_CROSSING)
				|| event.getName().equals(LootTableList.CHESTS_STRONGHOLD_CORRIDOR)
				|| event.getName().equals(LootTableList.CHESTS_DESERT_PYRAMID)
				|| event.getName().equals(LootTableList.CHESTS_JUNGLE_TEMPLE)
				|| event.getName().equals(LootTableList.CHESTS_JUNGLE_TEMPLE_DISPENSER)
				|| event.getName().equals(LootTableList.CHESTS_IGLOO_CHEST)
				|| event.getName().equals(LootTableList.CHESTS_WOODLAND_MANSION);
	}

} public class NemesisHandler {

	private static final String PREFIX = "notifications";

	@SubscribeEvent
	public void discovery(DiscoveryEvent ev) {
		
	}

	@SubscribeEvent
	public void promotion(PromotionEvent ev) {
		send("promotion", title(ev), ev.nemesis.getLevel());
		ev.nemesis.addToHistory(LogEntry.PROMOTION(ev.nemesis.getLevel()));
	}

	@SubscribeEvent
	public void demotion(DemotionEvent ev) {
		send("demotion", title(ev), ev.nemesis.getLevel(), ev.getSlayerName());
		ev.nemesis.addToHistory(LogEntry.PROMOTION(ev.nemesis.getLevel()));
	}

	@SubscribeEvent
	public void duel(DuelEvent ev) {
		send("duelIfCrowded", title(ev), ev.getLoser().getNameAndTitle());
		ev.getWinner().addToHistory(LogEntry.DUEL_WIN(ev.getLoser().getNameAndTitle()));
		ev.getLoser().addToHistory(LogEntry.DUEL_LOSS(ev.getWinner().getNameAndTitle()));
	}

	@SubscribeEvent
	public void register(RegisterEvent ev) {
		send("register", title(ev), ev.nemesis.getX(), ev.nemesis.getZ());
		ev.nemesis.addToHistory(LogEntry.CREATION(ev.nemesis.getX(), ev.nemesis.getZ()));
	}

	@SubscribeEvent
	public void death(DeathEvent event) {
		send("death", title(event), event.getSlayerName());
		event.nemesis.addToHistory(LogEntry.DIED(event.getSlayerName()));
	}

	@SubscribeEvent
	public void death(SpawnEvent event) {
		send("spawned", title(event));
		event.nemesis.addToHistory(LogEntry.SPAWNED());
	}

	private String title(NemesisEvent event) {
		return event.nemesis.getNameAndTitle();
	}

	private void send(String translationKey, Object... args) {
		TextComponentTranslation m = new TextComponentTranslation(PREFIX + "." + translationKey, args);
		m.setStyle(new Style());
		m.getStyle().setColor(TextFormatting.RED);
		sendGlobalMessage(m);
	}

	private void sendGlobalMessage(ITextComponent message) {
		if (NemesisSystem.SERVER == null) {
			return;
		}
		try {
			NemesisSystem.SERVER.getPlayerList().getPlayers().forEach(player -> player.sendMessage(message));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
} public class Reaper {

	private static final long MAX_UNLOAD_TIME = 1000;

	public static void init() {
		MinecraftForge.EVENT_BUS.register(new Reaper());
	}

	@SubscribeEvent
	public void cleanUp(WorldTickEvent event) {
		if (event.world.getTotalWorldTime() % 20 != 0) {
			return;
		}
		despawnLogic(event, NemesisRegistryProvider.get(event.world));
	}

	private void despawnLogic(WorldTickEvent event, INemesisRegistry registry) {
		registry.list().forEach((NemesisEntry nemesis) -> {
			if (nemesis.isSpawned()) {
				if (shouldBeDespawned(event.world, nemesis)) {
					despawnNemesis(event.world, registry, nemesis);
				} else {
					unloadNemesis(event.world, registry, nemesis);
				}
			}
		});
	}

	private void despawnNemesis(World world, INemesisRegistry registry, NemesisEntry nemesis) {
		// TODO increase NemesisEntry level every time they spawn but are not killed
		Entity entity = world.getEntityByID(nemesis.getSpawned());
		if (entity != null) {
			entity.setDead();
			world.removeEntity(entity);
		}
		nemesis.setSpawned(null);
		registry.update(nemesis);
	}

	private boolean shouldBeDespawned(World world, NemesisEntry nemesis) {
		if (nemesis.isLoaded()) {
			return false;
		}
		return (world.getTotalWorldTime() - nemesis.getUnloaded()) > MAX_UNLOAD_TIME;
	}

	private void unloadNemesis(World world, INemesisRegistry registry, NemesisEntry nemesis) {
		Entity entity = world.getEntityByID(nemesis.getSpawned());

		if (nemesis.isLoaded() && entity == null) {
			nemesis.setUnloaded(world.getTotalWorldTime());
			registry.update(nemesis);
			return;
		}

		if (entity == null) {
			return;
		}

		boolean playersNear = NemesisUtil.findPlayersAround(world, entity.getPosition(), 100).size() > 0;

		if (nemesis.isLoaded() && !playersNear) {
			nemesis.setUnloaded(world.getTotalWorldTime());
			registry.update(nemesis);
			return;
		}

		if (!nemesis.isLoaded() && playersNear) {
			nemesis.setUnloaded(null);
			registry.update(nemesis);
			return;
		}
	}

} public class SetAttackTargetHandler {

	public static void init() {
		MinecraftForge.EVENT_BUS.register(new SetAttackTargetHandler());
	}

	@SubscribeEvent
	public void stopBodyGuardsFromAttackingNemeses(LivingSetAttackTargetEvent event) {
		World world = event.getEntity().getEntityWorld();

		if (world.isRemote) {
			return;
		}

		Entity entity = event.getEntity();

		if (event.getTarget() == null || !(event.getEntity() instanceof EntityCreature)) {
			return;
		}

		if (hasTag(entity, NemesisSystem.TAG_BODY_GUARD) || hasTag(entity, ToroTraits.TAG_SUMMONED_MOB)) {
			if (event.getTarget().getTags().contains(NemesisSystem.TAG_NEMESIS)) {
				((EntityCreature) event.getEntityLiving()).setAttackTarget(null);
			}
		}
	}

	private static boolean hasTag(Entity e, String tag) {
		return e.getTags().contains(tag);
	}

} public class SpawnHandler {

	private static final int BUFF_CHANCE = 4;
	private static final int SPAWN_CHANCE = 2;
	private static final int MAX_SPAWN_DISTANCE = 100;
	public static final int SPAWN_COOLDOWN_PERIOD = 16000;

	public static void init() {
		MinecraftForge.EVENT_BUS.register(new SpawnHandler());
	}

	@SubscribeEvent
	public void handleSpawn(EntityJoinWorldEvent event) {

		if (event.getWorld().isRemote) {
			requestNemesisDataFromServer(event);
			return;
		}

		if (!(event.getEntity() instanceof EntityCreature) || !NemesisUtil.isNemesisClassEntity(event.getEntity())) {
			return;
		}

		EntityCreature entity = (EntityCreature) event.getEntity();

		NemesisActions.handleRandomPromotions(event.getWorld(), entity);

		if (entity.getTags().contains(NemesisSystem.TAG_NEMESIS)) {
			handleRespawnOfNemesis(event);
			return;
		}

		if (entity.getTags().contains(NemesisSystem.TAG_BODY_GUARD)) {
			BehaviorApi.setFollowSpeed(entity, 2);
			return;
		}

		NemesisEntry nemesis = getNemesisForSpawn(event);

		if (nemesis != null) {
			replaceEntityWithNemesis(entity, nemesis);
			MinecraftForge.EVENT_BUS.post(new SpawnEvent(nemesis, entity));
			return;
		}

		buffMobInAroundNemeses(event.getWorld(), entity);
	}

	private void buffMobInAroundNemeses(World world, EntityCreature entity) {
		if (!(entity instanceof EntityMob)) {
			return;
		}

		if (entity.getTags().contains(NemesisSystem.TAG_BUFF_MOB_REINFORCEMENT)) {
			return;
		}

		List<NemesisEntry> nemeses = NemesisRegistryProvider.get(world).list();
		sortByHighestLevel(nemeses);

		for (NemesisEntry nemesis : nemeses) {
			if (buffEntity(entity, nemesis)) {
				return;
			}
		}
	}

	private boolean buffEntity(EntityCreature entity, NemesisEntry nemesis) {

		if (entity.getRNG().nextInt(BUFF_CHANCE) != 0) {
			return false;
		}

		int buffAmount = determineBuffAmount(nemesis, entity.getPosition());

		if (buffAmount < 1) {
			return false;
		}

		updateSharedMonsterAttributes(entity, buffAmount);
		equipMob(entity, buffAmount);
		entity.addTag(NemesisSystem.TAG_BUFFED_MOB);

		spawnReinforcements(entity, buffAmount);

		return true;
	}

	private void equipMob(EntityCreature entity, int buffAmount) {
		int weaponRoll = entity.getRNG().nextInt(buffAmount);
		if (weaponRoll >= 5) {
			entity.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.DIAMOND_SWORD));
			entity.setItemStackToSlot(EntityEquipmentSlot.OFFHAND, new ItemStack(Items.DIAMOND_SWORD));
		} else if (weaponRoll >= 4) {
			entity.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.DIAMOND_SWORD));
		} else if (weaponRoll >= 3) {
			entity.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SWORD));
		} else if (weaponRoll >= 2) {
			entity.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.STONE_SWORD));
		} else {
			entity.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.WOODEN_SWORD));
		}

		int chestPlateRoll = entity.getRNG().nextInt(buffAmount);
		if (chestPlateRoll >= 5) {
			entity.setItemStackToSlot(EntityEquipmentSlot.CHEST, new ItemStack(Items.DIAMOND_CHESTPLATE));
		} else if (chestPlateRoll >= 4) {
			entity.setItemStackToSlot(EntityEquipmentSlot.CHEST, new ItemStack(Items.IRON_CHESTPLATE));
		} else if (chestPlateRoll >= 3) {
			entity.setItemStackToSlot(EntityEquipmentSlot.CHEST, new ItemStack(Items.GOLDEN_CHESTPLATE));
		}

	}

	private void updateSharedMonsterAttributes(EntityCreature entity, int buffAmount) {
		for (IAttributeInstance attribute : entity.getAttributeMap().getAllAttributes()) {
			if (attribute.getAttribute() == SharedMonsterAttributes.ATTACK_DAMAGE) {
				attribute.setBaseValue(determineAttackDamage(attribute.getAttributeValue(), buffAmount));
			}

			if (attribute.getAttribute() == SharedMonsterAttributes.MAX_HEALTH) {
				attribute.setBaseValue(determineMaxHealth(attribute.getAttributeValue(), buffAmount));
				entity.setHealth(entity.getMaxHealth());
			}
		}
	}

	protected static double determineAttackDamage(double initial, int buffAmount) {
		return initial * (1 + (double) buffAmount / 6);
	}

	protected static double determineMaxHealth(double initial, int buffAmount) {
		return initial * (1 + (double) buffAmount / 4);
	}

	protected static void sortByHighestLevel(List<NemesisEntry> nemeses) {
		nemeses.sort((a, b) -> b.getLevel() - a.getLevel());
	}

	protected static int determineBuffAmount(NemesisEntry nemesis, BlockPos pos) {
		if (nemesis.isDead()) {
			return 0;
		}

		double distance = getDistance(pos, nemesis.getX(), nemesis.getZ()) + 0.0001;
		double range = nemesis.getRange() * (1 + (nemesis.getLevel() / 2));

		if (distance > range) {
			return 0;
		}
		return (int) (range - distance + 1) / nemesis.getRange();
	}

	private void spawnReinforcements(EntityCreature entity, int buffAmount) {
		int roll = entity.getRNG().nextInt(buffAmount);
		for (int i = 0; i < roll; i++) {
			EntityCreature reinforcement = SpawnApi.getEntityFromString(entity.world, SpawnApi.getEntityString(entity));
			reinforcement.addTag(NemesisSystem.TAG_BUFF_MOB_REINFORCEMENT);
			SpawnApi.spawnEntityCreature(entity.world, reinforcement, entity.getPosition(), 10);
		}
	}

	protected static double getDistance(BlockPos pos, int x, int z) {
		double d0 = pos.getX() - x;
		double d2 = pos.getZ() - z;
		return (double) MathHelper.sqrt(d0 * d0 + d2 * d2);
	}

	private void requestNemesisDataFromServer(EntityJoinWorldEvent event) {
		if (event.getEntity() instanceof EntityCreature) {
			if (!event.getEntity().getTags().contains(NemesisSystem.TAG_NEMESIS)) {
				NemesisSystem.NETWORK.sendToServer(new MessageSyncNemesisRequest(event.getEntity().getPersistentID()));
			}
		}
	}

	private void replaceEntityWithNemesis(EntityCreature entity, NemesisEntry nemesis) {
		entity.setDead();
		spawnNemesis(entity.world, entity.getPosition(), nemesis);
	}

	private void handleRespawnOfNemesis(EntityJoinWorldEvent event) {
		Entity entity = event.getEntity();
		NemesisEntry nemesis = NemesisUtil.loadNemesisFromEntity(event.getEntity());
		if (nemesis == null) {
			/*
			 * missing nemesis data
			 */
			entity.setDead();
			event.setCanceled(true);
		} else if (entity.getTags().contains(NemesisSystem.TAG_SPAWNING)) {
			/*
			 * new nemesis spawn in progress
			 */
			entity.removeTag(NemesisSystem.TAG_SPAWNING);
			nemesis.setSpawned(entity.getEntityId());
			nemesis.setUnloaded(null);
			NemesisRegistryProvider.get(entity.world).update(nemesis);
			sendNemesisDataToClient(nemesis);
		} else if (!nemesis.isSpawned()) {
			/*
			 * nemesis has been marked as despawned
			 */
			event.setCanceled(true);
		} else {
			/*
			 * nemesis is marked unloaded, mark as loaded now he is respawning
			 */
			if (!entity.getPersistentID().equals(nemesis.getEntityUuid())) {
				event.setCanceled(true);
				return;
			}

			nemesis.setSpawned(entity.getEntityId());
			nemesis.setUnloaded(null);
			nemesis.setLastSpawned(entity.world.getTotalWorldTime());
			NemesisRegistryProvider.get(event.getWorld()).update(nemesis);
			sendNemesisDataToClient(nemesis);
		}
	}

	public static void spawnNemesis(World world, BlockPos pos, NemesisEntry nemesis) {
		if (nemesis.isDead()) {
			return;
		}
		if (nemesis.isSpawned()) {
			return;
		}

		String mobType = overrideMobType(nemesis.getMob());

		EntityCreature nemesisEntity = SpawnApi.getEntityFromString(world, mobType);

		if (nemesisEntity == null) {
			return;
		}

		nemesisEntity.setAttackTarget(nemesis.getTargetPlayer());
		nemesisEntity.addTag(NemesisSystem.TAG_SPAWNING);
		if (nemesisEntity instanceof INemesisEntity) {
			((INemesisEntity) nemesisEntity).setNemesis(nemesis);
		}

		EntityDecorator.decorate(nemesisEntity, nemesis);
		SpawnApi.spawnEntityCreature(world, nemesisEntity, pos, 1);

		spawnBodyGuard(nemesisEntity, nemesis);
		nemesisAnnounceEffects(nemesisEntity);

		nemesis.setSpawned(nemesisEntity.getEntityId());
		nemesis.setLastSpawned(world.getTotalWorldTime());
		nemesis.setEntityUuid(nemesisEntity.getPersistentID());
		nemesis.setUnloaded(null);
		NemesisRegistryProvider.get(world).update(nemesis);

		sendNemesisDataToClient(nemesis);
	}

	private static String overrideMobType(String mob) {
		switch (mob) {
		case "minecraft:zombie":
			return NemesisSystem.MODID + ":" + EntityZombieNemesis.NAME;
		case "minecraft:zombie_pigman":
			return NemesisSystem.MODID + ":" + EntityPigZombieNemesis.NAME;
		case "minecraft:zombie_villager":
			return NemesisSystem.MODID + ":" + EntityZombieVillagerNemesis.NAME;
		case "minecraft:husk":
			return NemesisSystem.MODID + ":" + EntityHuskNemesis.NAME;
		case "minecraft:skeleton":
			return NemesisSystem.MODID + ":" + EntitySkeletonNemesis.NAME;
		case "minecraft:stray":
			return NemesisSystem.MODID + ":" + EntityStrayNemesis.NAME;
		}
		return mob;
	}

	private static void sendNemesisDataToClient(NemesisEntry nemesis) {
		NemesisSystem.NETWORK.sendToAll(new MessageSyncNemesis(nemesis));
	}

	private static void nemesisAnnounceEffects(EntityCreature nemesisEntity) {
		World world = nemesisEntity.world;

		if (canSeeSky(nemesisEntity)) {
			world.addWeatherEffect(new EntityLightningBolt(nemesisEntity.world, nemesisEntity.posX, nemesisEntity.posY, nemesisEntity.posZ, true));
		}

		// TODO sound horn
	}

	private static boolean canSeeSky(Entity e) {
		return e.world.canSeeSky(new BlockPos(e.posX, e.posY + (double) e.getEyeHeight(), e.posZ));
	}

	private static void spawnBodyGuard(EntityLiving entity, NemesisEntry nemesis) {

		// TODO use nemesis colors

		int count = 3 + nemesis.getLevel() * 3;

		for (int i = 0; i < count; i++) {
			EntityCreature bodyGuard = new EntityZombie(entity.getEntityWorld());
			bodyGuard.addTag(NemesisSystem.TAG_BODY_GUARD);
			bodyGuard.getEntityData().setUniqueId(NemesisSystem.NBT_NEMESIS_ID, nemesis.getId());
			equipBodyGuard(bodyGuard);
			SpawnApi.spawnEntityCreature(entity.getEntityWorld(), bodyGuard, entity.getPosition(), 10);
			BehaviorApi.setFollowSpeed(bodyGuard, 1.5);
		}
	}

	private static void equipBodyGuard(EntityCreature bodyGuard) {
		int color = 0xffffff;
		bodyGuard.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.STONE_SWORD));
		bodyGuard.setItemStackToSlot(EntityEquipmentSlot.HEAD, colorArmor(new ItemStack(Items.LEATHER_HELMET, 1), color));
		bodyGuard.setItemStackToSlot(EntityEquipmentSlot.CHEST, colorArmor(new ItemStack(Items.LEATHER_CHESTPLATE, 1), color));
		bodyGuard.setItemStackToSlot(EntityEquipmentSlot.LEGS, colorArmor(new ItemStack(Items.LEATHER_LEGGINGS, 1), color));
		bodyGuard.setItemStackToSlot(EntityEquipmentSlot.FEET, colorArmor(new ItemStack(Items.LEATHER_BOOTS, 1), color));
	}

	protected static ItemStack colorArmor(ItemStack stack, int color) {
		ItemArmor armor = (ItemArmor) stack.getItem();
		armor.setColor(stack, color);
		return stack;
	}

	private static NemesisEntry getNemesisForSpawn(EntityEvent event) {

		if (!NemesisUtil.isNemesisClassEntity(event.getEntity())) {
			return null;
		}

		EntityLiving entity = (EntityLiving) event.getEntity();
		World world = entity.world;
		EntityPlayer targetPlayer = findVisiblePlayer(entity);

		if (targetPlayer == null) {
			return null;
		}

		if (world.rand.nextInt(SPAWN_CHANCE) != 0) {
			return null;
		}

		List<NemesisEntry> nemeses = NemesisRegistryProvider.get(event.getEntity().world).list();
		nemeses.removeIf(NemesisEntry::isSpawned);
		nemeses.removeIf(NemesisEntry::isDead);
		nemeses.removeIf((NemesisEntry n) -> notReadyToSpawn(world, n));
		nemeses.removeIf((NemesisEntry n) -> !inRage(entity, n));

		if (nemeses.size() < 1) {
			return null;
		}

		NemesisEntry selectNemesis = nemeses.get(event.getEntity().world.rand.nextInt(nemeses.size()));
		selectNemesis.setTargetPlayer(targetPlayer);
		return selectNemesis;
	}

	private static boolean inRage(Entity entity, NemesisEntry nemesis) {
		int x = (int) entity.posX;
		int z = (int) entity.posZ;

		int nx = nemesis.getX();
		int nz = nemesis.getZ();

		int r = nemesis.getRange();

		return x < nx + r && x > nx - r && z < nz + r && z > nz - r;
	}

	private static boolean notReadyToSpawn(World world, NemesisEntry n) {
		if (n.getLastSpawned() == null) {
			return false;
		}
		long delay = (n.getLastSpawned() + SPAWN_COOLDOWN_PERIOD) - world.getTotalWorldTime();
		return delay > 0;
	}

	private static boolean otherNemesisNearby(EntityLiving entity, World world) {
		int distance = 100;
		List<EntityLiving> entities = world
				.getEntitiesWithinAABB(EntityLiving.class, new AxisAlignedBB(entity.getPosition()).grow(distance, distance, distance));
		for (EntityLiving e : entities) {
			if (e.getTags().contains(NemesisSystem.TAG_NEMESIS) && !e.isDead) {
				return true;
			}
		}
		return false;
	}

	private static EntityPlayer findVisiblePlayer(EntityLiving entity) {
		int distance = MAX_SPAWN_DISTANCE;

		List<EntityPlayer> players = entity.world.getEntitiesWithinAABB(
				EntityPlayer.class,
				new AxisAlignedBB(entity.getPosition()).grow(distance, distance, distance));

		for (EntityPlayer player : players) {
			if (entity.getEntitySenses().canSee(player)) {
				return player;
			}
		}
		return null;
	}

} public class UpdateHandler {

	public static void init() {
		MinecraftForge.EVENT_BUS.register(new UpdateHandler());
	}

	@SubscribeEvent
	public void livingUpdate(LivingUpdateEvent event) {
		World world = event.getEntity().getEntityWorld();

		if (world.isRemote) {
			return;
		}

		if (world.getTotalWorldTime() % 20 != 0) {
			return;
		}

		if (!(event.getEntity() instanceof EntityLiving)) {
			return;
		}

		if (event.getEntity().getTags().contains(NemesisSystem.TAG_BODY_GUARD)) {
			handleBodyGuardUpdate(event);
		}
	}

	private void handleBodyGuardUpdate(LivingUpdateEvent event) {
		if (!(event.getEntity() instanceof EntityCreature)) {
			return;
		}

		EntityCreature bodyGuard = (EntityCreature) event.getEntity();
		World world = bodyGuard.world;

		if (bodyGuard.getTags().contains(DeathHandler.TAG_RONIN)) {
			flee(bodyGuard);
			return;
		}

		UUID id = bodyGuard.getEntityData().getUniqueId(NemesisSystem.NBT_NEMESIS_ID);

		if (id == null) {
			bodyGuard.addTag(DeathHandler.TAG_RONIN);
			return;
		}

		EntityLiving nemesisEntity = NemesisUtil.findNemesisAround(world, id, bodyGuard.getPosition(), 100);

		if (nemesisEntity == null) {
			bodyGuard.addTag(DeathHandler.TAG_RONIN);
			return;
		}

		followNemesisBoss(bodyGuard, nemesisEntity);
	}

	private void flee(EntityCreature bodyGuard) {
		bodyGuard.removeTag(NemesisSystem.TAG_BODY_GUARD);
		bodyGuard.setAttackTarget(null);
		dropItemsInHands(bodyGuard);
		BehaviorApi.setFollowSpeed(bodyGuard, 2);
		int distance = 1000;
		int degrees = bodyGuard.getRNG().nextInt(360);
		int x = distance * (int) Math.round(Math.cos(Math.toRadians(degrees)));
		int z = distance * (int) Math.round(Math.sin(Math.toRadians(degrees)));
		BlockPos from = bodyGuard.getPosition();
		BlockPos to = new BlockPos(from.getX() + x, from.getY(), from.getZ() + z);
		bodyGuard.setHomePosAndDistance(to, 50);
	}

	private void dropItemsInHands(EntityCreature bodyGuard) {
		Random rand = bodyGuard.getRNG();
		for(ItemStack item : bodyGuard.getHeldEquipment()){
			if (!item.isEmpty()) {
				EntityItem e = DeathHandler.damageAndDrop(bodyGuard, item);
				e.setVelocity(rand.nextDouble() - 0.5, rand.nextDouble() / 1.5, rand.nextDouble() - 0.5);
				bodyGuard.world.spawnEntity(e);
			}
		}
		bodyGuard.setHeldItem(EnumHand.MAIN_HAND, ItemStack.EMPTY);
		bodyGuard.setHeldItem(EnumHand.OFF_HAND, ItemStack.EMPTY);
	}

	private void followNemesisBoss(EntityCreature bodyGuard, EntityLiving nemesisEntity) {
		bodyGuard.setHomePosAndDistance(nemesisEntity.getPosition(), 20);
	}

} public class MessageOpenNemesisDetailsGui implements IMessage {

	private NemesisEntry nemesis;
	private NemesisKnowledge knowledge;

	public static void init(int packetId) {
		NemesisSystem.NETWORK.registerMessage(MessageOpenNemesisDetailsGui.Handler.class, MessageOpenNemesisDetailsGui.class, packetId, Side.CLIENT);
	}

	public MessageOpenNemesisDetailsGui() {

	}

	public MessageOpenNemesisDetailsGui(NemesisEntry nemesis, NemesisKnowledge knowledge) {
		this.nemesis = nemesis;
		this.knowledge = knowledge;
	}

	@Override
	public void fromBytes(ByteBuf buf) {
		nemesis = NemesisEntry.load(ByteBufUtils.readTag(buf));
		knowledge = NemesisKnowledge.load(ByteBufUtils.readTag(buf));
	}

	@Override
	public void toBytes(ByteBuf buf) {
		ByteBufUtils.writeTag(buf, NemesisEntry.save(nemesis));
		ByteBufUtils.writeTag(buf, NemesisKnowledge.save(knowledge));
	}

	public static class Handler implements IMessageHandler<MessageOpenNemesisDetailsGui, IMessage> {
		@Override
		public IMessage onMessage(final MessageOpenNemesisDetailsGui message, MessageContext ctx) {
			Minecraft.getMinecraft().addScheduledTask(() -> work(message));
			return null;
		}

		private static void work(MessageOpenNemesisDetailsGui message) {
			NemesisSystem.NEMESIS = message.nemesis;
			NemesisSystem.KNOWLEDGE = message.knowledge;
			NemesisSystem.PROXY.openGui(NemesisSystemGuiHandler.NEMESIS_DETAILS_GUI);
		}
	}

} public class MessageOpenNemesisDetailsGuiRequest implements IMessage {

	private UUID nemesisId;

	public static void init(int packetId) {
		NemesisSystem.NETWORK
				.registerMessage(MessageOpenNemesisDetailsGuiRequest.Handler.class, MessageOpenNemesisDetailsGuiRequest.class, packetId, Side.SERVER);
	}

	public MessageOpenNemesisDetailsGuiRequest() {

	}

	public MessageOpenNemesisDetailsGuiRequest(UUID nemesisId) {
		this.nemesisId = nemesisId;
	}

	@Override
	public void fromBytes(ByteBuf buf) {
		nemesisId = UUID.fromString(ByteBufUtils.readUTF8String(buf));
	}

	@Override
	public void toBytes(ByteBuf buf) {
		ByteBufUtils.writeUTF8String(buf, nemesisId.toString());
	}

	public static class Handler implements IMessageHandler<MessageOpenNemesisDetailsGuiRequest, IMessage> {
		@Override
		public IMessage onMessage(final MessageOpenNemesisDetailsGuiRequest message, MessageContext ctx) {
			final EntityPlayerMP player = ctx.getServerHandler().player;
			player.getServerWorld().addScheduledTask(() -> sendResponse(message, player));
			return null;
		}

		private static void sendResponse(MessageOpenNemesisDetailsGuiRequest message, EntityPlayerMP player) {
			NemesisEntry nemesis = NemesisRegistryProvider.get(player.world).getById(message.nemesisId);
			NemesisKnowledge knowledge = DiscoveryUtil.getGetPlayerKnowledgeOfNemesis(player, nemesis.getId());
			if (knowledge == null) {
				knowledge = new NemesisKnowledge();
			}
			NemesisSystem.NETWORK.sendTo(new MessageOpenNemesisDetailsGui(nemesis, knowledge), player);
		}
	}
} public class MessageOpenNemesisGui implements IMessage {

	private List<NemesisEntry> nemeses;
	private PlayerKnowledgeBase knowledgeBase;

	public static void init(int packetId) {
		NemesisSystem.NETWORK.registerMessage(MessageOpenNemesisGui.Handler.class, MessageOpenNemesisGui.class, packetId, Side.CLIENT);
	}

	public MessageOpenNemesisGui() {

	}

	public MessageOpenNemesisGui(List<NemesisEntry> nemeses, PlayerKnowledgeBase knowledgeBase) {
		this.nemeses = nemeses;
		this.knowledgeBase = knowledgeBase;
	}

	public MessageOpenNemesisGui(EntityPlayerMP player) {
		List<NemesisEntry> nemeses = NemesisRegistryProvider.get(player.getEntityWorld()).list();
		nemeses.removeIf(NemesisEntry::isDead);
		this.nemeses = nemeses;
		this.knowledgeBase = PlayerKnowledgeBase.get(player);
	}

	@Override
	public void fromBytes(ByteBuf buf) {
		nemeses = NemesisRegistry.readNemesesFromNBT(ByteBufUtils.readTag(buf));
		knowledgeBase = PlayerKnowledgeBase.load(ByteBufUtils.readTag(buf));
	}

	@Override
	public void toBytes(ByteBuf buf) {
		NBTTagCompound c = new NBTTagCompound();
		NemesisRegistry.writeNemesesToNBT(c, nemeses);
		ByteBufUtils.writeTag(buf, c);
		ByteBufUtils.writeTag(buf, PlayerKnowledgeBase.save(knowledgeBase));
	}

	public static class Handler implements IMessageHandler<MessageOpenNemesisGui, IMessage> {
		@Override
		public IMessage onMessage(final MessageOpenNemesisGui message, MessageContext ctx) {
			Minecraft.getMinecraft().addScheduledTask(() -> work(message));
			return null;
		}

		public static void work(MessageOpenNemesisGui message) {
			NemesisSystem.NEMESES = message.nemeses;
			NemesisSystem.KNOWLEDGE_BASE = message.knowledgeBase;

			//EntityPlayer player = Minecraft.getMinecraft().player;
			//player.openGui(NemesisSystem.INSTANCE, NemesisSystemGuiHandler.NEMESIS_LIST_GUI, player.world, (int) player.posX, (int) player.posY, (int) player.posZ);
			NemesisSystem.PROXY.openGui(NemesisSystemGuiHandler.NEMESIS_LIST_GUI);
		}
	}
} public class MessageOpenNemesisGuiRequest implements IMessage {

	public static void init(int packetId) {
		NemesisSystem.NETWORK.registerMessage(MessageOpenNemesisGuiRequest.Handler.class, MessageOpenNemesisGuiRequest.class, packetId, Side.SERVER);
	}

	public MessageOpenNemesisGuiRequest() {

	}

	@Override
	public void fromBytes(ByteBuf buf) {

	}

	@Override
	public void toBytes(ByteBuf buf) {

	}

	public static class Handler implements IMessageHandler<MessageOpenNemesisGuiRequest, IMessage> {
		@Override
		public IMessage onMessage(final MessageOpenNemesisGuiRequest message, MessageContext ctx) {
			final EntityPlayerMP player = ctx.getServerHandler().player;
			player.getServerWorld().addScheduledTask(() -> sendResponse(player));
			return null;
		}

		private static void sendResponse(EntityPlayerMP player) {
			NemesisSystem.NETWORK.sendTo(new MessageOpenNemesisGui(player), player);
		}
	}

} public class MessageSyncNemesis implements IMessage {

	private NemesisEntry nemesis;

	public static void init(int packetId) {
		NemesisSystem.NETWORK.registerMessage(MessageSyncNemesis.Handler.class, MessageSyncNemesis.class, packetId, Side.CLIENT);
	}

	public MessageSyncNemesis() {

	}

	public MessageSyncNemesis(NemesisEntry nemesis) {
		this.nemesis = nemesis;
	}

	public MessageSyncNemesis(EntityPlayer player) {
		List<NemesisEntry> nemeses = NemesisRegistryProvider.get(player.getEntityWorld()).list();
		nemeses.removeIf(NemesisEntry::isDead);
	}

	@Override
	public void fromBytes(ByteBuf buf) {
		nemesis = new NemesisEntry();
		nemesis.readFromNBT(ByteBufUtils.readTag(buf));
	}

	@Override
	public void toBytes(ByteBuf buf) {
		NBTTagCompound c = new NBTTagCompound();
		nemesis.writeToNBT(c);
		ByteBufUtils.writeTag(buf, c);
	}

	public static class Handler implements IMessageHandler<MessageSyncNemesis, IMessage> {
		@Override
		public IMessage onMessage(final MessageSyncNemesis message, MessageContext ctx) {
			Minecraft.getMinecraft().addScheduledTask(() -> work(message));
			return null;
		}

		public static void work(MessageSyncNemesis message) {
			NemesisEntry nemesis = message.nemesis;

			if (nemesis == null) {
				return;
			}

			Entity entity = Minecraft.getMinecraft().world.getEntityByID(nemesis.getSpawned());

			if (entity == null) {
				return;
			}

			entity.addTag(NemesisSystem.TAG_NEMESIS);
		}
	}

} public class MessageSyncNemesisRequest implements IMessage {

	private UUID entityUuid;

	public static void init(int packetId) {
		NemesisSystem.NETWORK.registerMessage(MessageSyncNemesisRequest.Handler.class, MessageSyncNemesisRequest.class, packetId, Side.SERVER);
	}

	public MessageSyncNemesisRequest() {

	}

	public MessageSyncNemesisRequest(UUID entityUuid) {
		this.entityUuid = entityUuid;
	}

	@Override
	public void fromBytes(ByteBuf buf) {
		entityUuid = UUID.fromString(ByteBufUtils.readUTF8String(buf));
	}

	@Override
	public void toBytes(ByteBuf buf) {
		ByteBufUtils.writeUTF8String(buf, entityUuid.toString());
	}

	public static class Handler implements IMessageHandler<MessageSyncNemesisRequest, IMessage> {
		@Override
		public IMessage onMessage(final MessageSyncNemesisRequest message, MessageContext ctx) {
			final EntityPlayerMP player = ctx.getServerHandler().player;
			player.getServerWorld().addScheduledTask(() -> sendResponse(player, message));
			return null;
		}

		private static void sendResponse(EntityPlayerMP player, MessageSyncNemesisRequest message) {

			UUID entityUuid = message.entityUuid;

			if (entityUuid == null) {
				return;
			}

			List<NemesisEntry> nemeses = NemesisRegistryProvider.get(player.world).list();

			for (NemesisEntry nemesis : nemeses) {
				if (entityUuid.equals(nemesis.getEntityUuid())) {
					sendPacketToClient(player, nemesis);
					return;
				}
			}
		}

		private static void sendPacketToClient(EntityPlayerMP player, NemesisEntry nemesis) {
			updateNameTag(player, nemesis);
			NemesisSystem.NETWORK.sendTo(new MessageSyncNemesis(nemesis), player);
		}

		private static void updateNameTag(EntityPlayerMP player, NemesisEntry nemesis) {
			Entity entity = player.world.getEntityByID(nemesis.getSpawned());
			if (entity != null) {
				entity.setCustomNameTag(nemesis.getNameAndTitle());
			}
		}
	}
}
import java.io.File;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.torocraft.nemesissystem.NemesisConfig;
import net.torocraft.nemesissystem.NemesisSystem;
import net.torocraft.nemesissystem.entities.Entities;
import net.torocraft.nemesissystem.handlers.AttackHandler;
import net.torocraft.nemesissystem.handlers.DeathHandler;
import net.torocraft.nemesissystem.handlers.DiscoveryHandler;
import net.torocraft.nemesissystem.handlers.DropHandler;
import net.torocraft.nemesissystem.handlers.LootHandler;
import net.torocraft.nemesissystem.handlers.NemesisHandler;
import net.torocraft.nemesissystem.handlers.Reaper;
import net.torocraft.nemesissystem.handlers.SetAttackTargetHandler;
import net.torocraft.nemesissystem.handlers.SpawnHandler;
import net.torocraft.nemesissystem.handlers.UpdateHandler;
import net.torocraft.nemesissystem.network.MessageOpenNemesisDetailsGui;
import net.torocraft.nemesissystem.network.MessageOpenNemesisDetailsGuiRequest;
import net.torocraft.nemesissystem.network.MessageOpenNemesisGui;
import net.torocraft.nemesissystem.network.MessageOpenNemesisGuiRequest;
import net.torocraft.nemesissystem.network.MessageSyncNemesis;
import net.torocraft.nemesissystem.network.MessageSyncNemesisRequest;
import net.torocraft.torotraits.ToroTraits;
package net.torocraft.nemesissystem.proxy;


public class CommonProxy {

	public void preInit(FMLPreInitializationEvent e) {
		checkToroTraitsVersion();
		initConfig(e.getSuggestedConfigurationFile());
		MinecraftForge.EVENT_BUS.register(new NemesisHandler());
		Entities.init();