package fi.dy.masa.litematica.schematic.placement; import java.io.File; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import com.google.common.collect.ImmutableMap; import com.google.gson.JsonObject; import net.minecraft.init.Blocks; import net.minecraft.util.Mirror; import net.minecraft.util.Rotation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.gen.structure.template.PlacementSettings; import fi.dy.masa.litematica.Litematica; import fi.dy.masa.litematica.materials.MaterialListBase; import fi.dy.masa.litematica.materials.MaterialListPlacement; import fi.dy.masa.litematica.schematic.ISchematic; import fi.dy.masa.litematica.schematic.ISchematicRegion; import fi.dy.masa.litematica.schematic.placement.SubRegionPlacement.RequiredEnabled; import fi.dy.masa.litematica.schematic.verifier.SchematicVerifier; import fi.dy.masa.litematica.selection.Box; import fi.dy.masa.litematica.selection.SelectionBox; import fi.dy.masa.litematica.util.BlockInfoListType; import fi.dy.masa.litematica.util.PositionUtils; import fi.dy.masa.malilib.util.IntBoundingBox; import fi.dy.masa.malilib.util.PositionUtils.CoordinateType; public class SchematicPlacement extends SchematicPlacementUnloaded { protected final ISchematic schematic; protected final int subRegionCount; protected boolean isRepeatedPlacement; @Nullable protected SchematicVerifier verifier; @Nullable protected MaterialListBase materialList; protected SchematicPlacement(ISchematic schematic, @Nullable String storageFile, @Nullable File schematicFile, BlockPos origin, String name, boolean enabled) { super(storageFile, schematicFile, origin, name, enabled); this.schematic = schematic; this.subRegionCount = schematic.getSubRegionCount(); } public SchematicPlacement copyAsFullyLoaded(boolean isRepeatedPlacement) { SchematicPlacement copy = new SchematicPlacement(this.schematic, null, this.schematicFile, this.origin, this.name, this.enabled); copy.copyFrom(this, ! isRepeatedPlacement); copy.isRepeatedPlacement = isRepeatedPlacement; return copy; } @Override public boolean isLoaded() { return true; } public boolean isRepeatedPlacement() { return this.isRepeatedPlacement; } public ISchematic getSchematic() { return schematic; } public int getSubRegionCount() { return this.subRegionCount; } public BlockInfoListType getSchematicVerifierType() { return this.verifierType; } public void setSchematicVerifierType(BlockInfoListType type) { this.verifierType = type; } public boolean hasVerifier() { return this.verifier != null; } public SchematicVerifier getSchematicVerifier() { if (this.verifier == null) { this.verifier = new SchematicVerifier(); } return this.verifier; } public Box getEclosingBox() { if (this.enclosingBox == null) { this.updateEnclosingBox(); } return this.enclosingBox; } public MaterialListBase getMaterialList() { if (this.materialList == null) { if (this.materialListData != null) { this.materialList = MaterialListPlacement.createFromJson(this.materialListData, this); } else { this.materialList = new MaterialListPlacement(this, true); } } return this.materialList; } public PlacementSettings getPlacementSettings() { PlacementSettings placement = new PlacementSettings(); placement.setMirror(this.mirror); placement.setRotation(this.rotation); placement.setIgnoreEntities(this.ignoreEntities); placement.setReplacedBlock(Blocks.STRUCTURE_VOID); return placement; } @Nullable public String getSelectedSubRegionName() { return this.selectedSubRegionName; } public void setSelectedSubRegionName(@Nullable String name) { this.selectedSubRegionName = name; } @Nullable public SubRegionPlacement getSelectedSubRegionPlacement() { return this.selectedSubRegionName != null ? this.relativeSubRegionPlacements.get(this.selectedSubRegionName) : null; } @Nullable public SubRegionPlacement getRelativeSubRegionPlacement(String areaName) { return this.relativeSubRegionPlacements.get(areaName); } public Collection<SubRegionPlacement> getAllSubRegionsPlacements() { return this.relativeSubRegionPlacements.values(); } public ImmutableMap<String, SubRegionPlacement> getEnabledRelativeSubRegionPlacements() { ImmutableMap.Builder<String, SubRegionPlacement> builder = ImmutableMap.builder(); for (Map.Entry<String, SubRegionPlacement> entry : this.relativeSubRegionPlacements.entrySet()) { SubRegionPlacement placement = entry.getValue(); if (placement.matchesRequirement(RequiredEnabled.PLACEMENT_ENABLED)) { builder.put(entry.getKey(), entry.getValue()); } } return builder.build(); } /* public ImmutableMap<String, Box> getAllSubRegionBoxes() { return this.getSubRegionBoxes(RequiredEnabled.ANY); } */ protected void updateEnclosingBox() { ImmutableMap<String, SelectionBox> boxes = this.getSubRegionBoxes(RequiredEnabled.ANY); BlockPos pos1 = null; BlockPos pos2 = null; for (SelectionBox box : boxes.values()) { BlockPos tmp; tmp = fi.dy.masa.malilib.util.PositionUtils.getMinCorner(box.getPos1(), box.getPos2()); if (pos1 == null) { pos1 = tmp; } else if (tmp.getX() < pos1.getX() || tmp.getY() < pos1.getY() || tmp.getZ() < pos1.getZ()) { pos1 = fi.dy.masa.malilib.util.PositionUtils.getMinCorner(tmp, pos1); } tmp = fi.dy.masa.malilib.util.PositionUtils.getMaxCorner(box.getPos1(), box.getPos2()); if (pos2 == null) { pos2 = tmp; } else if (tmp.getX() > pos2.getX() || tmp.getY() > pos2.getY() || tmp.getZ() > pos2.getZ()) { pos2 = fi.dy.masa.malilib.util.PositionUtils.getMaxCorner(tmp, pos2); } } if (pos1 != null && pos2 != null) { this.enclosingBox = new Box(pos1, pos2); this.gridSettings.setDefaultSize(PositionUtils.getAreaSizeFromRelativeEndPosition(pos2.subtract(pos1))); } } public ImmutableMap<String, SelectionBox> getSubRegionBoxes(RequiredEnabled required) { ImmutableMap.Builder<String, SelectionBox> builder = ImmutableMap.builder(); Map<String, ISchematicRegion> subRegions = this.schematic.getRegions(); for (Map.Entry<String, SubRegionPlacement> entry : this.relativeSubRegionPlacements.entrySet()) { SubRegionPlacement placement = entry.getValue(); if (placement.matchesRequirement(required)) { String name = entry.getKey(); ISchematicRegion region = subRegions.get(name); if (region != null) { BlockPos boxOriginRelative = placement.getPos(); BlockPos boxOriginAbsolute = PositionUtils.getTransformedBlockPos(boxOriginRelative, this.mirror, this.rotation).add(this.origin); BlockPos pos2 = new BlockPos(PositionUtils.getRelativeEndPositionFromAreaSize(region.getSize())); pos2 = PositionUtils.getTransformedBlockPos(pos2, this.mirror, this.rotation); pos2 = PositionUtils.getTransformedBlockPos(pos2, placement.getMirror(), placement.getRotation()).add(boxOriginAbsolute); builder.put(name, new SelectionBox(boxOriginAbsolute, pos2, name)); } else { Litematica.logger.warn("SchematicPlacement.getSubRegionBoxes(): Sub-region '{}' not found in the schematic '{}'", name, this.schematic.getMetadata().getName()); } } } return builder.build(); } public ImmutableMap<String, SelectionBox> getSubRegionBoxFor(String regionName, RequiredEnabled required) { ImmutableMap.Builder<String, SelectionBox> builder = ImmutableMap.builder(); SubRegionPlacement placement = this.relativeSubRegionPlacements.get(regionName); if (placement != null && placement.matchesRequirement(required)) { Map<String, ISchematicRegion> subRegions = this.schematic.getRegions(); ISchematicRegion region = subRegions.get(regionName); if (region != null) { BlockPos boxOriginRelative = placement.getPos(); BlockPos boxOriginAbsolute = PositionUtils.getTransformedBlockPos(boxOriginRelative, this.mirror, this.rotation).add(this.origin); BlockPos pos2 = new BlockPos(PositionUtils.getRelativeEndPositionFromAreaSize(region.getSize())); pos2 = PositionUtils.getTransformedBlockPos(pos2, this.mirror, this.rotation); pos2 = PositionUtils.getTransformedBlockPos(pos2, placement.getMirror(), placement.getRotation()).add(boxOriginAbsolute); builder.put(regionName, new SelectionBox(boxOriginAbsolute, pos2, regionName)); } else { Litematica.logger.warn("SchematicPlacement.getSubRegionBoxFor(): Sub-region '{}' not found in the schematic '{}'", regionName, this.schematic.getMetadata().getName()); } } return builder.build(); } public Set<String> getRegionsTouchingChunk(int chunkX, int chunkZ) { ImmutableMap<String, SelectionBox> map = this.getSubRegionBoxes(RequiredEnabled.PLACEMENT_ENABLED); final int chunkXMin = chunkX << 4; final int chunkZMin = chunkZ << 4; final int chunkXMax = chunkXMin + 15; final int chunkZMax = chunkZMin + 15; Set<String> set = new HashSet<>(); for (Map.Entry<String, SelectionBox> entry : map.entrySet()) { SelectionBox box = entry.getValue(); final int boxXMin = Math.min(box.getPos1().getX(), box.getPos2().getX()); final int boxZMin = Math.min(box.getPos1().getZ(), box.getPos2().getZ()); final int boxXMax = Math.max(box.getPos1().getX(), box.getPos2().getX()); final int boxZMax = Math.max(box.getPos1().getZ(), box.getPos2().getZ()); boolean notOverlapping = boxXMin > chunkXMax || boxZMin > chunkZMax || boxXMax < chunkXMin || boxZMax < chunkZMin; if (notOverlapping == false) { set.add(entry.getKey()); } } return set; } public ImmutableMap<String, IntBoundingBox> getBoxesWithinChunk(int chunkX, int chunkZ) { ImmutableMap<String, SelectionBox> subRegions = this.getSubRegionBoxes(RequiredEnabled.PLACEMENT_ENABLED); return PositionUtils.getBoxesWithinChunk(chunkX, chunkZ, subRegions); } @Nullable public IntBoundingBox getBoxWithinChunkForRegion(String regionName, int chunkX, int chunkZ) { Box box = this.getSubRegionBoxFor(regionName, RequiredEnabled.PLACEMENT_ENABLED).get(regionName); return box != null ? PositionUtils.getBoundsWithinChunkForBox(box, chunkX, chunkZ) : null; } public Set<ChunkPos> getTouchedChunks() { return PositionUtils.getTouchedChunks(this.getSubRegionBoxes(RequiredEnabled.PLACEMENT_ENABLED)); } public Set<ChunkPos> getTouchedChunksForRegion(String regionName) { return PositionUtils.getTouchedChunks(this.getSubRegionBoxFor(regionName, RequiredEnabled.PLACEMENT_ENABLED)); } protected void checkAreSubRegionsModified() { Map<String, ISchematicRegion> subRegions = this.schematic.getRegions(); if (subRegions.size() != this.relativeSubRegionPlacements.size()) { this.regionPlacementsModified = true; return; } for (Map.Entry<String, ISchematicRegion> entry : subRegions.entrySet()) { SubRegionPlacement placement = this.relativeSubRegionPlacements.get(entry.getKey()); if (placement == null || placement.isRegionPlacementModified(entry.getValue().getPosition())) { this.regionPlacementsModified = true; return; } } this.regionPlacementsModified = false; } public void toggleRenderEnclosingBox() { this.renderEnclosingBox = ! this.renderEnclosingBox; if (this.shouldRenderEnclosingBox()) { this.updateEnclosingBox(); } } public void toggleLocked() { this.locked = ! this.locked; } public void setCoordinateLocked(CoordinateType coord, boolean locked) { int mask = 0x1 << coord.ordinal(); if (locked) { this.coordinateLockMask |= mask; } else { this.coordinateLockMask &= ~mask; } } public boolean isCoordinateLocked(CoordinateType coord) { int mask = 0x1 << coord.ordinal(); return (this.coordinateLockMask & mask) != 0; } void toggleEnabled() { this.enabled = ! this.enabled; } void toggleIgnoreEntities() { this.ignoreEntities = ! this.ignoreEntities; } void setOrigin(BlockPos origin) { this.origin = origin; } void setRotation(Rotation rotation) { this.rotation = rotation; } void setMirror(Mirror mirror) { this.mirror = mirror; } void toggleSubRegionEnabled(String regionName) { SubRegionPlacement subRegion = this.relativeSubRegionPlacements.get(regionName); if (subRegion != null) { subRegion.toggleEnabled(); } } void toggleSubRegionIgnoreEntities(String regionName) { SubRegionPlacement subRegion = this.relativeSubRegionPlacements.get(regionName); if (subRegion != null) { subRegion.toggleIgnoreEntities(); } } void setSubRegionsEnabledState(boolean state, Collection<SubRegionPlacement> subRegions) { for (SubRegionPlacement subRegion : subRegions) { // Check that the sub-region is actually from this placement subRegion = this.relativeSubRegionPlacements.get(subRegion.getName()); if (subRegion != null && subRegion.isEnabled() != state) { subRegion.setEnabled(state); } } } void setSubRegionRotation(String regionName, Rotation rotation) { SubRegionPlacement subRegion = this.relativeSubRegionPlacements.get(regionName); if (subRegion != null) { subRegion.setRotation(rotation); } } void setSubRegionMirror(String regionName, Mirror mirror) { SubRegionPlacement subRegion = this.relativeSubRegionPlacements.get(regionName); if (subRegion != null) { subRegion.setMirror(mirror); } } /** * Moves the sub-region to the given <b>absolute</b> position. * @param regionName * @param newPos */ void moveSubRegionTo(String regionName, BlockPos newPos) { SubRegionPlacement subRegion = this.relativeSubRegionPlacements.get(regionName); if (subRegion != null) { // The input argument position is an absolute position, so need to convert to relative position here newPos = newPos.subtract(this.origin); // The absolute-based input position needs to be transformed if the entire placement has been rotated or mirrored newPos = PositionUtils.getReverseTransformedBlockPos(newPos, this.mirror, this.rotation); subRegion.setPos(newPos); } } void resetSubRegionToSchematicValues(String regionName) { SubRegionPlacement placement = this.relativeSubRegionPlacements.get(regionName); if (placement != null) { placement.resetToOriginalValues(); } } void resetAllSubRegionsToSchematicValues() { Map<String, ISchematicRegion> subRegions = this.schematic.getRegions(); this.relativeSubRegionPlacements.clear(); this.regionPlacementsModified = false; for (Map.Entry<String, ISchematicRegion> entry : subRegions.entrySet()) { String name = entry.getKey(); this.relativeSubRegionPlacements.put(name, new SubRegionPlacement(entry.getValue().getPosition(), name)); } } @Override boolean readBaseSettingsFromJson(JsonObject obj) { if (super.readBaseSettingsFromJson(obj)) { this.checkAreSubRegionsModified(); return true; } return false; } @Override @Nullable public JsonObject toJson() { if (this.schematic != null) { JsonObject obj = super.toJson(); if (obj != null) { if (this.materialList != null) { obj.add("material_list", this.materialList.toJson()); } } return obj; } return null; } public static SchematicPlacement createFor(ISchematic schematic, BlockPos origin, String name, boolean enabled) { return createFor(schematic, origin, name, enabled, true); } public static SchematicPlacement createFor(ISchematic schematic, BlockPos origin, String name, boolean enabled, boolean offsetToInfrontOfPlayer) { SchematicPlacement placement = new SchematicPlacement(schematic, null, schematic.getFile(), origin, name, enabled); placement.setBoundingBoxColorToNext(); placement.resetAllSubRegionsToSchematicValues(); if (offsetToInfrontOfPlayer) { placement.origin = PositionUtils.getPlacementPositionOffsetToInfrontOfPlayer(origin, placement); } return placement; } }