diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltTunnelInteractionHandler.java b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltTunnelInteractionHandler.java index 08f3dcb0a..b239b65b9 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltTunnelInteractionHandler.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltTunnelInteractionHandler.java @@ -42,7 +42,7 @@ public class BeltTunnelInteractionHandler { } World world = beltInventory.belt.getWorld(); - boolean onServer = !world.isRemote; + boolean onServer = !world.isRemote || beltInventory.belt.isVirtual(); boolean removed = false; BeltTunnelTileEntity nextTunnel = getTunnelOnSegement(beltInventory, upcomingSegment); @@ -128,7 +128,7 @@ public class BeltTunnelInteractionHandler { BeltTunnelTileEntity te = getTunnelOnSegement(beltInventory, offset); if (te == null) return; - te.flap(side, inward ^ side.getAxis() == Axis.Z); + te.flap(side, inward); } protected static BeltTunnelTileEntity getTunnelOnSegement(BeltInventory beltInventory, int offset) { diff --git a/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BeltTunnelTileEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BeltTunnelTileEntity.java index 4bc4cf3b6..aaf96c230 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BeltTunnelTileEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BeltTunnelTileEntity.java @@ -147,7 +147,7 @@ public class BeltTunnelTileEntity extends SmartTileEntity implements IInstanceRe if (world.isRemote) { if (flaps.containsKey(side)) flaps.get(side) - .set(inward ? -1 : 1); + .set(inward ^ side.getAxis() == Axis.Z ? -1 : 1); return; } diff --git a/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BrassTunnelTileEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BrassTunnelTileEntity.java index f24f86018..cca38928c 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BrassTunnelTileEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BrassTunnelTileEntity.java @@ -115,7 +115,7 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity { return; if (stackToDistribute.isEmpty() && !syncedOutputActive) return; - if (world.isRemote) + if (world.isRemote && !isVirtual()) return; if (distributionProgress == -1) { @@ -206,6 +206,7 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity { SelectionMode mode = selectionMode.get(); boolean force = mode == SelectionMode.FORCED_ROUND_ROBIN || mode == SelectionMode.FORCED_SPLIT; boolean split = mode == SelectionMode.FORCED_SPLIT || mode == SelectionMode.SPLIT; + boolean robin = mode == SelectionMode.FORCED_ROUND_ROBIN || mode == SelectionMode.ROUND_ROBIN; if (mode == SelectionMode.RANDOMIZE) indexStart = rand.nextInt(amountTargets); @@ -265,6 +266,8 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity { remainingOutputs--; if (!simulate) full.add(pair); + if (robin) + break; continue; } else if (!remainder.isEmpty() && !simulate) { full.add(pair); @@ -336,7 +339,7 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity { return null; ItemStack result = sideOutput.handleInsertion(stack, side, simulate); if (result.isEmpty() && !simulate) - tunnel.flap(side, true); + tunnel.flap(side, false); return result; } diff --git a/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotBehaviour.java b/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotBehaviour.java index f79ef2fe2..ce9bf9820 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotBehaviour.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotBehaviour.java @@ -80,7 +80,7 @@ public class DepotBehaviour extends TileEntityBehaviour { TransportedItemStack ts = iterator.next(); if (!tick(ts)) continue; - if (world.isRemote) + if (world.isRemote && !tileEntity.isVirtual()) continue; if (heldItem == null) { heldItem = ts; diff --git a/src/main/java/com/simibubi/create/content/logistics/block/depot/EjectorTileEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/depot/EjectorTileEntity.java index 69cc84372..a5d0bf6e1 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/depot/EjectorTileEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/depot/EjectorTileEntity.java @@ -135,7 +135,8 @@ public class EjectorTileEntity extends KineticTileEntity { world.getEntitiesWithinAABB(Entity.class, new AxisAlignedBB(pos).grow(-1 / 16f, 0, -1 / 16f)); // Launch Items - if (!world.isRemote) + boolean doLogic = !world.isRemote || isVirtual(); + if (doLogic) launchItems(); // Launch Entities @@ -169,11 +170,13 @@ public class EjectorTileEntity extends KineticTileEntity { AllPackets.channel.sendToServer(new EjectorElytraPacket(pos)); } - if (!world.isRemote) { + if (doLogic) { lidProgress.chase(1, .8f, Chaser.EXP); state = State.LAUNCHING; - world.playSound(null, pos, SoundEvents.BLOCK_WOODEN_TRAPDOOR_CLOSE, SoundCategory.BLOCKS, .35f, 1f); - world.playSound(null, pos, SoundEvents.BLOCK_CHEST_OPEN, SoundCategory.BLOCKS, .1f, 1.4f); + if (!world.isRemote) { + world.playSound(null, pos, SoundEvents.BLOCK_WOODEN_TRAPDOOR_CLOSE, SoundCategory.BLOCKS, .35f, 1f); + world.playSound(null, pos, SoundEvents.BLOCK_CHEST_OPEN, SoundCategory.BLOCKS, .1f, 1.4f); + } } } @@ -369,6 +372,8 @@ public class EjectorTileEntity extends KineticTileEntity { return; if (presentStackSize < maxStackSize.getValue()) return; + if (depotBehaviour.heldItem != null && depotBehaviour.heldItem.beltPosition < .49f) + return; Direction funnelFacing = getFacing().getOpposite(); ItemStack held = depotBehaviour.getHeldItemStack(); @@ -502,6 +507,9 @@ public class EjectorTileEntity extends KineticTileEntity { NBTUtil.readBlockPos(compound.getCompound("EarlyTargetPos"))); earlyTargetTime = compound.getFloat("EarlyTargetTime"); } + + if (compound.contains("ForceAngle")) + lidProgress.startWithValue(compound.getFloat("ForceAngle")); } public void updateSignal() { diff --git a/src/main/java/com/simibubi/create/foundation/ponder/content/EjectorScenes.java b/src/main/java/com/simibubi/create/foundation/ponder/content/EjectorScenes.java new file mode 100644 index 000000000..22718cf47 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/ponder/content/EjectorScenes.java @@ -0,0 +1,373 @@ +package com.simibubi.create.foundation.ponder.content; + +import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllItems; +import com.simibubi.create.content.logistics.block.depot.EjectorTileEntity; +import com.simibubi.create.foundation.gui.AllIcons; +import com.simibubi.create.foundation.ponder.ElementLink; +import com.simibubi.create.foundation.ponder.SceneBuilder; +import com.simibubi.create.foundation.ponder.SceneBuildingUtil; +import com.simibubi.create.foundation.ponder.Selection; +import com.simibubi.create.foundation.ponder.elements.InputWindowElement; +import com.simibubi.create.foundation.ponder.elements.ParrotElement; +import com.simibubi.create.foundation.ponder.elements.WorldSectionElement; +import com.simibubi.create.foundation.utility.NBTHelper; +import com.simibubi.create.foundation.utility.Pointing; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.ItemEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.items.ItemHandlerHelper; + +public class EjectorScenes { + + public static void ejector(SceneBuilder scene, SceneBuildingUtil util) { + scene.title("weighted_ejector", "Using Weighted Ejectors"); + scene.configureBasePlate(0, 0, 5); + scene.showBasePlate(); + + BlockPos ejectorPos = util.grid.at(4, 1, 2); + Selection ejectorS = util.select.position(ejectorPos); + BlockPos targetPos = util.grid.at(0, 1, 2); + Selection targetS = util.select.position(targetPos); + + scene.world.setBlock(targetPos, AllBlocks.ANDESITE_CASING.getDefaultState(), false); + scene.idle(5); + scene.world.showSection(targetS, Direction.DOWN); + + scene.idle(10); + ItemStack asStack = AllBlocks.WEIGHTED_EJECTOR.asStack(); + scene.overlay.showControls(new InputWindowElement(util.vector.topOf(targetPos), Pointing.DOWN).rightClick() + .whileSneaking() + .withItem(asStack), 50); + scene.idle(7); + Object slot = new Object(); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.OUTPUT, slot, new AxisAlignedBB(targetPos), 160); + + scene.overlay.showText(70) + .attachKeyFrame() + .colored(PonderPalette.OUTPUT) + .text("Sneak and Right-Click holding an Ejector to select its target location") + .pointAt(util.vector.blockSurface(targetPos, Direction.WEST)) + .placeNearTarget(); + scene.idle(80); + scene.overlay.showControls(new InputWindowElement(util.vector.topOf(ejectorPos), Pointing.DOWN).rightClick() + .withItem(asStack), 50); + scene.idle(7); + scene.world.setKineticSpeed(ejectorS, 0); + scene.world.modifyTileNBT(ejectorS, EjectorTileEntity.class, nbt -> { + NBTHelper.writeEnum(nbt, "State", EjectorTileEntity.State.RETRACTING); + nbt.putFloat("ForceAngle", 1); + }); + scene.world.showSection(ejectorS, Direction.DOWN); + scene.idle(10); + + scene.overlay.showText(60) + .colored(PonderPalette.OUTPUT) + .text("The placed ejector will now launch objects to the marked location") + .pointAt(util.vector.blockSurface(ejectorPos, Direction.WEST)) + .placeNearTarget(); + scene.idle(70); + + slot = new Object(); + AxisAlignedBB bb = new AxisAlignedBB(ejectorPos.west()); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.OUTPUT, slot, bb, 20); + scene.idle(10); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.GREEN, slot, bb.expand(-15, 15, 0), 100); + scene.idle(10); + + scene.overlay.showText(60) + .attachKeyFrame() + .colored(PonderPalette.GREEN) + .text("A valid target can be at any height or distance within range") + .pointAt(util.vector.blockSurface(targetPos, Direction.WEST)) + .placeNearTarget(); + scene.idle(70); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.RED, new Object(), bb.offset(-2, 0, -1), 60); + scene.idle(10); + scene.overlay.showText(50) + .colored(PonderPalette.RED) + .text("They cannot however be off to a side") + .pointAt(util.vector.blockSurface(targetPos.north() + .east(), Direction.WEST)) + .placeNearTarget(); + scene.idle(70); + scene.overlay.showSelectionWithText(util.select.position(ejectorPos.west()), 70) + .colored(PonderPalette.OUTPUT) + .text("If no valid Target was selected, it will simply target the block directly in front") + .placeNearTarget(); + scene.idle(80); + + scene.world.showSection(util.select.position(3, 0, 5), Direction.UP); + scene.world.showSection(util.select.fromTo(4, 1, 5, 4, 1, 3), Direction.DOWN); + scene.idle(12); + scene.world.setKineticSpeed(ejectorS, 32); + scene.idle(10); + scene.overlay.showText(50) + .attachKeyFrame() + .text("Supply Rotational Force in order to charge it up") + .pointAt(util.vector.topOf(4, 1, 3)) + .placeNearTarget(); + scene.idle(60); + + ItemStack copperBlock = AllBlocks.COPPER_BLOCK.asStack(); + ItemStack copperIngot = AllItems.COPPER_INGOT.asStack(); + scene.overlay.showControls(new InputWindowElement(util.vector.topOf(ejectorPos) + .add(0.5, 0, 0), Pointing.RIGHT).withItem(copperBlock), 30); + scene.idle(7); + scene.world.createItemOnBeltLike(ejectorPos, Direction.NORTH, copperBlock); + scene.idle(20); + scene.overlay.showText(50) + .text("Items placed on the ejector cause it to trigger") + .pointAt(util.vector.topOf(ejectorPos)) + .placeNearTarget(); + scene.idle(60); + + scene.world.modifyEntities(ItemEntity.class, Entity::remove); + scene.world.hideSection(targetS, Direction.SOUTH); + scene.idle(15); + scene.world.restoreBlocks(targetS); + scene.world.showSection(targetS, Direction.SOUTH); + scene.idle(10); + scene.world.createItemOnBeltLike(targetPos, Direction.SOUTH, copperIngot); + scene.idle(20); + scene.world.createItemOnBeltLike(ejectorPos, Direction.SOUTH, copperBlock); + scene.overlay.showText(60) + .attachKeyFrame() + .text("If Inventories are targeted, the ejector will wait until there is space") + .pointAt(util.vector.topOf(targetPos)) + .placeNearTarget(); + scene.idle(70); + scene.effects.indicateSuccess(targetPos); + scene.world.removeItemsFromBelt(targetPos); + scene.idle(40); + scene.world.hideSection(targetS, Direction.NORTH); + scene.idle(15); + scene.world.setBlock(targetPos, AllBlocks.ANDESITE_CASING.getDefaultState(), false); + scene.world.showSection(targetS, Direction.NORTH); + + Vec3d input = util.vector.of(4.8, 1 + 12 / 16f, 2.5); + Vec3d topOfSlot = input.add(0, 2 / 16f, 0); + scene.overlay.showControls(new InputWindowElement(topOfSlot, Pointing.DOWN).scroll() + .withWrench(), 60); + scene.overlay.showFilterSlotInput(input, 80); + scene.idle(10); + scene.overlay.showText(80) + .attachKeyFrame() + .text("Using the Wrench, a required Stack Size can be configured") + .pointAt(topOfSlot) + .placeNearTarget(); + scene.world.modifyTileNBT(ejectorS, EjectorTileEntity.class, nbt -> { + nbt.putInt("ScrollValue", 10); + }); + scene.idle(90); + + scene.world.showSection(util.select.fromTo(5, 1, 0, 4, 1, 1), Direction.DOWN); + scene.world.showSection(util.select.position(5, 0, 1), Direction.UP); + scene.idle(15); + + BlockPos beltPos = util.grid.at(4, 1, 0); + scene.world.createItemOnBeltLike(beltPos, Direction.UP, copperBlock); + scene.overlay.showText(100) + .text("It is now limited to this stack size, and only activates when its held stack reaches this amount") + .pointAt(util.vector.topOf(ejectorPos)) + .placeNearTarget(); + for (int i = 0; i < 4; i++) { + scene.idle(20); + scene.world.createItemOnBeltLike(beltPos, Direction.UP, copperBlock); + } + scene.idle(20); + scene.world.createItemOnBeltLike(beltPos, Direction.UP, ItemHandlerHelper.copyStackWithSize(copperBlock, 15)); + scene.idle(80); + + scene.world.hideSection(util.select.fromTo(5, 1, 0, 4, 1, 1), Direction.UP); + scene.world.hideSection(util.select.position(5, 0, 1), Direction.DOWN); + scene.idle(30); + scene.world.modifyEntities(ItemEntity.class, Entity::remove); + + scene.addKeyframe(); + ElementLink birb = scene.special.createBirb(util.vector.topOf(ejectorPos) + .add(0, -3 / 16f, 0), ParrotElement.FlappyPose::new); + scene.idle(15); + scene.world.modifyTileEntity(ejectorPos, EjectorTileEntity.class, ejector -> ejector.activateDeferred()); + scene.special.moveParrot(birb, util.vector.of(-2, 3, 0), 5); + scene.special.rotateParrot(birb, 0, 360 * 2, 0, 21); + scene.idle(5); + scene.special.moveParrot(birb, util.vector.of(-1, 0, 0), 3); + scene.idle(3); + scene.special.moveParrot(birb, util.vector.of(-0.75, -1, 0), 6); + scene.idle(6); + scene.special.moveParrot(birb, util.vector.of(-0.25, -2 + 3 / 16f, 0), 12); + scene.idle(15); + scene.special.changeBirbPose(birb, ParrotElement.FaceCursorPose::new); + scene.overlay.showText(80) + .text("Other Entities will always trigger an Ejector when stepping on it") + .pointAt(util.vector.topOf(targetPos)) + .placeNearTarget(); + + } + + public static void splitY(SceneBuilder scene, SceneBuildingUtil util) { + scene.title("weighted_ejector_tunnel", "Splitting item stacks using Weighted Ejectors"); + scene.configureBasePlate(0, 0, 5); + scene.world.showSection(util.select.layer(0), Direction.UP); + scene.idle(5); + scene.world.showSection(util.select.fromTo(4, 1, 5, 0, 1, 3), Direction.DOWN); + scene.idle(5); + scene.world.showSection(util.select.position(2, 2, 3), Direction.DOWN); + scene.idle(10); + scene.world.showSection(util.select.position(2, 1, 2), Direction.SOUTH); + scene.idle(5); + scene.world.showSection(util.select.fromTo(4, 1, 2, 3, 1, 1), Direction.SOUTH); + scene.world.showSection(util.select.fromTo(2, 1, 1, 2, 1, 0), Direction.SOUTH); + scene.idle(10); + + BlockPos ejectorPos = util.grid.at(2, 1, 2); + + scene.overlay.showText(80) + .attachKeyFrame() + .text("Combined with Brass Tunnels, Ejectors can split item stacks by specific amounts") + .pointAt(util.vector.topOf(ejectorPos)) + .placeNearTarget(); + scene.idle(90); + + BlockPos tunnel = util.grid.at(2, 2, 3); + scene.overlay.showControls(new InputWindowElement(util.vector.topOf(tunnel), Pointing.DOWN).scroll() + .withWrench(), 70); + scene.idle(10); + scene.overlay.showControls( + new InputWindowElement(util.vector.topOf(tunnel), Pointing.UP).showing(AllIcons.I_TUNNEL_PREFER_NEAREST), + 60); + scene.overlay.showCenteredScrollInput(tunnel, Direction.UP, 100); + scene.idle(10); + scene.overlay.showText(100) + .attachKeyFrame() + .colored(PonderPalette.BLUE) + .text("First, configure the Brass Tunnel to 'Prefer Nearest', in order to prioritize its side output") + .pointAt(util.vector.topOf(tunnel)) + .placeNearTarget(); + scene.idle(110); + + Vec3d input = util.vector.of(2.5, 1 + 12 / 16f, 2.8); + Vec3d topOfSlot = input.add(0, 2 / 16f, 0); + scene.overlay.showControls(new InputWindowElement(topOfSlot, Pointing.DOWN).scroll() + .withWrench(), 60); + scene.overlay.showFilterSlotInput(input, 80); + scene.idle(10); + scene.overlay.showText(80) + .attachKeyFrame() + .text("The Stack Size set on the Ejector now determines the amount to be split off") + .pointAt(topOfSlot) + .placeNearTarget(); + scene.world.modifyTileNBT(util.select.position(2, 1, 2), EjectorTileEntity.class, nbt -> { + nbt.putInt("ScrollValue", 10); + }); + scene.idle(90); + + scene.overlay.showControls(new InputWindowElement(util.vector.topOf(util.grid.at(4, 1, 3)), Pointing.DOWN) + .withItem(AllItems.COPPER_INGOT.asStack()), 20); + scene.idle(7); + scene.world.createItemOnBelt(util.grid.at(4, 1, 3), Direction.UP, AllItems.COPPER_INGOT.asStack(64)); + scene.idle(40); + scene.world.multiplyKineticSpeed(util.select.everywhere(), 1 / 16f); + scene.overlay.showText(80) + .attachKeyFrame() + .text("While a new stack of the configured size exits the side output...") + .pointAt(util.vector.blockSurface(util.grid.at(2, 1, 1), Direction.WEST)) + .placeNearTarget(); + scene.idle(90); + scene.overlay.showText(80) + .text("...the remainder will continue on its path") + .pointAt(util.vector.blockSurface(util.grid.at(0, 1, 3), Direction.UP)) + .placeNearTarget(); + scene.idle(90); + scene.world.multiplyKineticSpeed(util.select.everywhere(), 16f); + } + + public static void redstone(SceneBuilder scene, SceneBuildingUtil util) { + scene.title("weighted_ejector_redstone", "Controlling Weighted Ejectors with Redstone"); + scene.configureBasePlate(0, 0, 5); + scene.world.showSection(util.select.layer(0), Direction.UP); + scene.idle(5); + scene.world.showSection(util.select.fromTo(4, 1, 3, 4, 1, 5), Direction.DOWN); + scene.idle(5); + scene.world.showSection(util.select.fromTo(0, 1, 2, 0, 2, 2), Direction.DOWN); + scene.idle(10); + scene.world.showSection(util.select.position(4, 1, 2), Direction.SOUTH); + scene.idle(5); + Selection redstone = util.select.fromTo(3, 1, 2, 2, 1, 2); + scene.world.showSection(redstone, Direction.EAST); + + BlockPos ejectorPos = util.grid.at(4, 1, 2); + Vec3d topOf = util.vector.topOf(ejectorPos.up(2)); + ItemStack copper = AllItems.COPPER_INGOT.asStack(); + + for (int i = 0; i < 3; i++) { + scene.world.createItemEntity(topOf, util.vector.of(0, 0.1, 0), copper); + scene.idle(12); + scene.world.modifyEntities(ItemEntity.class, Entity::remove); + scene.world.createItemOnBeltLike(ejectorPos, Direction.UP, copper); + scene.idle(20); + if (i == 1) { + scene.world.toggleRedstonePower(redstone); + scene.effects.indicateRedstone(util.grid.at(2, 1, 2)); + scene.world.modifyTileNBT(util.select.position(4, 1, 2), EjectorTileEntity.class, + nbt -> nbt.putBoolean("Powered", true)); + } + } + + scene.idle(10); + scene.overlay.showText(60) + .colored(PonderPalette.RED) + .attachKeyFrame() + .pointAt(util.vector.topOf(ejectorPos)) + .placeNearTarget() + .text("When powered by Redstone, Ejectors will not activate"); + scene.idle(70); + + scene.world.toggleRedstonePower(redstone); + scene.idle(2); + scene.world.modifyTileNBT(util.select.position(4, 1, 2), EjectorTileEntity.class, + nbt -> nbt.putBoolean("Powered", false)); + scene.idle(5); + scene.world.hideSection(redstone, Direction.WEST); + scene.idle(10); + ElementLink observer = + scene.world.showIndependentSection(util.select.position(4, 1, 1), Direction.SOUTH); + scene.world.moveSection(observer, util.vector.of(0.5, 1.5, -0.5), 0); + scene.world.rotateSection(observer, 0, 30 - 180, 0, 0); + scene.idle(20); + scene.world.moveSection(observer, util.vector.of(-0.5, -1.5, 0.5), 10); + scene.world.rotateSection(observer, 0, -30 + 180, 0, 10); + scene.world.showSection(util.select.position(4, 1, 0), Direction.SOUTH); + + Selection observerRedstone = util.select.fromTo(4, 1, 1, 4, 1, 0); + for (int i = 0; i < 6; i++) { + scene.world.createItemEntity(topOf, util.vector.of(0, 0.1, 0), copper); + scene.idle(12); + scene.world.modifyEntities(ItemEntity.class, Entity::remove); + scene.world.createItemOnBeltLike(ejectorPos, Direction.UP, copper); + scene.idle(1); + scene.world.toggleRedstonePower(observerRedstone); + scene.effects.indicateRedstone(util.grid.at(4, 1, 1)); + scene.idle(3); + scene.world.toggleRedstonePower(observerRedstone); + scene.idle(16); + if (i == 3) + scene.markAsFinished(); + if (i == 1) { + scene.overlay.showText(60) + .attachKeyFrame() + .pointAt(util.vector.blockSurface(util.grid.at(4, 1, 1), Direction.NORTH)) + .placeNearTarget() + .text("Furthermore, Observers can detect when Ejectors activate"); + } + } + + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java b/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java index 3ade7e53e..942d0f754 100644 --- a/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java +++ b/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java @@ -96,6 +96,10 @@ public class PonderIndex { ProcessingScenes::emptyBlazeBurner); PonderRegistry.addStoryBoard(AllBlocks.BLAZE_BURNER, "blaze_burner", ProcessingScenes::blazeBurner); PonderRegistry.addStoryBoard(AllBlocks.DEPOT, "depot", BeltScenes::depot); + PonderRegistry.forComponents(AllBlocks.WEIGHTED_EJECTOR) + .addStoryBoard("weighted_ejector/eject", EjectorScenes::ejector) + .addStoryBoard("weighted_ejector/split", EjectorScenes::splitY) + .addStoryBoard("weighted_ejector/redstone", EjectorScenes::redstone); // Crafters PonderRegistry.forComponents(AllBlocks.MECHANICAL_CRAFTER) @@ -121,6 +125,12 @@ public class PonderIndex { .addStoryBoard("funnels/transposer", FunnelScenes::transposer); PonderRegistry.addStoryBoard(AllBlocks.ANDESITE_FUNNEL, "funnels/brass", FunnelScenes::brass); + // Tunnels + PonderRegistry.addStoryBoard(AllBlocks.ANDESITE_TUNNEL, "tunnels/andesite", TunnelScenes::andesite); + PonderRegistry.forComponents(AllBlocks.BRASS_TUNNEL) + .addStoryBoard("tunnels/brass", TunnelScenes::brass) + .addStoryBoard("tunnels/brass_modes", TunnelScenes::brassModes); + // Chassis & Super Glue PonderRegistry.forComponents(AllBlocks.LINEAR_CHASSIS, AllBlocks.SECONDARY_LINEAR_CHASSIS) .addStoryBoard("chassis/linear_group", ChassisScenes::linearGroup, PonderTag.CONTRAPTION_ASSEMBLY) diff --git a/src/main/java/com/simibubi/create/foundation/ponder/content/TunnelScenes.java b/src/main/java/com/simibubi/create/foundation/ponder/content/TunnelScenes.java new file mode 100644 index 000000000..01af4b41f --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/ponder/content/TunnelScenes.java @@ -0,0 +1,564 @@ +package com.simibubi.create.foundation.ponder.content; + +import java.util.Vector; + +import com.simibubi.create.AllItems; +import com.simibubi.create.content.contraptions.relays.belt.BeltBlock; +import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity; +import com.simibubi.create.content.logistics.block.belts.tunnel.BrassTunnelTileEntity; +import com.simibubi.create.foundation.gui.AllIcons; +import com.simibubi.create.foundation.ponder.ElementLink; +import com.simibubi.create.foundation.ponder.SceneBuilder; +import com.simibubi.create.foundation.ponder.SceneBuildingUtil; +import com.simibubi.create.foundation.ponder.elements.InputWindowElement; +import com.simibubi.create.foundation.ponder.elements.WorldSectionElement; +import com.simibubi.create.foundation.tileEntity.behaviour.filtering.SidedFilteringBehaviour; +import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollOptionBehaviour; +import com.simibubi.create.foundation.utility.Iterate; +import com.simibubi.create.foundation.utility.NBTHelper; +import com.simibubi.create.foundation.utility.Pointing; +import com.simibubi.create.foundation.utility.VecHelper; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.ItemEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +public class TunnelScenes { + + public static void andesite(SceneBuilder scene, SceneBuildingUtil util) { + scene.title("andesite_tunnel", "Using Andesite Tunnels"); + scene.configureBasePlate(0, 0, 5); + + scene.world.cycleBlockProperty(util.grid.at(2, 1, 2), BeltBlock.CASING); + + scene.world.showSection(util.select.layer(0), Direction.UP); + scene.idle(5); + scene.world.showSection(util.select.fromTo(4, 1, 5, 4, 1, 3), Direction.DOWN); + scene.idle(5); + scene.world.showSection(util.select.fromTo(4, 1, 2, 0, 1, 2), Direction.SOUTH); + scene.idle(10); + + Vector> tunnels = new Vector<>(3); + for (int i = 0; i < 3; i++) { + tunnels.add(scene.world.showIndependentSection(util.select.position(1 + i, 2, 4), Direction.DOWN)); + scene.world.moveSection(tunnels.get(i), util.vector.of(0, 0, -2), 0); + scene.idle(4); + } + + for (int i = 0; i < 3; i++) { + scene.world.cycleBlockProperty(util.grid.at(1 + i, 1, 2), BeltBlock.CASING); + scene.world.modifyTileNBT(util.select.position(1 + i, 1, 2), BeltTileEntity.class, + nbt -> NBTHelper.writeEnum(nbt, "Casing", BeltTileEntity.CasingType.ANDESITE), true); + scene.idle(4); + } + + scene.overlay.showText(60) + .attachKeyFrame() + .pointAt(util.vector.topOf(util.grid.at(1, 2, 2))) + .placeNearTarget() + .text("Andesite Tunnels can be used to cover up your belts"); + scene.idle(70); + + for (int i = 0; i < 3; i++) { + scene.world.cycleBlockProperty(util.grid.at(1 + i, 1, 2), BeltBlock.CASING); + scene.world.hideIndependentSection(tunnels.get(i), Direction.UP); + scene.idle(4); + } + scene.idle(10); + scene.world.showSection(util.select.fromTo(2, 1, 0, 0, 1, 1), Direction.SOUTH); + scene.idle(10); + scene.world.showSection(util.select.position(2, 2, 2), Direction.DOWN); + scene.idle(10); + scene.world.cycleBlockProperty(util.grid.at(2, 1, 2), BeltBlock.CASING); + + scene.overlay.showText(60) + .attachKeyFrame() + .pointAt(util.vector.blockSurface(util.grid.at(2, 2, 2), Direction.NORTH)) + .placeNearTarget() + .text("Whenever an Andesite Tunnel has connections to the sides..."); + scene.idle(70); + + scene.overlay.showControls(new InputWindowElement(util.vector.topOf(util.grid.at(4, 1, 2)), Pointing.DOWN) + .withItem(AllItems.COPPER_INGOT.asStack()), 20); + scene.idle(7); + scene.world.createItemOnBelt(util.grid.at(4, 1, 2), Direction.UP, AllItems.COPPER_INGOT.asStack(64)); + scene.idle(40); + scene.world.multiplyKineticSpeed(util.select.everywhere(), 1 / 16f); + scene.overlay.showText(80) + .attachKeyFrame() + .text("...they will split exactly one item off of any passing stacks") + .pointAt(util.vector.blockSurface(util.grid.at(2, 1, 0), Direction.WEST)) + .placeNearTarget(); + scene.idle(90); + scene.overlay.showText(80) + .text("The remainder will continue on its path") + .pointAt(util.vector.blockSurface(util.grid.at(0, 1, 2), Direction.UP)) + .placeNearTarget(); + scene.idle(90); + scene.world.multiplyKineticSpeed(util.select.everywhere(), 16f); + } + + public static void brass(SceneBuilder scene, SceneBuildingUtil util) { + scene.title("brass_tunnel", "Using Brass Tunnels"); + scene.configureBasePlate(1, 0, 5); + scene.world.cycleBlockProperty(util.grid.at(3, 1, 2), BeltBlock.CASING); + + scene.world.showSection(util.select.layer(0), Direction.UP); + scene.idle(5); + scene.world.showSection(util.select.fromTo(5, 1, 5, 5, 1, 3), Direction.DOWN); + scene.idle(5); + scene.world.showSection(util.select.fromTo(5, 1, 2, 1, 1, 2), Direction.SOUTH); + scene.idle(10); + + Vector> tunnels = new Vector<>(3); + for (int i = 0; i < 3; i++) { + tunnels.add(scene.world.showIndependentSection(util.select.position(2 + i, 2, 4), Direction.DOWN)); + scene.world.moveSection(tunnels.get(i), util.vector.of(0, 0, -2), 0); + scene.idle(4); + } + + for (int i = 0; i < 3; i++) { + scene.world.cycleBlockProperty(util.grid.at(2 + i, 1, 2), BeltBlock.CASING); + scene.world.modifyTileNBT(util.select.position(2 + i, 1, 2), BeltTileEntity.class, + nbt -> NBTHelper.writeEnum(nbt, "Casing", BeltTileEntity.CasingType.BRASS), true); + scene.idle(4); + } + + scene.overlay.showText(60) + .attachKeyFrame() + .pointAt(util.vector.topOf(util.grid.at(2, 2, 2))) + .placeNearTarget() + .text("Brass Tunnels can be used to cover up your belts"); + scene.idle(70); + + for (int i = 0; i < 3; i++) { + scene.world.cycleBlockProperty(util.grid.at(2 + i, 1, 2), BeltBlock.CASING); + scene.world.hideIndependentSection(tunnels.get(i), Direction.UP); + scene.idle(4); + } + scene.idle(10); + scene.world.showSection(util.select.fromTo(3, 1, 0, 1, 1, 1), Direction.SOUTH); + scene.idle(10); + scene.world.showSection(util.select.position(3, 2, 2), Direction.DOWN); + scene.idle(10); + scene.world.cycleBlockProperty(util.grid.at(3, 1, 2), BeltBlock.CASING); + scene.idle(10); + + BlockPos tunnelPos = util.grid.at(3, 2, 2); + for (Direction d : Iterate.horizontalDirections) { + if (d == Direction.SOUTH) + continue; + Vec3d filter = getTunnelFilterVec(tunnelPos, d); + scene.overlay.showFilterSlotInput(filter, 40); + scene.idle(3); + } + + scene.overlay.showText(60) + .attachKeyFrame() + .pointAt(getTunnelFilterVec(tunnelPos, Direction.WEST)) + .placeNearTarget() + .text("Brass Tunnels have filter slots on each open side"); + scene.idle(70); + + scene.rotateCameraY(70); + + scene.idle(20); + Vec3d tunnelFilterVec = getTunnelFilterVec(tunnelPos, Direction.EAST); + scene.overlay.showFilterSlotInput(tunnelFilterVec, 40); + scene.overlay.showText(60) + .attachKeyFrame() + .pointAt(tunnelFilterVec) + .placeNearTarget() + .text("Filters on inbound connections simply block non-matching items"); + ItemStack copper = AllItems.COPPER_INGOT.asStack(); + Class tunnelClass = BrassTunnelTileEntity.class; + scene.world.modifyTileEntity(tunnelPos, tunnelClass, te -> te.getBehaviour(SidedFilteringBehaviour.TYPE) + .setFilter(Direction.EAST, copper)); + scene.overlay.showControls(new InputWindowElement(tunnelFilterVec, Pointing.DOWN).withItem(copper), 30); + ItemStack zinc = AllItems.ZINC_INGOT.asStack(); + scene.world.createItemOnBelt(util.grid.at(5, 1, 2), Direction.EAST, zinc); + scene.idle(70); + scene.world.multiplyKineticSpeed(util.select.everywhere(), -2); + scene.idle(20); + scene.rotateCameraY(-70); + scene.world.multiplyKineticSpeed(util.select.everywhere(), -.5f); + scene.idle(20); + scene.world.modifyTileEntity(tunnelPos, tunnelClass, te -> te.getBehaviour(SidedFilteringBehaviour.TYPE) + .setFilter(Direction.EAST, ItemStack.EMPTY)); + + tunnelFilterVec = getTunnelFilterVec(tunnelPos, Direction.NORTH); + scene.overlay.showFilterSlotInput(tunnelFilterVec, 40); + tunnelFilterVec = getTunnelFilterVec(tunnelPos, Direction.WEST); + scene.overlay.showFilterSlotInput(tunnelFilterVec, 40); + scene.overlay.showText(60) + .attachKeyFrame() + .pointAt(tunnelFilterVec) + .placeNearTarget() + .text("Filters on outbound connections can be used to sort items by type"); + scene.idle(70); + + scene.overlay.showControls(new InputWindowElement(tunnelFilterVec, Pointing.LEFT).withItem(copper), 30); + scene.world.modifyTileEntity(tunnelPos, tunnelClass, te -> te.getBehaviour(SidedFilteringBehaviour.TYPE) + .setFilter(Direction.WEST, copper)); + scene.idle(4); + tunnelFilterVec = getTunnelFilterVec(tunnelPos, Direction.NORTH); + scene.overlay.showControls(new InputWindowElement(tunnelFilterVec, Pointing.RIGHT).withItem(zinc), 30); + scene.world.modifyTileEntity(tunnelPos, tunnelClass, te -> te.getBehaviour(SidedFilteringBehaviour.TYPE) + .setFilter(Direction.NORTH, zinc)); + + scene.world.multiplyKineticSpeed(util.select.everywhere(), 1.5f); + for (int i = 0; i < 6; i++) { + scene.world.createItemOnBelt(util.grid.at(5, 1, 2), Direction.EAST, i % 2 == 0 ? zinc : copper); + scene.idle(12); + } + + scene.idle(30); + scene.world.modifyTileEntity(tunnelPos, tunnelClass, te -> te.getBehaviour(SidedFilteringBehaviour.TYPE) + .setFilter(Direction.NORTH, ItemStack.EMPTY)); + scene.world.modifyTileEntity(tunnelPos, tunnelClass, te -> te.getBehaviour(SidedFilteringBehaviour.TYPE) + .setFilter(Direction.WEST, ItemStack.EMPTY)); + scene.idle(10); + + Vec3d tunnelTop = util.vector.topOf(tunnelPos); + scene.overlay.showControls(new InputWindowElement(tunnelTop, Pointing.DOWN).scroll() + .withWrench(), 80); + scene.idle(7); + scene.overlay.showCenteredScrollInput(tunnelPos, Direction.UP, 120); + scene.overlay.showText(120) + .attachKeyFrame() + .pointAt(tunnelTop) + .placeNearTarget() + .text( + "Whenever a passing item has multiple valid exits, the distribution mode will decide how to handle it"); + for (int i = 0; i < 3; i++) { + scene.idle(40); + scene.world.createItemOnBelt(util.grid.at(5, 1, 2), Direction.EAST, AllItems.BRASS_INGOT.asStack(63)); + } + scene.idle(30); + + scene.world.hideSection(util.select.position(3, 2, 2), Direction.UP); + scene.idle(5); + scene.world.hideSection(util.select.fromTo(5, 1, 2, 1, 1, 0), Direction.UP); + scene.idle(15); + + ElementLink newBelt = + scene.world.showIndependentSection(util.select.fromTo(3, 3, 2, 0, 3, 4) + .add(util.select.fromTo(5, 3, 3, 4, 3, 3)), Direction.DOWN); + scene.world.moveSection(newBelt, util.vector.of(0, -2, -1), 0); + scene.idle(15); + for (int i = 0; i < 3; i++) { + scene.idle(4); + scene.world.showSectionAndMerge(util.select.position(3, 4, 2 + i), Direction.DOWN, newBelt); + } + + scene.overlay.showSelectionWithText(util.select.fromTo(3, 1, 1, 3, 2, 3), 80) + .attachKeyFrame() + .placeNearTarget() + .text("Brass Tunnels on parallel belts will form a group"); + scene.idle(90); + + ItemStack item1 = new ItemStack(Items.CARROT); + ItemStack item2 = new ItemStack(Items.field_226638_pX_); + ItemStack item3 = new ItemStack(Items.SWEET_BERRIES); + + tunnelFilterVec = getTunnelFilterVec(tunnelPos, Direction.WEST); + BlockPos newTunnelPos = tunnelPos.up(2) + .south(); + scene.overlay + .showControls(new InputWindowElement(tunnelFilterVec.add(0, 0, -1), Pointing.RIGHT).withItem(item1), 20); + scene.world.modifyTileEntity(newTunnelPos.north(), tunnelClass, + te -> te.getBehaviour(SidedFilteringBehaviour.TYPE) + .setFilter(Direction.WEST, item1)); + scene.idle(4); + scene.overlay.showControls(new InputWindowElement(tunnelFilterVec, Pointing.DOWN).withItem(item2), 20); + scene.world.modifyTileEntity(newTunnelPos, tunnelClass, te -> te.getBehaviour(SidedFilteringBehaviour.TYPE) + .setFilter(Direction.WEST, item2)); + scene.idle(4); + scene.overlay.showControls(new InputWindowElement(tunnelFilterVec.add(0, 0, 1), Pointing.LEFT).withItem(item3), + 20); + scene.world.modifyTileEntity(newTunnelPos.south(), tunnelClass, + te -> te.getBehaviour(SidedFilteringBehaviour.TYPE) + .setFilter(Direction.WEST, item3)); + scene.idle(30); + + scene.overlay.showText(80) + .pointAt(tunnelTop) + .placeNearTarget() + .text("Incoming Items will now be distributed across all connected exits"); + scene.idle(90); + + BlockPos beltPos = util.grid.at(5, 3, 3); + Vec3d m = util.vector.of(0, 0.1, 0); + Vec3d spawn = util.vector.centerOf(util.grid.at(5, 3, 2)); + scene.world.createItemEntity(spawn, m, item1); + scene.idle(12); + scene.world.createItemOnBelt(beltPos, Direction.UP, item1); + scene.world.modifyEntities(ItemEntity.class, Entity::remove); + scene.world.createItemEntity(spawn, m, item2); + scene.idle(12); + scene.world.createItemOnBelt(beltPos, Direction.UP, item2); + scene.world.modifyEntities(ItemEntity.class, Entity::remove); + scene.world.createItemEntity(spawn, m, item3); + scene.idle(12); + scene.world.createItemOnBelt(beltPos, Direction.UP, item3); + scene.world.modifyEntities(ItemEntity.class, Entity::remove); + scene.idle(50); + + scene.world.showSectionAndMerge(util.select.position(3, 5, 2), Direction.DOWN, newBelt); + + scene.overlay.showText(80) + .pointAt(util.vector.blockSurface(tunnelPos.up() + .north(), Direction.WEST)) + .placeNearTarget() + .text("For this, items can also be inserted into the Tunnel block directly"); + scene.idle(20); + + beltPos = util.grid.at(3, 3, 3); + spawn = util.vector.centerOf(util.grid.at(3, 5, 1)); + scene.world.createItemEntity(spawn, m, item1); + scene.idle(12); + scene.world.createItemOnBelt(beltPos, Direction.EAST, item1); + scene.world.modifyEntities(ItemEntity.class, Entity::remove); + scene.world.createItemEntity(spawn, m, item2); + scene.idle(12); + scene.world.createItemOnBelt(beltPos, Direction.EAST, item2); + scene.world.modifyEntities(ItemEntity.class, Entity::remove); + scene.world.createItemEntity(spawn, m, item3); + scene.idle(12); + scene.world.createItemOnBelt(beltPos, Direction.EAST, item3); + scene.world.modifyEntities(ItemEntity.class, Entity::remove); + scene.idle(30); + + } + + protected static Vec3d getTunnelFilterVec(BlockPos pos, Direction d) { + return VecHelper.getCenterOf(pos) + .add(new Vec3d(d.getDirectionVec()).scale(.5)) + .add(0, 0.3, 0); + } + + public static void brassModes(SceneBuilder scene, SceneBuildingUtil util) { + scene.title("brass_tunnel_modes", "Distribution Modes of the Brass Tunnel"); + scene.configureBasePlate(0, 1, 5); + BlockState barrier = Blocks.BARRIER.getDefaultState(); + scene.world.setBlock(util.grid.at(1, 1, 0), barrier, false); + scene.world.showSection(util.select.layer(0), Direction.UP); + scene.idle(5); + scene.world.showSection(util.select.fromTo(1, 1, 1, 5, 1, 5) + .add(util.select.fromTo(3, 2, 5, 1, 2, 5)), Direction.DOWN); + scene.idle(10); + for (int i = 0; i < 3; i++) { + scene.world.showSection(util.select.position(3 - i, 2, 3), Direction.DOWN); + scene.idle(4); + } + + Vec3d tunnelTop = util.vector.topOf(util.grid.at(2, 2, 3)); + scene.overlay.showControls(new InputWindowElement(tunnelTop, Pointing.DOWN).scroll() + .withWrench(), 80); + scene.idle(7); + scene.overlay.showCenteredScrollInput(util.grid.at(2, 2, 3), Direction.UP, 120); + scene.overlay.showText(120) + .attachKeyFrame() + .pointAt(tunnelTop) + .placeNearTarget() + .text("Using a Wrench, the distribution behaviour of Brass Tunnels can be configured"); + scene.idle(130); + + Class tunnelClass = BrassTunnelTileEntity.class; + ElementLink blockage = + scene.world.showIndependentSection(util.select.position(4, 1, 0), Direction.UP); + scene.world.moveSection(blockage, util.vector.of(-3, 0, 0), 0); + + Vec3d modeVec = util.vector.of(4, 2.5, 3); + scene.overlay.showControls(new InputWindowElement(modeVec, Pointing.RIGHT).showing(AllIcons.I_TUNNEL_SPLIT), + 140); + + ElementLink blockage2 = null; + + for (int i = 0; i < 32; i++) { + if (i < 30) + scene.world.createItemOnBelt(util.grid.at(1, 1, 5), Direction.EAST, new ItemStack(Items.SNOWBALL, 12)); + scene.idle(i > 8 ? 30 : 40); + + if (i == 0) { + scene.overlay.showText(80) + .attachKeyFrame() + .pointAt(tunnelTop) + .placeNearTarget() + .text("'Split' will attempt to distribute the stack evenly between available outputs"); + } + + if (i == 2) { + scene.overlay.showText(60) + .text("If an output is unable to take more items, it will be skipped") + .pointAt(util.vector.blockSurface(util.grid.at(1, 1, 2), Direction.UP)) + .placeNearTarget() + .colored(PonderPalette.GREEN); + } + + if (i == 4) { + scene.overlay.showControls( + new InputWindowElement(modeVec, Pointing.RIGHT).showing(AllIcons.I_TUNNEL_FORCED_SPLIT), 140); + scene.world.modifyTileEntity(util.grid.at(1, 2, 3), tunnelClass, + te -> te.getBehaviour(ScrollOptionBehaviour.TYPE) + .setValue(BrassTunnelTileEntity.SelectionMode.FORCED_SPLIT.ordinal())); + } + + if (i == 5) { + scene.overlay.showText(80) + .attachKeyFrame() + .text("'Forced Split' will never skip outputs, and instead wait until they are free") + .pointAt(util.vector.blockSurface(util.grid.at(1, 1, 2), Direction.UP)) + .placeNearTarget() + .colored(PonderPalette.RED); + scene.idle(60); + scene.world.moveSection(blockage, util.vector.of(-1, 0, 0), 10); + scene.world.setBlock(util.grid.at(1, 1, 0), Blocks.AIR.getDefaultState(), false); + scene.world.multiplyKineticSpeed(util.select.everywhere(), 1.5f); + } + + if (i == 7) { + scene.world.modifyTileEntity(util.grid.at(1, 2, 3), tunnelClass, + te -> te.getBehaviour(ScrollOptionBehaviour.TYPE) + .setValue(BrassTunnelTileEntity.SelectionMode.ROUND_ROBIN.ordinal())); + scene.overlay.showControls( + new InputWindowElement(modeVec, Pointing.RIGHT).showing(AllIcons.I_TUNNEL_ROUND_ROBIN), 140); + scene.overlay.showText(80) + .attachKeyFrame() + .pointAt(tunnelTop) + .placeNearTarget() + .text("'Round Robin' keeps stacks whole, and cycles through outputs iteratively"); + } + + if (i == 7) { + scene.world.moveSection(blockage, util.vector.of(1, 0, 0), 10); + scene.world.setBlock(util.grid.at(1, 1, 0), barrier, false); + } + + if (i == 13) { + scene.overlay.showText(60) + .text("Once Again, if an output is unable to take more items, it will be skipped") + .placeNearTarget() + .pointAt(util.vector.blockSurface(util.grid.at(1, 1, 2), Direction.UP)) + .colored(PonderPalette.GREEN); + } + + if (i == 15) { + scene.overlay.showControls( + new InputWindowElement(modeVec, Pointing.RIGHT).showing(AllIcons.I_TUNNEL_FORCED_ROUND_ROBIN), 140); + scene.world.modifyTileEntity(util.grid.at(1, 2, 3), tunnelClass, + te -> te.getBehaviour(ScrollOptionBehaviour.TYPE) + .setValue(BrassTunnelTileEntity.SelectionMode.FORCED_ROUND_ROBIN.ordinal())); + } + + if (i == 16) { + scene.overlay.showText(50) + .attachKeyFrame() + .placeNearTarget() + .text("'Forced Round Robin' never skips outputs") + .pointAt(util.vector.blockSurface(util.grid.at(1, 1, 2), Direction.UP)) + .colored(PonderPalette.RED); + scene.idle(30); + scene.world.moveSection(blockage, util.vector.of(-1, 0, 0), 10); + scene.world.setBlock(util.grid.at(1, 1, 0), Blocks.AIR.getDefaultState(), false); + } + + if (i == 19) { + scene.overlay.showControls( + new InputWindowElement(modeVec, Pointing.RIGHT).showing(AllIcons.I_TUNNEL_PREFER_NEAREST), 140); + scene.world.modifyTileEntity(util.grid.at(1, 2, 3), tunnelClass, + te -> te.getBehaviour(ScrollOptionBehaviour.TYPE) + .setValue(BrassTunnelTileEntity.SelectionMode.PREFER_NEAREST.ordinal())); + scene.world.moveSection(blockage, util.vector.of(1, 0, 0), 10); + scene.world.setBlock(util.grid.at(1, 1, 0), barrier, false); + scene.overlay.showText(70) + .attachKeyFrame() + .text("'Prefer Nearest' prioritizes the outputs closest to the items' input location") + .pointAt(util.vector.blockSurface(util.grid.at(1, 1, 2), Direction.UP)) + .placeNearTarget() + .colored(PonderPalette.GREEN); + } + + if (i == 21) { + scene.world.setBlock(util.grid.at(2, 1, 0), Blocks.BARRIER.getDefaultState(), false); + blockage2 = scene.world.showIndependentSection(util.select.position(4, 1, 0), Direction.UP); + scene.world.moveSection(blockage2, util.vector.of(-2, 0, 0), 0); + } + + if (i == 25) { + scene.world.hideIndependentSection(blockage, Direction.DOWN); + scene.world.setBlock(util.grid.at(1, 1, 0), Blocks.AIR.getDefaultState(), false); + scene.world.hideIndependentSection(blockage2, Direction.DOWN); + scene.world.setBlock(util.grid.at(2, 1, 0), Blocks.AIR.getDefaultState(), false); + } + + if (i == 26) { + scene.overlay.showControls( + new InputWindowElement(modeVec, Pointing.RIGHT).showing(AllIcons.I_TUNNEL_RANDOMIZE), 140); + scene.world.modifyTileEntity(util.grid.at(1, 2, 3), tunnelClass, + te -> te.getBehaviour(ScrollOptionBehaviour.TYPE) + .setValue(BrassTunnelTileEntity.SelectionMode.RANDOMIZE.ordinal())); + } + + if (i == 27) { + scene.overlay.showText(70) + .attachKeyFrame() + .text("'Randomize' will distribute whole stacks to randomly picked outputs") + .pointAt(tunnelTop) + .placeNearTarget(); + } + } + + scene.world.hideSection(util.select.fromTo(3, 2, 5, 1, 2, 5), Direction.UP); + scene.idle(10); + scene.overlay + .showControls(new InputWindowElement(modeVec, Pointing.RIGHT).showing(AllIcons.I_TUNNEL_SYNCHRONIZE), 140); + scene.world.modifyTileEntity(util.grid.at(1, 2, 3), tunnelClass, + te -> te.getBehaviour(ScrollOptionBehaviour.TYPE) + .setValue(BrassTunnelTileEntity.SelectionMode.SYNCHRONIZE.ordinal())); + scene.idle(30); + scene.overlay.showText(70) + .attachKeyFrame() + .text("'Synchronize Inputs' is a unique setting for Brass Tunnels") + .pointAt(tunnelTop) + .placeNearTarget(); + + ItemStack item1 = new ItemStack(Items.CARROT); + ItemStack item2 = new ItemStack(Items.field_226638_pX_); + ItemStack item3 = AllItems.POLISHED_ROSE_QUARTZ.asStack(); + + scene.world.createItemOnBelt(util.grid.at(3, 1, 4), Direction.UP, item1); + scene.world.createItemOnBelt(util.grid.at(2, 1, 4), Direction.UP, item2); + scene.world.createItemOnBelt(util.grid.at(3, 1, 5), Direction.SOUTH, item1); + scene.world.createItemOnBelt(util.grid.at(2, 1, 5), Direction.SOUTH, item2); + + scene.idle(80); + scene.world.createItemOnBelt(util.grid.at(2, 1, 5), Direction.SOUTH, item2); + scene.rotateCameraY(-90); + scene.idle(20); + scene.world.multiplyKineticSpeed(util.select.everywhere(), .5f); + + scene.overlay.showText(70) + .text("Items are only allowed past if every tunnel in the group has one waiting") + .pointAt(util.vector.blockSurface(util.grid.at(2, 1, 4), Direction.UP)) + .placeNearTarget() + .colored(PonderPalette.OUTPUT); + scene.idle(60); + scene.world.createItemOnBelt(util.grid.at(1, 1, 5), Direction.SOUTH, item3); + scene.idle(90); + scene.rotateCameraY(90); + + scene.overlay.showText(100) + .text("This ensures that all affected belts supply items at the same rate") + .pointAt(util.vector.blockSurface(util.grid.at(1, 2, 3), Direction.WEST)) + .placeNearTarget() + .colored(PonderPalette.GREEN); + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/ponder/elements/InputWindowElement.java b/src/main/java/com/simibubi/create/foundation/ponder/elements/InputWindowElement.java index dfa195ead..7791bfb25 100644 --- a/src/main/java/com/simibubi/create/foundation/ponder/elements/InputWindowElement.java +++ b/src/main/java/com/simibubi/create/foundation/ponder/elements/InputWindowElement.java @@ -57,6 +57,11 @@ public class InputWindowElement extends AnimatedOverlayElement { icon = AllIcons.I_RMB; return this; } + + public InputWindowElement showing(AllIcons icon) { + this.icon = icon; + return this; + } public InputWindowElement leftClick() { icon = AllIcons.I_LMB; diff --git a/src/main/resources/ponder/tunnels/andesite.nbt b/src/main/resources/ponder/tunnels/andesite.nbt new file mode 100644 index 000000000..b98a5975a Binary files /dev/null and b/src/main/resources/ponder/tunnels/andesite.nbt differ diff --git a/src/main/resources/ponder/tunnels/brass.nbt b/src/main/resources/ponder/tunnels/brass.nbt new file mode 100644 index 000000000..065010581 Binary files /dev/null and b/src/main/resources/ponder/tunnels/brass.nbt differ diff --git a/src/main/resources/ponder/tunnels/brass_modes.nbt b/src/main/resources/ponder/tunnels/brass_modes.nbt new file mode 100644 index 000000000..e57c00dfb Binary files /dev/null and b/src/main/resources/ponder/tunnels/brass_modes.nbt differ diff --git a/src/main/resources/ponder/weighted_ejector/eject.nbt b/src/main/resources/ponder/weighted_ejector/eject.nbt new file mode 100644 index 000000000..fd7bfbea8 Binary files /dev/null and b/src/main/resources/ponder/weighted_ejector/eject.nbt differ diff --git a/src/main/resources/ponder/weighted_ejector/redstone.nbt b/src/main/resources/ponder/weighted_ejector/redstone.nbt new file mode 100644 index 000000000..efa6e7bfb Binary files /dev/null and b/src/main/resources/ponder/weighted_ejector/redstone.nbt differ diff --git a/src/main/resources/ponder/weighted_ejector/split.nbt b/src/main/resources/ponder/weighted_ejector/split.nbt new file mode 100644 index 000000000..82cad6980 Binary files /dev/null and b/src/main/resources/ponder/weighted_ejector/split.nbt differ