Add the CE quarries, a port of CE's improved BuildCraft quarries

Port CE's three quarries from BuildCraft 3.1.5 (Minecraft 1.2.5) to
BuildCraft 7.1. TileCEQuarry follows BuildCraft 7's TileQuarry, whose
state is private, with CE's changes:

- Twice the battery, so the arm moves faster.
- A configurable default area (64x64), mirrored when placed while
  sneaking, and a configurable lowest Y level (5).
- Lava no longer blocks the columns below it.
- The search continues from the last target and moves past each one
  once done, so fluids flowing back in do not hold the arm in place.
- No chunk loading.
- CEQ-3 removes lava sources; CEQ-4 turns fluids around the drill into
  glass and accepts five times the input.

Soft blocks are checked without BuildCraft's cache, which is stale in
chunks without players. The arm is a copy of EntityMechanicalArm, which
only works with BuildCraft's own quarry. The recipes keep CE's layout
with BuildCraft 7 items; GateRecipe checks the gates' material and
logic, which ShapedOreRecipe ignores. Textures come from CE.

Also give the EMC Builder a default.png for its back, sides and bottom
instead of substituting the bottom icon in code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
agomizi1cmandClaude Opus 5.5 committed 2026-10-27 11:13:02 +09:00
1 parent af0afcb98f
commit 7899ce82f7
24 files changed
+1424 -21

No files matched your search

@@ -0,0 +1,53 @@
package net.kanawo.cemodern;
import java.io.File;
import net.minecraftforge.common.config.Configuration;
/** Settings read from config/cemodern.cfg. */
public final class CEConfig {
private static final String CATEGORY_QUARRY = "quarry";
/** Side length of the area a CE quarry digs when no marker is next to it. */
public static int quarryDefaultSize = 64;
/** Where the quarry sits along the edge of its default area, counted from one corner. */
public static int quarryDefaultPosition = 0;
/** Lowest Y level CE quarries dig down to. Bedrock is at 0 to 4. */
public static int quarryMinY = 5;
private CEConfig() {}
public static void load(File file) {
Configuration config = new Configuration(file);
quarryDefaultSize = config.getInt(
"defaultArea",
CATEGORY_QUARRY,
quarryDefaultSize,
3,
130,
"Side length of the area a CE quarry digs when no marker is next to it.");
quarryDefaultPosition = config.getInt(
"defaultPosition",
CATEGORY_QUARRY,
quarryDefaultPosition,
0,
129,
"Where the quarry sits along the edge of its default area, counted from one corner."
+ " Sneak while placing it to count from the other corner.");
quarryMinY = config.getInt(
"miningYmin",
CATEGORY_QUARRY,
quarryMinY,
0,
255,
"Lowest Y level CE quarries dig down to. Bedrock is at 0 to 4.");
quarryDefaultPosition = Math.min(quarryDefaultPosition, quarryDefaultSize - 1);
if (config.hasChanged()) {
config.save();
}
}
}
@@ -13,7 +13,8 @@ import cpw.mods.fml.common.event.FMLPreInitializationEvent;
version = Tags.VERSION,
name = "CE-modern",
acceptedMinecraftVersions = "[1.7.10]",
dependencies = "required-after:BuildCraft|Builders;required-after:ProjectE")
dependencies = "required-after:BuildCraft|Builders;required-after:BuildCraft|Factory;"
+ "required-after:BuildCraft|Silicon;required-after:BuildCraft|Transport;required-after:ProjectE")
public class CEModern {
public static final String MODID = "cemodern";
@@ -1,9 +1,13 @@
package net.kanawo.cemodern;
import net.kanawo.cemodern.entity.EntityCEQuarryArm;
import net.kanawo.cemodern.tile.TileCEQuarry;
import net.kanawo.cemodern.tile.TileEMCBuilder;
import buildcraft.core.lib.render.RenderVoid;
import buildcraft.core.render.RenderBuilder;
import cpw.mods.fml.client.registry.ClientRegistry;
import cpw.mods.fml.client.registry.RenderingRegistry;
import cpw.mods.fml.common.event.FMLInitializationEvent;
public class ClientProxy extends CommonProxy {
@@ -15,5 +19,12 @@ public class ClientProxy extends CommonProxy {
// The Builder's own renderer (RenderBuilderTile) also draws BuildCraft's lit top while building. Its base
// renderer draws only the area box, path lasers and flying items.
ClientRegistry.bindTileEntitySpecialRenderer(TileEMCBuilder.class, new RenderBuilder());
// The quarries draw their area box and the frames flying into place. The arm is drawn by its parts.
RenderBuilder quarryRenderer = new RenderBuilder();
ClientRegistry.bindTileEntitySpecialRenderer(TileCEQuarry.Fast.class, quarryRenderer);
ClientRegistry.bindTileEntitySpecialRenderer(TileCEQuarry.Lava.class, quarryRenderer);
ClientRegistry.bindTileEntitySpecialRenderer(TileCEQuarry.Fluid.class, quarryRenderer);
RenderingRegistry.registerEntityRenderingHandler(EntityCEQuarryArm.class, new RenderVoid());
}
}
@@ -1,13 +1,26 @@
package net.kanawo.cemodern;
import net.kanawo.cemodern.block.BlockCEQuarry;
import net.kanawo.cemodern.block.BlockEMCBuilder;
import net.kanawo.cemodern.block.SchematicEMCBuilder;
import net.kanawo.cemodern.gui.GuiHandler;
import net.kanawo.cemodern.recipe.GateRecipe;
import net.kanawo.cemodern.tile.QuarryType;
import net.kanawo.cemodern.tile.TileCEQuarry;
import net.kanawo.cemodern.tile.TileEMCBuilder;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraftforge.oredict.RecipeSorter;
import net.minecraftforge.oredict.RecipeSorter.Category;
import net.minecraftforge.oredict.ShapedOreRecipe;
import buildcraft.BuildCraftBuilders;
import buildcraft.BuildCraftFactory;
import buildcraft.api.blueprints.BuilderAPI;
import buildcraft.builders.schematics.SchematicBuilderLike;
import buildcraft.transport.gates.GateDefinition.GateLogic;
import buildcraft.transport.gates.GateDefinition.GateMaterial;
import buildcraft.transport.gates.ItemGate;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.network.NetworkRegistry;
@@ -17,21 +30,100 @@ import moze_intel.projecte.gameObjs.ObjHandler;
public class CommonProxy {
public static BlockEMCBuilder emcBuilderBlock;
public static BlockCEQuarry quarry2Block;
public static BlockCEQuarry quarry3Block;
public static BlockCEQuarry quarry4Block;
public void preInit(FMLPreInitializationEvent event) {
CEConfig.load(event.getSuggestedConfigurationFile());
emcBuilderBlock = new BlockEMCBuilder();
GameRegistry.registerBlock(emcBuilderBlock, "emcBuilder");
GameRegistry.registerTileEntity(TileEMCBuilder.class, CEModern.MODID + ":EMCBuilder");
quarry2Block = registerQuarry(QuarryType.FAST, TileCEQuarry.Fast.class);
quarry3Block = registerQuarry(QuarryType.LAVA, TileCEQuarry.Lava.class);
quarry4Block = registerQuarry(QuarryType.FLUID, TileCEQuarry.Fluid.class);
}
private static BlockCEQuarry registerQuarry(QuarryType type, Class<? extends TileCEQuarry> tile) {
BlockCEQuarry block = new BlockCEQuarry(type);
GameRegistry.registerBlock(block, type.name);
GameRegistry.registerTileEntity(tile, CEModern.MODID + ":" + type.name);
return block;
}
public void init(FMLInitializationEvent event) {
NetworkRegistry.INSTANCE.registerGuiHandler(CEModern.instance, new GuiHandler());
BuilderAPI.schematicRegistry.registerSchematicBlock(emcBuilderBlock, SchematicEMCBuilder.class);
BuilderAPI.schematicRegistry.registerSchematicBlock(quarry2Block, SchematicBuilderLike.class);
BuilderAPI.schematicRegistry.registerSchematicBlock(quarry3Block, SchematicBuilderLike.class);
BuilderAPI.schematicRegistry.registerSchematicBlock(quarry4Block, SchematicBuilderLike.class);
GameRegistry.addShapelessRecipe(
new ItemStack(emcBuilderBlock),
BuildCraftBuilders.builderBlock,
ObjHandler.transmuteStone);
RecipeSorter.register(CEModern.MODID + ":gate", GateRecipe.class, Category.SHAPED, "after:forge:shapedore");
// CE's recipes, with BuildCraft 7's items in place of BuildCraft 3's.
GameRegistry.addRecipe(
new ShapedOreRecipe(
new ItemStack(quarry2Block),
"iri",
"gig",
"dqd",
'r',
"dustRedstone",
'i',
"gearIron",
'g',
"gearGold",
'd',
"gearDiamond",
'q',
BuildCraftBuilders.quarryBlock));
GameRegistry.addRecipe(
new GateRecipe(
new ItemStack(quarry3Block),
GateMaterial.DIAMOND,
GateLogic.AND,
"ete",
"cpc",
"gqg",
'e',
Items.ender_pearl,
't',
BuildCraftFactory.tankBlock,
'p',
BuildCraftFactory.pumpBlock,
'q',
quarry2Block,
'c',
"chipsetDiamond",
'g',
ItemGate.makeGateItem(GateMaterial.DIAMOND, GateLogic.AND)));
GameRegistry.addRecipe(
new GateRecipe(
new ItemStack(quarry4Block),
GateMaterial.IRON,
GateLogic.AND,
"ggg",
"wql",
"tit",
'g',
"blockGlass",
'w',
Items.water_bucket,
'l',
Items.lava_bucket,
'q',
quarry3Block,
't',
BuildCraftFactory.tankBlock,
'i',
ItemGate.makeGateItem(GateMaterial.IRON, GateLogic.AND)));
}
}
@@ -0,0 +1,111 @@
package net.kanawo.cemodern.block;
import java.util.ArrayList;
import net.kanawo.cemodern.CEModern;
import net.kanawo.cemodern.tile.QuarryType;
import net.kanawo.cemodern.tile.TileCEQuarry;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
import buildcraft.BuildCraftBuilders;
import buildcraft.api.tools.IToolWrench;
import buildcraft.core.lib.block.BlockBuildCraft;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
/**
* A CE quarry. Like BuildCraft's quarry it faces away from the placer, digs the area behind it, restarts when
* wrenched, and takes its frame with it when broken.
* <p>
* Textures are looked up as textures/blocks/{quarry2,quarry3,quarry4}/{front,top,side}.png. The side texture is
* also used for the back and the bottom.
*/
public class BlockCEQuarry extends BlockBuildCraft {
public final QuarryType type;
public BlockCEQuarry(QuarryType type) {
super(Material.iron);
this.type = type;
setHardness(10F);
setResistance(10F);
setStepSound(soundTypeAnvil);
setRotatable(true);
setBlockName("ce" + Character.toUpperCase(type.name.charAt(0)) + type.name.substring(1));
}
@Override
public TileEntity createNewTileEntity(World world, int metadata) {
return type.createTile();
}
/**
* Registers the icons directly. BlockBuildCraft's lookup would fall back to a missing "default" texture for the
* bottom, which has no texture of its own.
*/
@Override
@SideOnly(Side.CLIENT)
public void registerBlockIcons(IIconRegister register) {
String prefix = CEModern.MODID + ":" + type.name + "/";
IIcon side = register.registerIcon(prefix + "side");
// Indexed by side: bottom, top, front, back, left, right.
icons = new IIcon[16][];
icons[0] = new IIcon[] { side, register.registerIcon(prefix + "top"), register.registerIcon(prefix + "front"),
side, side, side };
}
@Override
public ArrayList<ItemStack> getDrops(World world, int x, int y, int z, int metadata, int fortune) {
if (BuildCraftBuilders.quarryOneTimeUse) {
return new ArrayList<ItemStack>();
}
return super.getDrops(world, x, y, z, metadata, fortune);
}
@Override
public void breakBlock(World world, int x, int y, int z, Block block, int metadata) {
if (world.isRemote) {
return;
}
BuildCraftBuilders.frameBlock.removeNeighboringFrames(world, x, y, z);
super.breakBlock(world, x, y, z, block, metadata);
}
@Override
public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float hitX,
float hitY, float hitZ) {
if (super.onBlockActivated(world, x, y, z, player, side, hitX, hitY, hitZ)) {
return true;
}
if (player.isSneaking()) {
return false;
}
// Restart the quarry if it is a wrench.
Item equipped = player.getCurrentEquippedItem() != null ? player.getCurrentEquippedItem()
.getItem() : null;
TileEntity tile = world.getTileEntity(x, y, z);
if (tile instanceof TileCEQuarry && equipped instanceof IToolWrench
&& ((IToolWrench) equipped).canWrench(player, x, y, z)) {
((TileCEQuarry) tile).reinitialize();
((IToolWrench) equipped).wrenchUsed(player, x, y, z);
return true;
}
return false;
}
}
@@ -3,7 +3,6 @@ package net.kanawo.cemodern.block;
import net.kanawo.cemodern.CEModern;
import net.kanawo.cemodern.gui.GuiHandler;
import net.kanawo.cemodern.tile.TileEMCBuilder;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.tileentity.TileEntity;
@@ -14,7 +13,6 @@ import buildcraft.api.events.BlockInteractionEvent;
import buildcraft.builders.BlockBuilder;
import buildcraft.builders.ItemConstructionMarker;
import buildcraft.core.lib.fluids.TankUtils;
import buildcraft.core.lib.utils.ResourceUtils;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@@ -74,23 +72,13 @@ public class BlockEMCBuilder extends BlockBuilder {
return true;
}
/** Textures are looked up as textures/blocks/emcbuilderBlock/{top,bottom,front,side,...}.png. */
/**
* Textures are looked up as textures/blocks/emcbuilderBlock/{top,front,default}.png. default.png covers the faces
* without a texture of their own: the back, the left, the right and the bottom.
*/
@Override
@SideOnly(Side.CLIENT)
public String[] getIconBlockNames() {
return new String[] { TEXTURE_PREFIX };
}
@Override
@SideOnly(Side.CLIENT)
public void registerBlockIcons(IIconRegister register) {
super.registerBlockIcons(register);
// Without bottom.png BlockBuildCraft would fall back to a missing "default" texture. Use the left face
// (index 4), which resolves to side.png, instead.
String bottom = TEXTURE_PREFIX + "/bottom";
if (!ResourceUtils.resourceExists(ResourceUtils.iconToResourcePath(register, bottom))) {
icons[0][0] = icons[0][4];
}
}
}
@@ -0,0 +1,152 @@
package net.kanawo.cemodern.entity;
import net.kanawo.cemodern.tile.TileCEQuarry;
import net.minecraft.entity.Entity;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import buildcraft.builders.BuilderProxy;
import buildcraft.core.lib.EntityBlock;
/**
* The mechanical arm of a CE quarry, the same as BuildCraft's {@code EntityMechanicalArm}, which only works with
* BuildCraft's own quarry tile.
* <p>
* It only draws the arm: three beams and the drill head, made of BuildCraft's {@link EntityBlock}s. The quarry spawns
* one on each side and moves its head. The entity is not registered, so it is neither sent to clients nor saved.
*/
public class EntityCEQuarryArm extends Entity {
private TileCEQuarry parent;
private double armSizeX;
private double armSizeZ;
private double xRoot;
private double yRoot;
private double zRoot;
private int headX, headY, headZ;
private EntityBlock xArm, yArm, zArm, head;
public EntityCEQuarryArm(World world) {
super(world);
makeParts(world);
noClip = true;
}
public EntityCEQuarryArm(World world, double x, double y, double z, double width, double height,
TileCEQuarry parent) {
this(world);
setPositionAndRotation(parent.xCoord, parent.yCoord, parent.zCoord, 0, 0);
this.xRoot = x;
this.yRoot = y;
this.zRoot = z;
this.motionX = 0.0;
this.motionY = 0.0;
this.motionZ = 0.0;
setArmSize(width, height);
setHead(x, y - 2, z);
this.parent = parent;
parent.setArm(this);
updatePosition();
}
public void setHead(double x, double y, double z) {
this.headX = (int) (x * 32D);
this.headY = (int) (y * 32D);
this.headZ = (int) (z * 32D);
}
private void setArmSize(double x, double z) {
armSizeX = x;
xArm.iSize = x;
armSizeZ = z;
zArm.kSize = z;
updatePosition();
}
private void makeParts(World world) {
xArm = BuilderProxy.proxy.newDrill(world, 0, 0, 0, 1, 0.5, 0.5, true);
yArm = BuilderProxy.proxy.newDrill(world, 0, 0, 0, 0.5, 1, 0.5, false);
zArm = BuilderProxy.proxy.newDrill(world, 0, 0, 0, 0.5, 0.5, 1, true);
head = BuilderProxy.proxy.newDrillHead(world, 0, 0, 0, 0.2, 1, 0.2);
head.shadowSize = 1.0F;
world.spawnEntityInWorld(xArm);
world.spawnEntityInWorld(yArm);
world.spawnEntityInWorld(zArm);
world.spawnEntityInWorld(head);
}
@Override
protected void entityInit() {}
@Override
protected void readEntityFromNBT(NBTTagCompound nbt) {
xRoot = nbt.getDouble("xRoot");
yRoot = nbt.getDouble("yRoot");
zRoot = nbt.getDouble("zRoot");
armSizeX = nbt.getDouble("armSizeX");
armSizeZ = nbt.getDouble("armSizeZ");
setArmSize(armSizeX, armSizeZ);
updatePosition();
}
@Override
protected void writeEntityToNBT(NBTTagCompound nbt) {
nbt.setDouble("xRoot", xRoot);
nbt.setDouble("yRoot", yRoot);
nbt.setDouble("zRoot", zRoot);
nbt.setDouble("armSizeX", armSizeX);
nbt.setDouble("armSizeZ", armSizeZ);
}
private void findAndJoinQuarry() {
TileEntity te = worldObj.getTileEntity((int) posX, (int) posY, (int) posZ);
if (te instanceof TileCEQuarry) {
parent = (TileCEQuarry) te;
parent.setArm(this);
} else {
setDead();
}
}
@Override
public void onUpdate() {
super.onUpdate();
updatePosition();
if (parent == null) {
findAndJoinQuarry();
}
if (parent == null) {
setDead();
}
}
public void updatePosition() {
double headPosX = headX / 32D;
double headPosY = headY / 32D;
double headPosZ = headZ / 32D;
xArm.setPosition(xRoot, yRoot, headPosZ + 0.25);
yArm.jSize = yRoot - headPosY - 1;
yArm.setPosition(headPosX + 0.25, headPosY + 1, headPosZ + 0.25);
zArm.setPosition(headPosX + 0.25, yRoot, zRoot);
head.setPosition(headPosX + 0.4, headPosY, headPosZ + 0.4);
}
@Override
public void setDead() {
if (worldObj != null && worldObj.isRemote) {
xArm.setDead();
yArm.setDead();
zArm.setDead();
head.setDead();
}
super.setDead();
}
}
@@ -0,0 +1,44 @@
package net.kanawo.cemodern.recipe;
import net.minecraft.inventory.InventoryCrafting;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import net.minecraftforge.oredict.ShapedOreRecipe;
import buildcraft.transport.gates.GateDefinition.GateLogic;
import buildcraft.transport.gates.GateDefinition.GateMaterial;
import buildcraft.transport.gates.ItemGate;
/**
* A shaped recipe whose gates must be of one material and logic. Gates are told apart only by NBT, which
* {@link ShapedOreRecipe} ignores, so without this any gate would do. Expansions on the gate are allowed.
*/
public class GateRecipe extends ShapedOreRecipe {
private final GateMaterial material;
private final GateLogic logic;
/** The recipe arguments are those of {@link ShapedOreRecipe}; give the gate as {@code ItemGate.makeGateItem}. */
public GateRecipe(ItemStack result, GateMaterial material, GateLogic logic, Object... recipe) {
super(result, recipe);
this.material = material;
this.logic = logic;
}
@Override
public boolean matches(InventoryCrafting inv, World world) {
if (!super.matches(inv, world)) {
return false;
}
for (int i = 0; i < inv.getSizeInventory(); i++) {
ItemStack stack = inv.getStackInSlot(i);
if (stack != null && stack.getItem() instanceof ItemGate
&& (ItemGate.getMaterial(stack) != material || ItemGate.getLogic(stack) != logic)) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,34 @@
package net.kanawo.cemodern.tile;
/** The three CE quarries. They share {@link TileCEQuarry} and differ only in how they treat fluids. */
public enum QuarryType {
/** CEQ-2: a faster BuildCraft quarry with a bigger battery. Skips fluids like BuildCraft's does. */
FAST("quarry2", 1),
/** CEQ-3: also targets lava sources and removes them. */
LAVA("quarry3", 1),
/** CEQ-4: targets any fluid and turns fluids next to the drill into glass before digging. */
FLUID("quarry4", 5);
/** Block registry name, also the texture folder name. */
public final String name;
/** How many times BuildCraft's quarry input rate this quarry accepts per tick. */
final int receiveMultiplier;
QuarryType(String name, int receiveMultiplier) {
this.name = name;
this.receiveMultiplier = receiveMultiplier;
}
public TileCEQuarry createTile() {
switch (this) {
case LAVA:
return new TileCEQuarry.Lava();
case FLUID:
return new TileCEQuarry.Fluid();
case FAST:
default:
return new TileCEQuarry.Fast();
}
}
}
@@ -0,0 +1,890 @@
package net.kanawo.cemodern.tile;
import java.util.List;
import net.kanawo.cemodern.CEConfig;
import net.kanawo.cemodern.entity.EntityCEQuarryArm;
import net.minecraft.block.Block;
import net.minecraft.block.BlockLiquid;
import net.minecraft.block.material.Material;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.MathHelper;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.IFluidBlock;
import buildcraft.BuildCraftBuilders;
import buildcraft.BuildCraftCore;
import buildcraft.api.blueprints.BuilderAPI;
import buildcraft.api.core.BuildCraftAPI;
import buildcraft.api.core.IAreaProvider;
import buildcraft.api.core.SafeTimeTracker;
import buildcraft.api.filler.FillerManager;
import buildcraft.api.statements.IStatementParameter;
import buildcraft.api.tiles.IControllable;
import buildcraft.api.tiles.IHasWork;
import buildcraft.api.transport.IPipeConnection;
import buildcraft.api.transport.IPipeTile;
import buildcraft.core.Box;
import buildcraft.core.Box.Kind;
import buildcraft.core.CoreConstants;
import buildcraft.core.DefaultProps;
import buildcraft.core.blueprints.Blueprint;
import buildcraft.core.blueprints.BptBuilderBase;
import buildcraft.core.blueprints.BptBuilderBlueprint;
import buildcraft.core.builders.TileAbstractBuilder;
import buildcraft.core.builders.patterns.FillerPattern;
import buildcraft.core.internal.IDropControlInventory;
import buildcraft.core.lib.RFBattery;
import buildcraft.core.lib.utils.BlockMiner;
import buildcraft.core.lib.utils.BlockUtils;
import buildcraft.core.lib.utils.Utils;
import buildcraft.core.proxy.CoreProxy;
import io.netty.buffer.ByteBuf;
/**
* The CE quarries, a port of CE's TileQuarry2 and TileQuarry4 for BuildCraft 1.2.5.
* <p>
* It works like BuildCraft's {@code TileQuarry}: it builds a frame, then moves a mechanical arm over the area and
* mines blocks with BuildCraft's {@link BlockMiner}. That class keeps its state private, so this one cannot extend it
* and follows it instead. What CE changed:
* <ul>
* <li>Twice the battery, so the arm, whose speed grows with the stored energy, moves faster.</li>
* <li>A configurable default area (64x64 by default), mirrored when the quarry is placed while sneaking.</li>
* <li>It stops at a configurable Y level (5 by default) instead of digging into the bedrock.</li>
* <li>Lava does not block the columns below it.</li>
* <li>It remembers where it is in the area, so each search continues from the last target instead of starting
* over.</li>
* <li>It does not load chunks.</li>
* <li>CEQ-3 removes lava sources, and CEQ-4 turns fluids around the drill into glass (see {@link QuarryType}).</li>
* </ul>
*/
public abstract class TileCEQuarry extends TileAbstractBuilder
implements IHasWork, ISidedInventory, IDropControlInventory, IPipeConnection, IControllable {
/** Tiles registered separately per type, so a quarry keeps its type when loaded from NBT. */
public static class Fast extends TileCEQuarry {
public Fast() {
super(QuarryType.FAST);
}
}
public static class Lava extends TileCEQuarry {
public Lava() {
super(QuarryType.LAVA);
}
}
public static class Fluid extends TileCEQuarry {
public Fluid() {
super(QuarryType.FLUID);
}
}
private enum Stage {
BUILDING,
DIGGING,
MOVING,
IDLE,
DONE
}
private enum ScanResult {
FOUND,
NONE,
PENDING
}
/** Blocks looked at per tick while searching, so an empty area does not stall a tick. */
private static final int SCAN_BUDGET = 4096;
/** RF to remove a lava source (CE: 100 MJ). */
private static final int LAVA_ERASE_ENERGY = 1000;
/** RF to turn one fluid block into glass (CE: 200 MJ). */
private static final int FLUID_REPLACE_ENERGY = 2000;
public final QuarryType type;
public EntityCEQuarryArm arm;
private final Box box = new Box();
private int targetX, targetY, targetZ;
private double headPosX, headPosY, headPosZ;
private double speed = 0.03;
private Stage stage = Stage.BUILDING;
private boolean movingHorizontally;
private boolean movingVertically;
private float headTrajectory;
/** True while the arm returns to its rest position after the last target. */
private boolean parking;
/**
* Next block to look at: a layer and an index into that layer's serpentine order. {@link Integer#MAX_VALUE}
* means start again from the top.
*/
private int scanY = Integer.MAX_VALUE;
private int scanIndex;
/** Whether the placer was sneaking, which mirrors the default area. */
private boolean mirrored;
private final SafeTimeTracker updateTracker = new SafeTimeTracker(BuildCraftCore.updateFactor);
private BptBuilderBase builder;
private boolean frameProducer = true;
private NBTTagCompound initNBT = null;
private BlockMiner miner;
protected TileCEQuarry(QuarryType type) {
this.type = type;
box.kind = Kind.STRIPES;
setBattery(
new RFBattery(
(int) (4 * 64 * BuilderAPI.BREAK_ENERGY * BuildCraftCore.miningMultiplier),
(int) (1000 * type.receiveMultiplier * BuildCraftCore.miningMultiplier),
0));
}
@Override
public void onBlockPlacedBy(EntityLivingBase entity, ItemStack stack) {
super.onBlockPlacedBy(entity, stack);
mirrored = entity.isSneaking();
}
private void createUtilsIfNeeded() {
if (!worldObj.isRemote && builder == null) {
if (!box.isInitialized()) {
setBoundaries();
}
initializeBlueprintBuilder();
}
if (stage != Stage.BUILDING) {
box.isVisible = false;
if (arm == null) {
createArm();
}
if (!worldObj.isRemote
&& (headPosX < box.xMin || headPosX > box.xMax || headPosZ < box.zMin || headPosZ > box.zMax)) {
setHead(box.xMin + 1, yCoord + 2, box.zMin + 1);
}
} else {
box.isVisible = true;
}
}
private void createArm() {
worldObj.spawnEntityInWorld(
new EntityCEQuarryArm(
worldObj,
box.xMin + CoreConstants.PIPE_MAX_POS,
yCoord + box.sizeY() - 1 + CoreConstants.PIPE_MIN_POS,
box.zMin + CoreConstants.PIPE_MAX_POS,
box.sizeX() - 2 + CoreConstants.PIPE_MIN_POS * 2,
box.sizeZ() - 2 + CoreConstants.PIPE_MIN_POS * 2,
this));
}
/** Callback from the arm once it is created. */
public void setArm(EntityCEQuarryArm arm) {
this.arm = arm;
}
/** The quarry does not load chunks, so it waits until every chunk of its area is loaded. */
private boolean areChunksLoaded() {
for (int chunkX = box.xMin >> 4; chunkX <= box.xMax >> 4; chunkX++) {
for (int chunkZ = box.zMin >> 4; chunkZ <= box.zMax >> 4; chunkZ++) {
if (!worldObj.blockExists(chunkX << 4, box.yMax, chunkZ << 4)) {
return false;
}
}
}
return true;
}
@Override
public void updateEntity() {
super.updateEntity();
if (worldObj.isRemote) {
if (stage != Stage.DONE) {
moveHead(speed);
}
return;
}
if (stage == Stage.DONE) {
if (mode == Mode.Loop) {
resetScan();
stage = Stage.IDLE;
} else {
return;
}
}
if (!areChunksLoaded()) {
return;
}
if (mode == Mode.Off && stage != Stage.MOVING) {
return;
}
createUtilsIfNeeded();
if (stage == Stage.BUILDING) {
if (builder != null && !builder.isDone(this)) {
builder.buildNextSlot(worldObj, this, xCoord, yCoord, zCoord);
} else {
stage = Stage.IDLE;
}
} else if (stage == Stage.DIGGING) {
dig();
} else if (stage == Stage.IDLE) {
// idling() sends its own network update when the stage changes.
idling();
return;
} else if (stage == Stage.MOVING) {
int energyUsed = getBattery()
.useEnergy(20, (int) Math.ceil(20D + (double) getBattery().getEnergyStored() / 10), false);
if (energyUsed >= 20) {
speed = 0.1 + energyUsed / 2000F;
moveHead(speed);
} else {
speed = 0;
}
}
if (updateTracker.markTimeIfDelay(worldObj)) {
sendNetworkUpdate();
}
}
private void dig() {
if (miner == null) {
stage = Stage.IDLE;
return;
}
int rfTaken = miner.acceptEnergy(getBattery().getEnergyStored());
getBattery().useEnergy(rfTaken, rfTaken, false);
// BlockMiner also reports hasMined() when the break event was cancelled, so check hasFailed() first.
if (miner.hasMined() && !miner.hasFailed()) {
// Collect items dropped around the head, such as the contents of a mined chest.
AxisAlignedBB around = AxisAlignedBB
.getBoundingBox(headPosX - 2, headPosY - 2, headPosZ - 2, headPosX + 3, headPosY + 3, headPosZ + 3);
@SuppressWarnings("unchecked")
List<EntityItem> items = worldObj.getEntitiesWithinAABB(EntityItem.class, around);
for (EntityItem entity : items) {
if (entity.isDead) {
continue;
}
ItemStack mineable = entity.getEntityItem();
if (mineable.stackSize <= 0) {
continue;
}
CoreProxy.proxy.removeEntity(entity);
miner.mineStack(mineable);
}
}
if (miner.hasMined() || miner.hasFailed()) {
miner = null;
scanIndex++;
if (!findFrame()) {
initializeBlueprintBuilder();
} else {
stage = Stage.IDLE;
}
}
}
/** Whether the frame next to the quarry is still there. If not, the quarry rebuilds it. */
private boolean findFrame() {
for (int i = 2; i < 6; i++) {
ForgeDirection o = ForgeDirection.getOrientation(i);
if (box.contains(xCoord + o.offsetX, yCoord + o.offsetY, zCoord + o.offsetZ)) {
return worldObj.getBlock(xCoord + o.offsetX, yCoord + o.offsetY, zCoord + o.offsetZ)
== BuildCraftBuilders.frameBlock;
}
}
return true;
}
private void idling() {
ScanResult result = parking ? ScanResult.NONE : findTarget();
if (result == ScanResult.PENDING) {
return;
}
if (result == ScanResult.FOUND) {
stage = Stage.MOVING;
} else {
int restX = box.xMin + 1, restY = yCoord + 2, restZ = box.zMin + 1;
if (headPosX == restX && headPosY == restY && headPosZ == restZ) {
parking = false;
stage = Stage.DONE;
} else {
setTarget(restX, restY, restZ);
parking = true;
stage = Stage.MOVING;
}
}
movingHorizontally = true;
movingVertically = true;
headTrajectory = (float) Math.atan2(targetZ - headPosZ, targetX - headPosX);
sendNetworkUpdate();
}
private void resetScan() {
scanY = Integer.MAX_VALUE;
scanIndex = 0;
}
/**
* Looks for the next block to dig, from the top layer down to {@link CEConfig#quarryMinY}. Each layer is walked
* in a serpentine order that starts where the layer above ended, and the search continues from where the last one
* stopped. If something is found, it becomes the target, and the search moves past it once the arm is done with it
* (see {@link #positionReached} and {@link #dig}). Each block is visited once per pass, so a fluid flowing back in
* behind the arm does not hold it in place.
*/
private ScanResult findTarget() {
int sizeX = box.sizeX() - 2;
int sizeZ = box.sizeZ() - 2;
int perLayer = sizeX * sizeZ;
int minY = Math.max(CEConfig.quarryMinY, 0);
int budget = SCAN_BUDGET;
if (scanY > yCoord + 3) {
scanY = yCoord + 3;
scanIndex = 0;
}
for (; scanY >= minY; scanY--, scanIndex = 0) {
for (; scanIndex < perLayer; scanIndex++) {
if (budget-- <= 0) {
return ScanResult.PENDING;
}
int row = scanIndex / sizeZ;
int column = scanIndex % sizeZ;
int searchX = (scanY & 1) == 0 ? row : sizeX - 1 - row;
int searchZ = (searchX & 1) == (scanY & 1) ? column : sizeZ - 1 - column;
int x = box.xMin + 1 + searchX;
int z = box.zMin + 1 + searchZ;
if (isTarget(x, scanY, z)) {
setTarget(x, scanY + 1, z);
return ScanResult.FOUND;
}
}
}
return ScanResult.NONE;
}
private boolean isTarget(int x, int y, int z) {
Block block = worldObj.getBlock(x, y, z);
return isFluidTarget(block, worldObj.getBlockMetadata(x, y, z)) || isMineable(block, x, y, z);
}
private boolean isMineable(Block block, int x, int y, int z) {
return BlockUtils.canChangeBlock(block, worldObj, x, y, z) && !isSoft(block, x, y, z)
&& !isFluid(block)
&& block != BuildCraftBuilders.frameBlock;
}
/**
* Same as {@link BuildCraftAPI#isSoftBlock}, without its cache. The cache is only updated when the block change is
* sent to players, so in a chunk without players a mined block would still read as solid.
*/
private boolean isSoft(Block block, int x, int y, int z) {
return block.isAir(worldObj, x, y, z) || BuildCraftAPI.softBlocks.contains(block)
|| block.isReplaceable(worldObj, x, y, z);
}
/** Fluids this quarry goes to even though it cannot mine them. */
private boolean isFluidTarget(Block block, int meta) {
switch (type) {
case LAVA:
return block instanceof BlockLiquid && block.getMaterial() == Material.lava && meta == 0;
case FLUID:
return isFluid(block);
case FAST:
default:
return false;
}
}
private static boolean isFluid(Block block) {
return block instanceof BlockLiquid || block instanceof IFluidBlock;
}
@Override
public void readFromNBT(NBTTagCompound nbt) {
super.readFromNBT(nbt);
if (nbt.hasKey("box")) {
box.initialize(nbt.getCompoundTag("box"));
}
targetX = nbt.getInteger("targetX");
targetY = nbt.getInteger("targetY");
targetZ = nbt.getInteger("targetZ");
headPosX = nbt.getDouble("headPosX");
headPosY = nbt.getDouble("headPosY");
headPosZ = nbt.getDouble("headPosZ");
if (nbt.hasKey("scanY")) {
scanY = nbt.getInteger("scanY");
scanIndex = nbt.getInteger("scanIndex");
}
parking = nbt.getBoolean("parking");
// The build state is loaded in initialize(), once the frame blueprint exists.
initNBT = (NBTTagCompound) nbt.getCompoundTag("bpt")
.copy();
}
@Override
public void writeToNBT(NBTTagCompound nbt) {
super.writeToNBT(nbt);
nbt.setInteger("targetX", targetX);
nbt.setInteger("targetY", targetY);
nbt.setInteger("targetZ", targetZ);
nbt.setDouble("headPosX", headPosX);
nbt.setDouble("headPosY", headPosY);
nbt.setDouble("headPosZ", headPosZ);
nbt.setInteger("scanY", scanY);
nbt.setInteger("scanIndex", scanIndex);
nbt.setBoolean("parking", parking);
NBTTagCompound boxTag = new NBTTagCompound();
box.writeToNBT(boxTag);
nbt.setTag("box", boxTag);
NBTTagCompound bptNBT = new NBTTagCompound();
if (builder != null) {
NBTTagCompound builderCpt = new NBTTagCompound();
builder.saveBuildStateToNBT(builderCpt, this);
bptNBT.setTag("builderState", builderCpt);
}
nbt.setTag("bpt", bptNBT);
}
/** Called by {@link #moveHead} when the arm arrives at its target. */
private void positionReached() {
if (worldObj.isRemote) {
return;
}
if (parking) {
parking = false;
stage = Stage.DONE;
return;
}
int x = targetX;
int y = targetY - 1;
int z = targetZ;
if (type == QuarryType.FLUID) {
// The target itself becomes glass and is then mined. Its neighbours stay glass, walling off the shaft.
replaceFluid(x, y, z);
replaceFluid(x + 1, y, z);
replaceFluid(x - 1, y, z);
replaceFluid(x, y, z + 1);
replaceFluid(x, y, z - 1);
replaceFluid(x, y + 1, z);
}
Block block = worldObj.getBlock(x, y, z);
if (type == QuarryType.LAVA && isFluidTarget(block, worldObj.getBlockMetadata(x, y, z))) {
worldObj.setBlockToAir(x, y, z);
useFluidEnergy(LAVA_ERASE_ENERGY);
scanIndex++;
stage = Stage.IDLE;
} else if (isMineable(block, x, y, z)) {
// dig() moves the search on once the block is mined.
miner = new BlockMiner(worldObj, this, x, y, z);
stage = Stage.DIGGING;
} else {
scanIndex++;
stage = Stage.IDLE;
}
}
private void replaceFluid(int x, int y, int z) {
if (worldObj.blockExists(x, y, z) && isFluid(worldObj.getBlock(x, y, z))) {
worldObj.setBlock(x, y, z, Blocks.glass);
useFluidEnergy(FLUID_REPLACE_ENERGY);
}
}
/** Takes what it can of the cost; like CE, a missing remainder does not stop the work. */
private void useFluidEnergy(int energy) {
int cost = (int) (energy * BuildCraftCore.miningMultiplier);
getBattery().useEnergy(0, cost, false);
}
@Override
protected int getNetworkUpdateRange() {
return DefaultProps.NETWORK_UPDATE_RANGE
+ (int) Math.ceil(Math.sqrt(yCoord * yCoord + box.sizeX() * box.sizeX() + box.sizeZ() * box.sizeZ()));
}
@Override
public void invalidate() {
super.invalidate();
destroy();
}
@Override
public void onChunkUnload() {
destroy();
}
@Override
public void destroy() {
if (arm != null) {
arm.setDead();
}
arm = null;
frameProducer = false;
if (miner != null) {
miner.invalidate();
}
}
@Override
public boolean hasWork() {
return stage != Stage.DONE;
}
/**
* Uses the marker next to the quarry if it spans at least 3x3, or else the default area from the config. The
* default area lies behind the quarry, with the quarry on its near edge.
*/
private void setBoundaries() {
IAreaProvider a = Utils.getNearbyAreaProvider(worldObj, xCoord, yCoord, zCoord);
if (a != null && (a.xMax() - a.xMin() + 1 < 3 || a.zMax() - a.zMin() + 1 < 3)) {
a = null;
}
if (a != null) {
box.initialize(a);
if (box.sizeY() < 5) {
box.yMax = box.yMin + 4;
}
a.removeFromWorld();
} else {
int size = CEConfig.quarryDefaultSize;
int before = mirrored ? size - CEConfig.quarryDefaultPosition - 1 : CEConfig.quarryDefaultPosition;
int after = size - before - 1;
int far = size - 1;
int xMin, zMin;
switch (worldObj.getBlockMetadata(xCoord, yCoord, zCoord) & 7) {
case 3:
xMin = xCoord - before;
zMin = zCoord - far - 1;
break;
case 4:
xMin = xCoord + 1;
zMin = zCoord - before;
break;
case 5:
xMin = xCoord - far - 1;
zMin = zCoord - after;
break;
case 2:
default:
xMin = xCoord - after;
zMin = zCoord + 1;
break;
}
box.initialize(xMin, yCoord, zMin, xMin + far, yCoord + 4, zMin + far);
}
sendNetworkUpdate();
}
/** Starts building the frame. Already placed frame blocks are kept. */
private void initializeBlueprintBuilder() {
Blueprint bpt = ((FillerPattern) FillerManager.registry.getPattern("buildcraft:frame"))
.getBlueprint(box, worldObj, new IStatementParameter[0], BuildCraftBuilders.frameBlock, 0);
if (bpt != null) {
builder = new BptBuilderBlueprint(bpt, worldObj, box.xMin, yCoord, box.zMin);
speed = 0;
stage = Stage.BUILDING;
sendNetworkUpdate();
}
}
@Override
public void writeData(ByteBuf stream) {
super.writeData(stream);
box.writeData(stream);
stream.writeInt(targetX);
stream.writeShort(targetY);
stream.writeInt(targetZ);
stream.writeDouble(headPosX);
stream.writeDouble(headPosY);
stream.writeDouble(headPosZ);
stream.writeFloat((float) speed);
stream.writeFloat(headTrajectory);
int flags = stage.ordinal();
flags |= movingHorizontally ? 0x10 : 0;
flags |= movingVertically ? 0x20 : 0;
stream.writeByte(flags);
}
@Override
public void readData(ByteBuf stream) {
super.readData(stream);
box.readData(stream);
targetX = stream.readInt();
targetY = stream.readUnsignedShort();
targetZ = stream.readInt();
headPosX = stream.readDouble();
headPosY = stream.readDouble();
headPosZ = stream.readDouble();
speed = stream.readFloat();
headTrajectory = stream.readFloat();
int flags = stream.readUnsignedByte();
stage = Stage.values()[flags & 0x07];
movingHorizontally = (flags & 0x10) != 0;
movingVertically = (flags & 0x20) != 0;
createUtilsIfNeeded();
if (arm != null) {
arm.setHead(headPosX, headPosY, headPosZ);
arm.updatePosition();
}
}
@Override
public void initialize() {
super.initialize();
if (!worldObj.isRemote && !box.initialized) {
setBoundaries();
}
createUtilsIfNeeded();
if (initNBT != null && builder != null) {
builder.loadBuildStateToNBT(initNBT.getCompoundTag("builderState"), this);
}
initNBT = null;
sendNetworkUpdate();
}
/** Rebuilds the frame and searches the whole area again, also after the quarry has finished. */
public void reinitialize() {
if (miner != null) {
miner.invalidate();
miner = null;
}
parking = false;
resetScan();
initializeBlueprintBuilder();
}
// The inventory holds an endless supply of frames for building the frame, and nothing else. It is hidden from
// pipes and hoppers by the sided inventory methods below.
@Override
public int getSizeInventory() {
return 1;
}
@Override
public ItemStack getStackInSlot(int i) {
return frameProducer ? new ItemStack(BuildCraftBuilders.frameBlock) : null;
}
@Override
public ItemStack decrStackSize(int i, int j) {
return frameProducer ? new ItemStack(BuildCraftBuilders.frameBlock, j) : null;
}
@Override
public void setInventorySlotContents(int i, ItemStack itemstack) {}
@Override
public ItemStack getStackInSlotOnClosing(int slot) {
return null;
}
@Override
public String getInventoryName() {
return "";
}
@Override
public boolean hasCustomInventoryName() {
return false;
}
@Override
public int getInventoryStackLimit() {
return 0;
}
@Override
public boolean isItemValidForSlot(int i, ItemStack itemstack) {
return false;
}
@Override
public boolean isUseableByPlayer(EntityPlayer entityplayer) {
return false;
}
@Override
public void openInventory() {}
@Override
public void closeInventory() {}
@Override
public boolean isBuildingMaterialSlot(int i) {
return true;
}
@Override
public int[] getAccessibleSlotsFromSide(int side) {
return new int[] {};
}
@Override
public boolean canInsertItem(int slot, ItemStack stack, int side) {
return false;
}
@Override
public boolean canExtractItem(int slot, ItemStack stack, int side) {
return false;
}
private void moveHead(double instantSpeed) {
if (movingHorizontally) {
if (Math.abs(targetX - headPosX) < instantSpeed * 2 && Math.abs(targetZ - headPosZ) < instantSpeed * 2) {
headPosX = targetX;
headPosZ = targetZ;
movingHorizontally = false;
if (!movingVertically) {
positionReached();
headPosY = targetY;
}
} else {
headPosX += MathHelper.cos(headTrajectory) * instantSpeed;
headPosZ += MathHelper.sin(headTrajectory) * instantSpeed;
}
}
if (movingVertically) {
if (Math.abs(targetY - headPosY) < instantSpeed * 2) {
headPosY = targetY;
movingVertically = false;
if (!movingHorizontally) {
positionReached();
headPosX = targetX;
headPosZ = targetZ;
}
} else if (targetY > headPosY) {
headPosY += instantSpeed;
} else {
headPosY -= instantSpeed;
}
}
if (arm != null && worldObj.isRemote) {
arm.setHead(headPosX, headPosY, headPosZ);
arm.updatePosition();
}
}
private void setHead(double x, double y, double z) {
headPosX = x;
headPosY = y;
headPosZ = z;
}
private void setTarget(int x, int y, int z) {
targetX = x;
targetY = y;
targetZ = z;
}
@Override
public AxisAlignedBB getRenderBoundingBox() {
return new Box(this).extendToEncompass(box)
.expand(50)
.getBoundingBox();
}
@Override
public Box getBox() {
return box;
}
@Override
public boolean acceptsControlMode(Mode mode) {
return mode == Mode.Off || mode == Mode.On || mode == Mode.Loop;
}
@Override
public boolean doDrop() {
return false;
}
@Override
public ConnectOverride overridePipeConnection(IPipeTile.PipeType type, ForgeDirection with) {
return type == IPipeTile.PipeType.ITEM ? ConnectOverride.CONNECT : ConnectOverride.DEFAULT;
}
}
@@ -1,2 +1,5 @@
tile.emcBuilder.name=EMC Builder
gui.cemodern.stored=EMC: %s
tile.ceQuarry2.name=Quarry of fast mining type
tile.ceQuarry3.name=Quarry of lava removal type
tile.ceQuarry4.name=Quarry of fluid replace type
@@ -1,2 +1,5 @@
tile.emcBuilder.name=EMCビルダー
gui.cemodern.stored=EMC: %s
tile.ceQuarry2.name=採掘強化型クアーリー
tile.ceQuarry3.name=溶岩除去型クアーリー
tile.ceQuarry4.name=液体対策型クアーリー
Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

+3 -3
View File
@@ -3,7 +3,7 @@
"modList": [{
"modid": "${modId}",
"name": "${modName}",
"description": "A Minecraft 1.7.10 port of CE. Adds the EMC Builder, a BuildCraft Builder that pays for its materials with EMC.",
"description": "A Minecraft 1.7.10 port of CE. Adds the EMC Builder, a BuildCraft Builder that pays for its materials with EMC, and the CE quarries, improved BuildCraft quarries.",
"version": "${modVersion}",
"mcversion": "${minecraftVersion}",
"url": "",
@@ -13,8 +13,8 @@
"logoFile": "",
"screenshots": [],
"parent": "",
"requiredMods": ["BuildCraft|Builders", "ProjectE"],
"dependencies": ["BuildCraft|Builders", "ProjectE"],
"requiredMods": ["BuildCraft|Builders", "BuildCraft|Factory", "BuildCraft|Silicon", "BuildCraft|Transport", "ProjectE"],
"dependencies": ["BuildCraft|Builders", "BuildCraft|Factory", "BuildCraft|Silicon", "BuildCraft|Transport", "ProjectE"],
"dependants": [],
"useDependencyInformation": false
}]