diff --git a/dependencies.gradle b/dependencies.gradle index f93c983880..18ee81c38d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -12,5 +12,7 @@ dependencies { compileOnly('curse.maven:cofh-lib-220333:2388748') compileOnly('curse.maven:minefactory-reloaded-66672:2366150') compileOnly('thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev') + + compileOnly("com.github.GTNewHorizons:StructureLib:1.4.18:dev") } diff --git a/src/main/java/vazkii/botania/api/lexicon/multiblock/Multiblock.java b/src/main/java/vazkii/botania/api/lexicon/multiblock/Multiblock.java index 02671146f1..c0f5e796ce 100644 --- a/src/main/java/vazkii/botania/api/lexicon/multiblock/Multiblock.java +++ b/src/main/java/vazkii/botania/api/lexicon/multiblock/Multiblock.java @@ -15,10 +15,15 @@ import java.util.HashMap; import java.util.List; +import cpw.mods.fml.common.FMLLog; import net.minecraft.block.Block; import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ChunkCoordinates; +import org.apache.logging.log4j.Level; +import vazkii.botania.api.lexicon.multiblock.compat.MultiblockCompatRegistry; import vazkii.botania.api.lexicon.multiblock.component.MultiblockComponent; +import vazkii.botania.common.Botania; /** * This class describes a Mutiblock object. It's used to display a @@ -39,10 +44,14 @@ public class Multiblock { * coords should be pivoted to the center of the structure. */ public void addComponent(MultiblockComponent component) { - if(getComponentForLocation(component.relPos.posX, component.relPos.posY, component.relPos.posZ) != null) - throw new IllegalArgumentException("Location in multiblock already occupied"); + ChunkCoordinates pos = component.relPos; + if(getComponentForLocation(pos.posX, pos.posY, pos.posZ) != null) { + MultiblockComponent comp = getComponentForLocation(pos.posX, pos.posY, pos.posZ); + throw new IllegalArgumentException( + "Location in multiblock {x=" + pos.posX + " y=" + pos.posY + " z=" + pos.posZ + "} already occupied by block " + comp.getBlock().getLocalizedName()); + } components.add(component); - changeAxisForNewComponent(component.relPos.posX, component.relPos.posY, component.relPos.posZ); + changeAxisForNewComponent(pos.posX, pos.posY, pos.posZ); calculateCostForNewComponent(component); addComponentToLocationCache(component); } @@ -138,6 +147,28 @@ public Multiblock[] createRotations() { return blocks; } + public MultiblockSet makeSetRegisterStructure( + Class controllerTileClass, Block controllerBlock, MultiblockComponent... extra + ) { + registerStructure(controllerTileClass, controllerBlock, extra); + return makeSet(); + } + + public void registerStructure( + Class controllerTileClass, Block controllerBlock, MultiblockComponent... extra + ) { + if (Botania.structureLibLoaded) { + try { + MultiblockCompatRegistry.registerMultiblock( + this, controllerTileClass, controllerBlock, extra + ); + } catch (Exception e) { + e.printStackTrace(); + FMLLog.log(Level.ERROR, "Failed to load a Botania Multiblock into StructureLib."); + } + } + } + /** * Makes a MultiblockSet from this Multiblock and its rotations using * createRotations(). @@ -176,7 +207,7 @@ private void addComponentToLocationCache(MultiblockComponent comp) { pos.posX, pos.posY, pos.posZ - ), comp); + ), comp); } /** diff --git a/src/main/java/vazkii/botania/api/lexicon/multiblock/compat/MultiblockCompatRegistry.java b/src/main/java/vazkii/botania/api/lexicon/multiblock/compat/MultiblockCompatRegistry.java new file mode 100644 index 0000000000..41064053fa --- /dev/null +++ b/src/main/java/vazkii/botania/api/lexicon/multiblock/compat/MultiblockCompatRegistry.java @@ -0,0 +1,228 @@ +package vazkii.botania.api.lexicon.multiblock.compat; + +import com.gtnewhorizon.structurelib.alignment.constructable.IMultiblockInfoContainer; +import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import com.gtnewhorizon.structurelib.structure.IStructureDefinition; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import com.gtnewhorizon.structurelib.structure.StructureDefinition; +import com.gtnewhorizon.structurelib.structure.StructureUtility; +import net.minecraft.block.Block; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ChunkCoordinates; +import vazkii.botania.api.lexicon.multiblock.Multiblock; +import vazkii.botania.api.lexicon.multiblock.component.MultiblockComponent; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * Only gets loaded if StructureLib is present inside the Modpack + */ +public class MultiblockCompatRegistry { + + public static void registerMultiblock( + Multiblock mb, Class controllerTileClass, + Block controllerBlock, + MultiblockComponent... extra + ) { + List components = mb.getComponents(); + if (extra.length != 0) { + components = new ArrayList<>(components); + Collections.addAll(components, extra); + } + + Map structureDataMap = new HashMap<>(); + int min_x = Integer.MAX_VALUE, max_x = Integer.MIN_VALUE; + int min_y = Integer.MAX_VALUE, max_y = Integer.MIN_VALUE; + int min_z = Integer.MAX_VALUE, max_z = Integer.MIN_VALUE; + + char ch = 'a'; + for (MultiblockComponent component : components) { + int hash = getHash(component.getBlock(), component.getMeta()); + ChunkCoordinates pos = component.getRelativePosition(); + RawStructureData structureData = structureDataMap.get(hash); + if (structureData == null) { + List coordinates = new ArrayList<>(); + structureData = new RawStructureData( + coordinates, + component.getBlock(), + component.getMeta(), + ch++ + ); + structureDataMap.put(hash, structureData); + } + structureData.getPositions().add(pos); + + min_x = Math.min(min_x, pos.posX); max_x = Math.max(max_x, pos.posX); + min_y = Math.min(min_y, pos.posY); max_y = Math.max(max_y, pos.posY); + min_z = Math.min(min_z, pos.posZ); max_z = Math.max(max_z, pos.posZ); + } + + int x_size = max_x - min_x + 1; + int y_size = max_y - min_y + 1; + int z_size = max_z - min_z + 1; + + //Data is saved in [z][y][x] format + String[][] stringData = new String[z_size][y_size]; + + char[] buffer = new char[x_size]; + Arrays.fill(buffer, ' '); + //Since Strings and their content are immutable, this is fine. + String filledWithSpaces = new String(buffer); + for (int z = 0; z < z_size; z++) { + for (int y = 0; y < y_size; y++) { + stringData[z][y] = filledWithSpaces; + } + } + + int controller_x = 0, controller_y = 0, controller_z = 0; + StringBuilder builder = new StringBuilder(x_size); + for (RawStructureData data : structureDataMap.values()) { + for (ChunkCoordinates pos : data.getPositions()) { + int x = pos.posX - min_x; + int z = pos.posZ - min_z; + //Data has to get flipped + int y = (stringData[z].length - 1) - (pos.posY - min_y); + + if (data.getBlock() == controllerBlock) { + controller_x = x; + controller_y = y; + controller_z = z; + } + + String s = stringData[z][y]; + builder.append(s); + builder.setCharAt(x, data.getBlockIdentifier()); + stringData[z][y] = builder.toString(); + builder.setLength(0); + } + } + + StructureDefinition.Builder structureBuilder = IStructureDefinition.builder() + .addShape("main", stringData); + + for (RawStructureData data : structureDataMap.values()) { + structureBuilder.addElement( + data.getBlockIdentifier(), + StructureUtility.ofBlock( + data.getBlock(), + data.getMetadata() + ) + ); + } + + IMultiblockInfoContainer.registerTileClass( + controllerTileClass, + new BotaniaMultiblockInfoContainer<>( + structureBuilder.build(), + controller_x, + controller_y, + controller_z + ) + ); + } + + private static int getHash(Block block, int metadata) { + return block.hashCode() * 31 + metadata; + } + + + private static class BotaniaMultiblockInfoContainer implements IMultiblockInfoContainer { + + private final IStructureDefinition structure; + private final int x_offset, y_offset, z_offset; + + public BotaniaMultiblockInfoContainer( + IStructureDefinition structure, + int x_offset, int y_offset, int z_offset + ) { + this.structure = structure; + this.x_offset = x_offset; + this.y_offset = y_offset; + this.z_offset = z_offset; + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly, T tileEntity, ExtendedFacing aSide) { + structure.buildOrHints( + tileEntity, + stackSize, + "main", + tileEntity.getWorldObj(), + noSideWay(aSide), + tileEntity.xCoord, + tileEntity.yCoord, + tileEntity.zCoord, + x_offset, + y_offset, + z_offset, + hintsOnly + ); + } + + @Override + public int survivalConstruct(ItemStack stackSize, int elementBudge, ISurvivalBuildEnvironment env, T tileEntity, ExtendedFacing aSide) { + return structure.survivalBuild( + tileEntity, + stackSize, + "main", + tileEntity.getWorldObj(), + noSideWay(aSide), + tileEntity.xCoord, + tileEntity.yCoord, + tileEntity.zCoord, + x_offset, + y_offset, + z_offset, + elementBudge, + env, + false + ); + } + + @Override + public String[] getDescription(ItemStack stackSize) { + return new String[0]; + } + + private static ExtendedFacing noSideWay(ExtendedFacing aSide) { + return aSide.getDirection().offsetY != 0 ? ExtendedFacing.DEFAULT : aSide.getOppositeDirection(); + } + } + + private static class RawStructureData { + private final List positions; + private final char identifier; + private final Block block; + private final int metadata; + + public RawStructureData(List positions, Block block, int metadata, char identifier) { + this.positions = positions; + this.identifier = identifier; + this.block = block; + this.metadata = metadata; + } + + public Block getBlock() { + return block; + } + + public int getMetadata() { + return metadata; + } + + public List getPositions() { + return positions; + } + + public char getBlockIdentifier() { + return identifier; + } + } +} diff --git a/src/main/java/vazkii/botania/common/Botania.java b/src/main/java/vazkii/botania/common/Botania.java index 232f334cbf..d6b8844dd1 100644 --- a/src/main/java/vazkii/botania/common/Botania.java +++ b/src/main/java/vazkii/botania/common/Botania.java @@ -42,6 +42,7 @@ public class Botania { public static boolean coloredLightsLoaded = false; public static boolean etFuturumLoaded = false; public static boolean storageDrawersLoaded = false; + public static boolean structureLibLoaded = false; public static ILightHelper lightHelper; @@ -61,6 +62,7 @@ public void preInit(FMLPreInitializationEvent event) { coloredLightsLoaded = Loader.isModLoaded("easycoloredlights"); etFuturumLoaded = Loader.isModLoaded("etfuturum"); storageDrawersLoaded = Loader.isModLoaded("StorageDrawers"); + structureLibLoaded = Loader.isModLoaded("structurelib"); lightHelper = coloredLightsLoaded ? new LightHelperColored() : new LightHelperVanilla(); proxy.preInit(event); diff --git a/src/main/java/vazkii/botania/common/block/tile/TileAlfPortal.java b/src/main/java/vazkii/botania/common/block/tile/TileAlfPortal.java index b02b9a7d9f..b70c3ba54c 100644 --- a/src/main/java/vazkii/botania/common/block/tile/TileAlfPortal.java +++ b/src/main/java/vazkii/botania/common/block/tile/TileAlfPortal.java @@ -116,7 +116,7 @@ public static MultiblockSet makeMultiblockSet() { mb.addComponent(0, 1, 0, ModBlocks.alfPortal, 0); mb.setRenderOffset(0, -1, 0); - return mb.makeSet(); + return mb.makeSetRegisterStructure(TileAlfPortal.class, ModBlocks.alfPortal); } @Override diff --git a/src/main/java/vazkii/botania/common/block/tile/TileEnchanter.java b/src/main/java/vazkii/botania/common/block/tile/TileEnchanter.java index 459f770e60..fb2fdde578 100644 --- a/src/main/java/vazkii/botania/common/block/tile/TileEnchanter.java +++ b/src/main/java/vazkii/botania/common/block/tile/TileEnchanter.java @@ -13,8 +13,13 @@ import java.util.ArrayList; import java.util.List; +import cpw.mods.fml.common.registry.GameRegistry; +import net.minecraft.block.Block; + import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.creativetab.CreativeTabs; import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.Entity; @@ -22,25 +27,30 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.init.Items; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.ChunkCoordinates; +import net.minecraft.util.IIcon; import net.minecraft.world.World; import vazkii.botania.api.BotaniaAPI; import vazkii.botania.api.internal.VanillaPacketDispatcher; import vazkii.botania.api.lexicon.multiblock.Multiblock; import vazkii.botania.api.lexicon.multiblock.MultiblockSet; import vazkii.botania.api.lexicon.multiblock.component.FlowerComponent; +import vazkii.botania.api.lexicon.multiblock.component.MultiblockComponent; import vazkii.botania.api.mana.IManaPool; import vazkii.botania.api.mana.spark.ISparkAttachable; import vazkii.botania.api.mana.spark.ISparkEntity; import vazkii.botania.api.mana.spark.SparkHelper; import vazkii.botania.client.core.helper.RenderHelper; import vazkii.botania.common.Botania; +import vazkii.botania.common.block.BlockModFlower; import vazkii.botania.common.block.ModBlocks; +import vazkii.botania.common.item.block.ItemBlockMod; public class TileEnchanter extends TileMod implements ISparkAttachable { @@ -81,20 +91,68 @@ public class TileEnchanter extends TileMod implements ISparkAttachable { { -1, 0, -1 }, { 1, 0, -1 }, { -1, 0, 1 }, { 1, 0, 1 } }; + //Note: It currently doesn't show up when looking at a Lapis Block in NEI and I have no idea how to fix it. public static MultiblockSet makeMultiblockSet() { Multiblock mb = new Multiblock(); for(int[] o : OBSIDIAN_LOCATIONS) mb.addComponent(o[0], o[1] + 1, o[2], Blocks.obsidian, 0); + + //Flowers don't seem to properly work in StructureLib (since they need a block below them) + //This solution also allows the flowers to cycle through each other + //Not sure if there's a better solution for this, but hey it works + Block physicsDefyingFlower = new StructureLibFlower(); + + List extraDisplayList = new ArrayList<>(); + for(int[] p : PYLON_LOCATIONS[0]) { mb.addComponent(p[0], p[1] + 1, p[2], ModBlocks.pylon, 0); - mb.addComponent(new FlowerComponent(new ChunkCoordinates(p[0], p[1], p[2]), ModBlocks.flower)); + + extraDisplayList.add(new MultiblockComponent( + new ChunkCoordinates(p[0], p[1], p[2]), + physicsDefyingFlower, + 0 + )); + extraDisplayList.add(new MultiblockComponent( + new ChunkCoordinates(p[0], p[1] - 1, p[2]), + Blocks.grass, + 0 + )); + } + for(int[] f : FLOWER_LOCATIONS) { + extraDisplayList.add(new MultiblockComponent( + new ChunkCoordinates(f[0], f[1] + 1, f[2]), + physicsDefyingFlower, + 0 + )); + extraDisplayList.add(new MultiblockComponent( + new ChunkCoordinates(f[0], f[1], f[2]), + Blocks.grass, + 0 + )); } - for(int[] f : FLOWER_LOCATIONS) - mb.addComponent(new FlowerComponent(new ChunkCoordinates(f[0], f[1] + 1, f[2]), ModBlocks.flower)); - mb.addComponent(0, 1, 0, Blocks.lapis_block, 0); + mb.addComponent(0, 1, 0, ModBlocks.enchanter, 0); + mb.registerStructure( + TileEnchanter.class, + ModBlocks.enchanter, + extraDisplayList.toArray(new MultiblockComponent[0]) + ); + + //Add the actual flowers + for(int[] p : PYLON_LOCATIONS[0]) { + mb.addComponent(new FlowerComponent( + new ChunkCoordinates(p[0], p[1], p[2]), + ModBlocks.flower + )); + } + for(int[] f : FLOWER_LOCATIONS) { + mb.addComponent(new FlowerComponent( + new ChunkCoordinates(f[0], f[1] + 1, f[2]), + ModBlocks.flower + )); + } return mb.makeSet(); } @@ -128,7 +186,7 @@ public void updateEntity() { if(getBlockMetadata() < PYLON_LOCATIONS.length) for(int[] pylon : PYLON_LOCATIONS[getBlockMetadata()]) { TileEntity tile = worldObj.getTileEntity(xCoord + pylon[0], yCoord + pylon[1], zCoord + pylon[2]); - if(tile != null && tile instanceof TilePylon) + if(tile instanceof TilePylon) ((TilePylon) tile).activated = false; } @@ -429,4 +487,52 @@ public EnchantmentData(int enchant, int level) { } } + private static class StructureLibFlower extends BlockModFlower { + + public StructureLibFlower() { + super(); + } + + @Override + public Block setBlockName(String par1Str) { + GameRegistry.registerBlock(this, ItemBlockMod.class, "flower_structurelib"); + return this; + } + + @Override + public IIcon getIcon(int par1, int par2) { + return super.getIcon(par1, (int) (BotaniaAPI.internalHandler.getWorldElapsedTicks() / 20) % 16); + } + + @Override + public String getUnlocalizedName() { + return "tile.flower_structurelib"; + } + + @Override + public void registerBlockIcons(IIconRegister par1IconRegister) { + //has to be empty + } + + @Override + public void getSubBlocks(Item par1, CreativeTabs par2CreativeTabs, List par3List) { + //has to be empty + } + + @Override + public boolean canPlaceBlockAt(World worldIn, int x, int y, int z) { + return true; + } + + @Override + public boolean canBlockStay(World worldIn, int x, int y, int z) { + return true; + } + + @Override + protected boolean canPlaceBlockOn(Block ground) { + return true; + } + } + } diff --git a/src/main/java/vazkii/botania/common/block/tile/TileTerraPlate.java b/src/main/java/vazkii/botania/common/block/tile/TileTerraPlate.java index f59d61d6d4..6a22524a69 100644 --- a/src/main/java/vazkii/botania/common/block/tile/TileTerraPlate.java +++ b/src/main/java/vazkii/botania/common/block/tile/TileTerraPlate.java @@ -57,7 +57,7 @@ public static MultiblockSet makeMultiblockSet() { mb.addComponent(0, 1, 0, ModBlocks.terraPlate, 0); mb.setRenderOffset(0, 1, 0); - return mb.makeSet(); + return mb.makeSetRegisterStructure(TileTerraPlate.class, ModBlocks.terraPlate); } @Override diff --git a/src/main/resources/assets/botania/lang/en_US.lang b/src/main/resources/assets/botania/lang/en_US.lang index 9fbb4ff72a..7dfec5a3f8 100644 --- a/src/main/resources/assets/botania/lang/en_US.lang +++ b/src/main/resources/assets/botania/lang/en_US.lang @@ -650,6 +650,7 @@ tile.botania:flower12.name=Mystical Brown Flower tile.botania:flower13.name=Mystical Green Flower tile.botania:flower14.name=Mystical Red Flower tile.botania:flower15.name=Mystical Black Flower +tile.botania:flower_structurelib.name=Any Botania Flower tile.botania:altar0.name=Petal Apothecary tile.botania:altar1.name=Forest Petal Apothecary tile.botania:altar2.name=Plains Petal Apothecary