package micdoodle8.mods.galacticraft.core.tick; import com.google.common.collect.Lists; import micdoodle8.mods.galacticraft.api.vector.BlockVec3; import micdoodle8.mods.galacticraft.api.vector.BlockVec3Dim; import micdoodle8.mods.galacticraft.api.world.IOrbitDimension; import micdoodle8.mods.galacticraft.core.GalacticraftCore; import micdoodle8.mods.galacticraft.core.blocks.BlockUnlitTorch; import micdoodle8.mods.galacticraft.core.dimension.SpaceRace; import micdoodle8.mods.galacticraft.core.dimension.SpaceRaceManager; import micdoodle8.mods.galacticraft.core.dimension.WorldDataSpaceRaces; import micdoodle8.mods.galacticraft.core.energy.grid.EnergyNetwork; import micdoodle8.mods.galacticraft.core.energy.tile.TileBaseConductor; import micdoodle8.mods.galacticraft.core.entities.player.GCPlayerStats; import micdoodle8.mods.galacticraft.core.fluid.FluidNetwork; import micdoodle8.mods.galacticraft.core.fluid.ThreadFindSeal; import micdoodle8.mods.galacticraft.core.network.GalacticraftPacketHandler; import micdoodle8.mods.galacticraft.core.network.PacketSimple; import micdoodle8.mods.galacticraft.core.network.PacketSimple.EnumSimplePacket; import micdoodle8.mods.galacticraft.core.tile.TileEntityFluidTank; import micdoodle8.mods.galacticraft.core.tile.TileEntityFluidTransmitter; import micdoodle8.mods.galacticraft.core.tile.TileEntityOxygenSealer; import micdoodle8.mods.galacticraft.core.tile.TileEntityPainter; import micdoodle8.mods.galacticraft.core.util.GCCoreUtil; import micdoodle8.mods.galacticraft.core.util.GCLog; import micdoodle8.mods.galacticraft.core.util.MapUtil; import micdoodle8.mods.galacticraft.core.util.WorldUtil; import micdoodle8.mods.galacticraft.core.wrappers.Footprint; import micdoodle8.mods.galacticraft.core.wrappers.ScheduledBlockChange; import micdoodle8.mods.galacticraft.core.wrappers.ScheduledDimensionChange; import net.minecraft.block.Block; import net.minecraft.block.BlockAir; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.init.Blocks; import net.minecraft.server.MinecraftServer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.BlockPos; import net.minecraft.world.ChunkCoordIntPair; import net.minecraft.world.World; import net.minecraft.world.WorldProvider; import net.minecraft.world.WorldServer; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.gen.ChunkProviderServer; import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; import net.minecraftforge.fml.common.gameevent.TickEvent.ServerTickEvent; import net.minecraftforge.fml.common.gameevent.TickEvent.WorldTickEvent; import net.minecraftforge.fml.common.network.NetworkRegistry; import java.awt.image.BufferedImage; import java.io.File; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; public class TickHandlerServer { private static Map<Integer, CopyOnWriteArrayList<ScheduledBlockChange>> scheduledBlockChanges = new ConcurrentHashMap<Integer, CopyOnWriteArrayList<ScheduledBlockChange>>(); private static Map<Integer, CopyOnWriteArrayList<BlockVec3>> scheduledTorchUpdates = new ConcurrentHashMap<Integer, CopyOnWriteArrayList<BlockVec3>>(); private static Map<Integer, Set<BlockPos>> edgeChecks = new TreeMap<Integer, Set<BlockPos>>(); private static LinkedList<EnergyNetwork> networkTicks = new LinkedList<EnergyNetwork>(); public static Map<Integer, Map<Long, List<Footprint>>> serverFootprintMap = new TreeMap<Integer, Map<Long, List<Footprint>>>(); public static List<BlockVec3Dim> footprintBlockChanges = Lists.newArrayList(); public static WorldDataSpaceRaces spaceRaceData = null; public static ArrayList<EntityPlayerMP> playersRequestingMapData = Lists.newArrayList(); private static long tickCount; public static LinkedList<TileEntityFluidTransmitter> oxygenTransmitterUpdates = new LinkedList<TileEntityFluidTransmitter>(); public static LinkedList<TileBaseConductor> energyTransmitterUpdates = new LinkedList<TileBaseConductor>(); private static CopyOnWriteArrayList<ScheduledDimensionChange> scheduledDimensionChanges = new CopyOnWriteArrayList<ScheduledDimensionChange>(); private final int MAX_BLOCKS_PER_TICK = 50000; private static List<GalacticraftPacketHandler> packetHandlers = Lists.newCopyOnWriteArrayList(); private static List<FluidNetwork> fluidNetworks = Lists.newArrayList(); public static void addFluidNetwork(FluidNetwork network) { fluidNetworks.add(network); } public static void removeFluidNetwork(FluidNetwork network) { fluidNetworks.remove(network); } public static void addPacketHandler(GalacticraftPacketHandler handler) { TickHandlerServer.packetHandlers.add(handler); } @SubscribeEvent public void worldUnloadEvent(WorldEvent.Unload event) { for (GalacticraftPacketHandler packetHandler : packetHandlers) { packetHandler.unload(event.world); } } public static void restart() { TickHandlerServer.scheduledBlockChanges.clear(); TickHandlerServer.scheduledTorchUpdates.clear(); TickHandlerServer.edgeChecks.clear(); TickHandlerServer.networkTicks.clear(); TickHandlerServer.serverFootprintMap.clear(); TickHandlerServer.oxygenTransmitterUpdates.clear(); // TickHandlerServer.hydrogenTransmitterUpdates.clear(); TickHandlerServer.energyTransmitterUpdates.clear(); TickHandlerServer.playersRequestingMapData.clear(); TickHandlerServer.networkTicks.clear(); for (SpaceRace race : SpaceRaceManager.getSpaceRaces()) { SpaceRaceManager.removeSpaceRace(race); } TickHandlerServer.spaceRaceData = null; TickHandlerServer.tickCount = 0L; TickHandlerServer.fluidNetworks.clear(); MapUtil.reset(); } public static void addFootprint(long chunkKey, Footprint print, int dimID) { Map<Long, List<Footprint>> footprintMap = TickHandlerServer.serverFootprintMap.get(dimID); List<Footprint> footprints; if (footprintMap == null) { footprintMap = new HashMap<Long, List<Footprint>>(); TickHandlerServer.serverFootprintMap.put(dimID, footprintMap); footprints = new ArrayList<Footprint>(); footprintMap.put(chunkKey, footprints); } else { footprints = footprintMap.get(chunkKey); if (footprints == null) { footprints = new ArrayList<Footprint>(); footprintMap.put(chunkKey, footprints); } } footprints.add(print); } public static void scheduleNewBlockChange(int dimID, ScheduledBlockChange change) { CopyOnWriteArrayList<ScheduledBlockChange> changeList = TickHandlerServer.scheduledBlockChanges.get(dimID); if (changeList == null) { changeList = new CopyOnWriteArrayList<ScheduledBlockChange>(); } changeList.add(change); TickHandlerServer.scheduledBlockChanges.put(dimID, changeList); } /** * Only use this for AIR blocks (any type of BlockAir) * * @param dimID * @param changeAdd List of <ScheduledBlockChange> */ public static void scheduleNewBlockChange(int dimID, List<ScheduledBlockChange> changeAdd) { CopyOnWriteArrayList<ScheduledBlockChange> changeList = TickHandlerServer.scheduledBlockChanges.get(dimID); if (changeList == null) { changeList = new CopyOnWriteArrayList<ScheduledBlockChange>(); } changeList.addAll(changeAdd); TickHandlerServer.scheduledBlockChanges.put(dimID, changeList); } public static void scheduleNewDimensionChange(ScheduledDimensionChange change) { scheduledDimensionChanges.add(change); } public static void scheduleNewTorchUpdate(int dimID, List<BlockVec3> torches) { CopyOnWriteArrayList<BlockVec3> updateList = TickHandlerServer.scheduledTorchUpdates.get(dimID); if (updateList == null) { updateList = new CopyOnWriteArrayList<BlockVec3>(); } updateList.addAll(torches); TickHandlerServer.scheduledTorchUpdates.put(dimID, updateList); } public static void scheduleNewEdgeCheck(int dimID, BlockPos edgeBlock) { Set<BlockPos> updateList = TickHandlerServer.edgeChecks.get(dimID); if (updateList == null) { updateList = new HashSet<BlockPos>(); } updateList.add(edgeBlock); TickHandlerServer.edgeChecks.put(dimID, updateList); } public static boolean scheduledForChange(int dimID, BlockPos test) { CopyOnWriteArrayList<ScheduledBlockChange> changeList = TickHandlerServer.scheduledBlockChanges.get(dimID); if (changeList != null) { for (ScheduledBlockChange change : changeList) { if (test.equals(change.getChangePosition())) { return true; } } } return false; } public static void scheduleNetworkTick(EnergyNetwork grid) { TickHandlerServer.networkTicks.add(grid); } public static void removeNetworkTick(EnergyNetwork grid) { TickHandlerServer.networkTicks.remove(grid); } @SubscribeEvent public void onServerTick(ServerTickEvent event) { MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); //Prevent issues when clients switch to LAN servers if (server == null) { return; } if (event.phase == Phase.START) { for (ScheduledDimensionChange change : TickHandlerServer.scheduledDimensionChanges) { try { GCPlayerStats stats = GCPlayerStats.get(change.getPlayer()); final WorldProvider provider = WorldUtil.getProviderForNameServer(change.getDimensionName()); final Integer dim = GCCoreUtil.getDimensionID(provider); GCLog.info("Found matching world (" + dim.toString() + ") for name: " + change.getDimensionName()); if (change.getPlayer().worldObj instanceof WorldServer) { final WorldServer world = (WorldServer) change.getPlayer().worldObj; WorldUtil.transferEntityToDimension(change.getPlayer(), dim, world); } stats.setTeleportCooldown(10); GalacticraftCore.packetPipeline.sendTo(new PacketSimple(EnumSimplePacket.C_CLOSE_GUI, GCCoreUtil.getDimensionID(change.getPlayer().worldObj), new Object[] {}), change.getPlayer()); } catch (Exception e) { GCLog.severe("Error occurred when attempting to transfer entity to dimension: " + change.getDimensionName()); e.printStackTrace(); } } TickHandlerServer.scheduledDimensionChanges.clear(); if (MapUtil.calculatingMap.get()) { MapUtil.BiomeMapNextTick_MultiThreaded(); } else if (!MapUtil.doneOverworldTexture) { MapUtil.makeOverworldTexture(); } if (TickHandlerServer.spaceRaceData == null) { World world = FMLCommonHandler.instance().getMinecraftServerInstance().worldServerForDimension(0); TickHandlerServer.spaceRaceData = (WorldDataSpaceRaces) world.getMapStorage().loadData(WorldDataSpaceRaces.class, WorldDataSpaceRaces.saveDataID); if (TickHandlerServer.spaceRaceData == null) { TickHandlerServer.spaceRaceData = new WorldDataSpaceRaces(WorldDataSpaceRaces.saveDataID); world.getMapStorage().setData(WorldDataSpaceRaces.saveDataID, TickHandlerServer.spaceRaceData); } } SpaceRaceManager.tick(); TileEntityOxygenSealer.onServerTick(); if (TickHandlerServer.tickCount % 33 == 0) { WorldServer[] worlds = server.worldServers; for (int i = worlds.length - 1; i >= 0; i--) { WorldServer world = worlds[i]; TileEntityPainter.onServerTick(world); } } if (TickHandlerServer.tickCount % 100 == 0) { WorldServer[] worlds = server.worldServers; for (int i = 0; i < worlds.length; i++) { WorldServer world = worlds[i]; ChunkProviderServer chunkProviderServer = world.theChunkProviderServer; Map<Long, List<Footprint>> footprintMap = TickHandlerServer.serverFootprintMap.get(GCCoreUtil.getDimensionID(world)); if (footprintMap != null) { boolean mapChanged = false; if (chunkProviderServer != null) { Iterator iterator = chunkProviderServer.loadedChunks.iterator(); while (iterator.hasNext()) { Chunk chunk = (Chunk) iterator.next(); long chunkKey = ChunkCoordIntPair.chunkXZ2Int(chunk.xPosition, chunk.zPosition); List<Footprint> footprints = footprintMap.get(chunkKey); if (footprints != null) { List<Footprint> toRemove = new ArrayList<Footprint>(); for (int j = 0; j < footprints.size(); j++) { footprints.get(j).age += 100; if (footprints.get(j).age >= Footprint.MAX_AGE) { toRemove.add(footprints.get(j)); } } if (!toRemove.isEmpty()) { footprints.removeAll(toRemove); } footprintMap.put(chunkKey, footprints); mapChanged = true; GalacticraftCore.packetPipeline.sendToDimension(new PacketSimple(EnumSimplePacket.C_UPDATE_FOOTPRINT_LIST, GCCoreUtil.getDimensionID(worlds[i]), new Object[] { chunkKey, footprints.toArray(new Footprint[footprints.size()]) }), GCCoreUtil.getDimensionID(worlds[i])); } } } if (mapChanged) { TickHandlerServer.serverFootprintMap.put(GCCoreUtil.getDimensionID(world), footprintMap); } } } } if (!footprintBlockChanges.isEmpty()) { for (BlockVec3Dim targetPoint : footprintBlockChanges) { WorldServer[] worlds = FMLCommonHandler.instance().getMinecraftServerInstance().worldServers; for (int i = 0; i < worlds.length; i++) { WorldServer world = worlds[i]; if (GCCoreUtil.getDimensionID(world) == targetPoint.dim) { long chunkKey = ChunkCoordIntPair.chunkXZ2Int((int) targetPoint.x >> 4, (int) targetPoint.z >> 4); GalacticraftCore.packetPipeline.sendToAllAround(new PacketSimple(EnumSimplePacket.C_FOOTPRINTS_REMOVED, GCCoreUtil.getDimensionID(world), new Object[] { chunkKey, new BlockVec3(targetPoint.x, targetPoint.y, targetPoint.z) }), new NetworkRegistry.TargetPoint(targetPoint.dim, targetPoint.x, targetPoint.y, targetPoint.z, 50)); // Map<Long, List<Footprint>> footprintMap = TickHandlerServer.serverFootprintMap.get(world.provider.dimensionId); // // if (footprintMap != null && !footprintMap.isEmpty()) // { // List<Footprint> footprints = footprintMap.get(chunkKey); // if (footprints != null) // GalacticraftCore.packetPipeline.sendToAllAround(new PacketSimple(EnumSimplePacket.C_UPDATE_FOOTPRINT_LIST, new Object[] { chunkKey, footprints.toArray(new Footprint[footprints.size()]) }), new NetworkRegistry.TargetPoint(targetPoint.dim, targetPoint.x, targetPoint.y, targetPoint.z, 50)); // } } } } footprintBlockChanges.clear(); } if (tickCount % 20 == 0) { if (!playersRequestingMapData.isEmpty()) { File baseFolder = new File(MinecraftServer.getServer().worldServerForDimension(0).getChunkSaveLocation(), "galacticraft/overworldMap"); if (!baseFolder.exists() && !baseFolder.mkdirs()) { GCLog.severe("Base folder(s) could not be created: " + baseFolder.getAbsolutePath()); } else { ArrayList<EntityPlayerMP> copy = new ArrayList<EntityPlayerMP>(playersRequestingMapData); BufferedImage reusable = new BufferedImage(400, 400, BufferedImage.TYPE_INT_RGB); for (EntityPlayerMP playerMP : copy) { GCPlayerStats stats = GCPlayerStats.get(playerMP); MapUtil.makeVanillaMap(playerMP.dimension, (int) Math.floor(stats.getCoordsTeleportedFromZ()) >> 4, (int) Math.floor(stats.getCoordsTeleportedFromZ()) >> 4, baseFolder, reusable); } playersRequestingMapData.removeAll(copy); } } } TickHandlerServer.tickCount++; EnergyNetwork.tickCount++; } else if (event.phase == Phase.END) { for (FluidNetwork network : new ArrayList<>(fluidNetworks)) { if (!network.pipes.isEmpty()) { network.tickEnd(); } else { fluidNetworks.remove(network); } } int maxPasses = 10; while (!TickHandlerServer.networkTicks.isEmpty()) { LinkedList<EnergyNetwork> pass = new LinkedList(); pass.addAll(TickHandlerServer.networkTicks); TickHandlerServer.networkTicks.clear(); for (EnergyNetwork grid : pass) { grid.tickEnd(); } if (--maxPasses <= 0) { break; } } maxPasses = 10; while (!TickHandlerServer.oxygenTransmitterUpdates.isEmpty()) { LinkedList<TileEntityFluidTransmitter> pass = new LinkedList(); pass.addAll(TickHandlerServer.oxygenTransmitterUpdates); TickHandlerServer.oxygenTransmitterUpdates.clear(); for (TileEntityFluidTransmitter newTile : pass) { if (!newTile.isInvalid()) { newTile.refresh(); } } if (--maxPasses <= 0) { break; } } maxPasses = 10; while (!TickHandlerServer.energyTransmitterUpdates.isEmpty()) { LinkedList<TileBaseConductor> pass = new LinkedList(); pass.addAll(TickHandlerServer.energyTransmitterUpdates); TickHandlerServer.energyTransmitterUpdates.clear(); for (TileBaseConductor newTile : pass) { if (!newTile.isInvalid()) { newTile.refresh(); } } if (--maxPasses <= 0) { break; } } } } private static Set<Integer> worldsNeedingUpdate = new HashSet<Integer>(); public static void markWorldNeedsUpdate(int dimension) { worldsNeedingUpdate.add(dimension); } @SubscribeEvent public void onWorldTick(WorldTickEvent event) { if (event.phase == Phase.START) { final WorldServer world = (WorldServer) event.world; CopyOnWriteArrayList<ScheduledBlockChange> changeList = TickHandlerServer.scheduledBlockChanges.get(GCCoreUtil.getDimensionID(world)); if (changeList != null && !changeList.isEmpty()) { int blockCount = 0; int blockCountMax = Math.max(this.MAX_BLOCKS_PER_TICK, changeList.size() / 4); List<ScheduledBlockChange> newList = new ArrayList<ScheduledBlockChange>(Math.max(0, changeList.size() - blockCountMax)); for (ScheduledBlockChange change : changeList) { if (++blockCount > blockCountMax) { newList.add(change); } else { if (change != null) { BlockPos changePosition = change.getChangePosition(); Block block = world.getBlockState(changePosition).getBlock(); //Only replace blocks of type BlockAir or fire - this is to prevent accidents where other mods have moved blocks if (changePosition != null && (block instanceof BlockAir || block == Blocks.fire)) { world.setBlockState(changePosition, change.getChangeID().getStateFromMeta(change.getChangeMeta()), change.getChangeUpdateFlag()); } } } } changeList.clear(); TickHandlerServer.scheduledBlockChanges.remove(GCCoreUtil.getDimensionID(world)); if (newList.size() > 0) { TickHandlerServer.scheduledBlockChanges.put(GCCoreUtil.getDimensionID(world), new CopyOnWriteArrayList<ScheduledBlockChange>(newList)); } } CopyOnWriteArrayList<BlockVec3> torchList = TickHandlerServer.scheduledTorchUpdates.get(GCCoreUtil.getDimensionID(world)); if (torchList != null && !torchList.isEmpty()) { for (BlockVec3 torch : torchList) { if (torch != null) { BlockPos pos = new BlockPos(torch.x, torch.y, torch.z); Block b = world.getBlockState(pos).getBlock(); if (b instanceof BlockUnlitTorch) { world.scheduleUpdate(pos, b, 2 + world.rand.nextInt(30)); } } } torchList.clear(); TickHandlerServer.scheduledTorchUpdates.remove(GCCoreUtil.getDimensionID(world)); } if (world.provider instanceof IOrbitDimension) { final Object[] entityList = world.loadedEntityList.toArray(); for (final Object o : entityList) { if (o instanceof Entity) { final Entity e = (Entity) o; if (e.worldObj.provider instanceof IOrbitDimension) { final IOrbitDimension dimension = (IOrbitDimension) e.worldObj.provider; if (e.posY <= dimension.getYCoordToTeleportToPlanet()) { int dim = 0; try { dim = GCCoreUtil.getDimensionID(WorldUtil.getProviderForNameServer(dimension.getPlanetToOrbit())); } catch (Exception ex) { } WorldUtil.transferEntityToDimension(e, dim, world, false, null); } } } } } int dimensionID = GCCoreUtil.getDimensionID(world); if (worldsNeedingUpdate.contains(dimensionID)) { worldsNeedingUpdate.remove(dimensionID); for (Object obj : event.world.loadedTileEntityList) { TileEntity tile = (TileEntity) obj; if (tile instanceof TileEntityFluidTank) { ((TileEntityFluidTank) tile).updateClient = true; } } } } else if (event.phase == Phase.END) { final WorldServer world = (WorldServer) event.world; for (GalacticraftPacketHandler handler : packetHandlers) { handler.tick(world); } int dimID = GCCoreUtil.getDimensionID(world); Set<BlockPos> edgesList = TickHandlerServer.edgeChecks.get(dimID); final HashSet<BlockPos> checkedThisTick = new HashSet(); if (edgesList != null && !edgesList.isEmpty()) { List<BlockPos> edgesListCopy = new ArrayList(); edgesListCopy.addAll(edgesList); for (BlockPos edgeBlock : edgesListCopy) { if (edgeBlock != null && !checkedThisTick.contains(edgeBlock)) { if (TickHandlerServer.scheduledForChange(dimID, edgeBlock)) { continue; } ThreadFindSeal done = new ThreadFindSeal(world, edgeBlock, 0, new ArrayList<TileEntityOxygenSealer>()); checkedThisTick.addAll(done.checkedAll()); } } TickHandlerServer.edgeChecks.remove(GCCoreUtil.getDimensionID(world)); } } } }