diff --git a/buildcraft_resources/changelog/7.1.9 b/buildcraft_resources/changelog/7.1.9 index e8a1305837..b142b5832e 100644 --- a/buildcraft_resources/changelog/7.1.9 +++ b/buildcraft_resources/changelog/7.1.9 @@ -1,3 +1,8 @@ +Improvements: + +* Builder "Needed" list now shows exact amount of required blocks on hover (asie) +* More Builder optimizations (asie) + Bugs fixed: * [#3056] PipeTransportFluids crash (asie) diff --git a/common/buildcraft/BuildCraftBuilders.java b/common/buildcraft/BuildCraftBuilders.java index ffd5281445..366cdfdc56 100644 --- a/common/buildcraft/BuildCraftBuilders.java +++ b/common/buildcraft/BuildCraftBuilders.java @@ -75,6 +75,7 @@ import buildcraft.builders.BlueprintServerDatabase; import buildcraft.builders.BuilderProxy; import buildcraft.builders.BuilderProxyClient; +import buildcraft.builders.BuilderTooltipHandler; import buildcraft.builders.BuildersGuiHandler; import buildcraft.builders.HeuristicBlockDetection; import buildcraft.builders.ItemBlueprintStandard; @@ -344,6 +345,8 @@ public void postInit(FMLPostInitializationEvent evt) { public void init(FMLInitializationEvent evt) { NetworkRegistry.INSTANCE.registerGuiHandler(instance, new BuildersGuiHandler()); + MinecraftForge.EVENT_BUS.register(new BuilderTooltipHandler()); + // Standard blocks ISchematicRegistry schemes = BuilderAPI.schematicRegistry; schemes.registerSchematicBlock(Blocks.air, SchematicAir.class); diff --git a/common/buildcraft/BuildCraftCore.java b/common/buildcraft/BuildCraftCore.java index 5ce0f2f609..a5087531ba 100644 --- a/common/buildcraft/BuildCraftCore.java +++ b/common/buildcraft/BuildCraftCore.java @@ -94,6 +94,7 @@ import buildcraft.core.Version; import buildcraft.core.blueprints.SchematicHelper; import buildcraft.core.blueprints.SchematicRegistry; +import buildcraft.core.blueprints.BuildingSlotMapIterator; import buildcraft.core.builders.patterns.FillerPattern; import buildcraft.core.builders.patterns.FillerRegistry; import buildcraft.core.builders.patterns.PatternBox; @@ -195,6 +196,7 @@ public enum RenderMode { public static boolean canEnginesExplode = false; public static int itemLifespan = 1200; public static int updateFactor = 10; + public static int builderMaxPerItemFactor = 1024; public static long longUpdateFactor = 40; public static BuildCraftConfiguration mainConfiguration; public static ConfigManager mainConfigManager; @@ -299,6 +301,8 @@ public void loadConfiguration(FMLPreInitializationEvent evt) { mainConfigManager.getCat("debug").setShowInGui(false); mainConfigManager.getCat("vars").setShowInGui(false); + mainConfigManager.register("general.builderMaxIterationsPerItemFactor", BuildCraftCore.builderMaxPerItemFactor, "Lower this number if BuildCraft builders/fillers are causing TPS lag. Raise it if you think they are being too slow.", ConfigManager.RestartRequirement.NONE); + mainConfigManager.register("general.miningBreaksPlayerProtectedBlocks", false, "Should BuildCraft miners be allowed to break blocks using player-specific protection?", ConfigManager.RestartRequirement.NONE); mainConfigManager.register("general.updateCheck", true, "Should I check the BuildCraft version on startup?", ConfigManager.RestartRequirement.NONE); mainConfigManager.register("display.hidePowerValues", false, "Should all power values (RF, RF/t) be hidden?", ConfigManager.RestartRequirement.NONE); @@ -549,19 +553,30 @@ public void postInit(FMLPostInitializationEvent event) { public void serverStarting(FMLServerStartingEvent event) { event.registerServerCommand(commandBuildcraft); + /* Increase the builder speed in singleplayer mode. + Singleplayer users generally run far fewer of them. */ + + if (FMLCommonHandler.instance().getSide() == Side.CLIENT) { + BuildingSlotMapIterator.MAX_PER_ITEM = builderMaxPerItemFactor * 4; + } else { + BuildingSlotMapIterator.MAX_PER_ITEM = builderMaxPerItemFactor; + } + if (Utils.CAULDRON_DETECTED) { BCLog.logger.warn("############################################"); BCLog.logger.warn("# #"); BCLog.logger.warn("# Cauldron has been detected! Please keep #"); - BCLog.logger.warn("# in mind that BuildCraft does NOT support #"); - BCLog.logger.warn("# Cauldron and we do not promise to fix #"); - BCLog.logger.warn("# bugs caused by its modifications to the #"); - BCLog.logger.warn("# Minecraft engine. Please reconsider. #"); + BCLog.logger.warn("# in mind that BuildCraft may NOT provide #"); + BCLog.logger.warn("# support to Cauldron users, as the mod is #"); + BCLog.logger.warn("# primarily tested without Bukkit/Spigot's #"); + BCLog.logger.warn("# changes to the Minecraft internals. #"); BCLog.logger.warn("# #"); BCLog.logger.warn("# Any lag caused by BuildCraft on top of #"); - BCLog.logger.warn("# Cauldron likely arises from our fixes to #"); - BCLog.logger.warn("# their bugs, so please don't report that #"); - BCLog.logger.warn("# either. Thanks for your attention! ~BC #"); + BCLog.logger.warn("# Cauldron likely arises from workarounds #"); + BCLog.logger.warn("# which we apply to make sure BuildCraft #"); + BCLog.logger.warn("# works properly with Cauldron installed. #"); + BCLog.logger.warn("# #"); + BCLog.logger.warn("# Thanks for your attention! ~ BC devs #"); BCLog.logger.warn("# #"); BCLog.logger.warn("############################################"); @@ -617,6 +632,7 @@ public void reloadConfig(ConfigManager.RestartRequirement restartType) { } else if (restartType == ConfigManager.RestartRequirement.WORLD) { reloadConfig(ConfigManager.RestartRequirement.NONE); } else { + builderMaxPerItemFactor = mainConfigManager.get("general.builderMaxIterationsPerItemFactor").getInt(); hideFluidNumbers = mainConfigManager.get("display.hideFluidValues").getBoolean(); hidePowerNumbers = mainConfigManager.get("display.hidePowerValues").getBoolean(); itemLifespan = mainConfigManager.get("general.itemLifespan").getInt(); @@ -625,6 +641,8 @@ public void reloadConfig(ConfigManager.RestartRequirement restartType) { miningMultiplier = (float) mainConfigManager.get("power.miningUsageMultiplier").getDouble(); miningAllowPlayerProtectedBlocks = mainConfigManager.get("general.miningBreaksPlayerProtectedBlocks").getBoolean(); + BuildingSlotMapIterator.MAX_PER_ITEM = builderMaxPerItemFactor; + if (mainConfigManager.get("general.updateCheck").getBoolean(true)) { Version.check(); } diff --git a/common/buildcraft/builders/BuilderTooltipHandler.java b/common/buildcraft/builders/BuilderTooltipHandler.java new file mode 100644 index 0000000000..c4469fa460 --- /dev/null +++ b/common/buildcraft/builders/BuilderTooltipHandler.java @@ -0,0 +1,35 @@ +package buildcraft.builders; + +import java.util.List; + +import net.minecraft.util.EnumChatFormatting; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.event.entity.player.ItemTooltipEvent; + +import buildcraft.builders.gui.ContainerBuilder; +import buildcraft.core.blueprints.RequirementItemStack; + +/** + * Created by asie on 10/6/15. + */ +public class BuilderTooltipHandler { + @SubscribeEvent + public void itemTooltipEvent(ItemTooltipEvent event) { + if (event.itemStack != null && event.entityPlayer != null && event.entityPlayer.openContainer != null + && event.entityPlayer.openContainer instanceof ContainerBuilder) { + ContainerBuilder containerBuilder = (ContainerBuilder) event.entityPlayer.openContainer; + TileBuilder builder = containerBuilder.getBuilder(); + if (builder != null) { + List needs = builder.getNeededItems(); + if (needs != null) { + for (RequirementItemStack ris : needs) { + if (ris.stack == event.itemStack) { + event.toolTip.add(EnumChatFormatting.GRAY + "" + EnumChatFormatting.ITALIC + "Needed: " + ris.size); + } + } + } + } + } + } +} diff --git a/common/buildcraft/builders/TileBuilder.java b/common/buildcraft/builders/TileBuilder.java index 2fda100d7d..36c1d6cd32 100644 --- a/common/buildcraft/builders/TileBuilder.java +++ b/common/buildcraft/builders/TileBuilder.java @@ -39,6 +39,7 @@ import buildcraft.api.core.IInvSlot; import buildcraft.api.core.IPathProvider; import buildcraft.api.core.Position; +import buildcraft.api.core.SafeTimeTracker; import buildcraft.api.robots.IRequestProvider; import buildcraft.api.tiles.IControllable; import buildcraft.api.tiles.IHasWork; @@ -80,6 +81,9 @@ public class TileBuilder extends TileAbstractBuilder implements IHasWork, IFluid }; public TankManager fluidTank = new TankManager(fluidTanks); + private SafeTimeTracker networkUpdateTracker = new SafeTimeTracker(BuildCraftCore.updateFactor / 2); + private boolean shouldUpdateRequirements; + private SimpleInventory inv = new SimpleInventory(28, "Builder", 64); private BptBuilderBase currentBuilder; private RecursiveBlueprintBuilder recursiveBuilder; @@ -216,7 +220,6 @@ public void initialize() { return; } - if (initNBT != null) { iterateBpt(true); @@ -262,7 +265,6 @@ public void initialize() { if (path != null && pathLasers.size() == 0) { createLasersForPath(); - sendNetworkUpdate(); } @@ -333,7 +335,7 @@ public void iterateBpt(boolean forceIterate) { currentPathIterator = null; } - updateRequirements(); + scheduleRequirementUpdate(); sendNetworkUpdate(); @@ -373,13 +375,13 @@ public void iterateBpt(boolean forceIterate) { done = false; } - updateRequirements(); + scheduleRequirementUpdate(); } else { if (currentBuilder != null && currentBuilder.isDone(this)) { currentBuilder.postProcessing(worldObj); currentBuilder = recursiveBuilder.nextBuilder(); - updateRequirements(); + scheduleRequirementUpdate(); } else { BlueprintBase bpt = instanciateBlueprint(); @@ -389,7 +391,7 @@ public void iterateBpt(boolean forceIterate) { currentBuilder = recursiveBuilder.nextBuilder(); - updateRequirements(); + scheduleRequirementUpdate(); } } @@ -572,6 +574,11 @@ public void updateEntity() { return; } + if (shouldUpdateRequirements && networkUpdateTracker.markTimeIfDelay(worldObj)) { + updateRequirements(); + shouldUpdateRequirements = false; + } + if ((currentBuilder == null || currentBuilder.isDone(this)) && box.isInitialized()) { box.reset(); @@ -591,7 +598,7 @@ public void updateEntity() { } if (!isBuilding && this.isBuildingBlueprint()) { - updateRequirements(); + scheduleRequirementUpdate(); } isBuilding = this.isBuildingBlueprint(); @@ -612,7 +619,7 @@ public boolean isBuildingBlueprint() { } public List getNeededItems() { - return requiredToBuild; + return worldObj.isRemote ? requiredToBuild : (currentBuilder instanceof BptBuilderBlueprint ? ((BptBuilderBlueprint) currentBuilder).getNeededItems() : null); } @Override @@ -716,16 +723,16 @@ public AxisAlignedBB getRenderBoundingBox() { public void build() { if (currentBuilder != null) { if (currentBuilder.buildNextSlot(worldObj, this, xCoord, yCoord, zCoord)) { - updateRequirements(); + scheduleRequirementUpdate(); } } } - public void updateRequirements() { + private void updateRequirements() { List reqCopy = null; if (currentBuilder instanceof BptBuilderBlueprint) { currentBuilder.initialize(); - reqCopy = ((BptBuilderBlueprint) currentBuilder).neededItems; + reqCopy = ((BptBuilderBlueprint) currentBuilder).getNeededItems(); } for (EntityPlayer p : guiWatchers) { @@ -733,11 +740,15 @@ public void updateRequirements() { } } - public void updateRequirements(EntityPlayer caller) { + public void scheduleRequirementUpdate() { + shouldUpdateRequirements = true; + } + + public void updateRequirementsOnGuiOpen(EntityPlayer caller) { List reqCopy = null; if (currentBuilder instanceof BptBuilderBlueprint) { currentBuilder.initialize(); - reqCopy = ((BptBuilderBlueprint) currentBuilder).neededItems; + reqCopy = ((BptBuilderBlueprint) currentBuilder).getNeededItems(); } BuildCraftCore.instance.sendToPlayer(caller, getItemRequirementsPacket(reqCopy)); @@ -830,7 +841,7 @@ public int getRequestsCount() { } else { BptBuilderBlueprint bpt = (BptBuilderBlueprint) currentBuilder; - return bpt.neededItems.size(); + return bpt.getNeededItems().size(); } } @@ -842,12 +853,13 @@ public ItemStack getRequest(int slot) { return null; } else { BptBuilderBlueprint bpt = (BptBuilderBlueprint) currentBuilder; + List neededItems = bpt.getNeededItems(); - if (bpt.neededItems.size() <= slot) { + if (neededItems.size() <= slot) { return null; } - RequirementItemStack requirement = bpt.neededItems.get(slot); + RequirementItemStack requirement = neededItems.get(slot); int qty = quantityMissing(requirement.stack, requirement.size); @@ -868,12 +880,13 @@ public ItemStack offerItem(int slot, ItemStack stack) { return stack; } else { BptBuilderBlueprint bpt = (BptBuilderBlueprint) currentBuilder; + List neededItems = bpt.getNeededItems(); - if (bpt.neededItems.size() <= slot) { + if (neededItems.size() <= slot) { return stack; } - RequirementItemStack requirement = bpt.neededItems.get(slot); + RequirementItemStack requirement = neededItems.get(slot); int qty = quantityMissing(requirement.stack, requirement.size); diff --git a/common/buildcraft/builders/gui/ContainerBuilder.java b/common/buildcraft/builders/gui/ContainerBuilder.java index 603b8eb405..27ca4f1aff 100644 --- a/common/buildcraft/builders/gui/ContainerBuilder.java +++ b/common/buildcraft/builders/gui/ContainerBuilder.java @@ -52,11 +52,15 @@ public ContainerBuilder(IInventory playerInventory, TileBuilder builder) { if (!builder.getWorldObj().isRemote && playerInventory instanceof InventoryPlayer) { // Refresh the requirements list for the player opening the GUI, // in case he does not have it. - builder.updateRequirements(((InventoryPlayer) playerInventory).player); + builder.updateRequirementsOnGuiOpen(((InventoryPlayer) playerInventory).player); builder.addGuiWatcher(((InventoryPlayer) playerInventory).player); } } + public TileBuilder getBuilder() { + return builder; + } + @Override public void onContainerClosed(EntityPlayer player) { super.onContainerClosed(player); diff --git a/common/buildcraft/core/blueprints/BptBuilderBlueprint.java b/common/buildcraft/core/blueprints/BptBuilderBlueprint.java index 1ecfd7c509..87758b9960 100644 --- a/common/buildcraft/core/blueprints/BptBuilderBlueprint.java +++ b/common/buildcraft/core/blueprints/BptBuilderBlueprint.java @@ -48,7 +48,6 @@ import buildcraft.core.builders.BuildingSlotBlock; import buildcraft.core.builders.BuildingSlotBlock.Mode; import buildcraft.core.builders.BuildingSlotEntity; -import buildcraft.core.builders.BuildingSlotMapIterator; import buildcraft.core.builders.IBuildingItemsProvider; import buildcraft.core.builders.TileAbstractBuilder; import buildcraft.core.lib.inventory.InventoryCopy; @@ -56,12 +55,12 @@ import buildcraft.core.lib.utils.BlockUtils; public class BptBuilderBlueprint extends BptBuilderBase { - public ArrayList neededItems = new ArrayList(); - protected HashSet builtEntities = new HashSet(); + protected HashMap> buildList = new HashMap>(); + protected int[] buildStageOccurences; + + private ArrayList neededItems = new ArrayList(); - private HashMap> buildList = new HashMap>(); - private int[] buildStageOccurences; private LinkedList entityList = new LinkedList(); private LinkedList postProcessing = new LinkedList(); private BuildingSlotMapIterator iterator; @@ -254,8 +253,6 @@ public void deploy() { } private void checkDone() { - recomputeNeededItems(); - if (getBuildListCount() == 0 && entityList.size() == 0) { done = true; } else { @@ -346,7 +343,7 @@ private BuildingSlot internalGetNextBlock(World world, TileAbstractBuilder build } if (iterator == null) { - iterator = new BuildingSlotMapIterator(buildList, builder, buildStageOccurences); + iterator = new BuildingSlotMapIterator(this, builder); } BuildingSlotBlock slot; @@ -473,6 +470,7 @@ private BuildingSlot internalGetNextBlock(World world, TileAbstractBuilder build return null; } + // TODO: Remove recomputeNeededItems() and replace with something more efficient private BuildingSlot internalGetNextEntity(World world, TileAbstractBuilder builder) { Iterator it = entityList.iterator(); @@ -481,12 +479,14 @@ private BuildingSlot internalGetNextEntity(World world, TileAbstractBuilder buil if (slot.isAlreadyBuilt(context)) { it.remove(); + recomputeNeededItems(); } else { if (checkRequirements(builder, slot.schematic)) { builder.consumeEnergy(slot.getEnergyRequirement()); useRequirements(builder, slot); it.remove(); + recomputeNeededItems(); postProcessing.add(slot); builtEntities.add(slot.sequenceNumber); return slot; @@ -656,7 +656,82 @@ public void useRequirements(IInventory inv, BuildingSlot slot) { } } - public void recomputeNeededItems() { + public List getNeededItems() { + return neededItems; + } + + protected void onRemoveBuildingSlotBlock(BuildingSlotBlock slot) { + buildStageOccurences[slot.buildStage]--; + LinkedList stacks = new LinkedList(); + + try { + stacks = slot.getRequirements(context); + } catch (Throwable t) { + // Defensive code against errors in implementers + t.printStackTrace(); + BCLog.logger.throwing(t); + } + + HashMap computeStacks = new HashMap(); + + for (ItemStack stack : stacks) { + if (stack == null || stack.getItem() == null || stack.stackSize == 0) { + continue; + } + + StackKey key = new StackKey(stack); + + if (!computeStacks.containsKey(key)) { + computeStacks.put(key, stack.stackSize); + } else { + Integer num = computeStacks.get(key); + num += stack.stackSize; + computeStacks.put(key, num); + } + } + + for (RequirementItemStack ris : neededItems) { + StackKey stackKey = new StackKey(ris.stack); + if (computeStacks.containsKey(stackKey)) { + Integer num = computeStacks.get(stackKey); + if (ris.size <= num) { + recomputeNeededItems(); + return; + } else { + neededItems.set(neededItems.indexOf(ris), new RequirementItemStack(ris.stack, ris.size - num)); + } + } + } + + sortNeededItems(); + } + + private void sortNeededItems() { + Collections.sort(neededItems, new Comparator() { + @Override + public int compare(RequirementItemStack o1, RequirementItemStack o2) { + if (o1.size != o2.size) { + return o1.size < o2.size ? 1 : -1; + } else { + ItemStack os1 = o1.stack; + ItemStack os2 = o2.stack; + if (Item.getIdFromItem(os1.getItem()) > Item.getIdFromItem(os2.getItem())) { + return -1; + } else if (Item.getIdFromItem(os1.getItem()) < Item.getIdFromItem(os2.getItem())) { + return 1; + } else if (os1.getItemDamage() > os2.getItemDamage()) { + return -1; + } else if (os1.getItemDamage() < os2.getItemDamage()) { + return 1; + } else { + return 0; + } + } + } + }); + } + + private void recomputeNeededItems() { neededItems.clear(); HashMap computeStacks = new HashMap(); @@ -730,28 +805,7 @@ public void recomputeNeededItems() { neededItems.add(new RequirementItemStack(e.getKey().stack.copy(), e.getValue())); } - Collections.sort(neededItems, new Comparator() { - @Override - public int compare(RequirementItemStack o1, RequirementItemStack o2) { - if (o1.size != o2.size) { - return o1.size < o2.size ? 1 : -1; - } else { - ItemStack os1 = o1.stack; - ItemStack os2 = o2.stack; - if (Item.getIdFromItem(os1.getItem()) > Item.getIdFromItem(os2.getItem())) { - return -1; - } else if (Item.getIdFromItem(os1.getItem()) < Item.getIdFromItem(os2.getItem())) { - return 1; - } else if (os1.getItemDamage() > os2.getItemDamage()) { - return -1; - } else if (os1.getItemDamage() < os2.getItemDamage()) { - return 1; - } else { - return 0; - } - } - } - }); + sortNeededItems(); } @Override @@ -793,5 +847,4 @@ public void loadBuildStateToNBT(NBTTagCompound nbt, IBuildingItemsProvider build builtEntities.add(i); } } - } diff --git a/common/buildcraft/core/builders/BuildingSlotMapIterator.java b/common/buildcraft/core/blueprints/BuildingSlotMapIterator.java similarity index 82% rename from common/buildcraft/core/builders/BuildingSlotMapIterator.java rename to common/buildcraft/core/blueprints/BuildingSlotMapIterator.java index 1d1a055ef1..a80ec7f73f 100755 --- a/common/buildcraft/core/builders/BuildingSlotMapIterator.java +++ b/common/buildcraft/core/blueprints/BuildingSlotMapIterator.java @@ -6,7 +6,7 @@ * License 1.0, or MMPL. Please check the contents of the license located in * http://www.mod-buildcraft.com/MMPL-1.0.txt */ -package buildcraft.core.builders; +package buildcraft.core.blueprints; import java.util.HashSet; import java.util.Iterator; @@ -17,10 +17,14 @@ import net.minecraft.item.ItemStack; import net.minecraft.world.WorldSettings; +import buildcraft.core.builders.BuilderItemMetaPair; +import buildcraft.core.builders.BuildingSlotBlock; +import buildcraft.core.builders.TileAbstractBuilder; import buildcraft.core.lib.fluids.Tank; public class BuildingSlotMapIterator { - private static final int MAX_PER_ITEM = 64; + public static int MAX_PER_ITEM = 512; + private final BptBuilderBlueprint builderBlueprint; private final Map> slotMap; private final Set availablePairs = new HashSet(); private final int[] buildStageOccurences; @@ -30,10 +34,10 @@ public class BuildingSlotMapIterator { private List slots; private int slotPos, slotFound; - public BuildingSlotMapIterator(Map> slots, TileAbstractBuilder builder, - int[] buildStageOccurences) { - this.slotMap = slots; - this.buildStageOccurences = buildStageOccurences; + public BuildingSlotMapIterator(BptBuilderBlueprint builderBlueprint, TileAbstractBuilder builder) { + this.builderBlueprint = builderBlueprint; + this.slotMap = builderBlueprint.buildList; + this.buildStageOccurences = builderBlueprint.buildStageOccurences; this.isCreative = builder == null || builder.getWorldObj().getWorldInfo().getGameType() == WorldSettings.GameType.CREATIVE; @@ -107,7 +111,7 @@ public BuildingSlotBlock next() { } public void remove() { - buildStageOccurences[slots.get(slotPos).buildStage]--; + builderBlueprint.onRemoveBuildingSlotBlock(slots.get(slotPos)); slots.set(slotPos, null); }