package mcjty.rftools.dimension; import mcjty.lib.varia.Coordinate; import mcjty.lib.varia.Logging; import mcjty.rftools.GeneralConfiguration; import mcjty.rftools.blocks.dimlets.DimletConfiguration; import mcjty.rftools.blocks.dimlets.DimletSetup; import mcjty.rftools.dimension.description.DimensionDescriptor; import mcjty.rftools.dimension.network.PacketCheckDimletConfig; import mcjty.rftools.dimension.network.PacketSyncDimensionInfo; import mcjty.rftools.dimension.world.GenericWorldProvider; import mcjty.rftools.items.dimensionmonitor.PhasedFieldGeneratorItem; import mcjty.rftools.items.dimlets.DimletKey; import mcjty.rftools.items.dimlets.DimletMapping; import mcjty.rftools.items.dimlets.KnownDimletConfiguration; import mcjty.rftools.network.PacketRegisterDimensions; import mcjty.rftools.network.RFToolsMessages; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.server.MinecraftServer; import net.minecraft.util.MathHelper; import net.minecraft.world.World; import net.minecraft.world.WorldSavedData; import net.minecraft.world.WorldServer; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.gen.ChunkProviderServer; import net.minecraftforge.common.DimensionManager; import net.minecraftforge.common.util.Constants; import java.util.*; public class RfToolsDimensionManager extends WorldSavedData { public static final String DIMMANAGER_NAME = "RFToolsDimensionManager"; private static RfToolsDimensionManager instance = null; private final Map<Integer, DimensionDescriptor> dimensions = new HashMap<Integer, DimensionDescriptor>(); private final Map<DimensionDescriptor, Integer> dimensionToID = new HashMap<DimensionDescriptor, Integer>(); private final Map<Integer, DimensionInformation> dimensionInformation = new HashMap<Integer, DimensionInformation>(); private final Set<Integer> reclaimedIds = new HashSet<Integer>(); public void syncFromServer(Map<Integer, DimensionDescriptor> dims, Map<Integer, DimensionInformation> dimInfo) { Logging.log("RfToolsDimensionManager.syncFromServer"); if (dims.isEmpty() || dimInfo.isEmpty()) { Logging.log("Dimension information from server is empty."); } for (Map.Entry<Integer, DimensionDescriptor> entry : dims.entrySet()) { int id = entry.getKey(); DimensionDescriptor descriptor = entry.getValue(); if (dimensions.containsKey(id)) { dimensionToID.remove(dimensions.get(id)); } dimensions.put(id, descriptor); dimensionToID.put(descriptor, id); } for (Map.Entry<Integer, DimensionInformation> entry : dimInfo.entrySet()) { int id = entry.getKey(); DimensionInformation info = entry.getValue(); dimensionInformation.put(id, info); } } public RfToolsDimensionManager(String identifier) { super(identifier); } public static void clearInstance() { if (instance != null) { instance.dimensions.clear(); instance.dimensionToID.clear(); instance.dimensionInformation.clear(); instance.reclaimedIds.clear(); instance = null; } } public static void cleanupDimensionInformation() { if (instance != null) { Logging.log("Cleaning up RFTools dimensions"); unregisterDimensions(); instance.getDimensions().clear(); instance.dimensionToID.clear(); instance.dimensionInformation.clear(); instance.reclaimedIds.clear(); instance = null; } } public static void unregisterDimensions() { if (instance == null) { return; } for (Map.Entry<Integer, DimensionDescriptor> me : instance.getDimensions().entrySet()) { int id = me.getKey(); if (DimensionManager.isDimensionRegistered(id)) { Logging.log(" Unregister dimension: " + id); try { DimensionManager.unregisterDimension(id); } catch (Exception e) { // We ignore this error. Logging.log(" Could not unregister dimension: " + id); } try { DimensionManager.unregisterProviderType(id); } catch (Exception e) { // We ignore this error. Logging.log(" Could not unregister provider: " + id); } } else { Logging.log(" Already unregistered! Dimension: " + id); } } } public void save(World world) { world.mapStorage.setData(DIMMANAGER_NAME, this); markDirty(); syncDimInfoToClients(world); } public void reclaimId(int id) { reclaimedIds.add(id); } /** * Freeze a dimension: avoid ticking all tile entities and remove all * active entities (they are still there but will not do anything). * Entities that are within range of a player having a PFG will be kept * active (but not tile entities). */ public static void freezeDimension(World world) { // First find all players that have a valid PFG. List<Coordinate> pfgList = new ArrayList<Coordinate>(); int radius = DimletConfiguration.phasedFieldGeneratorRange; if (radius > 0) { for (Object ent : world.playerEntities) { EntityPlayer player = (EntityPlayer) ent; // Check if this player has a valid PFG but don't consume energy. int cost = 0; if (DimletConfiguration.dimensionDifficulty != -1) { RfToolsDimensionManager dimensionManager = RfToolsDimensionManager.getDimensionManager(world); DimensionInformation information = dimensionManager.getDimensionInformation(world.provider.dimensionId); cost = information.getActualRfCost(); if (cost == 0) { DimensionDescriptor descriptor = dimensionManager.getDimensionDescriptor(world.provider.dimensionId); cost = descriptor.getRfMaintainCost(); } } if (checkValidPhasedFieldGenerator(player, false, cost)) { pfgList.add(new Coordinate((int) player.posX, (int) player.posY, (int) player.posZ)); } } } // If there are players with a valid PFG then we check if there are entities we want to keep. List tokeep = new ArrayList(); tokeep.addAll(world.playerEntities); // We want to keep all players for sure. // Add all entities that are within range of a PFG. for (Coordinate coordinate : pfgList) { getEntitiesInSphere(world, coordinate, radius, tokeep); } world.loadedEntityList.clear(); world.loadedEntityList.addAll(tokeep); world.loadedTileEntityList.clear(); } private static void getEntitiesInSphere(World world, Coordinate c, float radius, List tokeep) { int i = MathHelper.floor_double((c.getX() - radius) / 16.0D); int j = MathHelper.floor_double((c.getX() + 1 + radius) / 16.0D); int k = MathHelper.floor_double((c.getZ() - radius) / 16.0D); int l = MathHelper.floor_double((c.getZ() + 1 + radius) / 16.0D); for (int i1 = i; i1 <= j; ++i1) { for (int j1 = k; j1 <= l; ++j1) { if (world.getChunkProvider().chunkExists(i1, j1)) { Chunk chunk = world.getChunkFromChunkCoords(i1, j1); getEntitiesInSphere(chunk, c, radius, tokeep); } } } } private static void getEntitiesInSphere(Chunk chunk, Coordinate c, float radius, List entities) { float squaredRange = radius * radius; int i = MathHelper.floor_double((c.getY() - radius) / 16.0D); int j = MathHelper.floor_double((c.getY() + 1 + radius) / 16.0D); i = MathHelper.clamp_int(i, 0, chunk.entityLists.length - 1); j = MathHelper.clamp_int(j, 0, chunk.entityLists.length - 1); for (int k = i; k <= j; ++k) { List entityList = chunk.entityLists[k]; for (Object o : entityList) { if (!(o instanceof EntityPlayer)) { Entity entity = (Entity) o; float sqdist = c.squaredDistance((int) entity.posX, (int) entity.posY, (int) entity.posZ); if (sqdist < squaredRange) { entities.add(entity); break; } } } } } public static void unfreezeDimension(World world) { WorldServer worldServer = (WorldServer) world; for (Object chunk : worldServer.theChunkProviderServer.loadedChunks) { Chunk c = (Chunk) chunk; unfreezeChunk(c); } } public static void unfreezeChunk(Chunk chunk) { chunk.isChunkLoaded = true; chunk.worldObj.func_147448_a(chunk.chunkTileEntityMap.values()); for (List entityList : chunk.entityLists) { chunk.worldObj.loadedEntityList.addAll(entityList); } } public static boolean checkValidPhasedFieldGenerator(EntityPlayer player, boolean consume, int tickCost) { InventoryPlayer inventory = player.inventory; for (int i = 0 ; i < inventory.getHotbarSize() ; i++) { ItemStack slot = inventory.getStackInSlot(i); if (slot != null && slot.getItem() == DimletSetup.phasedFieldGeneratorItem) { PhasedFieldGeneratorItem pfg = (PhasedFieldGeneratorItem) slot.getItem(); int energyStored = pfg.getEnergyStored(slot); int toConsume; if (GeneralConfiguration.enableDynamicPhaseCost) { toConsume = (int) (DimensionTickEvent.MAXTICKS * tickCost * GeneralConfiguration.dynamicPhaseCostAmount); } else { toConsume = DimensionTickEvent.MAXTICKS * DimletConfiguration.PHASEDFIELD_CONSUMEPERTICK; } if (energyStored >= toConsume) { if (consume) { pfg.extractEnergy(slot, toConsume, false); } return true; } } } return false; } /** * Check if the client dimlet id's match with the server. * This is executed on the server to the clients. */ public void checkDimletConfig(EntityPlayer player) { if (!player.getEntityWorld().isRemote) { // Send over dimlet configuration to the client so that the client can check that the id's match. Logging.log("Send validation data to the client"); DimletMapping mapping = DimletMapping.getDimletMapping(player.getEntityWorld()); Map<Integer, DimletKey> dimlets = new HashMap<Integer, DimletKey>(); for (Integer id : mapping.getIds()) { dimlets.put(id, mapping.getKey(id)); } RFToolsMessages.INSTANCE.sendTo(new PacketCheckDimletConfig(dimlets), (EntityPlayerMP) player); } } /** * Here the information from the server arrives. This code is executed on the client. */ public void checkDimletConfigFromServer(Map<Integer, DimletKey> dimlets, World world) { Logging.log("Getting dimlet mapping from server"); DimletMapping mapping = DimletMapping.getDimletMapping(world); mapping.overrideServerMapping(dimlets); KnownDimletConfiguration.init(world, false); KnownDimletConfiguration.initCrafting(world); } public void syncDimInfoToClients(World world) { if (!world.isRemote) { // Sync to clients. Logging.log("Sync dimension info to clients!"); RFToolsMessages.INSTANCE.sendToAll(new PacketSyncDimensionInfo(dimensions, dimensionInformation)); } } public Map<Integer, DimensionDescriptor> getDimensions() { return dimensions; } public void registerDimensions() { Logging.log("Registering RFTools dimensions"); for (Map.Entry<Integer, DimensionDescriptor> me : dimensions.entrySet()) { int id = me.getKey(); Logging.log(" Dimension: " + id); registerDimensionToServerAndClient(id); } } private void registerDimensionToServerAndClient(int id) { if (!DimensionManager.isDimensionRegistered(id)) { DimensionManager.registerProviderType(id, GenericWorldProvider.class, false); DimensionManager.registerDimension(id, id); } RFToolsMessages.INSTANCE.sendToAll(new PacketRegisterDimensions(id)); } public static RfToolsDimensionManager getDimensionManager(World world) { if (instance != null) { return instance; } instance = (RfToolsDimensionManager) world.mapStorage.loadData(RfToolsDimensionManager.class, DIMMANAGER_NAME); if (instance == null) { instance = new RfToolsDimensionManager(DIMMANAGER_NAME); } return instance; } public DimensionDescriptor getDimensionDescriptor(int id) { return dimensions.get(id); } public Integer getDimensionID(DimensionDescriptor descriptor) { return dimensionToID.get(descriptor); } public DimensionInformation getDimensionInformation(int id) { return dimensionInformation.get(id); } /** * Get a world for a dimension, possibly loading it from the configuration manager. */ public static World getWorldForDimension(int id) { World w = DimensionManager.getWorld(id); if (w == null) { w = MinecraftServer.getServer().getConfigurationManager().getServerInstance().worldServerForDimension(id); } return w; } public void removeDimension(int id) { DimensionDescriptor descriptor = dimensions.get(id); dimensions.remove(id); dimensionToID.remove(descriptor); dimensionInformation.remove(id); if (DimensionManager.isDimensionRegistered(id)) { DimensionManager.unregisterDimension(id); } DimensionManager.unregisterProviderType(id); } public void recoverDimension(World world, int id, DimensionDescriptor descriptor, String name, String playerName, UUID player) { if (!DimensionManager.isDimensionRegistered(id)) { registerDimensionToServerAndClient(id); } DimensionInformation dimensionInfo = new DimensionInformation(name, descriptor, world, playerName, player); dimensions.put(id, descriptor); dimensionToID.put(descriptor, id); dimensionInformation.put(id, dimensionInfo); save(world); touchSpawnChunk(id); } public int countOwnedDimensions(UUID player) { int cnt = 0; for (Map.Entry<Integer, DimensionInformation> entry : dimensionInformation.entrySet()) { int id = entry.getKey(); DimensionInformation information = entry.getValue(); if (player.equals(information.getOwner())) { cnt++; } } return cnt; } public int createNewDimension(World world, DimensionDescriptor descriptor, String name, String playerName, UUID player) { int id = 0; while (!reclaimedIds.isEmpty()) { int rid = reclaimedIds.iterator().next(); reclaimedIds.remove(rid); if (!DimensionManager.isDimensionRegistered(rid)) { id = rid; break; } } if (id == 0) { id = DimensionManager.getNextFreeDimId(); } registerDimensionToServerAndClient(id); Logging.log("id = " + id + " for " + name + ", descriptor = " + descriptor.getDescriptionString()); dimensions.put(id, descriptor); dimensionToID.put(descriptor, id); DimensionInformation dimensionInfo = new DimensionInformation(name, descriptor, world, playerName, player); dimensionInformation.put(id, dimensionInfo); save(world); touchSpawnChunk(id); return id; } private void touchSpawnChunk(int id) { // Make sure world generation kicks in for at least one chunk so that our matter receiver // is generated and registered. WorldServer worldServerForDimension = MinecraftServer.getServer().worldServerForDimension(id); ChunkProviderServer providerServer = worldServerForDimension.theChunkProviderServer; if (!providerServer.chunkExists(0, 0)) { try { providerServer.loadChunk(0, 0); providerServer.populate(providerServer, 0, 0); providerServer.unloadChunksIfNotNearSpawn(0, 0); } catch (Exception e) { Logging.logError("Something went wrong during creation of the dimension!"); e.printStackTrace(); // We catch this exception to make sure our dimension tab is at least ok. } } } @Override public void readFromNBT(NBTTagCompound tagCompound) { dimensions.clear(); dimensionToID.clear(); dimensionInformation.clear(); reclaimedIds.clear(); NBTTagList lst = tagCompound.getTagList("dimensions", Constants.NBT.TAG_COMPOUND); for (int i = 0 ; i < lst.tagCount() ; i++) { NBTTagCompound tc = lst.getCompoundTagAt(i); int id = tc.getInteger("id"); DimensionDescriptor descriptor = new DimensionDescriptor(tc); dimensions.put(id, descriptor); dimensionToID.put(descriptor, id); DimensionInformation dimensionInfo = new DimensionInformation(descriptor, tc); dimensionInformation.put(id, dimensionInfo); } int[] lstIds = tagCompound.getIntArray("reclaimedIds"); for (int id : lstIds) { reclaimedIds.add(id); } } @Override public void writeToNBT(NBTTagCompound tagCompound) { NBTTagList lst = new NBTTagList(); for (Map.Entry<Integer,DimensionDescriptor> me : dimensions.entrySet()) { NBTTagCompound tc = new NBTTagCompound(); Integer id = me.getKey(); tc.setInteger("id", id); me.getValue().writeToNBT(tc); DimensionInformation dimensionInfo = dimensionInformation.get(id); dimensionInfo.writeToNBT(tc); lst.appendTag(tc); } tagCompound.setTag("dimensions", lst); List<Integer> ids = new ArrayList<Integer>(reclaimedIds); int[] lstIds = new int[ids.size()]; for (int i = 0 ; i < ids.size() ; i++) { lstIds[i] = ids.get(i); } tagCompound.setIntArray("reclaimedIds", lstIds); } }