package mhfc.net.common.world.exploration; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; import mhfc.net.MHFCMain; import mhfc.net.common.core.registry.MHFCExplorationRegistry; import mhfc.net.common.quests.world.GlobalAreaManager; import mhfc.net.common.quests.world.QuestFlair; import mhfc.net.common.world.AreaTeleportation; import mhfc.net.common.world.area.IActiveArea; import mhfc.net.common.world.area.IAreaType; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.util.ChatComponentText; public abstract class ExplorationAdapter implements IExplorationManager { protected Set<EntityPlayerMP> playerSet; protected Map<EntityPlayerMP, IActiveArea> playerToArea; private Map<IAreaType, List<IActiveArea>> areaInstances; private Map<IActiveArea, Set<EntityPlayerMP>> inhabitants; protected Map<EntityPlayerMP, CompletionStage<IActiveArea>> waitingOnTeleport; public ExplorationAdapter() { playerSet = new HashSet<>(); playerToArea = new HashMap<>(); areaInstances = new HashMap<>(); inhabitants = new IdentityHashMap<>(); waitingOnTeleport = new HashMap<>(); } protected abstract QuestFlair getFlairFor(IAreaType type); protected Set<EntityPlayerMP> getInhabitants(IActiveArea activeArea) { inhabitants.putIfAbsent(activeArea, new HashSet<>()); return inhabitants.get(activeArea); } protected List<IActiveArea> getAreasOfType(IAreaType type) { return areaInstances.getOrDefault(type, new ArrayList<>()); } protected abstract void transferIntoInstance(EntityPlayerMP player, IAreaType type, Consumer<IActiveArea> callback); @Override public void transferPlayerInto(EntityPlayerMP player, IAreaType type, Consumer<IActiveArea> callback) { if (waitingOnTeleport.containsKey(player)) { playerAlreadyTeleporting(player, type, callback); } else { transferIntoInstance(player, type, callback); } } protected void playerAlreadyTeleporting(EntityPlayerMP player, IAreaType type, Consumer<IActiveArea> callback) { Objects.requireNonNull(player); CompletionStage<IActiveArea> waitingFor = waitingOnTeleport.get(player); waitingFor.toCompletableFuture().cancel(true); waitingOnTeleport.remove(player); transferPlayerInto(player, type, callback); } protected void transferIntoNewInstance(EntityPlayerMP player, IAreaType type, Consumer<IActiveArea> callback) { player.addChatMessage(new ChatComponentText("Teleporting to instance when the area is ready")); Objects.requireNonNull(player); Objects.requireNonNull(type); CompletionStage<IActiveArea> unusedInstance = GlobalAreaManager.getInstance() .getUnusedInstance(type, getFlairFor(type)); waitingOnTeleport.put(player, unusedInstance); unusedInstance.handle((area, ex) -> { try { if (area != null) { addInstance(area); transferIntoInstance(player, area); } else { MHFCMain.logger().debug("Canceled teleport to area due to cancellation of area"); } } catch (Exception exception) { MHFCMain.logger().error("Error during transfer into {}, releasing player from exploration manager", area); MHFCExplorationRegistry.releasePlayer(player); if (area != null) { removeInstance(area); area = null; } } finally { waitingOnTeleport.remove(player); callback.accept(area); } return area; }); } protected void removePlayerFromInstance(EntityPlayerMP player) { IActiveArea currentInstance = getActiveAreaOf(player); Set<EntityPlayerMP> inhabitantSet = getInhabitants(currentInstance); inhabitantSet.remove(player); CompletionStage<IActiveArea> stagedFuture = waitingOnTeleport.get(player); if (stagedFuture != null) { stagedFuture.toCompletableFuture().cancel(true); } if (currentInstance == null) { return; } if (inhabitantSet.isEmpty()) { removeInstance(currentInstance); } } protected void transferIntoInstance(EntityPlayerMP player, IActiveArea area) { Objects.requireNonNull(player); Objects.requireNonNull(area); removePlayerFromInstance(player); playerToArea.put(player, area); Set<EntityPlayerMP> inhabitantSet = getInhabitants(area); inhabitantSet.add(player); AreaTeleportation.movePlayerToArea(player, area.getArea()); } protected void addInstance(IActiveArea activeArea) { Objects.requireNonNull(activeArea); MHFCMain.logger().debug( "Adding active area instance {} of type {} to exploration manager", activeArea, activeArea.getType()); inhabitants.put(activeArea, new HashSet<>()); getAreasOfType(activeArea.getType()).add(activeArea); } protected void removeInstance(IActiveArea activeArea) { Objects.requireNonNull(activeArea); MHFCMain.logger().debug( "Removing active area instance {} of type {} from exploration manager", activeArea, activeArea.getType()); inhabitants.remove(activeArea); getAreasOfType(activeArea.getType()).remove(activeArea); activeArea.close(); } @Override public IActiveArea getActiveAreaOf(EntityPlayerMP player) { Objects.requireNonNull(player); return playerToArea.get(player); } protected abstract void respawnWithoutInstance(EntityPlayerMP player); protected abstract void respawnInInstance(EntityPlayerMP player, IActiveArea instance); protected void throwOnIllegalPlayer(EntityPlayerMP player) throws IllegalArgumentException { Objects.requireNonNull(player); if (!playerSet.contains(player)) { throw new IllegalArgumentException("Player is not managed by exploration manager " + this.toString()); } } @Override public void respawn(EntityPlayerMP player) throws IllegalArgumentException { Objects.requireNonNull(player); throwOnIllegalPlayer(player); if (!playerToArea.containsKey(player)) { transferPlayerInto(player, initialAreaType(player), (t) -> {}); return; } IActiveArea activeAreaOf = getActiveAreaOf(player); if (activeAreaOf == null) { respawnWithoutInstance(player); } else { respawnInInstance(player, activeAreaOf); } } protected abstract IAreaType initialAreaType(EntityPlayerMP player); @Override public void onPlayerRemove(EntityPlayerMP player) { Objects.requireNonNull(player); playerSet.remove(player); removePlayerFromInstance(player); } @Override public void onPlayerAdded(EntityPlayerMP player) { Objects.requireNonNull(player); playerSet.add(player); } @Override public void initialAddPlayer(EntityPlayerMP player) throws IllegalArgumentException { throwOnIllegalPlayer(player); } }