package fi.dy.masa.litematica.schematic.placement; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import javax.annotation.Nullable; import net.minecraft.client.Minecraft; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3i; import fi.dy.masa.litematica.render.OverlayRenderer; import fi.dy.masa.litematica.selection.Box; import fi.dy.masa.litematica.util.PositionUtils; import fi.dy.masa.malilib.util.IntBoundingBox; public class GridPlacementManager { private final SchematicPlacementManager schematicPlacementManager; private final HashMap<SchematicPlacement, HashMap<Vec3i, SchematicPlacement>> gridPlacementsPerPlacement = new HashMap<>(); // This base placement set is used instead of the keySet of gridPlacementsPerPlacement // mostly because when logging in, the client player is at first in a wrong location, // and thus the repeat area might not overlap that initial client player's loaded area. // That would cause no grid placements to be created when loading the base placement from file, // and then the periodic update would not know to handle that base placement at all. private final HashSet<SchematicPlacement> basePlacements = new HashSet<>(); public GridPlacementManager(SchematicPlacementManager schematicPlacementManager) { this.schematicPlacementManager = schematicPlacementManager; } public void clear() { this.gridPlacementsPerPlacement.clear(); this.basePlacements.clear(); } public ArrayList<SchematicPlacement> getGridPlacementsForBasePlacement(SchematicPlacement basePlacement) { ArrayList<SchematicPlacement> gridPlacements = new ArrayList<>(); HashMap<Vec3i, SchematicPlacement> map = this.gridPlacementsPerPlacement.get(basePlacement); if (map != null) { gridPlacements.addAll(map.values()); } return gridPlacements; } void updateGridPlacementsFor(SchematicPlacement basePlacement) { // Never accidentally repeat the repeated placements if (basePlacement.isRepeatedPlacement()) { return; } boolean existedBefore = this.basePlacements.contains(basePlacement); boolean modified = false; // Grid repeating was enabled, or setting were changed if (basePlacement.isEnabled() && basePlacement.getGridSettings().isEnabled()) { // Grid settings changed for an existing grid placement if (existedBefore) { modified |= this.removeAllGridPlacementsOf(basePlacement); } else { this.basePlacements.add(basePlacement); } modified |= this.addGridPlacementsWithinLoadedAreaFor(basePlacement); } // Grid repeating disabled else if (existedBefore) { modified |= this.removeAllGridPlacementsOf(basePlacement); this.basePlacements.remove(basePlacement); } if (modified) { OverlayRenderer.getInstance().updatePlacementCache(); } } private HashMap<Vec3i, SchematicPlacement> createGridPlacementsWithinLoadedAreaFor(SchematicPlacement basePlacement) { Box currentArea = this.getCurrentLoadedArea(1); if (currentArea != null) { return this.createGridPlacementsWithinAreaFor(basePlacement, currentArea); } return new HashMap<>(); } private HashSet<Vec3i> getGridPointsWithinAreaFor(SchematicPlacement basePlacement, Box area) { HashSet<Vec3i> points = new HashSet<>(); GridSettings settings = basePlacement.getGridSettings(); Vec3i size = settings.getSize(); if (settings.isEnabled() && PositionUtils.areAllCoordinatesAtLeast(size, 1)) { IntBoundingBox repeat = settings.getRepeatCounts(); int sizeX = size.getX(); int sizeY = size.getY(); int sizeZ = size.getZ(); Box baseBox = basePlacement.getEclosingBox(); BlockPos repeatAreaMinCorner = baseBox.getPos1().add(-repeat.minX * sizeX, -repeat.minY * sizeY, -repeat.minZ * sizeZ); BlockPos repeatAreaMaxCorner = baseBox.getPos2().add( repeat.maxX * sizeX, repeat.maxY * sizeY, repeat.maxZ * sizeZ); Box repeatEnclosingBox = new Box(repeatAreaMinCorner, repeatAreaMaxCorner); // Get the box where the repeated placements intersect the target area Box intersectingBox = repeatEnclosingBox.createIntersectingBox(area); //System.out.printf("plop 2, size: %s, rep encl: %s .. %s\n", size, repeatEnclosingBox.getPos1(), repeatEnclosingBox.getPos2()); if (intersectingBox != null) { // Get the minimum and maximum repeat counts of the edge-most repeated placements that // touch the intersection box. BlockPos p1 = intersectingBox.getPos1(); BlockPos p2 = intersectingBox.getPos2(); BlockPos baseMinCorner = baseBox.getPos1(); //System.out.printf("inters min: %s, max: %s\n", p1, p2); int minX = (p1.getX() - baseMinCorner.getX()) / sizeX; int minY = (p1.getY() - baseMinCorner.getY()) / sizeY; int minZ = (p1.getZ() - baseMinCorner.getZ()) / sizeZ; int maxX = (p2.getX() - baseMinCorner.getX()) / sizeX; int maxY = (p2.getY() - baseMinCorner.getY()) / sizeY; int maxZ = (p2.getZ() - baseMinCorner.getZ()) / sizeZ; //System.out.printf("rep: x: %d .. %d, y: %d .. %d, z: %d .. %s\n", minX, maxX, minY, maxY, minZ, maxZ); for (int y = minY; y <= maxY; ++y) { for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { if (x != 0 || y != 0 || z != 0) { points.add(new Vec3i(x, y, z)); //System.out.printf("repeat placement @ %s [%d, %d, %d]\n", placement.getOrigin(), repX, repY, repZ); } } } } } } return points; } private HashMap<Vec3i, SchematicPlacement> createGridPlacementsWithinAreaFor(SchematicPlacement basePlacement, Box area) { HashSet<Vec3i> gridPoints = this.getGridPointsWithinAreaFor(basePlacement, area); return this.createGridPlacementsForPoints(basePlacement, gridPoints); } private HashMap<Vec3i, SchematicPlacement> createGridPlacementsForPoints(SchematicPlacement basePlacement, HashSet<Vec3i> gridPoints) { HashMap<Vec3i, SchematicPlacement> placements = new HashMap<>(); if (gridPoints.isEmpty() == false) { BlockPos baseOrigin = basePlacement.getOrigin(); Vec3i size = basePlacement.getGridSettings().getSize(); int sizeX = size.getX(); int sizeY = size.getY(); int sizeZ = size.getZ(); for (Vec3i point : gridPoints) { SchematicPlacement placement = basePlacement.copyAsFullyLoaded(true); placement.setOrigin(baseOrigin.add(point.getX() * sizeX, point.getY() * sizeY, point.getZ() * sizeZ)); placement.updateEnclosingBox(); placements.put(point, placement); //System.out.printf("repeat placement @ %s [%d, %d, %d]\n", placement.getOrigin(), point.getX(), point.getY(), point.getZ()); } } return placements; } private HashSet<Vec3i> getExistingOutOfRangeGridPointsFor(SchematicPlacement basePlacement, HashSet<Vec3i> currentGridPoints) { HashMap<Vec3i, SchematicPlacement> placements = this.gridPlacementsPerPlacement.get(basePlacement); if (placements != null) { HashSet<Vec3i> outOfRangePoints = new HashSet<>(placements.keySet()); outOfRangePoints.removeAll(currentGridPoints); return outOfRangePoints; } return new HashSet<>(); } private HashSet<Vec3i> getNewGridPointsFor(SchematicPlacement basePlacement, HashSet<Vec3i> currentGridPoints) { HashMap<Vec3i, SchematicPlacement> placements = this.gridPlacementsPerPlacement.get(basePlacement); if (placements != null) { HashSet<Vec3i> newPoints = new HashSet<>(currentGridPoints); newPoints.removeAll(placements.keySet()); return newPoints; } else { return currentGridPoints; } } /** * Creates and adds all the grid placements within the loaded area * for the provided normal placement * @param basePlacement * @return true if some placements were added */ private boolean addGridPlacementsWithinLoadedAreaFor(SchematicPlacement basePlacement) { HashMap<Vec3i, SchematicPlacement> placements = this.createGridPlacementsWithinLoadedAreaFor(basePlacement); if (placements.isEmpty() == false) { return this.addGridPlacements(basePlacement, placements); } return false; } /** * Removes all grid placements of the provided placement, and the base placement itself * so that the automatic updating doesn't re-create them. * @param basePlacement */ void onPlacementRemoved(SchematicPlacement basePlacement) { this.removeAllGridPlacementsOf(basePlacement); this.basePlacements.remove(basePlacement); } /** * Removes all repeated grid placements of the provided normal placement * @param basePlacement * @param updateOverlay * @return true if some placements were removed */ private boolean removeAllGridPlacementsOf(SchematicPlacement basePlacement) { HashMap<Vec3i, SchematicPlacement> placements = this.gridPlacementsPerPlacement.get(basePlacement); if (placements != null) { // Create a copy of the key set to avoid CME HashSet<Vec3i> points = new HashSet<>(placements.keySet()); return this.removeGridPlacements(basePlacement, points); } return false; } /** * Updates the grid placements for the provided normal placement, * adding or removing placements as needed so that the current * loaded area has all the required grid placements. * @return true if some placements were added or removed */ boolean createOrRemoveGridPlacementsForLoadedArea() { Box currentArea = this.getCurrentLoadedArea(1); boolean modified = false; if (currentArea != null) { for (SchematicPlacement basePlacement : this.basePlacements) { HashSet<Vec3i> currentGridPoints = this.getGridPointsWithinAreaFor(basePlacement, currentArea); HashSet<Vec3i> outOfRangePoints = this.getExistingOutOfRangeGridPointsFor(basePlacement, currentGridPoints); HashSet<Vec3i> newPoints = this.getNewGridPointsFor(basePlacement, currentGridPoints); //System.out.printf("c: %d, o: %d, n: %d\n", currentGridPoints.size(), outOfRangePoints.size(), newPoints.size()); if (outOfRangePoints.isEmpty() == false) { modified |= this.removeGridPlacements(basePlacement, outOfRangePoints); } if (newPoints.isEmpty() == false) { HashMap<Vec3i, SchematicPlacement> placements = this.createGridPlacementsForPoints(basePlacement, newPoints); modified |= this.addGridPlacements(basePlacement, placements); } } } if (modified) { OverlayRenderer.getInstance().updatePlacementCache(); } return modified; } /** * Adds the provided grid placements for the provided normal placement, * if there are no grid placements yet for those grid points. * @param basePlacement * @param placements * @return true if some placements were added */ private boolean addGridPlacements(SchematicPlacement basePlacement, HashMap<Vec3i, SchematicPlacement> placements) { boolean modified = false; if (placements.isEmpty() == false) { HashMap<Vec3i, SchematicPlacement> map = this.gridPlacementsPerPlacement.get(basePlacement); if (map == null) { map = new HashMap<>(); this.gridPlacementsPerPlacement.put(basePlacement, map); } for (Map.Entry<Vec3i, SchematicPlacement> entry : placements.entrySet()) { Vec3i point = entry.getKey(); SchematicPlacement placement = entry.getValue(); if (map.containsKey(point) == false) { map.put(point, placement); this.schematicPlacementManager.addVisiblePlacement(placement); this.schematicPlacementManager.addTouchedChunksFor(placement, false); modified = true; } } } return modified; } /** * Removes the grid placements of the provided normal placement * from the requested grid points, if they exist * @param basePlacement * @param gridPoints * @return true if some placements were removed */ private boolean removeGridPlacements(SchematicPlacement basePlacement, Collection<Vec3i> gridPoints) { HashMap<Vec3i, SchematicPlacement> map = this.gridPlacementsPerPlacement.get(basePlacement); boolean modified = false; if (gridPoints.isEmpty() == false && map != null) { for (Vec3i point : gridPoints) { SchematicPlacement placement = map.get(point); if (placement != null) { this.schematicPlacementManager.removeTouchedChunksFor(placement); this.schematicPlacementManager.removeVisiblePlacement(placement); map.remove(point); modified = true; } } } return modified; } @Nullable private Box getCurrentLoadedArea(int expandChunks) { Minecraft mc = Minecraft.getMinecraft(); EntityPlayer player = mc.player; if (player == null) { return null; } int centerChunkX = ((int) Math.floor(player.posX)) >> 4; int centerChunkZ = ((int) Math.floor(player.posZ)) >> 4; int chunkRadius = mc.gameSettings.renderDistanceChunks + expandChunks; BlockPos corner1 = new BlockPos( (centerChunkX - chunkRadius) << 4 , 0, (centerChunkZ - chunkRadius) << 4 ); BlockPos corner2 = new BlockPos(((centerChunkX + chunkRadius) << 4) + 15, 255, ((centerChunkZ + chunkRadius) << 4) + 15); return new Box(corner1, corner2); } }