From 6b0af48e99c2811f990362ad0033e078584f1287 Mon Sep 17 00:00:00 2001 From: DaComputerNerd Date: Fri, 10 Feb 2023 10:32:15 -0500 Subject: [PATCH 01/13] Add track merge event Can't test it due to java shenanigans, but I think this will work. It's worth noting that this will only be specific to merging as long as transferAll is. If anything else ends up using transferAll, all that needs to happen is adding an intermediate method that posts the event instead of transferAll doing it, then calls transferAll --- .../api/event/TrackGraphMergeEvent.java | 22 +++++++++++++++++++ .../content/logistics/trains/TrackGraph.java | 3 +++ 2 files changed, 25 insertions(+) create mode 100644 src/main/java/com/simibubi/create/api/event/TrackGraphMergeEvent.java diff --git a/src/main/java/com/simibubi/create/api/event/TrackGraphMergeEvent.java b/src/main/java/com/simibubi/create/api/event/TrackGraphMergeEvent.java new file mode 100644 index 000000000..af58070d2 --- /dev/null +++ b/src/main/java/com/simibubi/create/api/event/TrackGraphMergeEvent.java @@ -0,0 +1,22 @@ +package com.simibubi.create.api.event; + +import com.simibubi.create.content.logistics.trains.TrackGraph; + +import net.minecraftforge.eventbus.api.Event; + +public class TrackGraphMergeEvent extends Event{ + private TrackGraph mergedInto, mergedFrom; + + public TrackGraphMergeEvent(TrackGraph from, TrackGraph into) { + mergedInto = into; + mergedFrom = from; + } + + public TrackGraph getGraphMergedInto() { + return mergedInto; + } + + public TrackGraph getGraphMergedFrom() { + return mergedFrom; + } +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java index 897713e94..cda0e5f2f 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java @@ -19,6 +19,7 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import com.simibubi.create.Create; +import com.simibubi.create.api.event.TrackGraphMergeEvent; import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation; import com.simibubi.create.content.logistics.trains.entity.Train; import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgeData; @@ -41,6 +42,7 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.MinecraftForge; public class TrackGraph { @@ -247,6 +249,7 @@ public class TrackGraph { } public void transferAll(TrackGraph toOther) { + MinecraftForge.EVENT_BUS.post(new TrackGraphMergeEvent(this, toOther)); nodes.forEach((loc, node) -> { if (toOther.addNodeIfAbsent(node)) Create.RAILWAYS.sync.nodeAdded(toOther, node); From cc09f4c6343196c20765d3fa5f4a12c46f4d63ce Mon Sep 17 00:00:00 2001 From: cakeGit <65340665+cakeGit@users.noreply.github.com> Date: Tue, 28 Mar 2023 21:51:16 +0100 Subject: [PATCH 02/13] =?UTF-8?q?Fix=20=F0=9F=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fluids/actors/SpoutRenderer.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/SpoutRenderer.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/SpoutRenderer.java index cc6443589..933ebdc1e 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/SpoutRenderer.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/SpoutRenderer.java @@ -38,14 +38,24 @@ public class SpoutRenderer extends SafeTileEntityRenderer { .getValue(partialTicks); if (!fluidStack.isEmpty() && level != 0) { + boolean top = fluidStack.getFluid() + .getAttributes() + .isLighterThanAir(); + level = Math.max(level, 0.175f); float min = 2.5f / 16f; float max = min + (11 / 16f); float yOffset = (11 / 16f) * level; + ms.pushPose(); - ms.translate(0, yOffset, 0); - FluidRenderer.renderFluidBox(fluidStack, min, min - yOffset, min, max, min, max, buffer, ms, light, - false); + if (!top) ms.translate(0, yOffset, 0); + else ms.translate(0, max - min, 0); + + FluidRenderer.renderFluidBox(fluidStack, + min, min - yOffset, min, + max, min, max, + buffer, ms, light, false); + ms.popPose(); } From 596c9839d2b7d383107555611ec6b2b411581a2f Mon Sep 17 00:00:00 2001 From: NerdsOfAFeather Date: Sun, 23 Apr 2023 17:55:01 -0400 Subject: [PATCH 03/13] Open Ended Pipes now correctly handle Builder's Tea --- .../contraptions/fluids/OpenEndedPipe.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/OpenEndedPipe.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/OpenEndedPipe.java index bc4059b73..d200f0c75 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/OpenEndedPipe.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/OpenEndedPipe.java @@ -25,6 +25,7 @@ import net.minecraft.tags.BlockTags; import net.minecraft.tags.FluidTags; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.ItemStack; @@ -39,6 +40,7 @@ import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.Fluids; import net.minecraft.world.phys.AABB; +import net.minecraftforge.common.ForgeConfig; import net.minecraftforge.common.Tags; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.fluids.FluidStack; @@ -54,6 +56,7 @@ public class OpenEndedPipe extends FlowSource { registerEffectHandler(new MilkEffectHandler()); registerEffectHandler(new WaterEffectHandler()); registerEffectHandler(new LavaEffectHandler()); + registerEffectHandler(new TeaEffectHandler()); } private Level world; @@ -443,4 +446,23 @@ public class OpenEndedPipe extends FlowSource { } } + public static class TeaEffectHandler implements IEffectHandler { + @Override + public boolean canApplyEffects(OpenEndedPipe pipe, FluidStack fluid) { + return fluid.getFluid().isSame(AllFluids.TEA.get()); + } + + @Override + public void applyEffects(OpenEndedPipe pipe, FluidStack fluid) { + Level world = pipe.getWorld(); + if (world.getGameTime() % 5 != 0) + return; + List entities = world + .getEntitiesOfClass(LivingEntity.class, pipe.getAOE(), LivingEntity::isAffectedByPotions); + for (LivingEntity entity : entities) { + entity.addEffect(new MobEffectInstance(MobEffects.DIG_SPEED, 21, 0, false, false, false)); + } + } + } + } From 0347038c9adf7b40d8a1b8418a5ee53a2f1ece06 Mon Sep 17 00:00:00 2001 From: Walle123 Date: Mon, 1 May 2023 11:32:29 +0200 Subject: [PATCH 04/13] brass tunnel distribution speed config added --- .../belts/tunnel/BrassTunnelTileEntity.java | 18 +++++++++--------- .../create/foundation/config/CLogistics.java | 3 ++- 2 files changed, 11 insertions(+), 10 deletions(-) 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 c45ceff6b..e8b1ba8ed 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 @@ -66,7 +66,7 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity implements IHave ItemStack stackToDistribute; Direction stackEnteredFrom; - + float distributionProgress; int distributionDistanceLeft; int distributionDistanceRight; @@ -174,7 +174,7 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity implements IHave return; if (selectionMode.get() != SelectionMode.SYNCHRONIZE || syncedOutputActive) { - distributionProgress = 10; + distributionProgress = AllConfigs.SERVER.logistics.brassTunnelTimer.get(); sendData(); } return; @@ -528,7 +528,7 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity implements IHave continue; if (!tunnelTE.sides.contains(direction)) continue; - + BlockPos offset = tunnelTE.worldPosition.below() .relative(direction); @@ -579,11 +579,11 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity implements IHave compound.putBoolean("SyncedOutput", syncedOutputActive); compound.putBoolean("ConnectedLeft", connectedLeft); compound.putBoolean("ConnectedRight", connectedRight); - + compound.put("StackToDistribute", stackToDistribute.serializeNBT()); if (stackEnteredFrom != null) NBTHelper.writeEnum(compound, "StackEnteredFrom", stackEnteredFrom); - + compound.putFloat("DistributionProgress", distributionProgress); compound.putInt("PreviousIndex", previousOutputIndex); compound.putInt("DistanceLeft", distributionDistanceLeft); @@ -611,7 +611,7 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity implements IHave syncedOutputActive = compound.getBoolean("SyncedOutput"); connectedLeft = compound.getBoolean("ConnectedLeft"); connectedRight = compound.getBoolean("ConnectedRight"); - + stackToDistribute = ItemStack.of(compound.getCompound("StackToDistribute")); stackEnteredFrom = compound.contains("StackEnteredFrom") ? NBTHelper.readEnum(compound, "StackEnteredFrom", Direction.class) @@ -719,7 +719,7 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity implements IHave super.invalidate(); tunnelCapability.invalidate(); } - + @Override public void destroy() { super.destroy(); @@ -782,7 +782,7 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity implements IHave List allStacks = grabAllStacksOfGroup(true); if (allStacks.isEmpty()) return false; - + tooltip.add(componentSpacing.plainCopy() .append(Lang.translateDirect("tooltip.brass_tunnel.contains")) .withStyle(ChatFormatting.WHITE)); @@ -795,7 +795,7 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity implements IHave tooltip.add(componentSpacing.plainCopy() .append(Lang.translateDirect("tooltip.brass_tunnel.retrieve")) .withStyle(ChatFormatting.DARK_GRAY)); - + return true; } diff --git a/src/main/java/com/simibubi/create/foundation/config/CLogistics.java b/src/main/java/com/simibubi/create/foundation/config/CLogistics.java index cde5fe2d3..822f23230 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CLogistics.java +++ b/src/main/java/com/simibubi/create/foundation/config/CLogistics.java @@ -10,7 +10,7 @@ public class CLogistics extends ConfigBase { public final ConfigInt linkRange = i(256, 1, "linkRange", Comments.linkRange); public final ConfigInt displayLinkRange = i(64, 1, "displayLinkRange", Comments.displayLinkRange); public final ConfigInt vaultCapacity = i(20, 1, "vaultCapacity", Comments.vaultCapacity); - + public final ConfigInt brassTunnelTimer = i(1,10,10, "brassTunnelTimer",Comments.brassTunnelTimer); @Override public String getName() { return "logistics"; @@ -28,6 +28,7 @@ public class CLogistics extends ConfigBase { "The amount of ticks a portable storage interface waits for transfers until letting contraptions move along."; static String mechanicalArmRange = "Maximum distance in blocks a Mechanical Arm can reach across."; static String vaultCapacity = "The total amount of stacks a vault can hold per block in size."; + static String brassTunnelTimer = "The amount of ticks a brass tunnel waits between distributions"; } } From b8779299cd8ca01797ca20cdbc996ee036196691 Mon Sep 17 00:00:00 2001 From: Walle123 Date: Mon, 1 May 2023 11:57:13 +0200 Subject: [PATCH 05/13] brass tunnel distribution speed config added --- .../logistics/block/belts/tunnel/BrassTunnelTileEntity.java | 2 ++ .../java/com/simibubi/create/foundation/config/CLogistics.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) 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 e8b1ba8ed..7f889e767 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 @@ -11,6 +11,8 @@ import java.util.Set; import javax.annotation.Nullable; +import com.simibubi.create.foundation.config.AllConfigs; + import org.apache.commons.lang3.tuple.Pair; import com.simibubi.create.AllBlocks; diff --git a/src/main/java/com/simibubi/create/foundation/config/CLogistics.java b/src/main/java/com/simibubi/create/foundation/config/CLogistics.java index 822f23230..b850a88a7 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CLogistics.java +++ b/src/main/java/com/simibubi/create/foundation/config/CLogistics.java @@ -10,7 +10,7 @@ public class CLogistics extends ConfigBase { public final ConfigInt linkRange = i(256, 1, "linkRange", Comments.linkRange); public final ConfigInt displayLinkRange = i(64, 1, "displayLinkRange", Comments.displayLinkRange); public final ConfigInt vaultCapacity = i(20, 1, "vaultCapacity", Comments.vaultCapacity); - public final ConfigInt brassTunnelTimer = i(1,10,10, "brassTunnelTimer",Comments.brassTunnelTimer); + public final ConfigInt brassTunnelTimer = i(10,1,10, "brassTunnelTimer",Comments.brassTunnelTimer); @Override public String getName() { return "logistics"; From 81babd3a39438fa8275a6b06975d71661d10b73b Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Wed, 10 May 2023 14:41:46 +0200 Subject: [PATCH 06/13] Implement #4597 --- .../structureMovement/gantry/GantryContraptionEntity.java | 1 + .../com/simibubi/create/foundation/config/CLogistics.java | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/gantry/GantryContraptionEntity.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/gantry/GantryContraptionEntity.java index d20f50ef2..7c7cc7ada 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/gantry/GantryContraptionEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/gantry/GantryContraptionEntity.java @@ -176,6 +176,7 @@ public class GantryContraptionEntity extends AbstractContraptionEntity { } @Override + @OnlyIn(Dist.CLIENT) public void applyLocalTransforms(PoseStack matrixStack, float partialTicks) { } public void updateClientMotion() { diff --git a/src/main/java/com/simibubi/create/foundation/config/CLogistics.java b/src/main/java/com/simibubi/create/foundation/config/CLogistics.java index b850a88a7..401cb24ac 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CLogistics.java +++ b/src/main/java/com/simibubi/create/foundation/config/CLogistics.java @@ -10,7 +10,8 @@ public class CLogistics extends ConfigBase { public final ConfigInt linkRange = i(256, 1, "linkRange", Comments.linkRange); public final ConfigInt displayLinkRange = i(64, 1, "displayLinkRange", Comments.displayLinkRange); public final ConfigInt vaultCapacity = i(20, 1, "vaultCapacity", Comments.vaultCapacity); - public final ConfigInt brassTunnelTimer = i(10,1,10, "brassTunnelTimer",Comments.brassTunnelTimer); + public final ConfigInt brassTunnelTimer = i(10, 1, 10, "brassTunnelTimer", Comments.brassTunnelTimer); + @Override public String getName() { return "logistics"; @@ -28,7 +29,7 @@ public class CLogistics extends ConfigBase { "The amount of ticks a portable storage interface waits for transfers until letting contraptions move along."; static String mechanicalArmRange = "Maximum distance in blocks a Mechanical Arm can reach across."; static String vaultCapacity = "The total amount of stacks a vault can hold per block in size."; - static String brassTunnelTimer = "The amount of ticks a brass tunnel waits between distributions"; + static String brassTunnelTimer = "The amount of ticks a brass tunnel waits between distributions."; } } From 9e3140938bf50647217fc2ac24aba7ef2a97632b Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Wed, 10 May 2023 15:13:39 +0200 Subject: [PATCH 07/13] Implement #4577 for Encased Pipes --- .../fluids/pipes/EncasedPipeBlock.java | 23 ++++++++- .../fluids/pipes/FluidPipeBlock.java | 24 ++++++++- .../fluids/pipes/FluidPipeBlockRotation.java | 49 +++++++++++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlockRotation.java diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/EncasedPipeBlock.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/EncasedPipeBlock.java index 6ec931713..720227771 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/EncasedPipeBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/EncasedPipeBlock.java @@ -13,6 +13,8 @@ import java.util.function.Supplier; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllTileEntities; +import com.simibubi.create.content.contraptions.components.structureMovement.ITransformableBlock; +import com.simibubi.create.content.contraptions.components.structureMovement.StructureTransform; import com.simibubi.create.content.contraptions.fluids.FluidPropagator; import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour; import com.simibubi.create.content.contraptions.relays.elementary.EncasedBlock; @@ -36,7 +38,9 @@ import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; import net.minecraft.world.level.block.PipeBlock; +import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; @@ -46,7 +50,8 @@ import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.ticks.TickPriority; -public class EncasedPipeBlock extends Block implements IWrenchable, ISpecialBlockItemRequirement, ITE, EncasedBlock { +public class EncasedPipeBlock extends Block + implements IWrenchable, ISpecialBlockItemRequirement, ITE, EncasedBlock, ITransformableBlock { public static final Map FACING_TO_PROPERTY_MAP = PipeBlock.PROPERTY_BY_DIRECTION; private final Supplier casing; @@ -173,4 +178,20 @@ public class EncasedPipeBlock extends Block implements IWrenchable, ISpecialBloc EncasedPipeBlock.transferSixWayProperties(state, defaultBlockState())); FluidTransportBehaviour.loadFlows(level, pos); } + + @Override + public BlockState rotate(BlockState pState, Rotation pRotation) { + return FluidPipeBlockRotation.rotate(pState, pRotation); + } + + @Override + public BlockState mirror(BlockState pState, Mirror pMirror) { + return FluidPipeBlockRotation.mirror(pState, pMirror); + } + + @Override + public BlockState transform(BlockState state, StructureTransform transform) { + return FluidPipeBlockRotation.transform(state, transform); + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlock.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlock.java index 50a46213a..765dfbfca 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlock.java @@ -8,6 +8,8 @@ import javax.annotation.Nullable; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllTileEntities; +import com.simibubi.create.content.contraptions.components.structureMovement.ITransformableBlock; +import com.simibubi.create.content.contraptions.components.structureMovement.StructureTransform; import com.simibubi.create.content.contraptions.fluids.FluidPropagator; import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour; import com.simibubi.create.content.contraptions.relays.elementary.BracketedTileEntityBehaviour; @@ -36,7 +38,9 @@ import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; import net.minecraft.world.level.block.PipeBlock; +import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.SimpleWaterloggedBlock; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; @@ -50,8 +54,8 @@ import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.ticks.TickPriority; -public class FluidPipeBlock extends PipeBlock - implements SimpleWaterloggedBlock, IWrenchableWithBracket, ITE, EncasableBlock { +public class FluidPipeBlock extends PipeBlock implements SimpleWaterloggedBlock, IWrenchableWithBracket, + ITE, EncasableBlock, ITransformableBlock { private static final VoxelShape OCCLUSION_BOX = Block.box(4, 4, 4, 12, 12, 12); @@ -337,4 +341,20 @@ public class FluidPipeBlock extends PipeBlock public VoxelShape getOcclusionShape(BlockState pState, BlockGetter pLevel, BlockPos pPos) { return OCCLUSION_BOX; } + + @Override + public BlockState rotate(BlockState pState, Rotation pRotation) { + return FluidPipeBlockRotation.rotate(pState, pRotation); + } + + @Override + public BlockState mirror(BlockState pState, Mirror pMirror) { + return FluidPipeBlockRotation.mirror(pState, pMirror); + } + + @Override + public BlockState transform(BlockState state, StructureTransform transform) { + return FluidPipeBlockRotation.transform(state, transform); + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlockRotation.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlockRotation.java new file mode 100644 index 000000000..c60a3a2a7 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlockRotation.java @@ -0,0 +1,49 @@ +package com.simibubi.create.content.contraptions.fluids.pipes; + +import java.util.Map; + +import com.simibubi.create.content.contraptions.components.structureMovement.StructureTransform; +import com.simibubi.create.foundation.utility.Iterate; + +import net.minecraft.core.Direction; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.PipeBlock; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BooleanProperty; + +public class FluidPipeBlockRotation { + + public static final Map FACING_TO_PROPERTY_MAP = PipeBlock.PROPERTY_BY_DIRECTION; + + public static BlockState rotate(BlockState state, Rotation rotation) { + BlockState rotated = state; + for (Direction direction : Iterate.horizontalDirections) + rotated = rotated.setValue(FACING_TO_PROPERTY_MAP.get(rotation.rotate(direction)), + state.getValue(FACING_TO_PROPERTY_MAP.get(direction))); + return rotated; + } + + public static BlockState mirror(BlockState state, Mirror mirror) { + BlockState mirrored = state; + for (Direction direction : Iterate.horizontalDirections) + mirrored = mirrored.setValue(FACING_TO_PROPERTY_MAP.get(mirror.mirror(direction)), + state.getValue(FACING_TO_PROPERTY_MAP.get(direction))); + return mirrored; + } + + public static BlockState transform(BlockState state, StructureTransform transform) { + if (transform.mirror != null) + state = mirror(state, transform.mirror); + + if (transform.rotationAxis == Direction.Axis.Y) + return rotate(state, transform.rotation); + + BlockState rotated = state; + for (Direction direction : Iterate.directions) + rotated = rotated.setValue(FACING_TO_PROPERTY_MAP.get(transform.rotateFacing(direction)), + state.getValue(FACING_TO_PROPERTY_MAP.get(direction))); + return rotated; + } + +} From b4a8386cd8d9febf5528d34a20cebe52a1ed90c6 Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Wed, 10 May 2023 15:31:28 +0200 Subject: [PATCH 08/13] Implement #4534 as configurable multipliers --- .../com/simibubi/create/events/ClientEvents.java | 4 ++-- .../simibubi/create/foundation/config/CClient.java | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/simibubi/create/events/ClientEvents.java b/src/main/java/com/simibubi/create/events/ClientEvents.java index 0c4c4ffbb..883fa45ab 100644 --- a/src/main/java/com/simibubi/create/events/ClientEvents.java +++ b/src/main/java/com/simibubi/create/events/ClientEvents.java @@ -319,14 +319,14 @@ public class ClientEvents { if (AllFluids.CHOCOLATE.get() .isSame(fluid)) { - event.scaleFarPlaneDistance(1f / 32f); + event.scaleFarPlaneDistance(1f / 32f * AllConfigs.CLIENT.chocolateTransparencyMultiplier.getF()); event.setCanceled(true); return; } if (AllFluids.HONEY.get() .isSame(fluid)) { - event.scaleFarPlaneDistance(1f / 8f); + event.scaleFarPlaneDistance(1f / 8f * AllConfigs.CLIENT.honeyTransparencyMultiplier.getF()); event.setCanceled(true); return; } diff --git a/src/main/java/com/simibubi/create/foundation/config/CClient.java b/src/main/java/com/simibubi/create/foundation/config/CClient.java index 4fffd25fe..8b5a470a5 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CClient.java +++ b/src/main/java/com/simibubi/create/foundation/config/CClient.java @@ -31,7 +31,14 @@ public class CClient extends ConfigBase { public final ConfigInt ingameMenuConfigButtonOffsetX = i(-4, Integer.MIN_VALUE, Integer.MAX_VALUE, "ingameMenuConfigButtonOffsetX", Comments.ingameMenuConfigButtonOffsetX); public final ConfigBool ignoreFabulousWarning = b(false, "ignoreFabulousWarning", - Comments.ignoreFabulousWarning); + Comments.ignoreFabulousWarning); + + // custom fluid fog + public final ConfigGroup fluidFogSettings = group(1, "fluidFogSettings", Comments.fluidFogSettings); + public final ConfigFloat honeyTransparencyMultiplier = + f(1, .125f, 256, "honey", Comments.honeyTransparencyMultiplier); + public final ConfigFloat chocolateTransparencyMultiplier = + f(1, .125f, 256, "chocolate", Comments.chocolateTransparencyMultiplier); //overlay group public final ConfigGroup overlay = group(1, "goggleOverlay", @@ -147,6 +154,10 @@ public class CClient extends ConfigBase { static String trains = "Railway related settings"; static String mountedZoomMultiplier = "How far away the Camera should zoom when seated on a train"; static String showTrackGraphOnF3 = "Display nodes and edges of a Railway Network while f3 debug mode is active"; + + static String fluidFogSettings = "Configure your vision range when submerged in Create's custom fluids"; + static String honeyTransparencyMultiplier = "The vision range through honey will be multiplied by this factor"; + static String chocolateTransparencyMultiplier = "The vision range though chocolate will be multiplied by this factor"; } } From ee33858ddcf9ff91026c0fff6a6e4c829c7a8211 Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Wed, 10 May 2023 16:09:03 +0200 Subject: [PATCH 09/13] Move event invocation in case transferAll gets re-used --- .../simibubi/create/content/logistics/trains/TrackGraph.java | 3 --- .../create/content/logistics/trains/TrackPropagator.java | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java index cda0e5f2f..897713e94 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java @@ -19,7 +19,6 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import com.simibubi.create.Create; -import com.simibubi.create.api.event.TrackGraphMergeEvent; import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation; import com.simibubi.create.content.logistics.trains.entity.Train; import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgeData; @@ -42,7 +41,6 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.phys.Vec3; -import net.minecraftforge.common.MinecraftForge; public class TrackGraph { @@ -249,7 +247,6 @@ public class TrackGraph { } public void transferAll(TrackGraph toOther) { - MinecraftForge.EVENT_BUS.post(new TrackGraphMergeEvent(this, toOther)); nodes.forEach((loc, node) -> { if (toOther.addNodeIfAbsent(node)) Create.RAILWAYS.sync.nodeAdded(toOther, node); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackPropagator.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackPropagator.java index 611787364..75ca5e9eb 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackPropagator.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackPropagator.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Set; import com.simibubi.create.Create; +import com.simibubi.create.api.event.TrackGraphMergeEvent; import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation; import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalPropagator; @@ -16,6 +17,7 @@ import net.minecraft.util.Mth; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.MinecraftForge; public class TrackPropagator { @@ -135,6 +137,7 @@ public class TrackPropagator { if (graph == null) graph = other; else { + MinecraftForge.EVENT_BUS.post(new TrackGraphMergeEvent(other, graph)); other.transferAll(graph); manager.removeGraphAndGroup(other); sync.graphRemoved(other); From 3c1961c846acb8eb48fecda67672e9ac8540120f Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Wed, 10 May 2023 16:53:29 +0200 Subject: [PATCH 10/13] Implement #3906 - Fixed players dismounting from Trains upon assembly --- .../create/content/logistics/trains/entity/Carriage.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Carriage.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Carriage.java index 12d25eea7..5b628b511 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Carriage.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Carriage.java @@ -103,7 +103,7 @@ public class Carriage { DimensionalCarriageEntity dimensional = getDimensional(level); dimensional.alignEntity(entity); - dimensional.removeAndSaveEntity(entity, false); + dimensional.removeAndSaveEntity(entity, true); } public DimensionalCarriageEntity getDimensional(Level level) { @@ -723,8 +723,8 @@ public class Carriage { } - private void dismountPlayer(ServerLevel sLevel, ServerPlayer sp, Integer seat, boolean portal) { - if (!portal) { + private void dismountPlayer(ServerLevel sLevel, ServerPlayer sp, Integer seat, boolean capture) { + if (!capture) { sp.stopRiding(); return; } From f8ec8e5dedad061f736d602bf0c3f80b71d01f28 Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Wed, 10 May 2023 21:36:18 +0200 Subject: [PATCH 11/13] Fixed #4744 --- .../trains/management/display/GlobalTrainDisplayData.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/display/GlobalTrainDisplayData.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/display/GlobalTrainDisplayData.java index 605d6c03f..24a4ed405 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/display/GlobalTrainDisplayData.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/display/GlobalTrainDisplayData.java @@ -30,10 +30,11 @@ public class GlobalTrainDisplayData { } public static List prepare(String filter, int maxLines) { + String regex = filter.isBlank() ? filter : "\\Q" + filter.replace("*", "\\E.*\\Q") + "\\E"; return statusByDestination.entrySet() .stream() .filter(e -> e.getKey() - .matches(filter.replace("*", ".*"))) + .matches(regex)) .flatMap(e -> e.getValue() .stream()) .sorted() From beb61708a0f80ed808385cc3ebcbeef97b3bed90 Mon Sep 17 00:00:00 2001 From: TropheusJ <60247969+TropheusJ@users.noreply.github.com> Date: Thu, 11 May 2023 09:00:32 -0400 Subject: [PATCH 12/13] Add GameTests by TropheusJ (#4496) --- .github/workflows/gametest.yml | 23 + build.gradle | 12 + src/generated/resources/.cache/cache | 2 +- .../resources/assets/create/lang/en_us.json | 2 + .../fluids/actors/HosePulleyFluidHandler.java | 4 + .../itemAssembly/SequencedAssemblyRecipe.java | 5 +- .../block/depot/DepotTileEntity.java | 5 + .../block/redstone/NixieTubeTileEntity.java | 4 + .../display/FlapDisplaySection.java | 14 +- .../content/schematics/SchematicExport.java | 75 +++ .../schematics/ServerSchematicLoader.java | 88 ++-- .../client/SchematicAndQuillHandler.java | 83 +--- .../client/SchematicPromptScreen.java | 1 - .../foundation/command/AllCommands.java | 12 +- .../foundation/command/CreateTestCommand.java | 111 +++++ .../create/foundation/mixin/MainMixin.java | 53 ++ .../foundation/mixin/TestCommandMixin.java | 46 ++ .../accessor/GameTestHelperAccessor.java | 17 + .../foundation/utility/FilesHelper.java | 9 +- .../create/gametest/CreateGameTests.java | 39 ++ .../com/simibubi/create/gametest/TESTING.md | 15 + .../infrastructure/CreateGameTestHelper.java | 452 ++++++++++++++++++ .../infrastructure/CreateTestFunction.java | 122 +++++ .../infrastructure/GameTestGroup.java | 25 + .../gametest/tests/TestContraptions.java | 98 ++++ .../create/gametest/tests/TestFluids.java | 152 ++++++ .../create/gametest/tests/TestItems.java | 331 +++++++++++++ .../create/gametest/tests/TestMisc.java | 67 +++ .../create/gametest/tests/TestProcessing.java | 129 +++++ .../assets/create/lang/default/interface.json | 4 +- src/main/resources/create.mixins.json | 3 + .../gametest/contraptions/arrow_dispenser.nbt | Bin 0 -> 1054 bytes .../gametest/contraptions/crop_farming.nbt | Bin 0 -> 2276 bytes .../contraptions/mounted_fluid_drain.nbt | Bin 0 -> 1368 bytes .../contraptions/mounted_item_extract.nbt | Bin 0 -> 1436 bytes .../gametest/contraptions/ploughing.nbt | Bin 0 -> 855 bytes .../contraptions/redstone_contacts.nbt | Bin 0 -> 1501 bytes .../gametest/contraptions/train_observer.nbt | Bin 0 -> 3331 bytes .../gametest/fluids/3_pipe_combine.nbt | Bin 0 -> 1141 bytes .../gametest/fluids/3_pipe_split.nbt | Bin 0 -> 1128 bytes .../gametest/fluids/hose_pulley_transfer.nbt | Bin 0 -> 3467 bytes .../gametest/fluids/in_world_pumping_in.nbt | Bin 0 -> 971 bytes .../gametest/fluids/in_world_pumping_out.nbt | Bin 0 -> 853 bytes .../gametest/fluids/steam_engine.nbt | Bin 0 -> 1594 bytes .../gametest/items/andesite_tunnel_split.nbt | Bin 0 -> 1260 bytes .../gametest/items/arm_purgatory.nbt | Bin 0 -> 1132 bytes .../gametest/items/attribute_filters.nbt | Bin 0 -> 2151 bytes .../gametest/items/belt_coaster.nbt | Bin 0 -> 3459 bytes .../gametest/items/brass_tunnel_filtering.nbt | Bin 0 -> 1650 bytes .../items/brass_tunnel_prefer_nearest.nbt | Bin 0 -> 1371 bytes .../items/brass_tunnel_round_robin.nbt | Bin 0 -> 1556 bytes .../items/brass_tunnel_single_split.nbt | Bin 0 -> 1209 bytes .../gametest/items/brass_tunnel_split.nbt | Bin 0 -> 1564 bytes .../items/brass_tunnel_sync_input.nbt | Bin 0 -> 1715 bytes .../items/content_observer_counting.nbt | Bin 0 -> 933 bytes .../gametest/items/depot_display.nbt | Bin 0 -> 1526 bytes .../gametest/items/stockpile_switch.nbt | Bin 0 -> 504 bytes .../structures/gametest/items/storages.nbt | Bin 0 -> 1789 bytes .../items/vault_comparator_output.nbt | Bin 0 -> 1908 bytes .../gametest/misc/schematicannon.nbt | Bin 0 -> 1320 bytes .../structures/gametest/misc/shearing.nbt | Bin 0 -> 1078 bytes .../gametest/processing/brass_mixing.nbt | Bin 0 -> 2154 bytes .../gametest/processing/brass_mixing_2.nbt | Bin 0 -> 2846 bytes .../processing/crushing_wheel_crafting.nbt | Bin 0 -> 2715 bytes .../gametest/processing/iron_compacting.nbt | Bin 0 -> 1865 bytes .../precision_mechanism_crafting.nbt | Bin 0 -> 2413 bytes .../gametest/processing/sand_washing.nbt | Bin 0 -> 1370 bytes .../processing/stone_cobble_sand_crushing.nbt | Bin 0 -> 2508 bytes .../gametest/processing/track_crafting.nbt | Bin 0 -> 2098 bytes .../processing/water_filling_bottle.nbt | Bin 0 -> 1797 bytes .../gametest/processing/wheat_milling.nbt | Bin 0 -> 986 bytes 71 files changed, 1881 insertions(+), 122 deletions(-) create mode 100644 .github/workflows/gametest.yml create mode 100644 src/main/java/com/simibubi/create/content/schematics/SchematicExport.java create mode 100644 src/main/java/com/simibubi/create/foundation/command/CreateTestCommand.java create mode 100644 src/main/java/com/simibubi/create/foundation/mixin/MainMixin.java create mode 100644 src/main/java/com/simibubi/create/foundation/mixin/TestCommandMixin.java create mode 100644 src/main/java/com/simibubi/create/foundation/mixin/accessor/GameTestHelperAccessor.java create mode 100644 src/main/java/com/simibubi/create/gametest/CreateGameTests.java create mode 100644 src/main/java/com/simibubi/create/gametest/TESTING.md create mode 100644 src/main/java/com/simibubi/create/gametest/infrastructure/CreateGameTestHelper.java create mode 100644 src/main/java/com/simibubi/create/gametest/infrastructure/CreateTestFunction.java create mode 100644 src/main/java/com/simibubi/create/gametest/infrastructure/GameTestGroup.java create mode 100644 src/main/java/com/simibubi/create/gametest/tests/TestContraptions.java create mode 100644 src/main/java/com/simibubi/create/gametest/tests/TestFluids.java create mode 100644 src/main/java/com/simibubi/create/gametest/tests/TestItems.java create mode 100644 src/main/java/com/simibubi/create/gametest/tests/TestMisc.java create mode 100644 src/main/java/com/simibubi/create/gametest/tests/TestProcessing.java create mode 100644 src/main/resources/data/create/structures/gametest/contraptions/arrow_dispenser.nbt create mode 100644 src/main/resources/data/create/structures/gametest/contraptions/crop_farming.nbt create mode 100644 src/main/resources/data/create/structures/gametest/contraptions/mounted_fluid_drain.nbt create mode 100644 src/main/resources/data/create/structures/gametest/contraptions/mounted_item_extract.nbt create mode 100644 src/main/resources/data/create/structures/gametest/contraptions/ploughing.nbt create mode 100644 src/main/resources/data/create/structures/gametest/contraptions/redstone_contacts.nbt create mode 100644 src/main/resources/data/create/structures/gametest/contraptions/train_observer.nbt create mode 100644 src/main/resources/data/create/structures/gametest/fluids/3_pipe_combine.nbt create mode 100644 src/main/resources/data/create/structures/gametest/fluids/3_pipe_split.nbt create mode 100644 src/main/resources/data/create/structures/gametest/fluids/hose_pulley_transfer.nbt create mode 100644 src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_in.nbt create mode 100644 src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_out.nbt create mode 100644 src/main/resources/data/create/structures/gametest/fluids/steam_engine.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/andesite_tunnel_split.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/arm_purgatory.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/attribute_filters.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/belt_coaster.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/brass_tunnel_filtering.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/brass_tunnel_prefer_nearest.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/brass_tunnel_round_robin.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/brass_tunnel_single_split.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/brass_tunnel_split.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/brass_tunnel_sync_input.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/content_observer_counting.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/depot_display.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/stockpile_switch.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/storages.nbt create mode 100644 src/main/resources/data/create/structures/gametest/items/vault_comparator_output.nbt create mode 100644 src/main/resources/data/create/structures/gametest/misc/schematicannon.nbt create mode 100644 src/main/resources/data/create/structures/gametest/misc/shearing.nbt create mode 100644 src/main/resources/data/create/structures/gametest/processing/brass_mixing.nbt create mode 100644 src/main/resources/data/create/structures/gametest/processing/brass_mixing_2.nbt create mode 100644 src/main/resources/data/create/structures/gametest/processing/crushing_wheel_crafting.nbt create mode 100644 src/main/resources/data/create/structures/gametest/processing/iron_compacting.nbt create mode 100644 src/main/resources/data/create/structures/gametest/processing/precision_mechanism_crafting.nbt create mode 100644 src/main/resources/data/create/structures/gametest/processing/sand_washing.nbt create mode 100644 src/main/resources/data/create/structures/gametest/processing/stone_cobble_sand_crushing.nbt create mode 100644 src/main/resources/data/create/structures/gametest/processing/track_crafting.nbt create mode 100644 src/main/resources/data/create/structures/gametest/processing/water_filling_bottle.nbt create mode 100644 src/main/resources/data/create/structures/gametest/processing/wheat_milling.nbt diff --git a/.github/workflows/gametest.yml b/.github/workflows/gametest.yml new file mode 100644 index 000000000..1ec05830a --- /dev/null +++ b/.github/workflows/gametest.yml @@ -0,0 +1,23 @@ +name: gametest +on: [ pull_request, push, workflow_dispatch ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + + - name: checkout repository + uses: actions/checkout@v3 + + - name: setup Java + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17 + cache: gradle + + - name: make gradle wrapper executable + run: chmod +x ./gradlew + + - name: run gametests + run: ./gradlew prepareRunGameTestServer runGameTestServer --no-daemon diff --git a/build.gradle b/build.gradle index d8e3a2d87..05c9bdb9a 100644 --- a/build.gradle +++ b/build.gradle @@ -90,6 +90,18 @@ minecraft { } } } + + gameTestServer { + workingDirectory project.file('run/gametest') + arg '-mixin.config=create.mixins.json' + property 'forge.logging.console.level', 'info' + mods { + create { + source sourceSets.main + } + } + setForceExit false + } } } diff --git a/src/generated/resources/.cache/cache b/src/generated/resources/.cache/cache index d50c09db9..e7c958a52 100644 --- a/src/generated/resources/.cache/cache +++ b/src/generated/resources/.cache/cache @@ -559,7 +559,7 @@ bf2b0310500213ff853c748c236eb5d01f61658e assets/create/blockstates/yellow_toolbo 7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json f85edc574ee6de0de7693ffb031266643db6724a assets/create/lang/en_ud.json -eb624aafc91b284143c3a0cc7d9bbb8de66e8950 assets/create/lang/en_us.json +5ca6b7f3f7f515134269ff45496bb2be53d7e67c assets/create/lang/en_us.json 487a511a01b2a4531fb672f917922312db78f958 assets/create/models/block/acacia_window.json b48060cba1a382f373a05bf0039054053eccf076 assets/create/models/block/acacia_window_pane_noside.json 3066db1bf03cffa1a9c7fbacf47ae586632f4eb3 assets/create/models/block/acacia_window_pane_noside_alt.json diff --git a/src/generated/resources/assets/create/lang/en_us.json b/src/generated/resources/assets/create/lang/en_us.json index 2603c07fc..3064a4f83 100644 --- a/src/generated/resources/assets/create/lang/en_us.json +++ b/src/generated/resources/assets/create/lang/en_us.json @@ -1117,6 +1117,8 @@ "create.schematicAndQuill.convert": "Save and Upload Immediately", "create.schematicAndQuill.fallbackName": "My Schematic", "create.schematicAndQuill.saved": "Saved as %1$s", + "create.schematicAndQuill.failed": "Failed to save schematic, check logs for details", + "create.schematicAndQuill.instant_failed": "Schematic instant-upload failed, check logs for details", "create.schematic.invalid": "[!] Invalid Item - Use the Schematic Table instead", "create.schematic.error": "Schematic failed to Load - Check Game Logs", diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/HosePulleyFluidHandler.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/HosePulleyFluidHandler.java index 063a52eae..b5883e2d7 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/HosePulleyFluidHandler.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/HosePulleyFluidHandler.java @@ -125,4 +125,8 @@ public class HosePulleyFluidHandler implements IFluidHandler { return internalTank.isFluidValid(tank, stack); } + public SmartFluidTank getInternalTank() { + return internalTank; + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/itemAssembly/SequencedAssemblyRecipe.java b/src/main/java/com/simibubi/create/content/contraptions/itemAssembly/SequencedAssemblyRecipe.java index 980d506eb..e3e64cf4f 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/itemAssembly/SequencedAssemblyRecipe.java +++ b/src/main/java/com/simibubi/create/content/contraptions/itemAssembly/SequencedAssemblyRecipe.java @@ -43,7 +43,8 @@ public class SequencedAssemblyRecipe implements Recipe { protected List> sequence; protected int loops; protected ProcessingOutput transitionalItem; - protected List resultPool; + + public final List resultPool; public SequencedAssemblyRecipe(ResourceLocation recipeId, SequencedAssemblyRecipeSerializer serializer) { this.id = recipeId; @@ -213,7 +214,7 @@ public class SequencedAssemblyRecipe implements Recipe { public boolean isSpecial() { return true; } - + @Override public RecipeType getType() { return AllRecipeTypes.SEQUENCED_ASSEMBLY.getType(); diff --git a/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotTileEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotTileEntity.java index 308237dec..8203bebaa 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotTileEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotTileEntity.java @@ -7,6 +7,7 @@ import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.common.capabilities.Capability; @@ -33,4 +34,8 @@ public class DepotTileEntity extends SmartTileEntity { return depotBehaviour.getItemCapability(cap, side); return super.getCapability(cap, side); } + + public ItemStack getHeldItem() { + return depotBehaviour.getHeldItemStack(); + } } diff --git a/src/main/java/com/simibubi/create/content/logistics/block/redstone/NixieTubeTileEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/redstone/NixieTubeTileEntity.java index 28afb42ed..adeb4ba96 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/redstone/NixieTubeTileEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/redstone/NixieTubeTileEntity.java @@ -123,6 +123,10 @@ public class NixieTubeTileEntity extends SmartTileEntity { customText = Optional.empty(); } + public int getRedstoneStrength() { + return redstoneStrength; + } + // @Override diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/display/FlapDisplaySection.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/display/FlapDisplaySection.java index 4481a80ca..51b5f5ef4 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/display/FlapDisplaySection.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/display/FlapDisplaySection.java @@ -89,11 +89,11 @@ public class FlapDisplaySection { int max = Math.max(4, (int) (cyclingOptions.length * 1.75f)); if (spinningTicks > max) return 0; - + spinningTicks++; if (spinningTicks <= max && spinningTicks < 2) return spinningTicks == 1 ? 0 : spinning.length; - + int spinningFlaps = 0; for (int i = 0; i < spinning.length; i++) { int increasingChance = Mth.clamp(8 - spinningTicks, 1, 10); @@ -107,11 +107,11 @@ public class FlapDisplaySection { spinning[i + 1] &= continueSpin; if (spinningTicks > max) spinning[i] = false; - + if (spinning[i]) spinningFlaps++; } - + return spinningFlaps; } @@ -169,10 +169,14 @@ public class FlapDisplaySection { return !singleFlap; } + public Component getText() { + return component; + } + public static String[] getFlapCycle(String key) { return LOADED_FLAP_CYCLES.computeIfAbsent(key, k -> Lang.translateDirect("flap_display.cycles." + key) .getString() .split(";")); } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/schematics/SchematicExport.java b/src/main/java/com/simibubi/create/content/schematics/SchematicExport.java new file mode 100644 index 000000000..6bad9f5a6 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/schematics/SchematicExport.java @@ -0,0 +1,75 @@ +package com.simibubi.create.content.schematics; + +import com.simibubi.create.Create; +import com.simibubi.create.content.schematics.item.SchematicAndQuillItem; +import com.simibubi.create.foundation.utility.FilesHelper; +import com.simibubi.create.foundation.utility.Lang; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraft.world.phys.AABB; +import net.minecraftforge.fml.loading.FMLEnvironment; +import net.minecraftforge.fml.loading.FMLPaths; + +import javax.annotation.Nullable; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +public class SchematicExport { + public static final Path SCHEMATICS = FMLPaths.GAMEDIR.get().resolve("schematics"); + + /** + * Save a schematic to a file from a world. + * @param dir the directory the schematic will be created in + * @param fileName the ideal name of the schematic, may not be the name of the created file + * @param overwrite whether overwriting an existing schematic is allowed + * @param level the level where the schematic structure is placed + * @param first the first corner of the schematic area + * @param second the second corner of the schematic area + * @return a SchematicExportResult, or null if an error occurred. + */ + @Nullable + public static SchematicExportResult saveSchematic(Path dir, String fileName, boolean overwrite, Level level, BlockPos first, BlockPos second) { + BoundingBox bb = BoundingBox.fromCorners(first, second); + BlockPos origin = new BlockPos(bb.minX(), bb.minY(), bb.minZ()); + BlockPos bounds = new BlockPos(bb.getXSpan(), bb.getYSpan(), bb.getZSpan()); + + StructureTemplate structure = new StructureTemplate(); + structure.fillFromWorld(level, origin, bounds, true, Blocks.AIR); + CompoundTag data = structure.save(new CompoundTag()); + SchematicAndQuillItem.replaceStructureVoidWithAir(data); + SchematicAndQuillItem.clampGlueBoxes(level, new AABB(origin, origin.offset(bounds)), data); + + if (fileName.isEmpty()) + fileName = Lang.translateDirect("schematicAndQuill.fallbackName").getString(); + if (!overwrite) + fileName = FilesHelper.findFirstValidFilename(fileName, dir, "nbt"); + if (!fileName.endsWith(".nbt")) + fileName += ".nbt"; + Path file = dir.resolve(fileName).toAbsolutePath(); + + try { + Files.createDirectories(dir); + boolean overwritten = Files.deleteIfExists(file); + try (OutputStream out = Files.newOutputStream(file, StandardOpenOption.CREATE)) { + NbtIo.writeCompressed(data, out); + } + return new SchematicExportResult(file, dir, fileName, overwritten, origin, bounds); + } catch (IOException e) { + Create.LOGGER.error("An error occurred while saving schematic [" + fileName + "]", e); + return null; + } + } + + public record SchematicExportResult(Path file, Path dir, String fileName, boolean overwritten, BlockPos origin, BlockPos bounds) { + } +} diff --git a/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java b/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java index ffee13229..ffe6b5df9 100644 --- a/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java +++ b/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java @@ -8,6 +8,7 @@ import java.nio.file.Paths; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -16,8 +17,8 @@ import java.util.stream.Stream; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllItems; import com.simibubi.create.Create; +import com.simibubi.create.content.schematics.SchematicExport.SchematicExportResult; import com.simibubi.create.content.schematics.block.SchematicTableTileEntity; -import com.simibubi.create.content.schematics.item.SchematicAndQuillItem; import com.simibubi.create.content.schematics.item.SchematicItem; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.CSchematics; @@ -25,18 +26,14 @@ import com.simibubi.create.foundation.utility.Components; import com.simibubi.create.foundation.utility.FilesHelper; import com.simibubi.create.foundation.utility.Lang; +import net.minecraft.ChatFormatting; import net.minecraft.Util; import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; -import net.minecraft.world.phys.AABB; public class ServerSchematicLoader { @@ -164,7 +161,7 @@ public class ServerSchematicLoader { protected boolean validateSchematicSizeOnServer(ServerPlayer player, long size) { Integer maxFileSize = getConfig().maxTotalSchematicSize.get(); if (size > maxFileSize * 1000) { - + player.sendMessage(Lang.translateDirect("schematics.uploadTooLarge") .append(Components.literal(" (" + size / 1000 + " KB).")), Util.NIL_UUID); player.sendMessage(Lang.translateDirect("schematics.maxAllowedSize") @@ -284,10 +281,9 @@ public class ServerSchematicLoader { public void handleInstantSchematic(ServerPlayer player, String schematic, Level world, BlockPos pos, BlockPos bounds) { - String playerPath = getSchematicPath() + "/" + player.getGameProfile() - .getName(); - String playerSchematicId = player.getGameProfile() - .getName() + "/" + schematic; + String playerName = player.getGameProfile().getName(); + String playerPath = getSchematicPath() + "/" + playerName; + String playerSchematicId = playerName + "/" + schematic; FilesHelper.createFolderIfMissing(playerPath); // Unsupported Format @@ -310,43 +306,43 @@ public class ServerSchematicLoader { if (!AllItems.SCHEMATIC_AND_QUILL.isIn(player.getMainHandItem())) return; + // if there's too many schematics, delete oldest + Path playerSchematics = Paths.get(playerPath); + + if (!tryDeleteOldestSchematic(playerSchematics)) + return; + + SchematicExportResult result = SchematicExport.saveSchematic( + playerSchematics, schematic, true, + world, pos, pos.offset(bounds).offset(-1, -1, -1) + ); + if (result != null) + player.setItemInHand(InteractionHand.MAIN_HAND, SchematicItem.create(schematic, playerName)); + else Lang.translate("schematicAndQuill.instant_failed") + .style(ChatFormatting.RED) + .sendStatus(player); + } + + private boolean tryDeleteOldestSchematic(Path dir) { + try (Stream stream = Files.list(dir)) { + List files = stream.toList(); + if (files.size() < getConfig().maxSchematics.get()) + return true; + Optional oldest = files.stream().min(Comparator.comparingLong(this::getLastModifiedTime)); + Files.delete(oldest.orElseThrow()); + return true; + } catch (IOException | IllegalStateException e) { + Create.LOGGER.error("Error deleting oldest schematic", e); + return false; + } + } + + private long getLastModifiedTime(Path file) { try { - // Delete schematic with same name - Files.deleteIfExists(path); - - // Too many Schematics - long count; - try (Stream list = Files.list(Paths.get(playerPath))) { - count = list.count(); - } - - if (count >= getConfig().maxSchematics.get()) { - Stream list2 = Files.list(Paths.get(playerPath)); - Optional lastFilePath = list2.filter(f -> !Files.isDirectory(f)) - .min(Comparator.comparingLong(f -> f.toFile() - .lastModified())); - list2.close(); - if (lastFilePath.isPresent()) - Files.deleteIfExists(lastFilePath.get()); - } - - StructureTemplate t = new StructureTemplate(); - t.fillFromWorld(world, pos, bounds, true, Blocks.AIR); - - try (OutputStream outputStream = Files.newOutputStream(path)) { - CompoundTag nbttagcompound = t.save(new CompoundTag()); - SchematicAndQuillItem.replaceStructureVoidWithAir(nbttagcompound); - SchematicAndQuillItem.clampGlueBoxes(world, new AABB(pos, pos.offset(bounds)), nbttagcompound); - NbtIo.writeCompressed(nbttagcompound, outputStream); - player.setItemInHand(InteractionHand.MAIN_HAND, SchematicItem.create(schematic, player.getGameProfile() - .getName())); - - } catch (IOException e) { - e.printStackTrace(); - } + return Files.getLastModifiedTime(file).toMillis(); } catch (IOException e) { - Create.LOGGER.error("Exception Thrown in direct Schematic Upload: " + playerSchematicId); - e.printStackTrace(); + Create.LOGGER.error("Error getting modification time of file " + file.getFileName(), e); + throw new IllegalStateException(e); } } diff --git a/src/main/java/com/simibubi/create/content/schematics/client/SchematicAndQuillHandler.java b/src/main/java/com/simibubi/create/content/schematics/client/SchematicAndQuillHandler.java index ff5c0a3de..ee3912882 100644 --- a/src/main/java/com/simibubi/create/content/schematics/client/SchematicAndQuillHandler.java +++ b/src/main/java/com/simibubi/create/content/schematics/client/SchematicAndQuillHandler.java @@ -1,13 +1,14 @@ package com.simibubi.create.content.schematics.client; import java.io.IOException; -import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import org.apache.commons.io.IOUtils; +import com.simibubi.create.content.schematics.SchematicExport; + +import com.simibubi.create.content.schematics.SchematicExport.SchematicExportResult; + +import net.minecraft.ChatFormatting; import com.simibubi.create.AllItems; import com.simibubi.create.AllKeys; @@ -15,12 +16,10 @@ import com.simibubi.create.AllSpecialTextures; import com.simibubi.create.Create; import com.simibubi.create.CreateClient; import com.simibubi.create.content.schematics.ClientSchematicLoader; -import com.simibubi.create.content.schematics.item.SchematicAndQuillItem; import com.simibubi.create.content.schematics.packet.InstantSchematicPacket; import com.simibubi.create.foundation.gui.ScreenOpener; import com.simibubi.create.foundation.networking.AllPackets; import com.simibubi.create.foundation.utility.AnimationTickHolder; -import com.simibubi.create.foundation.utility.FilesHelper; import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.RaycastHelper; import com.simibubi.create.foundation.utility.RaycastHelper.PredicateTraceResult; @@ -33,16 +32,10 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Direction.AxisDirection; import net.minecraft.core.Vec3i; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.levelgen.structure.BoundingBox; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult.Type; @@ -52,8 +45,8 @@ public class SchematicAndQuillHandler { private Object outlineSlot = new Object(); - private BlockPos firstPos; - private BlockPos secondPos; + public BlockPos firstPos; + public BlockPos secondPos; private BlockPos selectedPos; private Direction selectedFace; private int range = 10; @@ -212,58 +205,30 @@ public class SchematicAndQuillHandler { } public void saveSchematic(String string, boolean convertImmediately) { - StructureTemplate t = new StructureTemplate(); - BoundingBox bb = BoundingBox.fromCorners(firstPos, secondPos); - BlockPos origin = new BlockPos(bb.minX(), bb.minY(), bb.minZ()); - BlockPos bounds = new BlockPos(bb.getXSpan(), bb.getYSpan(), bb.getZSpan()); - Level level = Minecraft.getInstance().level; - - t.fillFromWorld(level, origin, bounds, true, Blocks.AIR); - - if (string.isEmpty()) - string = Lang.translateDirect("schematicAndQuill.fallbackName") - .getString(); - - String folderPath = "schematics"; - FilesHelper.createFolderIfMissing(folderPath); - String filename = FilesHelper.findFirstValidFilename(string, folderPath, "nbt"); - String filepath = folderPath + "/" + filename; - - Path path = Paths.get(filepath); - OutputStream outputStream = null; - try { - outputStream = Files.newOutputStream(path, StandardOpenOption.CREATE); - CompoundTag nbttagcompound = t.save(new CompoundTag()); - SchematicAndQuillItem.replaceStructureVoidWithAir(nbttagcompound); - SchematicAndQuillItem.clampGlueBoxes(level, new AABB(origin, origin.offset(bounds)), nbttagcompound); - NbtIo.writeCompressed(nbttagcompound, outputStream); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (outputStream != null) - IOUtils.closeQuietly(outputStream); + SchematicExportResult result = SchematicExport.saveSchematic( + SchematicExport.SCHEMATICS, string, false, + Minecraft.getInstance().level, firstPos, secondPos + ); + LocalPlayer player = Minecraft.getInstance().player; + if (result == null) { + Lang.translate("schematicAndQuill.failed") + .style(ChatFormatting.RED) + .sendStatus(player); + return; } + Path file = result.file(); + Lang.translate("schematicAndQuill.saved", file) + .sendStatus(player); firstPos = null; secondPos = null; - LocalPlayer player = Minecraft.getInstance().player; - Lang.translate("schematicAndQuill.saved", filepath) - .sendStatus(player); - if (!convertImmediately) return; - if (!Files.exists(path)) { - Create.LOGGER.error("Missing Schematic file: " + path.toString()); - return; - } try { - if (!ClientSchematicLoader.validateSizeLimitation(Files.size(path))) + if (!ClientSchematicLoader.validateSizeLimitation(Files.size(file))) return; - AllPackets.channel.sendToServer(new InstantSchematicPacket(filename, origin, bounds)); - + AllPackets.channel.sendToServer(new InstantSchematicPacket(result.fileName(), result.origin(), result.bounds())); } catch (IOException e) { - Create.LOGGER.error("Error finding Schematic file: " + path.toString()); - e.printStackTrace(); - return; + Create.LOGGER.error("Error instantly uploading Schematic file: " + file, e); } } @@ -271,4 +236,4 @@ public class SchematicAndQuillHandler { return CreateClient.OUTLINER; } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/schematics/client/SchematicPromptScreen.java b/src/main/java/com/simibubi/create/content/schematics/client/SchematicPromptScreen.java index 3fb353c40..4f9723252 100644 --- a/src/main/java/com/simibubi/create/content/schematics/client/SchematicPromptScreen.java +++ b/src/main/java/com/simibubi/create/content/schematics/client/SchematicPromptScreen.java @@ -109,5 +109,4 @@ public class SchematicPromptScreen extends AbstractSimiScreen { CreateClient.SCHEMATIC_AND_QUILL_HANDLER.saveSchematic(nameField.getValue(), convertImmediately); onClose(); } - } diff --git a/src/main/java/com/simibubi/create/foundation/command/AllCommands.java b/src/main/java/com/simibubi/create/foundation/command/AllCommands.java index 8664b0a5e..6942b6e5e 100644 --- a/src/main/java/com/simibubi/create/foundation/command/AllCommands.java +++ b/src/main/java/com/simibubi/create/foundation/command/AllCommands.java @@ -11,6 +11,8 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.world.entity.player.Player; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.loading.FMLLoader; public class AllCommands { @@ -20,7 +22,7 @@ public class AllCommands { LiteralCommandNode util = buildUtilityCommands(); - LiteralCommandNode createRoot = dispatcher.register(Commands.literal("create") + LiteralArgumentBuilder root = Commands.literal("create") .requires(cs -> cs.hasPermission(0)) // general purpose .then(new ToggleDebugCommand().register()) @@ -38,8 +40,12 @@ public class AllCommands { .then(GlueCommand.register()) // utility - .then(util) - ); + .then(util); + + if (!FMLLoader.isProduction() && FMLLoader.getDist() == Dist.CLIENT) + root.then(CreateTestCommand.register()); + + LiteralCommandNode createRoot = dispatcher.register(root); createRoot.addChild(buildRedirect("u", util)); diff --git a/src/main/java/com/simibubi/create/foundation/command/CreateTestCommand.java b/src/main/java/com/simibubi/create/foundation/command/CreateTestCommand.java new file mode 100644 index 000000000..f42edbed2 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/command/CreateTestCommand.java @@ -0,0 +1,111 @@ +package com.simibubi.create.foundation.command; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; + +import com.mojang.brigadier.suggestion.SuggestionsBuilder; + +import com.simibubi.create.CreateClient; + +import com.simibubi.create.content.schematics.SchematicExport; +import com.simibubi.create.content.schematics.SchematicExport.SchematicExportResult; +import com.simibubi.create.content.schematics.client.SchematicAndQuillHandler; + +import com.simibubi.create.foundation.utility.Components; + +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.level.ServerLevel; +import net.minecraftforge.fml.loading.FMLPaths; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +/** + * This command allows for quick exporting of GameTests. + * It is only registered in a client development environment. It is not safe in production or multiplayer. + */ +public class CreateTestCommand { + private static final Path gametests = FMLPaths.GAMEDIR.get() + .getParent() + .resolve("src/main/resources/data/create/structures/gametest") + .toAbsolutePath(); + + public static ArgumentBuilder register() { + return literal("test") + .then(literal("export") + .then(argument("path", StringArgumentType.greedyString()) + .suggests(CreateTestCommand::getSuggestions) + .executes(ctx -> handleExport( + ctx.getSource(), + ctx.getSource().getLevel(), + StringArgumentType.getString(ctx, "path") + )) + ) + ); + } + + private static int handleExport(CommandSourceStack source, ServerLevel level, String path) { + SchematicAndQuillHandler handler = CreateClient.SCHEMATIC_AND_QUILL_HANDLER; + if (handler.firstPos == null || handler.secondPos == null) { + source.sendFailure(Components.literal("You must select an area with the Schematic and Quill first.")); + return 0; + } + SchematicExportResult result = SchematicExport.saveSchematic( + gametests, path, true, + level, handler.firstPos, handler.secondPos + ); + if (result == null) + source.sendFailure(Components.literal("Failed to export, check logs").withStyle(ChatFormatting.RED)); + else { + sendSuccess(source, "Successfully exported test!", ChatFormatting.GREEN); + sendSuccess(source, "Overwritten: " + result.overwritten(), ChatFormatting.AQUA); + sendSuccess(source, "File: " + result.file(), ChatFormatting.GRAY); + } + return 0; + } + + private static void sendSuccess(CommandSourceStack source, String text, ChatFormatting color) { + source.sendSuccess(Components.literal(text).withStyle(color), true); + } + + // find existing tests and folders for autofill + private static CompletableFuture getSuggestions(CommandContext context, + SuggestionsBuilder builder) throws CommandSyntaxException { + String path = builder.getRemaining(); + if (!path.contains("/") || path.contains("..")) + return findInDir(gametests, builder); + int lastSlash = path.lastIndexOf("/"); + Path subDir = gametests.resolve(path.substring(0, lastSlash)); + if (Files.exists(subDir)) + findInDir(subDir, builder); + return builder.buildFuture(); + } + + private static CompletableFuture findInDir(Path dir, SuggestionsBuilder builder) { + try (Stream paths = Files.list(dir)) { + paths.filter(p -> Files.isDirectory(p) || p.toString().endsWith(".nbt")) + .forEach(path -> { + String file = path.toString() + .replaceAll("\\\\", "/") + .substring(gametests.toString().length() + 1); + if (Files.isDirectory(path)) + file += "/"; + builder.suggest(file); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + return builder.buildFuture(); + } +} diff --git a/src/main/java/com/simibubi/create/foundation/mixin/MainMixin.java b/src/main/java/com/simibubi/create/foundation/mixin/MainMixin.java new file mode 100644 index 000000000..da309104b --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/mixin/MainMixin.java @@ -0,0 +1,53 @@ +package com.simibubi.create.foundation.mixin; + +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTestRegistry; +import net.minecraft.gametest.framework.GameTestRunner; +import net.minecraft.gametest.framework.GameTestServer; +import net.minecraft.server.Main; + +import net.minecraft.server.MinecraftServer; + +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +import java.util.Collection; + +@Mixin(Main.class) +public class MainMixin { + + /** + * Forge completely bypasses vanilla's + * {@link GameTestServer#create(Thread, LevelStorageAccess, PackRepository, Collection, BlockPos)}, + * which causes tests to generate at bedrock level in a regular world. This causes interference + * (ex. darkness, liquids, gravel) that makes tests fail and act inconsistently. Replacing the server Forge + * makes with one made by vanilla's factory causes tests to run on a superflat, as they should. + *

+ * The system property 'create.useOriginalGametestServer' may be set to true to avoid this behavior. + * This may be desirable for other mods which pull Create into their development environments. + */ + @ModifyVariable( + method = "lambda$main$5", + at = @At( + value = "STORE", + ordinal = 0 + ), + require = 0 // don't crash if this fails + ) + private static MinecraftServer create$correctlyInitializeGametestServer(MinecraftServer original) { + if (original instanceof GameTestServer && !Boolean.getBoolean("create.useOriginalGametestServer")) { + return GameTestServer.create( + original.getRunningThread(), + original.storageSource, + original.getPackRepository(), + GameTestRunner.groupTestsIntoBatches(GameTestRegistry.getAllTestFunctions()), + BlockPos.ZERO + ); + } + return original; + } +} diff --git a/src/main/java/com/simibubi/create/foundation/mixin/TestCommandMixin.java b/src/main/java/com/simibubi/create/foundation/mixin/TestCommandMixin.java new file mode 100644 index 000000000..f78936a54 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/mixin/TestCommandMixin.java @@ -0,0 +1,46 @@ +package com.simibubi.create.foundation.mixin; + +import com.simibubi.create.gametest.infrastructure.CreateTestFunction; + +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTestRegistry; +import net.minecraft.gametest.framework.MultipleTestTracker; +import net.minecraft.gametest.framework.TestCommand; + +import net.minecraft.gametest.framework.TestFunction; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.server.level.ServerLevel; + +import net.minecraft.world.level.block.entity.StructureBlockEntity; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import javax.annotation.Nullable; + +@Mixin(TestCommand.class) +public class TestCommandMixin { + @Redirect( + method = "runTest(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;Lnet/minecraft/gametest/framework/MultipleTestTracker;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/gametest/framework/GameTestRegistry;getTestFunction(Ljava/lang/String;)Lnet/minecraft/gametest/framework/TestFunction;" + ), + require = 0 // don't crash if this fails. non-critical + ) + private static TestFunction create$getCorrectTestFunction(String testName, + ServerLevel level, BlockPos pos, @Nullable MultipleTestTracker tracker) { + StructureBlockEntity be = (StructureBlockEntity) level.getBlockEntity(pos); + CompoundTag data = be.getTileData(); + if (!data.contains("CreateTestFunction", Tag.TAG_STRING)) + return GameTestRegistry.getTestFunction(testName); + String name = data.getString("CreateTestFunction"); + CreateTestFunction function = CreateTestFunction.NAMES_TO_FUNCTIONS.get(name); + if (function == null) + throw new IllegalStateException("Structure block has CreateTestFunction attached, but test [" + name + "] doesn't exist"); + return function; + } +} diff --git a/src/main/java/com/simibubi/create/foundation/mixin/accessor/GameTestHelperAccessor.java b/src/main/java/com/simibubi/create/foundation/mixin/accessor/GameTestHelperAccessor.java new file mode 100644 index 000000000..3c3c280d6 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/mixin/accessor/GameTestHelperAccessor.java @@ -0,0 +1,17 @@ +package com.simibubi.create.foundation.mixin.accessor; + +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.gametest.framework.GameTestInfo; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(GameTestHelper.class) +public interface GameTestHelperAccessor { + @Accessor + GameTestInfo getTestInfo(); + @Accessor + boolean getFinalCheckAdded(); + @Accessor + void setFinalCheckAdded(boolean value); +} diff --git a/src/main/java/com/simibubi/create/foundation/utility/FilesHelper.java b/src/main/java/com/simibubi/create/foundation/utility/FilesHelper.java index a4b3c8b78..536ffa943 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/FilesHelper.java +++ b/src/main/java/com/simibubi/create/foundation/utility/FilesHelper.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; @@ -27,15 +28,15 @@ public class FilesHelper { } } - public static String findFirstValidFilename(String name, String folderPath, String extension) { + public static String findFirstValidFilename(String name, Path folderPath, String extension) { int index = 0; String filename; - String filepath; + Path filepath; do { filename = slug(name) + ((index == 0) ? "" : "_" + index) + "." + extension; index++; - filepath = folderPath + "/" + filename; - } while (Files.exists(Paths.get(filepath))); + filepath = folderPath.resolve(filename); + } while (Files.exists(filepath)); return filename; } diff --git a/src/main/java/com/simibubi/create/gametest/CreateGameTests.java b/src/main/java/com/simibubi/create/gametest/CreateGameTests.java new file mode 100644 index 000000000..f50450caa --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/CreateGameTests.java @@ -0,0 +1,39 @@ +package com.simibubi.create.gametest; + +import java.util.Collection; + +import com.simibubi.create.gametest.infrastructure.CreateTestFunction; + +import com.simibubi.create.gametest.tests.TestContraptions; +import com.simibubi.create.gametest.tests.TestFluids; +import com.simibubi.create.gametest.tests.TestItems; +import com.simibubi.create.gametest.tests.TestMisc; +import com.simibubi.create.gametest.tests.TestProcessing; + +import net.minecraft.gametest.framework.GameTestGenerator; +import net.minecraft.gametest.framework.TestFunction; +import net.minecraftforge.event.RegisterGameTestsEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus; + +@EventBusSubscriber(bus = Bus.MOD) +public class CreateGameTests { + private static final Class[] testHolders = { + TestContraptions.class, + TestFluids.class, + TestItems.class, + TestMisc.class, + TestProcessing.class + }; + + @SubscribeEvent + public static void registerTests(RegisterGameTestsEvent event) { + event.register(CreateGameTests.class); + } + + @GameTestGenerator + public static Collection generateTests() { + return CreateTestFunction.getTestsFrom(testHolders); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/TESTING.md b/src/main/java/com/simibubi/create/gametest/TESTING.md new file mode 100644 index 000000000..3dec254c9 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/TESTING.md @@ -0,0 +1,15 @@ +# Adding to GameTests + +#### Adding Tests +All tests must be static, take a `CreateGameTestHelper`, return void, and be annotated with `@GameTest`. +Non-annotated methods will be ignored. The annotation must also specify a structure template. +Classes holding registered tests must be annotated with `GameTestGroup`. + +#### Adding Groups/Classes +Added test classes must be added to the list in `CreateGameTests`. They must be annotated with +`@GameTestGroup` and given a structure path. + +#### Exporting Structures +Structures can be quickly exported using the `/create test export` command (or `/c test export`). +Select an area with the Schematic and Quill, and run it to quickly export a test structure +directly to the correct directory. diff --git a/src/main/java/com/simibubi/create/gametest/infrastructure/CreateGameTestHelper.java b/src/main/java/com/simibubi/create/gametest/infrastructure/CreateGameTestHelper.java new file mode 100644 index 000000000..51b86f548 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/infrastructure/CreateGameTestHelper.java @@ -0,0 +1,452 @@ +package com.simibubi.create.gametest.infrastructure; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import com.simibubi.create.foundation.mixin.accessor.GameTestHelperAccessor; + +import it.unimi.dsi.fastutil.objects.Object2LongArrayMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LeverBlock; +import net.minecraftforge.fluids.FluidStack; + +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemHandlerHelper; + +import org.jetbrains.annotations.Contract; + +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.content.logistics.block.belts.tunnel.BrassTunnelTileEntity.SelectionMode; +import com.simibubi.create.content.logistics.block.redstone.NixieTubeTileEntity; +import com.simibubi.create.foundation.item.ItemHelper; +import com.simibubi.create.foundation.tileEntity.IMultiTileContainer; +import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; +import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType; +import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollOptionBehaviour; +import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollValueBehaviour; +import com.simibubi.create.foundation.utility.RegisteredObjects; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Registry; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.gametest.framework.GameTestInfo; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.phys.Vec3; + +import org.jetbrains.annotations.NotNull; + +/** + * A helper class expanding the functionality of {@link GameTestHelper}. + * This class may replace the default helper parameter if a test is registered through {@link CreateTestFunction}. + */ +public class CreateGameTestHelper extends GameTestHelper { + public static final int TICKS_PER_SECOND = 20; + public static final int TEN_SECONDS = 10 * TICKS_PER_SECOND; + public static final int FIFTEEN_SECONDS = 15 * TICKS_PER_SECOND; + public static final int TWENTY_SECONDS = 20 * TICKS_PER_SECOND; + + private CreateGameTestHelper(GameTestInfo testInfo) { + super(testInfo); + } + + public static CreateGameTestHelper of(GameTestHelper original) { + GameTestHelperAccessor access = (GameTestHelperAccessor) original; + CreateGameTestHelper helper = new CreateGameTestHelper(access.getTestInfo()); + //noinspection DataFlowIssue // accessor applied at runtime + GameTestHelperAccessor newAccess = (GameTestHelperAccessor) helper; + newAccess.setFinalCheckAdded(access.getFinalCheckAdded()); + return helper; + } + + // blocks + + /** + * Flip the direction of any block with the {@link BlockStateProperties#FACING} property. + */ + public void flipBlock(BlockPos pos) { + BlockState original = getBlockState(pos); + if (!original.hasProperty(BlockStateProperties.FACING)) + fail("FACING property not in block: " + Registry.BLOCK.getId(original.getBlock())); + Direction facing = original.getValue(BlockStateProperties.FACING); + BlockState reversed = original.setValue(BlockStateProperties.FACING, facing.getOpposite()); + setBlock(pos, reversed); + } + + public void assertNixiePower(BlockPos pos, int strength) { + NixieTubeTileEntity nixie = getBlockEntity(AllTileEntities.NIXIE_TUBE.get(), pos); + int actualStrength = nixie.getRedstoneStrength(); + if (actualStrength != strength) + fail("Expected nixie tube at %s to have power of %s, got %s".formatted(pos, strength, actualStrength)); + } + + /** + * Turn off a lever. + */ + public void powerLever(BlockPos pos) { + assertBlockPresent(Blocks.LEVER, pos); + if (!getBlockState(pos).getValue(LeverBlock.POWERED)) { + pullLever(pos); + } + } + + /** + * Turn on a lever. + */ + public void unpowerLever(BlockPos pos) { + assertBlockPresent(Blocks.LEVER, pos); + if (getBlockState(pos).getValue(LeverBlock.POWERED)) { + pullLever(pos); + } + } + + /** + * Set the {@link SelectionMode} of a belt tunnel at the given position. + * @param pos + * @param mode + */ + public void setTunnelMode(BlockPos pos, SelectionMode mode) { + ScrollValueBehaviour behavior = getBehavior(pos, ScrollOptionBehaviour.TYPE); + behavior.setValue(mode.ordinal()); + } + + // block entities + + /** + * Get the block entity of the expected type. If the type does not match, this fails the test. + */ + public T getBlockEntity(BlockEntityType type, BlockPos pos) { + BlockEntity be = getBlockEntity(pos); + BlockEntityType actualType = be == null ? null : be.getType(); + if (actualType != type) { + String actualId = actualType == null ? "null" : RegisteredObjects.getKeyOrThrow(actualType).toString(); + String error = "Expected block entity at pos [%s] with type [%s], got [%s]".formatted( + pos, RegisteredObjects.getKeyOrThrow(type), actualId + ); + fail(error); + } + return (T) be; + } + + /** + * Given any segment of an {@link IMultiTileContainer}, get the controller for it. + */ + public T getControllerBlockEntity(BlockEntityType type, BlockPos anySegment) { + T be = getBlockEntity(type, anySegment).getControllerTE(); + if (be == null) + fail("Could not get block entity controller with type [%s] from pos [%s]".formatted(RegisteredObjects.getKeyOrThrow(type), anySegment)); + return be; + } + + /** + * Get the expected {@link TileEntityBehaviour} from the given position, failing if not present. + */ + public T getBehavior(BlockPos pos, BehaviourType type) { + T behavior = TileEntityBehaviour.get(getLevel(), absolutePos(pos), type); + if (behavior == null) + fail("Behavior at " + pos + " missing, expected " + type.getName()); + return behavior; + } + + // entities + + /** + * Spawn an item entity at the given position with no velocity. + */ + public ItemEntity spawnItem(BlockPos pos, ItemStack stack) { + Vec3 spawn = Vec3.atCenterOf(absolutePos(pos)); + ServerLevel level = getLevel(); + ItemEntity item = new ItemEntity(level, spawn.x, spawn.y, spawn.z, stack, 0, 0, 0); + level.addFreshEntity(item); + return item; + } + + /** + * Spawn item entities given an item and amount. The amount will be split into multiple entities if + * larger than the item's max stack size. + */ + public void spawnItems(BlockPos pos, Item item, int amount) { + while (amount > 0) { + int toSpawn = Math.min(amount, item.getMaxStackSize()); + amount -= toSpawn; + ItemStack stack = new ItemStack(item, toSpawn); + spawnItem(pos, stack); + } + } + + /** + * Get the first entity found at the given position. + */ + public T getFirstEntity(EntityType type, BlockPos pos) { + List list = getEntitiesBetween(type, pos.north().east().above(), pos.south().west().below()); + if (list.isEmpty()) + fail("No entities at pos: " + pos); + return list.get(0); + } + + /** + * Get a list of all entities between two positions, inclusive. + */ + public List getEntitiesBetween(EntityType type, BlockPos pos1, BlockPos pos2) { + BoundingBox box = BoundingBox.fromCorners(absolutePos(pos1), absolutePos(pos2)); + List entities = getLevel().getEntities(type, e -> box.isInside(e.blockPosition())); + return (List) entities; + } + + + // transfer - fluids + + public IFluidHandler fluidStorageAt(BlockPos pos) { + BlockEntity be = getBlockEntity(pos); + if (be == null) + fail("BlockEntity not present"); + Optional handler = be.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY).resolve(); + if (handler.isEmpty()) + fail("handler not present"); + return handler.get(); + } + + /** + * Get the content of the tank at the pos. + * content is determined by what the tank allows to be extracted. + */ + public FluidStack getTankContents(BlockPos tank) { + IFluidHandler handler = fluidStorageAt(tank); + return handler.drain(Integer.MAX_VALUE, FluidAction.SIMULATE); + } + + /** + * Get the total capacity of a tank at the given position. + */ + public long getTankCapacity(BlockPos pos) { + IFluidHandler handler = fluidStorageAt(pos); + long total = 0; + for (int i = 0; i < handler.getTanks(); i++) { + total += handler.getTankCapacity(i); + } + return total; + } + + /** + * Get the total fluid amount across all fluid tanks at the given positions. + */ + public long getFluidInTanks(BlockPos... tanks) { + long total = 0; + for (BlockPos tank : tanks) { + total += getTankContents(tank).getAmount(); + } + return total; + } + + /** + * Assert that the given fluid stack is present in the given tank. The tank might also hold more than the fluid. + */ + public void assertFluidPresent(FluidStack fluid, BlockPos pos) { + FluidStack contained = getTankContents(pos); + if (!fluid.isFluidEqual(contained)) + fail("Different fluids"); + if (fluid.getAmount() != contained.getAmount()) + fail("Different amounts"); + } + + /** + * Assert that the given tank holds no fluid. + */ + public void assertTankEmpty(BlockPos pos) { + assertFluidPresent(FluidStack.EMPTY, pos); + } + + public void assertTanksEmpty(BlockPos... tanks) { + for (BlockPos tank : tanks) { + assertTankEmpty(tank); + } + } + + // transfer - items + + public IItemHandler itemStorageAt(BlockPos pos) { + BlockEntity be = getBlockEntity(pos); + if (be == null) + fail("BlockEntity not present"); + Optional handler = be.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).resolve(); + if (handler.isEmpty()) + fail("handler not present"); + return handler.get(); + } + + /** + * Get a map of contained items to their amounts. This is not safe for NBT! + */ + public Object2LongMap getItemContent(BlockPos pos) { + IItemHandler handler = itemStorageAt(pos); + Object2LongMap map = new Object2LongArrayMap<>(); + for (int i = 0; i < handler.getSlots(); i++) { + ItemStack stack = handler.getStackInSlot(i); + if (stack.isEmpty()) + continue; + Item item = stack.getItem(); + long amount = map.getLong(item); + amount += stack.getCount(); + map.put(item, amount); + } + return map; + } + + /** + * Get the combined total of all ItemStacks inside the inventory. + */ + public long getTotalItems(BlockPos pos) { + IItemHandler storage = itemStorageAt(pos); + long total = 0; + for (int i = 0; i < storage.getSlots(); i++) { + total += storage.getStackInSlot(i).getCount(); + } + return total; + } + + /** + * Of the provided items, assert that at least one is present in the given inventory. + */ + public void assertAnyContained(BlockPos pos, Item... items) { + IItemHandler handler = itemStorageAt(pos); + boolean noneFound = true; + for (int i = 0; i < handler.getSlots(); i++) { + for (Item item : items) { + if (handler.getStackInSlot(i).is(item)) { + noneFound = false; + break; + } + } + } + if (noneFound) + fail("No matching items " + Arrays.toString(items) + " found in handler at pos: " + pos); + } + + /** + * Assert that the inventory contains all the provided content. + */ + public void assertContentPresent(Object2LongMap content, BlockPos pos) { + IItemHandler handler = itemStorageAt(pos); + Object2LongMap map = new Object2LongArrayMap<>(content); + for (int i = 0; i < handler.getSlots(); i++) { + ItemStack stack = handler.getStackInSlot(i); + if (stack.isEmpty()) + continue; + Item item = stack.getItem(); + long amount = map.getLong(item); + amount -= stack.getCount(); + if (amount == 0) + map.removeLong(item); + else map.put(item, amount); + } + if (!map.isEmpty()) + fail("Storage missing content: " + map); + } + + /** + * Assert that all the given inventories hold no items. + */ + public void assertContainersEmpty(List positions) { + for (BlockPos pos : positions) { + assertContainerEmpty(pos); + } + } + + /** + * Assert that the given inventory holds no items. + */ + @Override + public void assertContainerEmpty(@NotNull BlockPos pos) { + IItemHandler storage = itemStorageAt(pos); + for (int i = 0; i < storage.getSlots(); i++) { + if (!storage.getStackInSlot(i).isEmpty()) + fail("Storage not empty"); + } + } + + /** @see CreateGameTestHelper#assertContainerContains(BlockPos, ItemStack) */ + public void assertContainerContains(BlockPos pos, ItemLike item) { + assertContainerContains(pos, item.asItem()); + } + + /** @see CreateGameTestHelper#assertContainerContains(BlockPos, ItemStack) */ + @Override + public void assertContainerContains(@NotNull BlockPos pos, @NotNull Item item) { + assertContainerContains(pos, new ItemStack(item)); + } + + /** + * Assert that the inventory holds at least the given ItemStack. It may also hold more than the stack. + */ + public void assertContainerContains(BlockPos pos, ItemStack item) { + IItemHandler storage = itemStorageAt(pos); + ItemStack extracted = ItemHelper.extract(storage, stack -> ItemHandlerHelper.canItemStacksStack(stack, item), item.getCount(), true); + if (extracted.isEmpty()) + fail("item not present: " + item); + } + + // time + + /** + * Fail unless the desired number seconds have passed since test start. + */ + public void assertSecondsPassed(int seconds) { + if (getTick() < (long) seconds * TICKS_PER_SECOND) + fail("Waiting for %s seconds to pass".formatted(seconds)); + } + + /** + * Get the total number of seconds that have passed since test start. + */ + public long secondsPassed() { + return getTick() % 20; + } + + /** + * Run an action later, once enough time has passed. + */ + public void whenSecondsPassed(int seconds, Runnable run) { + runAfterDelay((long) seconds * TICKS_PER_SECOND, run); + } + + // numbers + + /** + * Assert that a number is <1 away from its expected value + */ + public void assertCloseEnoughTo(double value, double expected) { + assertInRange(value, expected - 1, expected + 1); + } + + public void assertInRange(double value, double min, double max) { + if (value < min) + fail("Value %s below expected min of %s".formatted(value, min)); + if (value > max) + fail("Value %s greater than expected max of %s".formatted(value, max)); + } + + // misc + + @Contract("_->fail") // make IDEA happier + @Override + public void fail(@NotNull String exceptionMessage) { + super.fail(exceptionMessage); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/infrastructure/CreateTestFunction.java b/src/main/java/com/simibubi/create/gametest/infrastructure/CreateTestFunction.java new file mode 100644 index 000000000..16cfcd163 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/infrastructure/CreateTestFunction.java @@ -0,0 +1,122 @@ +package com.simibubi.create.gametest.infrastructure; + +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestGenerator; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.gametest.framework.StructureUtils; +import net.minecraft.gametest.framework.TestFunction; +import net.minecraft.world.level.block.Rotation; + +import net.minecraft.world.level.block.entity.StructureBlockEntity; + +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Stream; + +/** + * An extension to game tests implementing functionality for {@link CreateGameTestHelper} and {@link GameTestGroup}. + * To use, create a {@link GameTestGenerator} that provides tests using {@link #getTestsFrom(Class[])}. + */ +public class CreateTestFunction extends TestFunction { + // for structure blocks and /test runthis + public static final Map NAMES_TO_FUNCTIONS = new HashMap<>(); + + public final String fullName; + public final String simpleName; + + protected CreateTestFunction(String fullName, String simpleName, String pBatchName, String pTestName, + String pStructureName, Rotation pRotation, int pMaxTicks, long pSetupTicks, + boolean pRequired, int pRequiredSuccesses, int pMaxAttempts, Consumer pFunction) { + super(pBatchName, pTestName, pStructureName, pRotation, pMaxTicks, pSetupTicks, pRequired, pRequiredSuccesses, pMaxAttempts, pFunction); + this.fullName = fullName; + this.simpleName = simpleName; + NAMES_TO_FUNCTIONS.put(fullName, this); + } + + @Override + public String getTestName() { + return simpleName; + } + + /** + * Get all Create test functions from the given classes. This enables functionality + * of {@link CreateGameTestHelper} and {@link GameTestGroup}. + */ + public static Collection getTestsFrom(Class... classes) { + return Stream.of(classes) + .map(Class::getDeclaredMethods) + .flatMap(Stream::of) + .map(CreateTestFunction::of) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(TestFunction::getTestName)) + .toList(); + } + + @Nullable + public static TestFunction of(Method method) { + GameTest gt = method.getAnnotation(GameTest.class); + if (gt == null) // skip non-test methods + return null; + Class owner = method.getDeclaringClass(); + GameTestGroup group = owner.getAnnotation(GameTestGroup.class); + String simpleName = owner.getSimpleName() + '.' + method.getName(); + validateTestMethod(method, gt, owner, group, simpleName); + + String structure = "%s:gametest/%s/%s".formatted(group.namespace(), group.path(), gt.template()); + Rotation rotation = StructureUtils.getRotationForRotationSteps(gt.rotationSteps()); + + String fullName = owner.getName() + "." + method.getName(); + return new CreateTestFunction( + // use structure for test name since that's what MC fills structure blocks with for some reason + fullName, simpleName, gt.batch(), structure, structure, rotation, gt.timeoutTicks(), gt.setupTicks(), + gt.required(), gt.requiredSuccesses(), gt.attempts(), asConsumer(method) + ); + } + + private static void validateTestMethod(Method method, GameTest gt, Class owner, GameTestGroup group, String simpleName) { + if (gt.template().isEmpty()) + throw new IllegalArgumentException(simpleName + " must provide a template structure"); + + if (!Modifier.isStatic(method.getModifiers())) + throw new IllegalArgumentException(simpleName + " must be static"); + + if (method.getReturnType() != void.class) + throw new IllegalArgumentException(simpleName + " must return void"); + + if (method.getParameterCount() != 1 || method.getParameterTypes()[0] != CreateGameTestHelper.class) + throw new IllegalArgumentException(simpleName + " must take 1 parameter of type CreateGameTestHelper"); + + if (group == null) + throw new IllegalArgumentException(owner.getName() + " must be annotated with @GameTestGroup"); + } + + private static Consumer asConsumer(Method method) { + return (helper) -> { + try { + method.invoke(null, helper); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + }; + } + + @Override + public void run(@NotNull GameTestHelper helper) { + // give structure block test info + StructureBlockEntity be = (StructureBlockEntity) helper.getBlockEntity(BlockPos.ZERO); + be.getTileData().putString("CreateTestFunction", fullName); + super.run(CreateGameTestHelper.of(helper)); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/infrastructure/GameTestGroup.java b/src/main/java/com/simibubi/create/gametest/infrastructure/GameTestGroup.java new file mode 100644 index 000000000..cb24dc5ce --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/infrastructure/GameTestGroup.java @@ -0,0 +1,25 @@ +package com.simibubi.create.gametest.infrastructure; + +import com.simibubi.create.Create; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Allows for test method declarations to be concise by moving subdirectories and namespaces to the class level. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface GameTestGroup { + /** + * The subdirectory to search for test structures in. + */ + String path(); + + /** + * The namespace to search for test structures in. + */ + String namespace() default Create.ID; +} diff --git a/src/main/java/com/simibubi/create/gametest/tests/TestContraptions.java b/src/main/java/com/simibubi/create/gametest/tests/TestContraptions.java new file mode 100644 index 000000000..524475898 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/tests/TestContraptions.java @@ -0,0 +1,98 @@ +package com.simibubi.create.gametest.tests; + +import java.util.List; + +import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper; +import com.simibubi.create.gametest.infrastructure.GameTestGroup; + +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.projectile.Arrow; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Blocks; +import net.minecraftforge.fluids.FluidStack; + +@GameTestGroup(path = "contraptions") +public class TestContraptions { + @GameTest(template = "arrow_dispenser", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void arrowDispenser(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(2, 3, 1); + helper.pullLever(lever); + BlockPos pos1 = new BlockPos(0, 5, 0); + BlockPos pos2 = new BlockPos(4, 5, 4); + helper.succeedWhen(() -> { + helper.assertSecondsPassed(7); + List arrows = helper.getEntitiesBetween(EntityType.ARROW, pos1, pos2); + if (arrows.size() != 4) + helper.fail("Expected 4 arrows"); + helper.powerLever(lever); // disassemble contraption + BlockPos dispenser = new BlockPos(2, 5, 2); + // there should be 1 left over + helper.assertContainerContains(dispenser, Items.ARROW); + }); + } + + @GameTest(template = "crop_farming", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void cropFarming(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(4, 3, 1); + helper.pullLever(lever); + BlockPos output = new BlockPos(1, 3, 12); + helper.succeedWhen(() -> helper.assertAnyContained(output, Items.WHEAT, Items.POTATO, Items.CARROT)); + } + + @GameTest(template = "mounted_item_extract", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS) + public static void mountedItemExtract(CreateGameTestHelper helper) { + BlockPos barrel = new BlockPos(1, 3, 2); + Object2LongMap content = helper.getItemContent(barrel); + BlockPos lever = new BlockPos(1, 5, 1); + helper.pullLever(lever); + BlockPos outputPos = new BlockPos(4, 2, 1); + helper.succeedWhen(() -> { + helper.assertContentPresent(content, outputPos); // verify all extracted + helper.powerLever(lever); + helper.assertContainerEmpty(barrel); // verify nothing left + }); + } + + @GameTest(template = "mounted_fluid_drain", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void mountedFluidDrain(CreateGameTestHelper helper) { + BlockPos tank = new BlockPos(1, 3, 2); + FluidStack fluid = helper.getTankContents(tank); + if (fluid.isEmpty()) + helper.fail("Tank empty"); + BlockPos lever = new BlockPos(1, 5, 1); + helper.pullLever(lever); + BlockPos output = new BlockPos(4, 2, 1); + helper.succeedWhen(() -> { + helper.assertFluidPresent(fluid, output); // verify all extracted + helper.powerLever(lever); // disassemble contraption + helper.assertTankEmpty(tank); // verify nothing left + }); + } + + @GameTest(template = "ploughing") + public static void ploughing(CreateGameTestHelper helper) { + BlockPos dirt = new BlockPos(4, 2, 1); + BlockPos lever = new BlockPos(3, 3, 2); + helper.pullLever(lever); + helper.succeedWhen(() -> helper.assertBlockPresent(Blocks.FARMLAND, dirt)); + } + + @GameTest(template = "redstone_contacts") + public static void redstoneContacts(CreateGameTestHelper helper) { + BlockPos end = new BlockPos(5, 10, 1); + BlockPos lever = new BlockPos(1, 3, 2); + helper.pullLever(lever); + helper.succeedWhen(() -> helper.assertBlockPresent(Blocks.DIAMOND_BLOCK, end)); + } + + // FIXME: trains do not enjoy being loaded in structures + // https://gist.github.com/TropheusJ/f2d0a7df48360d2e078d0987c115c6ef +// @GameTest(template = "train_observer") +// public static void trainObserver(CreateGameTestHelper helper) { +// helper.fail("NYI"); +// } +} diff --git a/src/main/java/com/simibubi/create/gametest/tests/TestFluids.java b/src/main/java/com/simibubi/create/gametest/tests/TestFluids.java new file mode 100644 index 000000000..0624e29d5 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/tests/TestFluids.java @@ -0,0 +1,152 @@ +package com.simibubi.create.gametest.tests; + +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.content.contraptions.fluids.actors.HosePulleyFluidHandler; +import com.simibubi.create.content.contraptions.relays.gauge.SpeedGaugeTileEntity; +import com.simibubi.create.content.contraptions.relays.gauge.StressGaugeTileEntity; + +import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper; + +import com.simibubi.create.gametest.infrastructure.GameTestGroup; + +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.util.Mth; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.RedStoneWireBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.RedstoneSide; +import net.minecraft.world.level.material.Fluids; +import net.minecraftforge.fluids.FluidAttributes; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; + +@GameTestGroup(path = "fluids") +public class TestFluids { + @GameTest(template = "hose_pulley_transfer", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS) + public static void hosePulleyTransfer(CreateGameTestHelper helper) { + // there was supposed to be redstone here built in, but it kept popping off, so put it there manually + BlockPos brokenRedstone = new BlockPos(4, 8, 3); + BlockState redstone = Blocks.REDSTONE_WIRE.defaultBlockState() + .setValue(RedStoneWireBlock.NORTH, RedstoneSide.NONE) + .setValue(RedStoneWireBlock.SOUTH, RedstoneSide.NONE) + .setValue(RedStoneWireBlock.EAST, RedstoneSide.UP) + .setValue(RedStoneWireBlock.WEST, RedstoneSide.SIDE) + .setValue(RedStoneWireBlock.POWER, 14); + helper.setBlock(brokenRedstone, redstone); + // pump + BlockPos lever = new BlockPos(6, 9, 3); + helper.pullLever(lever); + helper.succeedWhen(() -> { + helper.assertSecondsPassed(15); + // check filled + BlockPos filledLowerCorner = new BlockPos(8, 3, 2); + BlockPos filledUpperCorner = new BlockPos(10, 5, 4); + BlockPos.betweenClosed(filledLowerCorner, filledUpperCorner) + .forEach(pos -> helper.assertBlockPresent(Blocks.WATER, pos)); + // check emptied + BlockPos emptiedLowerCorner = new BlockPos(2, 3, 2); + BlockPos emptiedUpperCorner = new BlockPos(4, 5, 4); + BlockPos.betweenClosed(emptiedLowerCorner, emptiedUpperCorner) + .forEach(pos -> helper.assertBlockPresent(Blocks.AIR, pos)); + // check nothing left in pulley + BlockPos pulleyPos = new BlockPos(8, 7, 4); + IFluidHandler storage = helper.fluidStorageAt(pulleyPos); + if (storage instanceof HosePulleyFluidHandler hose) { + IFluidHandler internalTank = hose.getInternalTank(); + if (!internalTank.drain(1, FluidAction.SIMULATE).isEmpty()) + helper.fail("Pulley not empty"); + } else { + helper.fail("Not a pulley"); + } + }); + } + + @GameTest(template = "in_world_pumping_out") + public static void inWorldPumpingOutput(CreateGameTestHelper helper) { + BlockPos pumpPos = new BlockPos(3, 2, 2); + BlockPos waterPos = pumpPos.west(); + BlockPos basinPos = pumpPos.east(); + helper.flipBlock(pumpPos); + helper.succeedWhen(() -> { + helper.assertBlockPresent(Blocks.WATER, waterPos); + helper.assertTankEmpty(basinPos); + }); + } + + @GameTest(template = "in_world_pumping_in") + public static void inWorldPumpingPickup(CreateGameTestHelper helper) { + BlockPos pumpPos = new BlockPos(3, 2, 2); + BlockPos basinPos = pumpPos.east(); + BlockPos waterPos = pumpPos.west(); + FluidStack expectedResult = new FluidStack(Fluids.WATER, FluidAttributes.BUCKET_VOLUME); + helper.flipBlock(pumpPos); + helper.succeedWhen(() -> { + helper.assertBlockPresent(Blocks.AIR, waterPos); + helper.assertFluidPresent(expectedResult, basinPos); + }); + } + + @GameTest(template = "steam_engine") + public static void steamEngine(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(4, 3, 3); + helper.pullLever(lever); + BlockPos stressometer = new BlockPos(5, 2, 5); + BlockPos speedometer = new BlockPos(4, 2, 5); + helper.succeedWhen(() -> { + StressGaugeTileEntity stress = helper.getBlockEntity(AllTileEntities.STRESSOMETER.get(), stressometer); + SpeedGaugeTileEntity speed = helper.getBlockEntity(AllTileEntities.SPEEDOMETER.get(), speedometer); + float capacity = stress.getNetworkCapacity(); + helper.assertCloseEnoughTo(capacity, 2048); + float rotationSpeed = Mth.abs(speed.getSpeed()); + helper.assertCloseEnoughTo(rotationSpeed, 16); + }); + } + + @GameTest(template = "3_pipe_combine", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS) + public static void threePipeCombine(CreateGameTestHelper helper) { + BlockPos tank1Pos = new BlockPos(5, 2, 1); + BlockPos tank2Pos = tank1Pos.south(); + BlockPos tank3Pos = tank2Pos.south(); + long initialContents = helper.getFluidInTanks(tank1Pos, tank2Pos, tank3Pos); + + BlockPos pumpPos = new BlockPos(2, 2, 2); + helper.flipBlock(pumpPos); + helper.succeedWhen(() -> { + helper.assertSecondsPassed(13); + // make sure fully drained + helper.assertTanksEmpty(tank1Pos, tank2Pos, tank3Pos); + // and fully moved + BlockPos outputTankPos = new BlockPos(1, 2, 2); + long moved = helper.getFluidInTanks(outputTankPos); + if (moved != initialContents) + helper.fail("Wrong amount of fluid amount. expected [%s], got [%s]".formatted(initialContents, moved)); + // verify nothing was duped or deleted + }); + } + + @GameTest(template = "3_pipe_split", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void threePipeSplit(CreateGameTestHelper helper) { + BlockPos pumpPos = new BlockPos(2, 2, 2); + BlockPos tank1Pos = new BlockPos(5, 2, 1); + BlockPos tank2Pos = tank1Pos.south(); + BlockPos tank3Pos = tank2Pos.south(); + BlockPos outputTankPos = new BlockPos(1, 2, 2); + + long totalContents = helper.getFluidInTanks(tank1Pos, tank2Pos, tank3Pos, outputTankPos); + helper.flipBlock(pumpPos); + + helper.succeedWhen(() -> { + helper.assertSecondsPassed(7); + FluidStack contents = helper.getTankContents(outputTankPos); + if (!contents.isEmpty()) { + helper.fail("Tank not empty: " + contents.getAmount()); + } + long newTotalContents = helper.getFluidInTanks(tank1Pos, tank2Pos, tank3Pos); + if (newTotalContents != totalContents) { + helper.fail("Wrong total fluid amount. expected [%s], got [%s]".formatted(totalContents, newTotalContents)); + } + }); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/tests/TestItems.java b/src/main/java/com/simibubi/create/gametest/tests/TestItems.java new file mode 100644 index 000000000..43dc317e3 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/tests/TestItems.java @@ -0,0 +1,331 @@ +package com.simibubi.create.gametest.tests; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Stream; + +import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllItems; +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.content.logistics.block.belts.tunnel.BrassTunnelTileEntity.SelectionMode; +import com.simibubi.create.content.logistics.block.depot.DepotTileEntity; +import com.simibubi.create.content.logistics.block.redstone.NixieTubeTileEntity; +import com.simibubi.create.content.logistics.trains.management.display.FlapDisplayLayout; +import com.simibubi.create.content.logistics.trains.management.display.FlapDisplaySection; +import com.simibubi.create.content.logistics.trains.management.display.FlapDisplayTileEntity; +import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper; +import com.simibubi.create.gametest.infrastructure.GameTestGroup; +import com.simibubi.create.foundation.utility.Components; + +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import net.minecraft.Util; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.item.EnchantedBookItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.enchantment.EnchantmentInstance; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.RedstoneLampBlock; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemHandlerHelper; + +@GameTestGroup(path = "items") +public class TestItems { + @GameTest(template = "andesite_tunnel_split") + public static void andesiteTunnelSplit(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(2, 6, 2); + helper.pullLever(lever); + Map outputs = Map.of( + new BlockPos(2, 2, 1), new ItemStack(AllItems.BRASS_INGOT.get(), 1), + new BlockPos(3, 2, 1), new ItemStack(AllItems.BRASS_INGOT.get(), 1), + new BlockPos(4, 2, 2), new ItemStack(AllItems.BRASS_INGOT.get(), 3) + ); + helper.succeedWhen(() -> outputs.forEach(helper::assertContainerContains)); + } + + @GameTest(template = "arm_purgatory", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void armPurgatory(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(2, 3, 2); + BlockPos depot1Pos = new BlockPos(3, 2, 1); + DepotTileEntity depot1 = helper.getBlockEntity(AllTileEntities.DEPOT.get(), depot1Pos); + BlockPos depot2Pos = new BlockPos(1, 2, 1); + DepotTileEntity depot2 = helper.getBlockEntity(AllTileEntities.DEPOT.get(), depot2Pos); + helper.pullLever(lever); + helper.succeedWhen(() -> { + helper.assertSecondsPassed(5); + ItemStack held1 = depot1.getHeldItem(); + boolean held1Empty = held1.isEmpty(); + int held1Count = held1.getCount(); + ItemStack held2 = depot2.getHeldItem(); + boolean held2Empty = held2.isEmpty(); + int held2Count = held2.getCount(); + if (held1Empty && held2Empty) + helper.fail("No item present"); + if (!held1Empty && held1Count != 1) + helper.fail("Unexpected count on depot 1: " + held1Count); + if (!held2Empty && held2Count != 1) + helper.fail("Unexpected count on depot 2: " + held2Count); + }); + } + + @GameTest(template = "attribute_filters", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void attributeFilters(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(2, 3, 1); + BlockPos end = new BlockPos(11, 2, 2); + Map outputs = Map.of( + new BlockPos(3, 2, 1), new ItemStack(AllBlocks.BRASS_BLOCK.get()), + new BlockPos(4, 2, 1), new ItemStack(Items.APPLE), + new BlockPos(5, 2, 1), new ItemStack(Items.WATER_BUCKET), + new BlockPos(6, 2, 1), EnchantedBookItem.createForEnchantment( + new EnchantmentInstance(Enchantments.ALL_DAMAGE_PROTECTION, 1) + ), + new BlockPos(7, 2, 1), Util.make( + new ItemStack(Items.NETHERITE_SWORD), + s -> s.setDamageValue(1) + ), + new BlockPos(8, 2, 1), new ItemStack(Items.IRON_HELMET), + new BlockPos(9, 2, 1), new ItemStack(Items.COAL), + new BlockPos(10, 2, 1), new ItemStack(Items.POTATO) + ); + helper.pullLever(lever); + helper.succeedWhen(() -> { + outputs.forEach(helper::assertContainerContains); + helper.assertContainerEmpty(end); + }); + } + + @GameTest(template = "belt_coaster", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void beltCoaster(CreateGameTestHelper helper) { + BlockPos input = new BlockPos(1, 5, 6); + BlockPos output = new BlockPos(3, 8, 6); + BlockPos lever = new BlockPos(1, 5, 5); + helper.pullLever(lever); + helper.succeedWhen(() -> { + long outputItems = helper.getTotalItems(output); + if (outputItems != 27) + helper.fail("Expected 27 items, got " + outputItems); + long remainingItems = helper.getTotalItems(input); + if (remainingItems != 2) + helper.fail("Expected 2 items remaining, got " + remainingItems); + }); + } + + @GameTest(template = "brass_tunnel_filtering") + public static void brassTunnelFiltering(CreateGameTestHelper helper) { + Map outputs = Map.of( + new BlockPos(3, 2, 2), new ItemStack(Items.COPPER_INGOT, 13), + new BlockPos(4, 2, 3), new ItemStack(AllItems.ZINC_INGOT.get(), 4), + new BlockPos(4, 2, 4), new ItemStack(Items.IRON_INGOT, 2), + new BlockPos(4, 2, 5), new ItemStack(Items.GOLD_INGOT, 24), + new BlockPos(3, 2, 6), new ItemStack(Items.DIAMOND, 17) + ); + BlockPos lever = new BlockPos(2, 3, 2); + helper.pullLever(lever); + helper.succeedWhen(() -> outputs.forEach(helper::assertContainerContains)); + } + + @GameTest(template = "brass_tunnel_prefer_nearest", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void brassTunnelPreferNearest(CreateGameTestHelper helper) { + List tunnels = List.of( + new BlockPos(3, 3, 1), + new BlockPos(3, 3, 2), + new BlockPos(3, 3, 3) + ); + List out = List.of( + new BlockPos(5, 2, 1), + new BlockPos(5, 2, 2), + new BlockPos(5, 2, 3) + ); + BlockPos lever = new BlockPos(2, 3, 2); + helper.pullLever(lever); + // tunnels reconnect and lose their modes + tunnels.forEach(tunnel -> helper.setTunnelMode(tunnel, SelectionMode.PREFER_NEAREST)); + helper.succeedWhen(() -> + out.forEach(pos -> + helper.assertContainerContains(pos, AllBlocks.BRASS_CASING.get()) + ) + ); + } + + @GameTest(template = "brass_tunnel_round_robin", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void brassTunnelRoundRobin(CreateGameTestHelper helper) { + List outputs = List.of( + new BlockPos(7, 3, 1), + new BlockPos(7, 3, 2), + new BlockPos(7, 3, 3) + ); + brassTunnelModeTest(helper, SelectionMode.ROUND_ROBIN, outputs); + } + + @GameTest(template = "brass_tunnel_split") + public static void brassTunnelSplit(CreateGameTestHelper helper) { + List outputs = List.of( + new BlockPos(7, 2, 1), + new BlockPos(7, 2, 2), + new BlockPos(7, 2, 3) + ); + brassTunnelModeTest(helper, SelectionMode.SPLIT, outputs); + } + + private static void brassTunnelModeTest(CreateGameTestHelper helper, SelectionMode mode, List outputs) { + BlockPos lever = new BlockPos(2, 3, 2); + List tunnels = List.of( + new BlockPos(3, 3, 1), + new BlockPos(3, 3, 2), + new BlockPos(3, 3, 3) + ); + helper.pullLever(lever); + tunnels.forEach(tunnel -> helper.setTunnelMode(tunnel, mode)); + helper.succeedWhen(() -> { + long items = 0; + for (BlockPos out : outputs) { + helper.assertContainerContains(out, AllBlocks.BRASS_CASING.get()); + items += helper.getTotalItems(out); + } + if (items != 10) + helper.fail("expected 10 items, got " + items); + }); + } + + @GameTest(template = "brass_tunnel_sync_input", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void brassTunnelSyncInput(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(1, 3, 2); + List redstoneBlocks = List.of( + new BlockPos(3, 4, 1), + new BlockPos(3, 4, 2), + new BlockPos(3, 4, 3) + ); + List tunnels = List.of( + new BlockPos(5, 3, 1), + new BlockPos(5, 3, 2), + new BlockPos(5, 3, 3) + ); + List outputs = List.of( + new BlockPos(7, 2, 1), + new BlockPos(7, 2, 2), + new BlockPos(7, 2, 3) + ); + helper.pullLever(lever); + tunnels.forEach(tunnel -> helper.setTunnelMode(tunnel, SelectionMode.SYNCHRONIZE)); + helper.succeedWhen(() -> { + if (helper.secondsPassed() < 9) { + helper.setBlock(redstoneBlocks.get(0), Blocks.AIR); + helper.assertSecondsPassed(3); + outputs.forEach(helper::assertContainerEmpty); + helper.setBlock(redstoneBlocks.get(1), Blocks.AIR); + helper.assertSecondsPassed(6); + outputs.forEach(helper::assertContainerEmpty); + helper.setBlock(redstoneBlocks.get(2), Blocks.AIR); + helper.assertSecondsPassed(9); + } else { + outputs.forEach(out -> helper.assertContainerContains(out, AllBlocks.BRASS_CASING.get())); + } + }); + } + + @GameTest(template = "content_observer_counting") + public static void contentObserverCounting(CreateGameTestHelper helper) { + BlockPos chest = new BlockPos(3, 2, 1); + long totalChestItems = helper.getTotalItems(chest); + BlockPos chestNixiePos = new BlockPos(2, 3, 1); + NixieTubeTileEntity chestNixie = helper.getBlockEntity(AllTileEntities.NIXIE_TUBE.get(), chestNixiePos); + + BlockPos doubleChest = new BlockPos(2, 2, 3); + long totalDoubleChestItems = helper.getTotalItems(doubleChest); + BlockPos doubleChestNixiePos = new BlockPos(1, 3, 3); + NixieTubeTileEntity doubleChestNixie = helper.getBlockEntity(AllTileEntities.NIXIE_TUBE.get(), doubleChestNixiePos); + + helper.succeedWhen(() -> { + String chestNixieText = chestNixie.getFullText().getString(); + long chestNixieReading = Long.parseLong(chestNixieText); + if (chestNixieReading != totalChestItems) + helper.fail("Chest nixie detected %s, expected %s".formatted(chestNixieReading, totalChestItems)); + String doubleChestNixieText = doubleChestNixie.getFullText().getString(); + long doubleChestNixieReading = Long.parseLong(doubleChestNixieText); + if (doubleChestNixieReading != totalDoubleChestItems) + helper.fail("Double chest nixie detected %s, expected %s".formatted(doubleChestNixieReading, totalDoubleChestItems)); + }); + } + + @GameTest(template = "depot_display", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void depotDisplay(CreateGameTestHelper helper) { + BlockPos displayPos = new BlockPos(5, 3, 1); + List depots = Stream.of( + new BlockPos(2, 2, 1), + new BlockPos(1, 2, 1) + ).map(pos -> helper.getBlockEntity(AllTileEntities.DEPOT.get(), pos)).toList(); + List levers = List.of( + new BlockPos(2, 5, 0), + new BlockPos(1, 5, 0) + ); + levers.forEach(helper::pullLever); + FlapDisplayTileEntity display = helper.getBlockEntity(AllTileEntities.FLAP_DISPLAY.get(), displayPos).getController(); + helper.succeedWhen(() -> { + for (int i = 0; i < 2; i++) { + FlapDisplayLayout line = display.getLines().get(i); + MutableComponent textComponent = Components.empty(); + line.getSections().stream().map(FlapDisplaySection::getText).forEach(textComponent::append); + String text = textComponent.getString().toLowerCase(Locale.ROOT).trim(); + + DepotTileEntity depot = depots.get(i); + ItemStack item = depot.getHeldItem(); + String name = Registry.ITEM.getKey(item.getItem()).getPath(); + + if (!name.equals(text)) + helper.fail("Text mismatch: wanted [" + name + "], got: " + text); + } + }); + } + + @GameTest(template = "stockpile_switch") + public static void stockpileSwitch(CreateGameTestHelper helper) { + BlockPos chest = new BlockPos(1, 2, 1); + BlockPos lamp = new BlockPos(2, 3, 1); + helper.assertBlockProperty(lamp, RedstoneLampBlock.LIT, false); + IItemHandler chestStorage = helper.itemStorageAt(chest); + for (int i = 0; i < 18; i++) { // insert 18 stacks + ItemHandlerHelper.insertItem(chestStorage, new ItemStack(Items.DIAMOND, 64), false); + } + helper.succeedWhen(() -> helper.assertBlockProperty(lamp, RedstoneLampBlock.LIT, true)); + } + + @GameTest(template = "storages", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void storages(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(12, 3, 2); + BlockPos startChest = new BlockPos(13, 3, 1); + Object2LongMap originalContent = helper.getItemContent(startChest); + BlockPos endShulker = new BlockPos(1, 3, 1); + helper.pullLever(lever); + helper.succeedWhen(() -> helper.assertContentPresent(originalContent, endShulker)); + } + + @GameTest(template = "vault_comparator_output") + public static void vaultComparatorOutput(CreateGameTestHelper helper) { + BlockPos smallInput = new BlockPos(1, 4, 1); + BlockPos smallNixie = new BlockPos(3, 2, 1); + helper.assertNixiePower(smallNixie, 0); + helper.whenSecondsPassed(1, () -> helper.spawnItems(smallInput, Items.BREAD, 64 * 9)); + + BlockPos medInput = new BlockPos(1, 5, 4); + BlockPos medNixie = new BlockPos(4, 2, 4); + helper.assertNixiePower(medNixie, 0); + helper.whenSecondsPassed(2, () -> helper.spawnItems(medInput, Items.BREAD, 64 * 77)); + + BlockPos bigInput = new BlockPos(1, 6, 8); + BlockPos bigNixie = new BlockPos(5, 2, 7); + helper.assertNixiePower(bigNixie, 0); + helper.whenSecondsPassed(3, () -> helper.spawnItems(bigInput, Items.BREAD, 64 * 240)); + + helper.succeedWhen(() -> { + helper.assertNixiePower(smallNixie, 7); + helper.assertNixiePower(medNixie, 7); + helper.assertNixiePower(bigNixie, 7); + }); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/tests/TestMisc.java b/src/main/java/com/simibubi/create/gametest/tests/TestMisc.java new file mode 100644 index 000000000..829bd3d4b --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/tests/TestMisc.java @@ -0,0 +1,67 @@ +package com.simibubi.create.gametest.tests; + +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.content.schematics.SchematicExport; +import com.simibubi.create.content.schematics.block.SchematicannonTileEntity; +import com.simibubi.create.content.schematics.block.SchematicannonTileEntity.State; +import com.simibubi.create.content.schematics.item.SchematicItem; + +import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper; +import com.simibubi.create.gametest.infrastructure.GameTestGroup; + +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.animal.Sheep; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Blocks; + +import static com.simibubi.create.gametest.infrastructure.CreateGameTestHelper.FIFTEEN_SECONDS; + +@GameTestGroup(path = "misc") +public class TestMisc { + @GameTest(template = "schematicannon", timeoutTicks = FIFTEEN_SECONDS) + public static void schematicannon(CreateGameTestHelper helper) { + // load the structure + BlockPos whiteEndBottom = helper.absolutePos(new BlockPos(5, 2, 1)); + BlockPos redEndTop = helper.absolutePos(new BlockPos(5, 4, 7)); + ServerLevel level = helper.getLevel(); + SchematicExport.saveSchematic( + SchematicExport.SCHEMATICS.resolve("uploaded/Deployer"), "schematicannon_gametest", true, + level, whiteEndBottom, redEndTop + ); + ItemStack schematic = SchematicItem.create("schematicannon_gametest.nbt", "Deployer"); + // deploy to pos + BlockPos anchor = helper.absolutePos(new BlockPos(1, 2, 1)); + schematic.getOrCreateTag().putBoolean("Deployed", true); + schematic.getOrCreateTag().put("Anchor", NbtUtils.writeBlockPos(anchor)); + // setup cannon + BlockPos cannonPos = new BlockPos(3, 2, 6); + SchematicannonTileEntity cannon = helper.getBlockEntity(AllTileEntities.SCHEMATICANNON.get(), cannonPos); + cannon.inventory.setStackInSlot(0, schematic); + // run + cannon.state = State.RUNNING; + cannon.statusMsg = "running"; + helper.succeedWhen(() -> { + if (cannon.state != State.STOPPED) { + helper.fail("Schematicannon not done"); + } + BlockPos lastBlock = new BlockPos(1, 4, 7); + helper.assertBlockPresent(Blocks.RED_WOOL, lastBlock); + }); + } + + @GameTest(template = "shearing") + public static void shearing(CreateGameTestHelper helper) { + BlockPos sheepPos = new BlockPos(2, 1, 2); + Sheep sheep = helper.getFirstEntity(EntityType.SHEEP, sheepPos); + sheep.shear(SoundSource.NEUTRAL); + helper.succeedWhen(() -> { + helper.assertItemEntityPresent(Items.WHITE_WOOL, sheepPos, 2); + }); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/tests/TestProcessing.java b/src/main/java/com/simibubi/create/gametest/tests/TestProcessing.java new file mode 100644 index 000000000..ca2767c7d --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/tests/TestProcessing.java @@ -0,0 +1,129 @@ +package com.simibubi.create.gametest.tests; + +import java.util.List; + +import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllItems; + +import com.simibubi.create.Create; +import com.simibubi.create.content.contraptions.itemAssembly.SequencedAssemblyRecipe; +import com.simibubi.create.content.contraptions.processing.ProcessingOutput; +import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper; +import com.simibubi.create.gametest.infrastructure.GameTestGroup; +import com.simibubi.create.foundation.item.ItemHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestAssertException; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.alchemy.PotionUtils; +import net.minecraft.world.item.alchemy.Potions; +import net.minecraftforge.items.IItemHandler; + +@GameTestGroup(path = "processing") +public class TestProcessing { + @GameTest(template = "brass_mixing", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void brassMixing(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(2, 3, 2); + BlockPos chest = new BlockPos(7, 3, 1); + helper.pullLever(lever); + helper.succeedWhen(() -> helper.assertContainerContains(chest, AllItems.BRASS_INGOT.get())); + } + + @GameTest(template = "brass_mixing_2", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS) + public static void brassMixing2(CreateGameTestHelper helper) { + BlockPos basinLever = new BlockPos(3, 3, 1); + BlockPos armLever = new BlockPos(3, 3, 5); + BlockPos output = new BlockPos(1, 2, 3); + helper.pullLever(armLever); + helper.whenSecondsPassed(7, () -> helper.pullLever(armLever)); + helper.whenSecondsPassed(10, () -> helper.pullLever(basinLever)); + helper.succeedWhen(() -> helper.assertContainerContains(output, AllItems.BRASS_INGOT.get())); + } + + @GameTest(template = "crushing_wheel_crafting", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void crushingWheelCrafting(CreateGameTestHelper helper) { + BlockPos chest = new BlockPos(1, 4, 3); + List levers = List.of( + new BlockPos(2, 3, 2), + new BlockPos(6, 3, 2), + new BlockPos(3, 7, 3) + ); + levers.forEach(helper::pullLever); + ItemStack expected = new ItemStack(AllBlocks.CRUSHING_WHEEL.get(), 2); + helper.succeedWhen(() -> helper.assertContainerContains(chest, expected)); + } + + @GameTest(template = "precision_mechanism_crafting", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS) + public static void precisionMechanismCrafting(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(6, 3, 6); + BlockPos output = new BlockPos(11, 3, 1); + helper.pullLever(lever); + + SequencedAssemblyRecipe recipe = (SequencedAssemblyRecipe) helper.getLevel().getRecipeManager() + .byKey(Create.asResource("sequenced_assembly/precision_mechanism")) + .orElseThrow(() -> new GameTestAssertException("Precision Mechanism recipe not found")); + Item result = recipe.getResultItem().getItem(); + Item[] possibleResults = recipe.resultPool.stream() + .map(ProcessingOutput::getStack) + .map(ItemStack::getItem) + .filter(item -> item != result) + .toArray(Item[]::new); + + helper.succeedWhen(() -> { + helper.assertContainerContains(output, result); + helper.assertAnyContained(output, possibleResults); + }); + } + + @GameTest(template = "sand_washing", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void sandWashing(CreateGameTestHelper helper) { + BlockPos leverPos = new BlockPos(5, 3, 1); + helper.pullLever(leverPos); + BlockPos chestPos = new BlockPos(8, 3, 2); + helper.succeedWhen(() -> helper.assertContainerContains(chestPos, Items.CLAY_BALL)); + } + + @GameTest(template = "stone_cobble_sand_crushing", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void stoneCobbleSandCrushing(CreateGameTestHelper helper) { + BlockPos chest = new BlockPos(1, 6, 2); + BlockPos lever = new BlockPos(2, 3, 1); + helper.pullLever(lever); + ItemStack expected = new ItemStack(Items.SAND, 5); + helper.succeedWhen(() -> helper.assertContainerContains(chest, expected)); + } + + @GameTest(template = "track_crafting", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void trackCrafting(CreateGameTestHelper helper) { + BlockPos output = new BlockPos(7, 3, 2); + BlockPos lever = new BlockPos(2, 3, 1); + helper.pullLever(lever); + ItemStack expected = new ItemStack(AllBlocks.TRACK.get(), 6); + helper.succeedWhen(() -> { + helper.assertContainerContains(output, expected); + IItemHandler handler = helper.itemStorageAt(output); + ItemHelper.extract(handler, stack -> stack.sameItem(expected), 6, false); + helper.assertContainerEmpty(output); + }); + } + + @GameTest(template = "water_filling_bottle") + public static void waterFillingBottle(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(3, 3, 3); + BlockPos output = new BlockPos(2, 2, 4); + ItemStack expected = PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.WATER); + helper.pullLever(lever); + helper.succeedWhen(() -> helper.assertContainerContains(output, expected)); + } + + @GameTest(template = "wheat_milling") + public static void wheatMilling(CreateGameTestHelper helper) { + BlockPos output = new BlockPos(1, 2, 1); + BlockPos lever = new BlockPos(1, 7, 1); + helper.pullLever(lever); + ItemStack expected = new ItemStack(AllItems.WHEAT_FLOUR.get(), 3); + helper.succeedWhen(() -> helper.assertContainerContains(output, expected)); + } +} diff --git a/src/main/resources/assets/create/lang/default/interface.json b/src/main/resources/assets/create/lang/default/interface.json index 86e0d8237..af689e00f 100644 --- a/src/main/resources/assets/create/lang/default/interface.json +++ b/src/main/resources/assets/create/lang/default/interface.json @@ -269,6 +269,8 @@ "create.schematicAndQuill.convert": "Save and Upload Immediately", "create.schematicAndQuill.fallbackName": "My Schematic", "create.schematicAndQuill.saved": "Saved as %1$s", + "create.schematicAndQuill.failed": "Failed to save schematic, check logs for details", + "create.schematicAndQuill.instant_failed": "Schematic instant-upload failed, check logs for details", "create.schematic.invalid": "[!] Invalid Item - Use the Schematic Table instead", "create.schematic.error": "Schematic failed to Load - Check Game Logs", @@ -920,7 +922,7 @@ "create.contraption.minecart_contraption_too_big": "This Cart Contraption seems too big to pick up", "create.contraption.minecart_contraption_illegal_pickup": "A mystical force is binding this Cart Contraption to the world", - + "enchantment.create.capacity.desc": "Increases Backtank air capacity.", "enchantment.create.potato_recovery.desc": "Potato Cannon projectiles have a chance to be reused." diff --git a/src/main/resources/create.mixins.json b/src/main/resources/create.mixins.json index dc5b8e9a8..c316ea3bd 100644 --- a/src/main/resources/create.mixins.json +++ b/src/main/resources/create.mixins.json @@ -8,10 +8,13 @@ "ClientboundMapItemDataPacketMixin", "ContraptionDriverInteractMixin", "CustomItemUseEffectsMixin", + "MainMixin", "MapItemSavedDataMixin", + "TestCommandMixin", "accessor.AbstractProjectileDispenseBehaviorAccessor", "accessor.DispenserBlockAccessor", "accessor.FallingBlockEntityAccessor", + "accessor.GameTestHelperAccessor", "accessor.LivingEntityAccessor", "accessor.NbtAccounterAccessor", "accessor.ServerLevelAccessor" diff --git a/src/main/resources/data/create/structures/gametest/contraptions/arrow_dispenser.nbt b/src/main/resources/data/create/structures/gametest/contraptions/arrow_dispenser.nbt new file mode 100644 index 0000000000000000000000000000000000000000..62320b6d4b0a0299c227279e39f56425a6fbc8e2 GIT binary patch literal 1054 zcmV+(1mXK1iwFP!00000|HYV1Z__Xs$Di2F)^-e#5C=X0CxpZap-DSTyl7ltV$uX$ zCNK56Xjxnwcilip`zCw@&U`11NL+E+y35St7X}iQsy4~b@o!J^f8uoj&fgmI*mqCgg0Hkn{3-BrqXo%Y>XQ6LPjp$eATLTPEae znUJ$(Le7>6InP`iXX0VH&eX$@1coLsU1x^e^R9F5VY<#M9)=_^G=X6OACllqQ0D>A z`atK9?~a8SZe4)cv-(QtsZ_@~^q%rDFG_vBebJtT?V6_Co+^j+t>I7%?co;}Z5_$W zp*d*2?ri3;va6M7=53n7+HRp_RqgR=(w^XU4@fzL8-)@^WaC6?sXV&f@(nypsCr--wU2|3dQXNEYhdYDkO7~LeXM~a}S9hah$PsKqAhTiM0a~akfmX z9k|X|J8+$`7`x6sCe{vIXDsS5XM#BQuh|X)(Vqu>eG&Acn&{$aUT&JXZq38QdO@sq%Oykt6YB-B>rE4!X@WCDoUvXI%N0YMeN3zuxXxHF zppG-v3uwgIGO=DjBhHqI^#a!!>jkbemSNY~$HaPp>x|_*=1frMtG%E-#Tw+Vr?pV+ zls_Lly#M9-<8NO-eBb`@j_w1uz6i2)+3tsIX z2(H>8xLdbYH#|%%`>|Rr?}Y>=mi^fEr3ub7!I>e>SoUK%Vu-VkiDln)#GqGgHoN0nH zOK@h0vyTa-gdxs8#&upD^Gawf+Pmat57;YyBH-?#zbjr=Q+~c*@TTqU0~qWmITot@ z39kqFSWeA-kv#%C?)K$s))@Q2nnl?;$rk*_2mR&m^pkiJ5Y7(iL;CP19m3d)`hfuEeSMt%i0O znT)~B>FlFC75nqwVw{n#0OMz61KIiI#k=wkUdqIQ1)wv!uT^OZt~w?66aanbm=)+V YuKAu&O(|=$i2VY;0P~qV=wcWE0HKW#$^ZZW literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/contraptions/crop_farming.nbt b/src/main/resources/data/create/structures/gametest/contraptions/crop_farming.nbt new file mode 100644 index 0000000000000000000000000000000000000000..45e0887b89e925ed64f98c0499d84fddcde4b0d0 GIT binary patch literal 2276 zcmaJ?eK-?p8}AKCanvQuu4dq7p;cWZ4qJk*qWdjcsY6 z`COCcqnhQ?T4?aa(MzhCah=ckc6z$@Gjmu=+vb zO4ZQ1og$S@Ia$dqg(#O5#51B~UrGW6H&`eemzrZ9Zr=fx^mrYE3FPeE2x-RjVTDgN z*uvwNpO4LaxZ3USEZCiv9&n-xJ2xK^$o$=;+gsvBO@C2}h%#&fqU(xoc8XfyNgE6W zJ{9CAw(PE>eWp)iPdWUujmDU23=^FKwzr89eGZ2z__D|wZMow={@n1C_-Xd#hzNt) zr5e4F_O9^CAQ2*twz6IOASOFRzCU7qoN*34O%Cj2STrf4un{z*MU{B0s_b!6D5ZC% z3?azaHp&aTN~#pJf0W3mPv87Dhl}XjrzT}L&ReSET@oj|267pa85urno+Y0iV9oE} z#VWBkcHy4R5UdsFc1rWI{?Ht(Zv-gtIP-hL{d+PC;9YO}3q8x^H5Pb*EiOK_21du0 ze3&U~O$1S+$bu;cyCTZk2S&Yk7`?kyG+^LgLXLMVAR9P3q=izdMw>_{o2Fm6)ZoU1 zO6jaz(1iO`1!*-MBJB9j?zRy#|Y7Tx_rVpvJ_j*<9Hp;tZ z8)worb)7iN+v@xYqyxyK8$S|nnWP=^wpr7eWd7J@MZ9H)BS37dDgS{Gug+oNQT@%9 zQY0z%+ptBfgZ96TFQqAm*>As>zcju)Ejw}F*#E4u#8M9H+*{UiJ4N&Q_eCwyzfStF zV$**^{;B(0%GW9_*y2u{F2#>y<3ysU^T&`YwYQBUUxPf_@gwIgowX^_W=$v3ZS`s} zi9lQyytqByihdgE3-RxQMvP43PoIxl7W}m=5>0uFuuP-(>&J8)O(ll5d0_UMnH7-R z+YqG_e&r2~Qyy6+%9+NEx@1*iSNf#Q0VdP>I!OcjP$>_J*A$e220_xYWG#eibnphN{Ti>W8pV0yuLS~aB6n!Tox;^o58MX{ zYtaBbVO%b;)fPa#yx^lohF@b~kswAs-Iryz`-NW!#VwV_GluDim_W*nbrRq=r{-$r zoCwlb|9R^{x%J^of$ka?V#>F*NPvW`2jAzcL-vGd6Cx&%w8}mzYSA{XeRO|N4}f{I zl4F}-d8%4{vHP>qFvI6~4y)FA=^gZ>JN>`Mf$3PqVQoLg=82jQ4d+qb8ZV@Vt?B<) zNB5~CfXe#u%T4+zutcAcU(4<0dPj76itjnA-6o=a-cV6ANLR%o&=-M$Dix(~&_$Mt zxQg=fd;Q_zMRBx|TR^MWZ~)cHBvtb(lDt4x?=PfklCm$V0BUlQQJYm@jo08>d}3&`WJI)d*=c%po}tLaNu23X>jF+U}R~q zOab6bUGypmgN(Mm{FvwMxP7lUVM75#45fQ@{B(FM)&5m5|0o$h{upi@c9G^k8-1FW zNxv|CUdx2cyz@xyANWB<$-e~A-6d26(biA7YMB_ny|Y?v5fSS$qLcGeG`^3=XCK{7 zklq~Wu@AlMBK{18sbwi%?%+Q4b(5uH_W~_0U-_&s9A?B=?eZJyn38ta2-%|>d&WC@ zL|Lv4G)GVQx&&W0gYguM; zJm8)EbAQ@g7`jA1Occ#x`7u~`!~H#~yQdE1TX=IoB@lv6n9Plw|nuDyg$ylJNw zgQ5Dbk5s8)k|JOO$zwNSxm$7#{d;+%O(|I_BpLNcape^^n45XgiQ~L_mTM z#2dMjW*Uy@mN+|y-xLIgzUoHx$_q%-hw3&RVK>;{y?!60(F}K#b8Ik?Ke#MHv0L?y(>;M1& literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/contraptions/mounted_fluid_drain.nbt b/src/main/resources/data/create/structures/gametest/contraptions/mounted_fluid_drain.nbt new file mode 100644 index 0000000000000000000000000000000000000000..85f7e96666344db1dca7a3a1f94b83d325dde982 GIT binary patch literal 1368 zcmV-e1*iHSiwFP!00000|HW9%ZyQAvfBR*x*9jCs3kW2*Ac4dYBvjERuG2JWOJe1t zEeOe)tS9y;>z&o^Y}|0l1*t+p91v$N`~mz24dNf*fP}aK5?9nC7gXNt?riL}cjHtM zShB{u``(-1`})0EI{*zZBla0J06>0g+1G4`nhx$YZ}J_gxM7FtPLaw@LlSPtw(Z=Q=zT#A|cz z_4vq_Hxbwt7es=v@KRSw7A&k1&s$-UAihge9E~>dcY_`gRBM3I;Zd^R^83e$%JOEa zy8KeQ>WGPj^yyO@bk>6x+>oN?)hG^V*zJ2UZGgVR6BA1r zVE_cpG-kP9%~LfN~Q{rlBvR@WX`H*v#^&2DJfcDZ3mS0+Q;(V zr49TUB{wqWaiG3I#fXOo7T9eP5SNLg+sD$dxsBW_x3FNb)$5^+?D6Wc+)rQcA-ogh zfNK`iwna#zXyrJ8)ZBJM?s@kJ(vsXZrL2XFvW8sna7Gzu1sC+p1qW(3IP+*|fYWjX zJEUu9Cyl>-e}J6o(Fl@Q)wbgx@Q#weL$gDoNJ(o@OFc%V`c1#j{8UFixX*kfaIFc} z<_O1vjOJ@!w*Gqaj}O+rdv9@cTR6WvuyB_|;*O6*pHQUs0L5Ox^@ zC(hk?$b24Q$WpYmHOl$}30<3H@qI65y)Mp%0|%NrxU{)1aKzcrSe1`9$dJPGXpFic z*%w#oFc3#*uQkMZC7`5^ivcyVfa0wkHcTANQu9WJBajmfr(!n+#1KTdPh6@&y~9T| zq&*C>JwC=}l%ihP*8Csy`17rw^snGU?~8Z7{{HP>jd*q*FK2nYp5@W{uktuTrEn~d zFFq;@s6Hc)7tz}leVn>>SsgUkFu=k%KqJ0L!DiPsI{ zVc>u%4~0c#sn7e= z*!P*=pPC)YpI4`&yhlkWRi^vJDB!g-yw${DMLZURscgeUwkI1sU;(X4bO|pTg)}zi z!{=izr|{+boD*N}R|&rH_&J4q|8O3@S)0u_%K)8L-fcHd`DQWm!1ZF`4vKA}n7WlS zb;((19DnoT@at7)p-G>T;7--^rL)gMTI$z?3kxVy4~R#Fpz;lC(FQXaCW@Xz!n_eV z>bfM7O@S%vgdATD+Q~ZK6k$wlsNqAYlNJ~#%VT58lMGw5snn1n zp@=3~aAbE={|cH`G#~+m+9;XVHW-2jIhZ{z1RtiCx!vjcT85_J#92I8G}BBbvd8;< z$&CBNix87Z9xT{x{_hYpCpK?QZAY7+41C;DWDAAMdK;<)l}I`Qrmp#Dd{NW al@%h$Jv{g@?jyy03;zPIr8f956953px~}yA literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/contraptions/mounted_item_extract.nbt b/src/main/resources/data/create/structures/gametest/contraptions/mounted_item_extract.nbt new file mode 100644 index 0000000000000000000000000000000000000000..dead6de25da871b2abf64d47d2a6be6e8fdf137e GIT binary patch literal 1436 zcmYk+c{tR090%~^2?u{1F3}5>3*5{W=5Ni^!(ezRC?t$q=f!Sq3O~ift<$oGl25!m*kh_ zu0cyvn)%%hx9rAhFJa0EOkDGF-rsKtiQ{|6=G_c^KG!PC>M+WRmVUMxrw-4t_^(a| zuap0Jxv`@4&;d)}3_%`;(K6?6I0Pcc%<|#J2dP3& zvAGrfxD*+*jTC=k!`I)$i9g(H;Keh9_TU)9h}cFKgwGinDwz$C|XeF;GnP9tlgS-5k(K^+H5EMX1)bHWl!YC9bC zUjdV!*=c>Bz`*}2sIe41Y7GVoGFK}{7?;TQKtWO306}V|7l7^g1_FRd%$IM~A6A^2uA~ z`u|!T#M|?D|07>4uB3%|1%SCdBN`NjC9nf++cP9lF2-5jxSP2d(<1{OD_f$0V=wle z=AE)|th0>O1*bClD8lo0H8C{5zLbh^hlXfyfLhxLrxxYBsSA~=i0C8C2(8IG))`YZ zr^tRvr_Ab-Z6PDYmZ$xCL`s;lOEx#JBtg$WbF79BJ99mYhBmKei&CA(p3@sWB7ayb zONut=*Udi=UaNODlyBU8W-{DopHW}z>NX+AxVWv;G)E0$IwS=1l);vUP56v825;m? z6_1W=V$j{L{Ot!^HH1cArTV)vFs{ZjQ8XB6%S7absP&u%P*UFkp_&U8$6-RA#ycI- zpl&Cmgs3Eo%jMu1m!XCd2-thi1bsIt$FIw^u!02x?lcucM8QXnT{#}RjGwkBNLP(LTM#0Fyi&nP_mV?dg?~}GOfMrp5lA<_P^981N*kSq zhrHOX-FT8|j`aehd}wpJx^?u%xeLorSfBIai)jWO7o|orb@pA4f|ru-dZl3+D?4sQ Z7J7=Ei_C{#{wCU3@=n*T1Cg`<;2)&xug?Gg literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/contraptions/ploughing.nbt b/src/main/resources/data/create/structures/gametest/contraptions/ploughing.nbt new file mode 100644 index 0000000000000000000000000000000000000000..64329baa3259f70a483a3eb1996c476336de0db0 GIT binary patch literal 855 zcmV-d1E~BTiwFP!00000|D~7RZrU&u$4@>Cfv)Q=R%x0h?K#@3PW#X*P2Du4?&BtM zlLJ^yDgul=EoqvuGFa#JtSh70|0HAvI>NOld zgt=srVF}e^0KJQhCzs_=y?H-?px~w6w0;CYN=YTwJK8j!gT{Bz1Qw0wz@s%b9<8zQ zXpN1>ci_<)8;{o5c(lgGv-Z(JW8={p8;{o5ctQsrt+DZFjg3cZY&^t)M{8_6T4UqU z8XM0tPlLxdXgmjv@1O}R8qa~pbKvnDcsvIl-+{+(;Q<&zkY7jwWKpJX$cOovLrXChHw5#Cp&IaKR6wH~SU z`(m;9-FD)ezVr-aq<6VE445h^Cufbjf;TgR`IINpQP?pW6cnLNmO^ zol?VsDqAX_g;ek84J*OBiQq}~DyA9FC-ImG6>*tn9rtBD5JE>2?|jl!7J_)3!QS_j{e_Wu2&$EV;amcSc{kp#kRU hy3#v`G&VogIDSJVJx5V>mTLZ=@E5WN-FH(D008NZsiFV? literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/contraptions/redstone_contacts.nbt b/src/main/resources/data/create/structures/gametest/contraptions/redstone_contacts.nbt new file mode 100644 index 0000000000000000000000000000000000000000..60b165139b62885e7b7da2e2357fe042de86572b GIT binary patch literal 1501 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/fluids/3_pipe_combine.nbt b/src/main/resources/data/create/structures/gametest/fluids/3_pipe_combine.nbt new file mode 100644 index 0000000000000000000000000000000000000000..b7a9d2b54f87abfcfe51fb84266e230661efedd9 GIT binary patch literal 1141 zcmV-*1d96~iwFP!00000|J9gHkK05T#~;7OaTYBgUL-yOHzZCKEo`<_1*&C5XbW7z ztUa5I*6~=Lant1#@c}q;7j8A8mkAh)|i;J#>A{OCT6WMG21{kYA*H znVu?a>l>8a!Rxnpy^YuJN&pAk3D)$=weUf)U?~?&URJdJeadEYg>DC6vl!lpa)hI| z&Xa=0Q%TZ`5Jq3)<(Q`u7l8nV_tZ}(WI^E&JEKuf&gCt-$mAvVceLO|Dxt+ttA1|| zj8z-zfj7x0jgMFAyVpYf{&tiZ>{P_KCH5f9dLfl=INTW%V^CWphl7gzoRt6l@1}wPbc}EDFd1E(1_^UGn zm*RNgQhKTwikx~-S4|Z)n8q5}zM@ZPE@<4-icx7r749cyBM@v!cfm?1^{Q-nN83W9 zIZ0VWl4({fGMot$UgQz2^Y7zE{`qaoL^BJVJ!XL+&?=C(>p%osWC@*KUJt1_aUVN;Ou z+$dK z*v|;^(j0MGL0;A6d)z2H?sYBSOKg;HEZ?KQ+xBqH6tixeq1Uz_Y8v|HKV62l?T2oT zq5l76=)uD-L!FmyC^QUx{(btLjl67l*KJp*tWsN76;8*tzh&oYHz6h zyRkGo_0RE~gwHBBU+Z@I7*t+xFs9Xcx6yV~R^5cpW;DJ6(H*MY+PcGz17fcpUGkI( zu%50vJ6yXpTh1v>n!Qx9lumSXS)E|Cq}uLZJ+CC!cXn}QvWvc_^<9U@Nxa-G4hr(#(~jYBFQ6~3&vBdJ-@(zjgqb^ H4jBLd3e7JB literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/fluids/3_pipe_split.nbt b/src/main/resources/data/create/structures/gametest/fluids/3_pipe_split.nbt new file mode 100644 index 0000000000000000000000000000000000000000..88f648db6b679ee8e855201a36e9601f52e5d188 GIT binary patch literal 1128 zcmV-u1eg0CiwFP!00000|J9hwZrer_h7Yfzs1!+yUVuJAb_Kc$nj*H-AW0L~sM@6M zMjex5X<&*&aE4Kwm5>L>GTW}(F8T;v^o6?YvS&z+q)_C9Nfg5-V9+Fg9)9PaGdG0* zLvRIqMjZf9e}nq$4I!YZWRg(<>KKB5obc#Wgn04g5S)w){nF}iz!Q>4TEEezv3h9i z9vY{I#x-cH9?V)}V%8cHv(}iHwZ_D3_h8l<6SLNsn6<{ltTiU)ReSW%n3%Q3#H=+Y zX00(XyFHk-#>A{OCT6WMF>8&9+3UfqH6~`QF)?e6iCJq*%zh7MtuZlcjfq)nOw3yI zDsu=w?m)N~N%n-^r9@Wn!Q&YdGO0#N5BzbFWyx7d4WlC>gjQSN|Cq%X z7_%9ClTKM$kFNOOF-v2Hes2H%#q#Bgzr^0>|1Lwg@_-2WZORwv zk;1lqLfJh$|BUCGc>bjXaKIhom|nRSJ}Bla<$}qxiq^kN*>tAR?Eq{R!-r9haP-DW zQm}X;NqQQ>=m$I<@KoX?5Ww(`dUQzU6s}@08s+3f-dNyEXotTy=S7O~uK(9Is9BAM zv1&s-@WvUX@xfBvyLO2GitS-jTv)-Pny3{^?A? zrPy9Ll^!XEBB!cX*ec`lFts(ZeNUgzT+q0u6{FIMD&1RVBM@vsx4{%s30K+jskVhi zGm^51B$KR|XV??QyvQS3*Wdj{{rPRvM6(K<9ae!M(JGNo>O=&aX9=C0Vg*YUHS=ip zqs}}wOTk?Y53NnTfBla?U)$6nhU0~twKYc>pskx`ETb(`o7EU9z1Q&RQtV>b6r?;i z3OZ|Qf87+llh`a4e>Wz#^1rRgn?*3_ZE_!rKC2O?3CXIi)X0w z-VKF@p>MxV|9Yb?8{T!>6)LOL(p9C?vFvYIx!R3l8(vUv9yYvj-_4utF}0;`?8eA; zS-g42y4fBcJG{A!b>r{Wo2@N?_QrX|n;{G`l29qB`f)T2z%56zkLzznJj-Z4iHKn7 z6pajza*j9DPpbijnGsyc_=|FM4#1H-1Mh?+f(GCpb1C^8T$$s3Rma`Vr5V&e!F>{b zt6Y4o-qYKl@`Q~ssrI{#cSq&6KH<|Tjn^Qp0XsH`y&}5gDHC8lT?M!K?ytY6)|sm-yI7m-qVH*a)!}iu*xU_IeO2Mq7w1W{x@^JOp*yVz u4oPHp0d%hN`}CMP58Yvmyk(DZV04>E@`&bw@f63N-{60~0sz$x82|vk9z3!D literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/fluids/hose_pulley_transfer.nbt b/src/main/resources/data/create/structures/gametest/fluids/hose_pulley_transfer.nbt new file mode 100644 index 0000000000000000000000000000000000000000..f42e51fe69c5b98e3404ae8c7c137e096db2b8c7 GIT binary patch literal 3467 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_out.nbt b/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_out.nbt new file mode 100644 index 0000000000000000000000000000000000000000..cfa909345d2c68f3f4ddd38ee3bf207e3030ea50 GIT binary patch literal 853 zcmV-b1FHNViwFP!00000|Gkz$Pt-sZ$6vQQ%WfAU2Q~4cC%*&j+6K78zJeYXUlOMtp@uu-(cdBjf>f8T+CME zVzwF=bF&7s)wr0g#>H$kE@rE7F|!)XR^wu}8W*$GxR|Zxx*GH*e0f)#F>iu-n#Lc~ z1mLH8ngQF&5Th?=W^R6+eyU>PX%>;v3!sTyX=-ThKS&Hm?MR;kYISgQxWob zHyb2bK&Ip%52>$v&q#ci#^*FH()fbLJ6Dy(p~v6#OcV0J<9koelJw#zbAfbvnR`sWPzv1Z!yxRjVgrjd_Vbmk@B6 z^L@R*MeYGxk%Lqly7^V1_+CuAp2@;{Ri(4#@<&N2G}6-VHc!i2wh;h(Oy?`M8?bb0 z>Z-NP^FHbarzTR?law8g6gxZ~pqV7JuCcKqQ%zTR8#=vsAfk1iAl$gX98z*}A6gc` z|7lh_92T8p(qWa0Iu0?(^vNZMWz5x<)C~GxF#LCi<%yZC4*VWhBBd(dphmJE%VCw5 zxx(DoW1>W%m*ND15JV1;mt&9*NIZQ{Z#tz5SC1cdKffj=} f!re0Av@NS#^BuaSgiPr^`vAWIBT%)Z_6q<2sZpW3 literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/fluids/steam_engine.nbt b/src/main/resources/data/create/structures/gametest/fluids/steam_engine.nbt new file mode 100644 index 0000000000000000000000000000000000000000..4edf9e7bd5db6f09a2baf4f2a9a99aca3835199e GIT binary patch literal 1594 zcmZw9c{mdc0LO7hBr7eVWsg{6bIdY^wD5WfHPnzoZZVM#mfUC3R@(NyntKQ9*nXBib&?GV8+49m!gRMV40y5Oe{t2WGL&f;d&ja@r@iC3`HnK-ax=YI#e!Vi$y=w8we1T+l6L5TFFTZxRfum9ei+LFxPVDuSwIDodZ^RhdxxE)A6aN*3;l z!)+oa(Ea0{`%`siK4pVcV>%!F`~5bescQ6}^hftt=BM-rRIB*I`U$$^{y%tcSn=?U zICR4c1-}Nhvm5(&dR_=9nUFn5@!R3aEJ^HE zkANR8Nm!cEK-`cC{uk!h2{F<>Cd_3QOfi<= zA^%ugF9Mn6Xo7_h-JJr$iF!?+UV5}*+pddNa0}yu!Rr0vQEu{}TZf`Xd!VDz=iPV8 zy6Vag)Pd~YIo`3&Ux#F_8E2vmeDhrqHMxVry}KUdfKFLIdC_r0)%t+vEXicTfBjtE zJBo2=dp%Vv@FslY-IB@X$11=r-{K)VlJPy57i>6~40ouo1N)S5?5bc#oeE2AjXQKW zYHUJjAQOR~Q(qYjfceC2ayE`y-Og(I#%gFDpy*msqA|zm;LeoyO3ARfAS}Dd0qiw$ zl&A#F@h_+PR=T37e9pu4$P;O28R=_sCuk3x$L4>%SXqdS5GGTR^)B%Xj$*55LU7#(Fj9t(LQQeAae zqD{1|$O`LBNGJ4#e3wi_C>lxDu0x_K;SvrQ%!aexd%iw4%T z3wIYxy{xDA<#m~T;Dvk8g>fvM@Ckv+xI0D=2ccs^`LkCTlkrYtv?Web&Wh7q4lPBv zQ2wP+y*+>q^o!^^A$GAVG3`~Gz)X_QbmP&H4T3ad`*hamm1?52RYiL6f<{)lIpjsb zCFiaoTyi4NG(g9)by|d8s({`zDn;<(``ota5CBKkm1eTf?E}B|izsq%o(JezcpH4x zI8@Ua{nf(pKvL8fQPT0-nXK$6Mk%V8dic$F ScvEt^L`u0MR>2=7CG|I3tP`#P literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/items/andesite_tunnel_split.nbt b/src/main/resources/data/create/structures/gametest/items/andesite_tunnel_split.nbt new file mode 100644 index 0000000000000000000000000000000000000000..ab4e2beafd62635ec547bc180917ea8cb46c5b36 GIT binary patch literal 1260 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 AI{*Lx literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/items/belt_coaster.nbt b/src/main/resources/data/create/structures/gametest/items/belt_coaster.nbt new file mode 100644 index 0000000000000000000000000000000000000000..df3cb2642aea36757daca5824b4cdf0ef1fa6859 GIT binary patch literal 3459 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 UwN7R0hZfLV!@k|X1@iIz2apVvIRF3v literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/items/brass_tunnel_filtering.nbt b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_filtering.nbt new file mode 100644 index 0000000000000000000000000000000000000000..c877ddd0aa2ae5c48689090ddbb76b45d01afff8 GIT binary patch literal 1650 zcmZ{idr%UH7KgEXP?)*bLTFVQe6T9I7(TKTHIWS^+fvJSv_4pTl_sl^s6nfhMk=Og zEo4@T`K+`8Wo1)P#5QGpgqWEkH?f<#KCn%_&EAFCQ1AOsi@^%od9^uPRHS@Qx7`ak=heb(QFaXbGujGYkxtI1DALPqEdNXP_z87;vki^d?QhaU}-){C$TTUp&6fF8WEM$;rO3tLUtqUSD6E%rZiLc;1X9WeNyr*(3F z70ILEqH@ck4;~ZBI>!;;j7I>@+?2iEI_Oe-s;wt)>yb)zUXRKBELb%{J}~ac}9_w6ZH6wJisM1a>XEOz^vHEC8fpq;529`wa%~z znO!V19X#f}Mwn!uOgBw+N;|ckQcpGC%4)UVTSLLJlepZ1=m9!~r!tn71GG09L9I4{QBg^coJUcQJJj z9@W}4t>%1B{(_FL05 z=st@@3(YH_=JfpBLJCAMF-D@@@<>V<*{FFo%AqG?T*va?Tj(dka-DvPVJi%X1U2No zxfyEI$Ouy3u|+?z>9YW^m$o67bj0afh=T?Y`LRx60vD}zek2)r9^Jj~0S^80k?D&iBZ^P84)MRVnE_+w2Dt?5S!%UX7 zX=|^z%`Q$omp8zX2=id{C2MyQLUlB#A%6^`U80njs1rP^u1&B~3ZssAu?L(-u7Axd z`Z4cNqv!9?{voaprw-AZDe_C}M&sqw(iv#tx=IWUpXhm$;nXa7s2zUZra#L6HUleF zpSx!yYZ^WVP7zJ(SFTT{@UBm I3jqN98|nihcK`qY literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/items/brass_tunnel_prefer_nearest.nbt b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_prefer_nearest.nbt new file mode 100644 index 0000000000000000000000000000000000000000..bdbbacd9e4e2262b3f8a723117bdd57e9314e991 GIT binary patch literal 1371 zcmV-h1*G~PiwFP!00000|LvLWZre5#hL2=Pu@$@Bunz06J1J0LZQ1|~)y%t~WoRvHtt(wLZC3udJ;F)NLUS!qnnN@HSPKMxCyiCJk(%t~WoRvHttXThvA zCT68EF)NLUS!qnnz6G<=n3$Eu#H=(XW~DJP2NujqV`5es6SLBon3cxFT(Y#79gW7Z z(6|r7V!KG19~7WLyj$YOde`>CjQYpQdaiXjAh zBu}zAc!x)a??VXh`>8RfEzBQvm`x}78jZ)qAhq>gZ%X1H!6Trh>}zdd;~op!~0 zvh`R(7#(DnSYblUK?(tDf&}|XD&)pqC8b{IC-;$)L~aY?O7>JPo>#CmJ|%okMYUe|@Ioqi za$bmpWyhS&Reg@z%k&0&h^uv{E8G$6G&^(P8Ezw(B0=MmWWEp%3aU1x+1zr~ip$#f z@i~V3Uc7(N3(kW5l&o@t<4_;xBtbaBeonWE<5EU9qYT&JsiOq*vl z$<#F&Md>QUmr2;dKj!o@VMV^){57^|PMOJ9+x(Ah<9tfnxL>es;Hz!ERND+TY}2jj zJ7D$|14?5m29(BB%6T2kI!)hUrH|RSVD>GTeG6vaf;q5Y4lI}h3+BLrInXfgtVl|Q zpmLr+8{#3BEUB($t!{2jnh;l?73K1Of~c91h=M;$8RJ9jon6VL3p~LKif3iKTfS+@ zS%If()n%g)PU$7x>QvM0d-cLoUhv3#R=t94HEuI_&|Pd!AiOyW4D zPaTFaQBM-Z<_KGvdRP?d$BlOc?U8q{@D@+me2zX+!>?VLF)kL^kGR1B1BsZ@E^k+d zydHb{`GR0KBTfY;a*JtYZ{6`vH88;)BToc9li{4zC#BXPb*Dqz2Z`Jt*R$Xny61eg dS;;#39TDUs76Mph;PLZM_y-{=f~*}N003Ejo*V!G literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/items/brass_tunnel_round_robin.nbt b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_round_robin.nbt new file mode 100644 index 0000000000000000000000000000000000000000..310e8b882a89b2e74f572f201557e2801898b526 GIT binary patch literal 1556 zcmY+Cdpr{g6vtIqT!ci%bV>Ti!aNosQC_ztv=*A@9>|#Drm09MVYa3*73z~BuP~4D z+H^JZSk$B!Ml0F_X@o7;O?^JMPv`SF=X1{Y{LcC3oNd_~<$l=e^bKC@JE#`RG~hIP zXY5`a$WrX}w$&);qMREL>{d5z5^DTbyCPH3ZRcV9jaDaCa2UQe>>+n%OFk%YZx-%q z%6q#*0Gs`Pc1b<|J-g)V=<$q9UZ<2b?>__Oexmqs<}dAcq`&v~@kJ2L7ZvVKysB$!f*qpKn)QFLw*%Jb|B^|CK4p5@^RsF| zo(ngAP+pNZwYHx=#T5T-nOI~B#WL~;o=M9ORJ_hQqQJ|dEauJ=S75<$b5P^w21U>9 zs0D!I6+v+-O7b@ERC_)VD7EtV*^mbj1u0}EfBc^XXNI$?*Q&oVLW`=`OhR?i#j2c1 zu~aG@$XtqYJrmw3_u-9h0Wvb~=7gyFYV>|H7xyimw>TI_(4|WQZYqx{i<={hnF|q* z_GO4H_OI57&LV?Yp&5-0FQ2*8b;myl@04$tW}uf3dQSvzjM?-hz5Qs+p^E7&;dP9a z(TY=G>2kXKm4|96TPYKVowrOTn$c23mN&hXZGm}tLNIH*G*^(s5hxM`l*aqAnCN4d z(Q)LG16F-cKgzWfh^xw*B%d;M$nT5k$BoueYLHY{qC#L%w*Fv_pd) z)w7$(LidC^9nu_KpQKk`sz?{mKXpQ!1(qugp->K=M47%p=R|JB7`k2cI zlq7M1C0L)7;!fO!EaNJ<5u?Bp_AS7$A{HX|Ep0nsvOF!I3_|tt1<<`w;RxrB3|-r% z(?iYQ{{)dM)y58_uGI}3}Q(w9FUgH~H3l>Kyk_jxz;aKui&8M`SkUAvwDDN=EK&D=6K~ zqgZrYh~!Cuys!D^-&NPOQ|mF?%OPw2k>av;nsd70C^Zo60AO-3)jlC~Ceg|n*)%+x zIWQB!allgx3oBoZUeEC_Voccakp~AI7(M}tN~JV<=KMsB z&@LK5(lHZ z+LrjPTi(={r&lw+K1XX^u;Mg3uGdXkJVn7SY~?J5w`h>@kc)xFhI$7qTLgyEh$spg z$L0E~!OxM2ur4UusUMB$nGFM0+cLP|CX4Z0_7ZyiSa+(h=z6?i*&wkky+)>Nf(nw= zop%pxg8Rcv6Rg!?$`zVys@+RaO30ia8;}jJ->i`A2MRJvgBQ7zKl)Kuh8-3`f@JTW pXR#*{Dyd2cg%MTldcF?)#U3Bn{s#xyU-2J8Hq3df%1q_t{sXYT0tEm7 literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/items/brass_tunnel_single_split.nbt b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_single_split.nbt new file mode 100644 index 0000000000000000000000000000000000000000..ec6eaf4e38066244c32acca40039a44207f153c9 GIT binary patch literal 1209 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/items/brass_tunnel_sync_input.nbt b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_sync_input.nbt new file mode 100644 index 0000000000000000000000000000000000000000..284d4e143faeaa380bc47b0f6ed718998f2dc4f5 GIT binary patch literal 1715 zcmYL|dpz6s9>xvFr4_A9N8)ZOQ`%P|k*;;Cp?c72sj<2wii8BMTS?P!ET>{3%czo| zSdo=nG!0s5X&mhcKXTl9q#A42RXT!_ik*cz>Dt%X_pkTsd40am^T+e~IM9rA{(L*j zbxT=uD9fSt`^ras-1~X6^_H)xsDu)oVDsrMNOV zZEU;lx=JfR%p1WD_a0l1`s-~7W`@A9Ca7nXnbg`n z>mE+Pd@x$N*yL{roGUZg6weTf zgjsnU#0o=__aa>blFg8)oA9G7DiAu}I@qxGjp~iVhWvYCNPJ@X+Z%&h6pUH8GciB5 zS~!GN6c(k z zW^j3z-;y{X3=M3_Pu2MXk$c!r4-iQWd?5f%eaUah?)tJl=Va3c20|aglYr&p!329T zw;oprQe`Nzc|;wH`qpC<)=+Pfs=4JCpW^KdpZO7A@z({?d4Bl3w7q-J#V5ctSx06?!Ytd}JxiKVFosgnGFLh>xuuyRFVUc-To(k%C@}!_9J_A^kRb`@ z`N?V{?Rm>SgOT*HO=4|bWL;qdMr{n3@b&43+PaZI^F&)$O{vWBeS4FOOpxyT%8UWm z5}6CEULSi+oMFb~nJ%dLl10qQRIK(Fcmh86r@iM*n(!p%k`Z89B6EUy>tm^6r8zS} zR~Ux5WDFeo7|ReV&6vI)WBp2GP?*LSn0GGgt@ioZ+i#skwro)KBih#&UT&C%W#%E= z%!|%FxE1Gy>cIzgD-zxyi@Rge*#rMX;2^sz|L7oRvo0`M2dDOLvN$WBK1@wL@1}^p z-raJWiGD*5C6$x)R&`$0zW%1E=EDB&U3bCC=j3Ym%Rx~fJ?2}~)5O0GvkbCptkC-H z9yc3CyjWwD@a?4(!2wBWZsl=@AZN-weA~OY-8|5y&#pn{YL6KWn#8*rR-}^0dc0(! z<(Ifwtxyar;X5juZdY-F02IJ%Z5Ts}4c-2XEzc)e#MMZ7XfnLM9cNS5i7gLm&EYRr z#&iT64yhpi=Sq})AAxNGtvBqb3m8d-{3^k^Uygq4&;5tqb#H9t*{B~E$A4RFxH~i4 w5s6NH6wv4vgP0;c@r|lY=hP`S6ZJg{N2ff@jgv{mw|8{XpF24f!gX~12a2de3jhEB literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/items/content_observer_counting.nbt b/src/main/resources/data/create/structures/gametest/items/content_observer_counting.nbt new file mode 100644 index 0000000000000000000000000000000000000000..61719d37359cf244bc72255ae2c6f715fd681dd9 GIT binary patch literal 933 zcmV;W16uqaiwFP!00000|J|5RZ__Xoz+YmgP1B8m#+b&jN5o;1IPHSa5YoiN5StKi zsod1LjMW5W7W9QJyg3Ix`> zG@A83eZu-Mp_||wjw8wvovn+@uUAi=e*bd$@Y|2@=VJt38si{{CUtee4!W@JGb#t$ z?FM`plJj;LM5i9qj^YvXsSC~l>CkWj?|0!*2K#X&b5$Nsy-tpv(vq1vQa@efULQp>*u}Bt8 zqO}U4d~Z<&qwB3*+S@;vm=dz)5;LAiSMvIsCz47pKap^jUIU!{zO#(=T{G+S=Gtsp`zMr(iq7R%oKwN*R^0RyQL^IlGXVd`wVduwxf&EV z0#ve6%If6)n{~?Q_2sNLmayJj#(L`(x|?4q^$JF}Qn10f1DDLCDn@r!!X?gCjLsRd zU0~7q&R*cGm__G1dx5iJ7M<@FIV(ozY?V5vD;B}p-iV8Ma7@p{zMR}=$$&0+vu|Pb zb4$q2)^6*`cI(20)i-3Uxt+|!d)YLNg0q0O#i&CooboktE0{&+r0X@hNp3s(+;+@! zvrBqX%*`(CNjamNN47zYP-%_I8QnZW2F}R985uZZ17~dDj18Qzfitdft`A8_g`mkB zui*lIMFzBd_g6Oyr*sdz*DRJBfAX&)SH_FSl+i9YJrZ(x!mag)pFHkFF%u_H93p=% zu8f5R8|6Y_O60piMXLI1z}!rpu9q}xjwQjuxZkhLRE87hLkc*gy_xK_H{8tw*OIP; z^fp2}Ea;#7rgXC#k2)c3%K>D-UKqzL*TFH(b+BYZLeP9p1bIst58?=5WgC70DfN8- HtrP$NQ47_{ literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/items/depot_display.nbt b/src/main/resources/data/create/structures/gametest/items/depot_display.nbt new file mode 100644 index 0000000000000000000000000000000000000000..f266d3e7eb5fa6be6a29bdee7bb88c75a2cfe5dc GIT binary patch literal 1526 zcmYk5dpr{g6vt(Yx!r7QWmz?uydT9T$>#mIc~ugwJH)&rA#AgYP#U+El()H4UUv(T zOWu#nb8eE?NFFhd)E0UmyDFc1JD>CUem>`X&hK;1AHO4$1QPicLQcj%v{BQ&?YT?R zdRYOCjiMLJv1Bpox%|!HrN4^u9;*hPch+#9vJQWkL+l~e%}dFyvXfzOjR0MN_~l8i zFh);&Q^NM(g*U`I0fR^br(%e5h(|P!!>iX>_Ewk&1c-E|@US8=?_Z*W{Kq{3Wb6Rd zmMA%M5zvx+TX1$<$IVvfaEIrm1M03jp5f7)MJXEum?3~>HZcScti$xwBLti_P)(!& zzmmmnBWP>T%??7pceiSjAE%UJ+x_v|s?#?nwzXSRFxhP_k{K+p|H2Xw)&E{muaVrM*4q@$s@qrfHzFo)w5l3oUd9%UkvYv6?U z&~xH=gKX=N5&)mxlTlx|EH%!H&Vc_Mqo19Rzh@(?J6hrzNt&)rt7;H_%BpJE(2Us} zV}CjF5SxC#kgR{@337*D-A&{}!kAc2%G%?>%KrI0`k z4oolid(wib{W}xH%?p-qiB_lbg~6zeUGk!?ZYYZB*NkPGVlLv*7~cuM{%Rs9HUY!` zq>g}tjreDPV6*RtzeP2<`qn3#=9Uxt1csX+D-1xdjUP&VbfQo3qiuJ1_}epfyA7*1 zL^notilqN^7Ett4W&xiqamSZEGbQw~S|)ai0uQC3Q13!ITBl2wQ1VXaA%0OHA(Tg7 zvl^Y&lo@T0aJx$iSe1)uj$%E4&@z-P0g*n;ZhQY?=gW@g_Eq}gixVLxivA#*EVlL) z6cB7!FJb0eBSmP8Yxl2_n$|#qo2O-oSRjoyA%J4_W7{qGKoDJ|8FrW`3IzMT*44Wi zVcYFDHkwmPkx8#u(5Ho=`WLP&NRRk*I>}rTx~A?Y^NvUzBwKqXc%OPP>%f;VxaymB z+DZ1xNM8zhD$Z- zLIqxj@i#Tnl3HCy}do#u{@Ex`y4Pxi!%(Mv7SUg+e)wQ@MlthYrdUq-> zfAl! zEj!`>`eG;DQM}peVlOdMW(!5qVu0NlwMtjFA75^>XrasN5*5pAF*BLeNuxaW27bu1 zrAP9Qo^%LqIxRccYkxr1to!^v`W7?`ZQi%jztHq$ChH;nB z@FN+BD)SP{?!3U(!Qmx_pD!|paH8=f@Yt|dCT>=x)K(=1l!(Z`f{5$Y literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/items/stockpile_switch.nbt b/src/main/resources/data/create/structures/gametest/items/stockpile_switch.nbt new file mode 100644 index 0000000000000000000000000000000000000000..65d268da8af1359dae76671dc6e51a73e606f4f3 GIT binary patch literal 504 zcmVK*7rrnLqO_D5*{jsG%P~HCp0XA)oZ3$EP}-&SUiHoBUmDWB_dcN zf(8BiztZp#4U5q52@M-$6(G!-a!Bt9>-~Ubz|{uc)yiP$WJNdS4n2*yz3xhkXr{X2 z6I~TB`>r&hW02n3Cqr!C+S^lWQn^r`-J550gqz^VI(JmO#OM@`$TGA)-fBVas ub*{`tm6B`-_~(;p(stTo`y(+9i?_IwB!8mw${K*hANU6j@^JTK2LJ$9i}8y9 literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/items/storages.nbt b/src/main/resources/data/create/structures/gametest/items/storages.nbt new file mode 100644 index 0000000000000000000000000000000000000000..f42e7a870fa3acc0859ac3992c23e34cd912d8cf GIT binary patch literal 1789 zcmYk3dpHvcAIGuh%v)$h9Cs!0$lNA7N4e!ya?r5l?vTs7jOCnAY$m;Xx87_nH4(XU z%q4}+3~iX#bz6ohOhYo4d2`KWE_uB<&r`qW^Zfq!J>Sb8KONHFq`uDno7{z4V^CG1 z;X<%h6m_;tD1jc0wSQJ)U#n}JQ8Kd8U1WUWgf0nqdA2`@N%^FEN1FL6t!jO=9*moA zz_=%~gu5A-mh1s<-`r zf8Z4lY?AzmNgw1e+aF*#9lv0P;<)rn+@lbFk&VvtIeGwXhJr zR_CY6NuF0TgSPtsZN2a?zOTb#wV;meW$xMWoReVQ zjjFsb^Vc5t_Ee(V^Pub1OK)eR#cMg+ zyMZ|EDj9}F%SpJGVmhkBgYR2W&?L;jJ6D>h*`|l8{AbwI?j-U^+3sAF-_TlGxy!~p zpK9lQTAqD;CgAo!iN977mB1qElsh{S?Y0Vu`?d3PzB);C0xL_?%xC5r9kRKZDJeFI z>5=AWs)A#JYq`Q(;o}qV8K-9MY1yxb`z*+2^OGhzm8-J=d7xR#Jvhh$IgE)KuFxj@ zi(|IcbaMDH`7Eo0z$7~4?nb7>`+GC!z|5p_Ti=BrvkJ}u;Qy~p7^&f6vFu%qz?lrd zS7%#rj+2o6UI3q&5P#Q_xV!|RZ)93YLULpcMwK~3oN07Xb9PhFy(Q1M5EAk)4wr>S{ zD!^(c(*3otPVk8+z^h!jYf)M)9JqG9kwO~%CnrPxS5r}lVGtPkDrA#i{pRyel%7$5UYWD$qO4Ux^uwGP#RW&HjJ!4NJP40bt-X@w_h-pqQuYKPv05Z zQ4o8`m16VXRqkkMKwFD9wlmiiP@4)U&;>7SzdIJk1_EiaK-z<=pHNiXvtS@-8r_rkN!E2$qY18)LN4`b{?W)zTU`S z=@ebWjvOB@A`CFq#8W93pw~-?f(4Eqq{0SLf$Ogm$STl4{!dY#m?~E`w@b%HNs_f5 zPYP7@cPVLpxf%0}+r$QvA{I zjY-VS6Aq-+hIv-`W!?A&;hxmws$i#f5a%#!O`=O~ylE|=7~=F1;4$MXUFOO|BA-{$No_4Mq&!w0`*0tY2e(}4!8JN5o#%D)-&(3C z`tFRK()*yndfTYnDtP`>Egv$~=h;ViiJ2XCMLTyq3h7Ops*EPJyeG@Wv6D@VLLb)~ zHSjCF&ox@)&Qxh2FA?;hoGG{LKAO5~kaBj_9X-aRZUWE3nZFLNqT?^ZZmM8?ppxB3 z0d*_lO;_?~MuK9u;Z6uK6=c}u*<+gT5ZyH5*q^w9h-}in*&nkeS0yjQDl3BbR-Ne_ zGLIAP54;O7dfp|Hd}y^U`8R9wgnd+?@1K3Cd8Q%)s-%Ve3n>otI{E?a*Y}wzF%4?A z7)piB;$Aae$d8MZ5`}EbtPd9)4>WSEK<_XV*RDzGb8S{zQVvd-jS0`$ zBwe&@E@j3ExinA17Pju@uF^QjIzM_kPtQ50&Uyd%zQ6bLe!jo=`~7_1_X|r~zGQKH z=*SNU>+^s%xe}FOX`07jXS6lXo0Ed!tIQmj4A~y;ifGKMja<=FS>{-|Np6B63i6E( zN;jslDDhEG6kAy(Z@Q`<<&zxoUVTtut+YFAy$w$+wwk$5YhtJZkDzB&fmS;b85Zrj33BaQ2-Uu#2$#d8@x&$)zD%1Vuw{Cn8fr5`$f)+ulhr zvPA?oyjyEW#j!;QF_u95paNLt_^!!z|I&pqpGCL>9wG2>`_TrVdTGL`lFm!+qFEYb2{nz$~;{>q- zG8W4tt6t1lInq^0$@9&bE3)#)1RvQfd^)SuBAtEz+MT2a5%cA7(}jj&%Qx|X5@7%m zuCTcd_e(U^^kqEl2~t1v!H1Kdh(>-u-u1%geyctKCkneCVA!6HYz|*GJYb>as5{al zRMp%IKPGtnwysfHLGs>(8ue%hxEUh%?+=#K1Qvr~XzlNgnU@yec-u{R@9vhxr`5Cs zuP*e6mn2kNOslFBcaEa`Jli~@80|cp&}xj|AH!$U_=s`6aixXcc*idw{g@^#K;PjSZ)tX4j}|( z6Q*vNy-p3eVaS3LQtivUnxosYZ>9^?2DGtB(b=1Es22^Z^TgQxUAt7ZJok)vg0=SB z^3Oh2jSvy#ypo=WB!cYdTF8hyBGf)kusOxt>BNOIxJ|JLs<*vk__K9+>#?#57{vEc zU1L@XZab@@U3-*PJm~|t;@AsqR<7reoSAq&O%FJhoyq$d>7ZYYd zGiKK;);9_)2qF3UDyPucLU$*6GM77mzPwJoOp7P#LV>Wsly2 zVHa@SGg?=MMeolow(pB7xiTL7oLyK;0(M>cCnKeoQaqTYG<@gI?R`d5&onvlh`}M# z-55C);|ekY9}-~&n4T@YJPa+R21I4+S^<90cc(LoOxHllFw4?)tPsSEP=7H6S4%_? zb9FfJKFZ+QBH)w)?ik?1jbihS!H0qiP6=9yD_Ot&mC?luotGCM;Tqaz@xn9rfN}ukaE!N*z2|VPNaz9%fCk{N zS-|CE2k2G*7}Wd@#>VrHm?ypMds#A#l6`nBRZb*N&7fiEd7(q~K%C&*&(_jD_!il8 zq%RP8v+Z0Gsm;x8+J^8%912Jgk$!5fzGzHTYU5OC58dG}y7dOki64Brm~IhuF3%}K;0x39hm8)v7A{si8V%0B5MGu({!gu65Y7_oNk$XJw5s(6YDW} zrB~-7*n~OpG<0EN3$~e^b_0az!~nuLfB<2@CV(*i3DWn)0{k!8zy9-CLyeU4gzNT2 z-s^K%RjHsf#!=n5E|m&wSMcHM)=<6zD~qGz618ZmHPEQ`g@^)aKq*X+AFK4KE0;{= z0=20WdH{s^-vGjxWi|nED)}l9^t}OQVr-ZC5RlFPqB7a^Hk(S%piLuEXx<IvxRJ&x#tkR`#o(%z2W3@@_*%Qf=}Lzt}frnySr z*nSy(b5qYf&gsuF@2?HJrqdC5>u0kcznFgD%#4+tuiofOOC{gBedDfD`E0+lN5p)u zrED{Md;N;a_R5`q!p`5}|L^0wLo@qL$qz2y81u~J`QKj{EAtCi%(!saMNaLTL1(*U z^$bo!<^DE8)irR3G2`+_##t%3dHnnk z@2!_U=M!)34v+X9U?1(b`^-Cz zwblFAtg(L7es}wu-Fpjv?_hsn-}qQP<5vxn@MN7kvA1uz+|=4`Y`pK5No3>|<>F00 zrseKSDV;0&J@5ba__u2Q)4y4reyhGGo#*Vy&v)ZbO}%UDxN+v0)t~llD&1Qzo2s~cw-n@WxqUw) z>(Tk%*!TWx^AEg<6WGjnSdT-Z%+W<;+nz7435Ls6nUcC4RT};Z74mhAfAG6O zRsgSyvxMmiwnd6PQX$M21>AU7HD*|R%HUYc7J3!LTLqE`1*uup21=hG8mKLlH}T4X zRZ@pmIEI?G1UIkBs;IEx2kU_(-zzzbr9cS>L?2pl)6KLcaC2`K$Ko{Us|MeIrl-x$ zJG}4avfCT-x4krbGdp(|?{6fbKwe>B+REA%_*%g2tXY;U$b_{(yKk>(W4mwLGb`5# z=+d)h3&92jm;lZ8)sX75LAME{W>w+?uv(x$X61%J%mA6p%b$JiFvwvxcWgj1hY!D= z*$#4}L?y^Ps9V7%fOQ@Q<|v>#RCl5}-Obbo;sl6OOF_Qf$_EUB%hwC`coyF_+4eVn z)#T33I{)j}{`B-ukiQiAvm)61+B5UhCEpdT&+MM=|6g8y)3JLt(^Bhh{Q8&rIxRZ) z)46Bc^=IdQe*58=O*Fs!kCV@KTJK(XHg&4l_qluPt8T{Jy!twKu6_EMTjyV%?p=Fp zzoL&%(R97r@%BZIUDMlN|G(^3{`qzPqr0CAiYA)9l;5_>`1@kZpSS<8-~Ac#|Gydo F0{{zrfiwUB literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/misc/shearing.nbt b/src/main/resources/data/create/structures/gametest/misc/shearing.nbt new file mode 100644 index 0000000000000000000000000000000000000000..2656d5a5bb6af8c5bde319507d74cb6bdad0e2f4 GIT binary patch literal 1078 zcmV-61j+j!iwFP!00000|HYWUZzDw%fZxu{I<|91!W{*oKmpMp0lFFq5+_zjM2=Vi zgZLqtu|2j|+1=Tj9iMffxq^;@KY;>@tDpc0DA1jtLxPqHiH3#(@gE#-c0EowyyKCO z4oj=`c)xk`y|=T{J~sgx!1Cx!PyjGTXwMC(i&RHC5_uEHK@(`&*G&j_6_3&;)Dw|N zg;x!~aWGYS_tqOxB67`>3@2dVW=m@o^{2WFL2AHuc`kOY0hB=Fx*SHMNT_m&8q`{e zoThsF#&=Ku`q9sCQ)nD#LUA3*)Bx+iy4}Mzer2~`e&?yj&vt(N_Rfdje4T85Q3v)K zPXt^!;ptE&TY1K3=~iE+(;V!zCzB`@1I0)B#j!|*iUu!?WE{&`Pw{jtVD7j5Q}*d^ zxQ3m!_3vqkyeSeaPcP4e7(xKeeICdA@vSHwV>uf8QVqngg)xmbTsll|PGg+P`?0_h zZ?^imRGCSuOc;u)Khlv(^LS6ncqnHkV`MLx$n6Nrnhr$C)eh4m6fgq@q5qOaR{{o!DBsX71ap$ zWGs~_+L0J(BP@mi!I+T>o(rIUoW7#4D8m4^;EB}5-7oUk{p0uHeB+o|hgu}%#xg%` zwiNEi2U2G?b$c{`c4S`I-n(|Yu-kd-UU}}m4|d<0P>b-aEHvB4YTdsdz~w!jcA~*) zH#?G2V;*a-PXsrWBv3aGLU}VVP`w|^!6`0ipDt1q0MF==%{_KlS*7j2%CjAt;uiA@ ziC4=RHqhcjA;hhr#eBvxCh%a02SYs==3oL3XUn*pE#q>wjLX?FE@$H5Y#EocWn9jd zaXDMY<-E8b9*oP`GA?J!xSTEHa<+TkZ8sfRQ5aHby4%)^;^I5Q7t=HbjLoYyiQ3$2BDHLcr5 z>4|~DUPNI3z_EYnVVfc1-hk((DYDGv^EQJ=3YyUD;48aOrq4u1@LU7aYAWimKEq%t zmg6zLT8ASZ=lElo%c-7NBuuemnk+7|8MdGE(PG7`=POEn+8dePXZgA|jQ_&((SKam wBj3gF|6TW=g)ZhllsZ4WbzOz_SeL?jn`{22X|v6L7VDqD-^u(jmt+wD09Gj#`2YX_ literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/processing/brass_mixing.nbt b/src/main/resources/data/create/structures/gametest/processing/brass_mixing.nbt new file mode 100644 index 0000000000000000000000000000000000000000..a10e5e46876093a671575928b528a5390ebc32d5 GIT binary patch literal 2154 zcma)+e>fA`AIG;At1H~Pm15ETQhHo8Z73?#(~2mRX`z-e8>5i&d*t!0?CVD~W~(OT zrthL{EI;Q**N}y<`8izL_RPrdRc7^_?yY<8f8X=R>zwC&&htL!bI$vGj7S>*U)z@% znkeiYXIpEjt|yiGkiZ=pUD6n$jqE+u!4!DJMsPq zr~Sle0d96E;faQCFd_&6GtMRw32cpEG_+QD`CXh%|7e(C~ny9)@nJV8}|uXB(rRZ`m_!zXCTbm?Xw?5^ z_J;11iskN=FJ<+7obW+Cc;?*8%Q|t})9=1Ot8+pXKrPO%S(Vh_;fH$!slq#1pTHPi z;^GHd{}{13|DY$C5%NPviof|~Rt(|eAZ4bQ+-FWV?C@=Ioi>d^K48tw$$sxYZsU7p zJztr_m6fk3yH=DxsZ@D18jUCeZqiAM^lQ2=5IfqIze9;uuFcl6gi&v*<1eN!J64>B zklAMBPz-T6n2%5X zZGS^eW}dBq@@0EK6E#n1&DqE?e;;PsG+;nQkEXamm5f1_G{R|WAbBiQxS>Y>{>LTTWcS_xZ}!8>t<9bs4Z#uq16OY6k7hEcbwcF+C&{*vF6}^ zyjYU_xpPW|4T$A(j8#bps;PGb9RImq_OD=;fyY~e3w^hQCmK|uH*&gRtcUVS$Y>); ze|SNH#rTYxg4jJ4QpzD4_BlmFk)zV=pusKSc3~m%X+IO>cMpMyUz|`PW`Jl%_9!JkD_3I5N za3Zq@?K+S4sP6F;RS5lI;`NdenDzcoNPO%Iq(<=kTmj&GmKS*XtvBi+L>dtf zw~*GeHAZtQ!J!RZ+5X(kgZFo=`ovg9K6B<)g)Ok7bI<=8kx1e?iq9Rd=p7l_`a?~X zK-|7q{5tzs|CUt~I=P%lT9TcS-X!wGsnIp3Ta8nAoa-QJxY@~0u-B*64s;XZT|Gi<#;Pj=eyIUEabn1HAQh<1>r~Hst|g> zB2x05^f-!}en0y*N&R{|6F}taV2YpJE~){H3QAcbV-1Gita zptjijHYpcW-=eJ*8SY5I0P*lFbS1@do_1tI<_u)YQtoi3RhwUk{JbZ$ukCvFRuuJJ z5qtUx!*!TL&sXqq6@K!D2CE_BQ62)fvulOaYiCcBL@XWaSDKx6(!@z?zdT;vCdx^vjG(2q(kezL-UUh^7WUX433u(-t1E685r_jec8YG)KZ+) zVwPB4v;u%NLMwpZpaYNq(BJSQS|=`lHg}RB1;s6l3Y|!qBCy4VmmyqTZGG?9$ox!@ z#5nzz`fA_K|MMs|(38C;c`of(pQ$2B>h1P?-(3rg>>9%-q{+kA!zV+PS=tT%eO=0+ zIym=Hb4|e)78$~J_k!(&JubHioiAkc1q=pA#Zi`BVq{U_iE&fsOLA;8 z-m~=6L9@zW_Rz5T^0fzM3F8&$~X)&&Jx0skvT(H->h!l%u_+RN!XXQ46C-0H)ru+`>HUd+H&~u&DFW?Sa u%OV8YEvI5)t?q4qHDnyJxu>RX#mu8U5+y*Ppd6)@Qa6RR@m$PN0N{TlEk(xw literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/processing/brass_mixing_2.nbt b/src/main/resources/data/create/structures/gametest/processing/brass_mixing_2.nbt new file mode 100644 index 0000000000000000000000000000000000000000..44de42dfea2bf58181979644850faef5d3bc52fc GIT binary patch literal 2846 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 GIr1;~(w`3i literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/processing/crushing_wheel_crafting.nbt b/src/main/resources/data/create/structures/gametest/processing/crushing_wheel_crafting.nbt new file mode 100644 index 0000000000000000000000000000000000000000..099e093a93f170665dd3b83372af89ffbaad555c GIT binary patch literal 2715 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/processing/sand_washing.nbt b/src/main/resources/data/create/structures/gametest/processing/sand_washing.nbt new file mode 100644 index 0000000000000000000000000000000000000000..75ade7053ccdf27359f701f6bc19068a02178478 GIT binary patch literal 1370 zcmY+>dpOez9LMo4L$a1qC!VH~Yns_w#_Acxd0kFNXFFMRzgY?xkPMdi*qoM z4$Z>R#HR4H_@SLl?qOz3x#XB+PS1IsQ%~RL_2>Ja&-2}zwrSmex7NZK4DUzpNG$hv ziM}6%SCen4edD|9BBwbl1#IWN&yF)=M1&b_dz`9jZ;Y>+-@zNKkawT(K2j@BO))-R ztzrTa@19#I&y_F;G4q=Eb3vy?O)2>fVU!Nz}hlTaWJ^s2aUM6 zsKqJi_DKtWrKzP!&*I}iMknZrBVh2vchK#u8-gFsEbc?lQU9a5MyRS%zawk{_{1L$ z2z0dDzoX%PYJs0y%+IZ?S^~AAC-QHBjP@gt%^*-PF|Jb(7QAch8srPhE{Wu_hEo^92j_#48o zJ1-nvYqnVLT2L<*VJ*1>b#DaA1XXunV#Ofi^#l~kEP zpFT)k75Ow-YB**}Y&&oc&;dOtZBiU4|F-6;zpuNkZ{qs~OC=>ry`T&pqwcWhOBBOO zTdv#+KApeq4>*5-3m0g;OU8Qp&cFO3Y>+rd7WT76P zwd``lrBk$g(^H+X`ge0sew1OWrRTe)pBxN=@VzGER)c)|;~{l#Ok#Af=yJ zR`gUvW|i?0rfnloep7Zd6Y4sQp&}X!2Ba*om2|M@UayjJSKPrk>2aXK)=wOb=GG16 zKS1A8bt%1aYPyot{{j|@^!V!9G|rxZ#CXd)*vaku)r{%d>GwsI_L5ndG`MNuvdYjo zweVP9FzzEnSm!$~X^GS^4$8WNAa`{tEg`L9+DdgHMiAy4BOHbduW#`C%1oqS~x?QLKp7-dDF>aV4~vv6P$X3?x@41fkMLj6QKr zYh-g?*D&n8=D+&vfMLeG0+gtb@#Y3Xl%u;#ptT@>uEy-#O32PRqAtg?+8QHyOp6$J z*M^R;9JR^h)sILN*IMwVmjg2>vtyS$DGTNnxAA%(?Odn{qLkL_EaAz~^XEp_Ph-bW zq=_dbo(zotytj!KmuX=!+$Z`wd5=kfnf&1>dr;43X8`aZD7!3eG1rB}D-)&%jA@h$ ztEzh%yzOOc&u{#q_X3tywB2p@tD&si_-EQ#A8LO?j1!vPHi9v#bxbL;71D5$ymeqm z--#;dXm(hzr;>~!;WFO6{%0L6PD`FK3^V2?>Ek{KU|45;*oEgB*B!KSXJ$)m0#8s} vUw;Ahs1bSGgLlmo8A(>1Xkrl1z literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/processing/stone_cobble_sand_crushing.nbt b/src/main/resources/data/create/structures/gametest/processing/stone_cobble_sand_crushing.nbt new file mode 100644 index 0000000000000000000000000000000000000000..d56e9423d7fb0a826f2d386fb36c16d6f4519f4a GIT binary patch literal 2508 zcmY*adpy(o8$Y(4;mi~#IxV+jmGg6FGR!4uq+U{vxs_w6xr|ss8*`mHCHy3e5uH$M zxviX`VQF(>A#z#Bahp03ne9ZzZ>rOI{l0&^U+>rRd7jVfd3~Pe{r+laDtz$-x_xZy`W;u>Cl=w!J!@yfnzf8T6~TB^|z$NpL8@;Dbi-CfQ5oBdG8_x@$& zNOtw0-s`=)UdOI0xxLc)8+~6no*36WUS#c}z1ILh$#}S>xhYKUjaHsN`p&G0vthpG ztjP0`vTwJ;rAA1fibnlvRgAeM8E)k&+8jBTV;05YJo(9F%1LCxIqS>K54R5-xCDag zg@PPsP!?LLdn}A`{Vb~EOb|FLv*`OT|-rE(+0e{PnW3AIbC z?Cm+wJ9dRwjW2_U89%(f8X6XTGbgjHQAu(q48L&sv2MOMSZ1ZIG7KcCAoy8;sa+M%IB zg#(#xGeOx3eDQ7!y^&-bmWHU+UJ;2=tEett`R#%RT!pDLyIB19Dm)x&S zn0KvOM<@D`NBi7QcwoGURmEwm_lIdt((JjHktT;T!Suh(OsqJN^UG<-Ulf`?5>-Ox zs`VuzTg}-#hL+pp-dp>^Fy2oPr;De<63j$a)l1Q*5rS7lqadYYl0~8LqD`-PzTc&n zn@7~MdbCe0yX@Rh0VhYmyptdRrjF@HXW^Vw_}qYTR9h3e;4EAhNO#EQSpM6#6bj&%b!;hS@{wepIHSxfOF`c4hs}qCL z!dU)0yKcI^!{qU6dSUj&lGiHCHa{O=h^BOk}(lM7u{eD&8tE3ij3}geo4A>sp|9Qtbdt7pxG3JMk49vFae) z7m#G}M{Mrphn7AV953kgJX12;7fD+td#AoQ7&Z3WRJNjB%L>Ji%T?1m%g}E*^}tPm zu3ygwLcK89B0VVIa+~nwQmu7NJdyV^Ma(y`lA`TA8s& zu(Ca5_$G{=Sc3D*IdLRyS*uNh!P|V@m{|?XG)wr@rn7K}wthbjyHr;VOb9sB4D4Yn z9(+3tC}_5+e?s25*imaW^sXbEmE3PrKM`Rbi*1NV)BK*!sm*nZa1&f)Dld@n^Ff%v z_i}GLg6J9+KBdjD!h=Ia?coehUkBr%KUo@ov4^}$$O|==tznHK;lL~o8kkVLT5lnz zT`+joO-=mpa0r9m>f0Lp92@OTEf2QqHw$I2|m=? zo}k~3(-4(ZYh6?30lj)ZlS48$86wF&R)R5xJ7v5yBbcw1`q}CAw(Wi!WLo|t=zmoG zw$!;rxeNMmFm}^cF?vkGj(k4uJUM1pnSW!(W?q_Npz!l!eTd=53XVMuOgF4w$osTY zJE=fG%pt6@0{_QUYYTIeyI6K!K(p3nXHOhyWsdYAKH^r%bXj4Ypc8#z_z}}H@oo$w z5f(BLbm{JYE^EJ39>9_!TPGVr>viRrDBV#gjB&KXW1p_63kWyNhZ?9JrtGos$=#_d zTf}Ri>)SkKdlPdy_=^0JuaMSZsKEaM3*S@80NEc@qR6*T^;M}^ild9=Ff0z^@Q27T1ML&iay*n#}q3~LQ;b&Wn|%Wb?> zMO4O{zs80bJ<>Jqnb~Bd%SXlanvfXrKC^Jli>=(7!zDa2cQ&ke9$n$mr&n_lDD>v~ zpbh(%hJ&A)YkPOD{rA4IQ;qS9?sCU;9ru|BFFHTmVcn||{Zzv(Z@YF}>h4>$p2tA) zGiPbG^y!O9rE&NRbFh?xIW#1az`qx80}^X{+)x!8eM<>k-E#2o44Tpn{yjG1+0~@G zM@3A8ra6n>Xhx#`m7!&G@07>6@1_{8FZQ(16dN^oBx=@S40^oIGhSJgI*GqD6H(q> z;n8*Q10lw8)UE9U(ZhTx<44k>)AYNpS#-$(1M8OSskgdM>nYi{;A~Hgln@Z1rml^S z_F-98cGojh3bUa`Rd!``RQpO+@dnL!X&|a;{EtF>NZS62fND dUWrxhw29}b6FC=!l@Q)KH!Ye?X9@SWH00_ ziey^4U4FJXCqDEZtNH}V=4DJ9JL3x$(6^v)xf;7Hd;CnAO(`>&dCDOA?u&a0eJLa4^UiXEbcyLCQzP_XW1b3Or}#8$ zDf^`?%lPLY$^Omg(aTD5rjSQpL0n|QgHm;Yzm_W*u6ho`xp%$@Rfl zrdzHDxZP-)-%Zd{mNMj~Ks?T5m$y$#KJ#cm;^in%bfi+ka3 z0>Ko2(^xYg_*(-(HhtEAQy8`>EZW-W<#0F&*MaUkJ{-j8QG8vA;6A2hoQMqJLo|+s ziRE!d*ykH>23adVO}V+E(KZ1g(>K5aR5>VWPf~v9kK$#+kSK=*5j2W9sV5ZEEU+jc*;HC_jrGrCFjd zzfkslZB&YDYm})zVl-FQ85(z?aCUbK`5`fGh2j?_QXJXR;qWk9O-bL+ShYo`EFBC` z=&r>pWJ8Ghw@a_4z-1lJKv5|vJ3+NefhGM#7|j96y9+UA|i>Cz`LwV ztQINRvLF%@$GI;3og?AWo29wrgYp^LWG_zqu9yA$oezio5I;trd`7-ERL&}@acyqZ z!PE3E9pSJm8Wx+V*#7?N#qL$uqlYZl)}{^c~GcOHdd&=rgBe zP1A{=xmxJeSNU{L&N1u{?xf+>)s;s_d~M6(nQ_P=e1Bg1(_4v}qiyYJ2TCeqTMLf4 zp}Y?FLW=12K@QwyJ(-O=z>kO0?VX@6byOj&y$qN%%lI>3fgm7>t|fy70c8SRpR%eK zN6#8;WyqP*LtoFhDGIW!Hk#buIv~QXXiO}yNKz%p z;>|yPKMxH!sf^2$mL#9fwa~@_aCrc$08jml(*QOkl-^V=s@NL8%}-U&8BMJbOg_wM z6-=AAZi>y<>7sWr_QKhQo1LwwrjUUdy>&zENZOs$nOZwTPN$Xazt@VjSL2=EdU}Is z@*@qZ^+j5ut*>ewxt#N1?tDF(keQzH7&*{=k=I7)D%j$`;qeFfdc%EFi^FR#KsXpx zYX^H3rD)&$N^AGR?EE2dAkDwIeAwROdE6e;d=ls?$j%HEEJ_{>wb?#byU|3#aov&b=?Xg2UT}Q=)XV(jMzqoZ(if->~dV zesGA`vNraz?&Vwrwo&V1w11`3TX}Zk{HkM%6ih!0Lc8VASszF zQ~DhUHfae%>|61Ti8R`biBxEanS4!Pq5{NoNQn3h*ZS0om&Q@X11XkN;C_G#d`dO* z`SBP48o+@iN;*wde()ZNUD-^OnONeVC6MPtu$1`>sXQJ16||xzwK3q*zruERd2fV! z<(6Bs7x1NH7Y&u}ijsc~w=;_C<*-cqqS+o)AvtH(xcd^zxBXUrfK7k^+D6}CYgc$U=|Z4BV=(2xKjSXe4*9>B{-H~36V6=zu`%ce8nAj z1OMED^&GZr`0SM=cax62wyaXV;WIn*pkTkx|BGIEN;_Rg+eQa!;opkXza>1vp1v?y zWnFt~m52*=;QF4t!R#Oy^Vi`_dI_J?Wr!vI36;jI1yX97|d-Vr_YeDP-DlD+g0uj*T1eg$FZ&P^<;w{0Q?8%TOS7i literal 0 HcmV?d00001 diff --git a/src/main/resources/data/create/structures/gametest/processing/water_filling_bottle.nbt b/src/main/resources/data/create/structures/gametest/processing/water_filling_bottle.nbt new file mode 100644 index 0000000000000000000000000000000000000000..d2b527bdf19541a5142d5ef1c134ce64a2ed3408 GIT binary patch literal 1797 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 IhYS(`04c)v00000 literal 0 HcmV?d00001 From 3ad4195dd6865fb0994c37f9d95555aed35373e9 Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Fri, 12 May 2023 13:41:28 +0200 Subject: [PATCH 13/13] Squashed commit of the following: commit a162e18c9c4d91c4020e86cf718d59d3a33b2817 Merge: 374848f97 beb61708a Author: simibubi <31564874+simibubi@users.noreply.github.com> Date: Fri May 12 13:40:58 2023 +0200 Merge branch 'mc1.18/dev' into mc1.18/computercraft commit 374848f978cf0cc1d91ff0b98a8bd506f1b81130 Author: simibubi <31564874+simibubi@users.noreply.github.com> Date: Fri May 12 13:22:29 2023 +0200 Compilation dependency toggle commit b2cd60b6195bcbdacaa47c8b8a05929cb160fe5b Merge: 8e1e4e8bd ee3a079ba Author: simibubi <31564874+simibubi@users.noreply.github.com> Date: Wed May 10 14:37:32 2023 +0200 Merge pull request #4650 from ChristianLW/mc1.18/computercraft Small tweaks to the wiki pages for ComputerCraft integration commit ee3a079bacbee6fccdf14a356846e12d67edece6 Author: Christian L.W Date: Thu Apr 13 00:55:20 2023 +0200 Small tweaks to the wiki pages commit 8e1e4e8bd3beb310e4e114e7d36134caf1c7eb79 Author: caelwarner Date: Mon Mar 13 18:31:56 2023 -0700 Added computer to display source ponder tag - Added advanced computer to display source ponder tag - Added missing lang entry for computer display source commit 952941e5fc86036bf2c3555ead662cdb9ea9a29d Author: caelwarner Date: Mon Mar 13 16:31:16 2023 -0700 Added documentation for train station peripherals and train schedules - Added in depth documentation for working with train stations and train schedules in Lua - Fixed small formatting issues in Lua-Rotation-Speed-Controller.md and Lua-Sequenced-Gearshift.md commit 7f3ca1cfa06b227211a41503c4ae31c3cf854ba5 Author: caelwarner Date: Mon Mar 13 16:29:05 2023 -0700 Added isTrainEnroute to station peripheral API - isTrainEnroute checks if a train is currently navigating to the station - Reworded null station exception to "station is not connected to a track" - Refactored StationPeripheral#inAssemblyMode to StationPeripheral#isInAssemblyMode - Added a check to StationPeripheral#disassemble to make sure the station isn't in assembly mode commit fac1ebcd3f1d1d570735fa6b2d57641b20c4ad8d Author: caelwarner Date: Sat Mar 11 16:12:58 2023 -0800 Added documentation for most peripherals - Lua documentation has been added for all peripherals except the train station (not looking forward to writing that one) - This documentation will be added to the GitHub wiki pages commit 3e21996984febc8b256ac8abbb11df9dbe150630 Author: caelwarner Date: Sat Mar 11 15:54:36 2023 -0800 Updated DisplayLinkPeripheral#write to move cursor to the end of the text - This change was made to be more inline with ComputerCraft's builtin display API commit 7141c10025c948ce5a78f1d484964f4324ffb463 Author: caelwarner Date: Sat Mar 11 11:45:43 2023 -0800 Added isTrainImminent and hasSchedule to train station API - Added isTrainImminent to check if a train is incoming to the station and hasSchedule to check if the currently present train has a schedule - Added StationPeripheral#getTrainOrThrow to consolidate repetitive null checks commit 909484ed5b80ac1c397e04a1ad41acab6cc8e13a Author: caelwarner Date: Sat Mar 11 11:15:58 2023 -0800 Added getSchedule to train station lua API - Added getSchedule which serializes the currently present train's schedule into a lua table - Refactored StationPeripheral#setSchedule to use a more generic method of serializing NBT tags to lua tables - Moved schedule entry special data from root tag to "Data" - Added StringHelper#camelCaseToSnakeCase - Added variety of put methods to CreateLuaTable commit 31ad3aa671339e7642f1c89118a218ff7c7eec15 Author: caelwarner Date: Wed Mar 8 18:22:23 2023 -0800 Extended train station peripheral API - Train station peripherals can now assemble and disassemble trains, check if the station is in assembly mode, set the assembly mode of the station, get and change the station name, check if a train is present at the station and get and change the currently present train name. - Refactored StationEditPacket. Moved most of the logic that was previously in StationEditPacket to StationTileEntity. This allows us to call this logic without having to send a packet. - Made Train#owner nullable. This is needed so that computers can assemble trains. All Train#owner is currently used for is to display the train status to the correct play. commit 574cd93a89d49d8ac50b865b9b7f83955fa16ffc Author: caelwarner Date: Wed Nov 30 00:37:47 2022 -0800 Serialize hasAttachedComputer in ComputerBehaviour - This eliminates some edge cases were peripherals don't realize they're being controlled by a computer on a world save and load commit 94e3ed44ad1deaa383c8da61928f67697dc13273 Author: caelwarner Date: Wed Oct 26 16:57:12 2022 -0700 Added ComputerScreen - ComputerScreen shows that tile entity currently has computers attached and therefore cannot be controlled manually commit 9afdcaded7006c1c2671cd3070185d500c3a3dca Author: caelwarner Date: Thu Oct 20 10:18:37 2022 -0700 Refactored PeripheralBase to SyncedPeripheral commit 7d47fdcd061ca8efaab5688ac2724fec3e0bf1b7 Author: caelwarner Date: Wed Oct 19 22:45:47 2022 -0700 Made LuaFunction's final commit 56a1210fff386fd538733a0a0998dd1bc0bd060f Author: caelwarner Date: Wed Oct 19 22:39:38 2022 -0700 Created ComputerBehaviour behaviour - ComputerBehaviour replaces ComputerControllable and SyncedComputerControllable commit 19d283b92376da793e7c88c443adbb3cd70b2c43 Author: caelwarner Date: Wed Oct 19 16:05:48 2022 -0700 Moved all peripheral classes to computercraft.peripherals package commit ab18034b985fe9cecf4de5add48557371789d756 Author: caelwarner Date: Wed Oct 19 15:58:56 2022 -0700 Added Train Station as peripheral - Train station can set a new auto-schedule for the train currently at the station - Added CreateLuaTable to add helper functions for working with lua tables - Added StringHelper util class to convert snake case to camel case commit 1091f3227c806f09fb443320900f7563ed264655 Author: caelwarner Date: Thu Oct 6 21:11:24 2022 -0700 Changed Display Link Peripheral API - Changed the Display Link Peripheral API to be more in line with the Monitor API - Added write, setCursorPos, getCursorPos, getSize, isColor, isColour, clearLine - Removed void writeLine, setLine, writeLines, setLines commit 18bfb216b1bd5c1b6fab99370318536ccf27b069 Author: caelwarner Date: Thu Oct 6 02:50:41 2022 -0700 Changed method of checking if a computer attached - After talking with SquidDev from CC: Tweaked I've changed to monitoring IPeripheral#attach and IPeripheral#detach for changes in the number of computers connected to the network, then updating the client using AttachedComputerPacket - This works with wired full modems, wired cabled modems and directly connected computers - Added SyncedPeripheralBase and SyncedComputerControllable for TE's and peripherals that want to be aware of attached computers commit 96dc4db6dc6cbf6519725109dbaa69851dcb0dbb Author: caelwarner Date: Tue Oct 4 21:11:38 2022 -0700 Sequenced Gearshift screen "greys out" when being controlled by a computer - This is to stop players from trying to using both the builtin sequencing and a computer to control the Sequenced Gearshift at the same time, leading to undefined behaviour - The "greyed out" screen should have a message added explaining why it's greyed out. - Added ComputerControllable#isComputerControlled to check if a tile entity is connected to a modem commit 9a807814013e54f59230d7f57f4c212164311e64 Author: caelwarner Date: Tue Oct 4 19:36:08 2022 -0700 Added PeripheralBase commit d404f073196f43eff9c0c66485693b5aed68051a Author: caelwarner Date: Mon Oct 3 20:46:16 2022 -0700 Added invalidateCaps - Changed setRemoved to invalidateCaps. I don't know why I wasn't just using invalidateCaps from the beginning commit 654476d9f33b78eb05d620ed8dd318cc9244b670 Author: caelwarner Date: Mon Oct 3 20:05:25 2022 -0700 Added Rotation Speed Controller and Sequenced Gearshift as peripherals - Rotation Speed Controller can get and set targetSpeed - Sequenced Gearshift can rotate by a certain angle and move a certain distance commit 1420406ab72204a8e34043c64404a16990104d23 Author: caelwarner Date: Mon Oct 3 16:38:12 2022 -0700 Added Speedometer and Stressometer as peripherals - Speedometer can get current speed - Stressometer can get current stress level as well as network stress capacity - Made GaugeTileEntity abstract commit 47b8619d07c9f58e6c3a34a9db97aea53071b9bd Author: caelwarner Date: Mon Oct 3 16:17:05 2022 -0700 Refactored peripheralHandler to peripheral - peripheralHandler was the wrong name. It's just a peripheral. - Changed peripheral type from "cdl" to "Create_DisplayLink" - Added equals function to DisplayLinkPeripheral commit 6591c2d46efc74d91f9a0c3d3c2aa1679fe68790 Author: caelwarner Date: Mon Oct 3 14:29:04 2022 -0700 ComputerCraft integration for Display Links - CC computers can now control display links through a variety of functions - Added ComputerControllable interface to define a tile entity as controllable by CC computers - Added CC: Tweaked soft dependency --- build.gradle | 19 ++ gradle.properties | 4 + src/main/java/com/simibubi/create/Create.java | 2 + .../java/com/simibubi/create/compat/Mods.java | 9 + .../AbstractComputerBehaviour.java | 57 ++++ .../computercraft/AttachedComputerPacket.java | 37 +++ .../computercraft/ComputerCraftProxy.java | 30 ++ .../compat/computercraft/ComputerScreen.java | 96 +++++++ .../FallbackComputerBehaviour.java | 16 ++ .../implementation/ComputerBehaviour.java | 74 +++++ .../implementation/CreateLuaTable.java | 172 +++++++++++ .../peripherals/DisplayLinkPeripheral.java | 108 +++++++ .../SequencedGearshiftPeripheral.java | 54 ++++ .../SpeedControllerPeripheral.java | 35 +++ .../peripherals/SpeedGaugePeripheral.java | 26 ++ .../peripherals/StationPeripheral.java | 269 ++++++++++++++++++ .../peripherals/StressGaugePeripheral.java | 31 ++ .../peripherals/SyncedPeripheral.java | 50 ++++ .../advanced/SpeedControllerTileEntity.java | 29 +- .../ConfigureSequencedGearshiftPacket.java | 3 + .../advanced/sequencer/Instruction.java | 6 +- .../sequencer/InstructionSpeedModifiers.java | 8 + .../sequencer/SequencedGearshiftScreen.java | 33 ++- .../SequencedGearshiftTileEntity.java | 39 ++- .../relays/gauge/GaugeTileEntity.java | 3 +- .../relays/gauge/SpeedGaugeTileEntity.java | 32 +++ .../relays/gauge/StressGaugeTileEntity.java | 25 ++ .../block/display/AllDisplayBehaviours.java | 11 + .../block/display/DisplayLinkTileEntity.java | 47 ++- .../display/source/ComputerDisplaySource.java | 33 +++ .../block/display/source/DisplaySource.java | 4 + .../logistics/trains/entity/Train.java | 19 +- .../logistics/trains/entity/TrainPacket.java | 10 +- .../station/AbstractStationScreen.java | 20 +- .../edgePoint/station/StationEditPacket.java | 100 +------ .../edgePoint/station/StationScreen.java | 2 + .../edgePoint/station/StationTileEntity.java | 122 +++++++- .../management/schedule/IScheduleInput.java | 6 +- .../schedule/ScheduleDataEntry.java | 22 +- .../condition/FluidThresholdCondition.java | 5 +- .../condition/ItemThresholdCondition.java | 5 +- .../condition/RedstoneLinkCondition.java | 5 +- .../condition/ScheduleWaitCondition.java | 16 +- .../destination/ScheduleInstruction.java | 12 +- .../create/foundation/gui/AllGuiTextures.java | 7 +- .../foundation/networking/AllPackets.java | 3 +- .../ponder/content/PonderIndex.java | 10 + .../foundation/utility/StringHelper.java | 46 +++ .../assets/create/textures/gui/computer.png | Bin 0 -> 1016 bytes wiki/Lua-Display-Link.md | 88 ++++++ wiki/Lua-Rotation-Speed-Controller.md | 18 ++ wiki/Lua-Sequenced-Gearshift.md | 28 ++ wiki/Lua-Speedometer.md | 10 + wiki/Lua-Stressometer.md | 18 ++ wiki/Lua-Train-Schedule.md | 195 +++++++++++++ wiki/Lua-Train-Station.md | 179 ++++++++++++ wiki/README.md | 2 + 57 files changed, 2152 insertions(+), 158 deletions(-) create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/AbstractComputerBehaviour.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/AttachedComputerPacket.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/ComputerCraftProxy.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/ComputerScreen.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/FallbackComputerBehaviour.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/implementation/ComputerBehaviour.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/implementation/CreateLuaTable.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/DisplayLinkPeripheral.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SequencedGearshiftPeripheral.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedControllerPeripheral.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedGaugePeripheral.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StationPeripheral.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StressGaugePeripheral.java create mode 100644 src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SyncedPeripheral.java create mode 100644 src/main/java/com/simibubi/create/content/logistics/block/display/source/ComputerDisplaySource.java create mode 100644 src/main/java/com/simibubi/create/foundation/utility/StringHelper.java create mode 100644 src/main/resources/assets/create/textures/gui/computer.png create mode 100644 wiki/Lua-Display-Link.md create mode 100644 wiki/Lua-Rotation-Speed-Controller.md create mode 100644 wiki/Lua-Sequenced-Gearshift.md create mode 100644 wiki/Lua-Speedometer.md create mode 100644 wiki/Lua-Stressometer.md create mode 100644 wiki/Lua-Train-Schedule.md create mode 100644 wiki/Lua-Train-Station.md create mode 100644 wiki/README.md diff --git a/build.gradle b/build.gradle index 05c9bdb9a..1ec83e22f 100644 --- a/build.gradle +++ b/build.gradle @@ -147,6 +147,14 @@ repositories { includeGroup "maven.modrinth" } } + maven { + // Location of maven for CC: Tweaked + name = "squiddev" + url = "https://squiddev.cc/maven/" + content { + includeGroup "org.squiddev" + } + } } dependencies { @@ -174,6 +182,11 @@ dependencies { compileOnly fg.deobf("top.theillusivec4.curios:curios-forge:${curios_minecraft_version}-${curios_version}:api") runtimeOnly fg.deobf("top.theillusivec4.curios:curios-forge:${curios_minecraft_version}-${curios_version}") + if (cc_tweaked_enable.toBoolean()) { + compileOnly fg.deobf("org.squiddev:cc-tweaked-${cc_tweaked_minecraft_version}:${cc_tweaked_version}:api") + runtimeOnly fg.deobf("org.squiddev:cc-tweaked-${cc_tweaked_minecraft_version}:${cc_tweaked_version}") + } + // implementation fg.deobf("curse.maven:druidcraft-340991:3101903") // implementation fg.deobf("com.ferreusveritas.dynamictrees:DynamicTrees-1.16.5:0.10.0-Beta25") // runtimeOnly fg.deobf("vazkii.arl:AutoRegLib:1.4-35.69") @@ -190,6 +203,12 @@ dependencies { } } +sourceSets.main.java { + if (!cc_tweaked_enable.toBoolean()) { + exclude 'com/simibubi/create/compat/computercraft/implementation/**' + } +} + sourceSets.main.resources { srcDir 'src/generated/resources' exclude '.cache/' diff --git a/gradle.properties b/gradle.properties index da1c49bc1..1615ed75c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -27,6 +27,10 @@ jei_version = 9.7.0.209 curios_minecraft_version = 1.18.2 curios_version = 5.0.7.0 +cc_tweaked_enable = true +cc_tweaked_minecraft_version = 1.18.2 +cc_tweaked_version = 1.100.10 + # curseforge information projectId = 328085 curse_type = beta diff --git a/src/main/java/com/simibubi/create/Create.java b/src/main/java/com/simibubi/create/Create.java index 420c74a30..67acdc81b 100644 --- a/src/main/java/com/simibubi/create/Create.java +++ b/src/main/java/com/simibubi/create/Create.java @@ -9,6 +9,7 @@ import com.google.gson.GsonBuilder; import com.mojang.logging.LogUtils; import com.simibubi.create.api.behaviour.BlockSpoutingBehaviour; import com.simibubi.create.compat.Mods; +import com.simibubi.create.compat.computercraft.ComputerCraftProxy; import com.simibubi.create.compat.curios.Curios; import com.simibubi.create.content.CreateItemGroup; import com.simibubi.create.content.contraptions.TorquePropagator; @@ -130,6 +131,7 @@ public class Create { ContraptionMovementSetting.registerDefaults(); AllArmInteractionPointTypes.register(); BlockSpoutingBehaviour.registerDefaults(); + ComputerCraftProxy.register(); ForgeMod.enableMilkFluid(); CopperRegistries.inject(); diff --git a/src/main/java/com/simibubi/create/compat/Mods.java b/src/main/java/com/simibubi/create/compat/Mods.java index 55fe10952..36bd60e52 100644 --- a/src/main/java/com/simibubi/create/compat/Mods.java +++ b/src/main/java/com/simibubi/create/compat/Mods.java @@ -5,7 +5,10 @@ import java.util.function.Supplier; import com.simibubi.create.foundation.utility.Lang; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; import net.minecraftforge.fml.ModList; +import net.minecraftforge.registries.ForgeRegistries; /** * For compatibility with and without another mod present, we have to define load conditions of the specific code @@ -14,6 +17,8 @@ public enum Mods { DYNAMICTREES, TCONSTRUCT, CURIOS, + + COMPUTERCRAFT, STORAGEDRAWERS, XLPACKETS; @@ -51,4 +56,8 @@ public enum Mods { toExecute.get().run(); } } + + public Block getBlock(String id) { + return ForgeRegistries.BLOCKS.getValue(new ResourceLocation(asId(), id)); + } } diff --git a/src/main/java/com/simibubi/create/compat/computercraft/AbstractComputerBehaviour.java b/src/main/java/com/simibubi/create/compat/computercraft/AbstractComputerBehaviour.java new file mode 100644 index 000000000..a8620450b --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/AbstractComputerBehaviour.java @@ -0,0 +1,57 @@ +package com.simibubi.create.compat.computercraft; + +import com.simibubi.create.foundation.tileEntity.SmartTileEntity; +import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; +import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType; + +import net.minecraft.nbt.CompoundTag; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; + +public class AbstractComputerBehaviour extends TileEntityBehaviour { + + public static final BehaviourType TYPE = new BehaviourType<>(); + + boolean hasAttachedComputer; + + public AbstractComputerBehaviour(SmartTileEntity te) { + super(te); + this.hasAttachedComputer = false; + } + + @Override + public void read(CompoundTag nbt, boolean clientPacket) { + hasAttachedComputer = nbt.getBoolean("HasAttachedComputer"); + super.read(nbt, clientPacket); + } + + @Override + public void write(CompoundTag nbt, boolean clientPacket) { + nbt.putBoolean("HasAttachedComputer", hasAttachedComputer); + super.write(nbt, clientPacket); + } + + public boolean isPeripheralCap(Capability cap) { + return false; + } + + public LazyOptional getPeripheralCapability() { + return LazyOptional.empty(); + } + + public void removePeripheral() {} + + public void setHasAttachedComputer(boolean hasAttachedComputer) { + this.hasAttachedComputer = hasAttachedComputer; + } + + public boolean hasAttachedComputer() { + return hasAttachedComputer; + } + + @Override + public BehaviourType getType() { + return TYPE; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/AttachedComputerPacket.java b/src/main/java/com/simibubi/create/compat/computercraft/AttachedComputerPacket.java new file mode 100644 index 000000000..3181217fe --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/AttachedComputerPacket.java @@ -0,0 +1,37 @@ +package com.simibubi.create.compat.computercraft; + +import com.simibubi.create.foundation.networking.TileEntityDataPacket; +import com.simibubi.create.foundation.tileEntity.SmartTileEntity; +import com.simibubi.create.foundation.tileEntity.SyncedTileEntity; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; + +public class AttachedComputerPacket extends TileEntityDataPacket { + + private final boolean hasAttachedComputer; + + public AttachedComputerPacket(BlockPos tilePos, boolean hasAttachedComputer) { + super(tilePos); + this.hasAttachedComputer = hasAttachedComputer; + } + + public AttachedComputerPacket(FriendlyByteBuf buffer) { + super(buffer); + this.hasAttachedComputer = buffer.readBoolean(); + } + + @Override + protected void writeData(FriendlyByteBuf buffer) { + buffer.writeBoolean(hasAttachedComputer); + } + + @Override + protected void handlePacket(SyncedTileEntity tile) { + if (tile instanceof SmartTileEntity smartTile) { + smartTile.getBehaviour(AbstractComputerBehaviour.TYPE) + .setHasAttachedComputer(hasAttachedComputer); + } + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/ComputerCraftProxy.java b/src/main/java/com/simibubi/create/compat/computercraft/ComputerCraftProxy.java new file mode 100644 index 000000000..e424bfdad --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/ComputerCraftProxy.java @@ -0,0 +1,30 @@ +package com.simibubi.create.compat.computercraft; + +import java.util.function.Function; + +import com.simibubi.create.compat.Mods; +import com.simibubi.create.compat.computercraft.implementation.ComputerBehaviour; +import com.simibubi.create.foundation.tileEntity.SmartTileEntity; + +public class ComputerCraftProxy { + + public static void register() { + fallbackFactory = FallbackComputerBehaviour::new; + Mods.COMPUTERCRAFT.executeIfInstalled(() -> ComputerCraftProxy::registerWithDependency); + } + + private static void registerWithDependency() { + /* Comment if computercraft.implementation is not in the source set */ + computerFactory = ComputerBehaviour::new; + } + + private static Function fallbackFactory; + private static Function computerFactory; + + public static AbstractComputerBehaviour behaviour(SmartTileEntity ste) { + if (computerFactory == null) + return fallbackFactory.apply(ste); + return computerFactory.apply(ste); + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/ComputerScreen.java b/src/main/java/com/simibubi/create/compat/computercraft/ComputerScreen.java new file mode 100644 index 000000000..2d55a2554 --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/ComputerScreen.java @@ -0,0 +1,96 @@ +package com.simibubi.create.compat.computercraft; + +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.simibubi.create.compat.Mods; +import com.simibubi.create.foundation.gui.AbstractSimiScreen; +import com.simibubi.create.foundation.gui.AllGuiTextures; +import com.simibubi.create.foundation.gui.AllIcons; +import com.simibubi.create.foundation.gui.element.GuiGameElement; +import com.simibubi.create.foundation.gui.widget.AbstractSimiWidget; +import com.simibubi.create.foundation.gui.widget.ElementWidget; +import com.simibubi.create.foundation.gui.widget.IconButton; +import com.simibubi.create.foundation.utility.Lang; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public class ComputerScreen extends AbstractSimiScreen { + + private final AllGuiTextures background = AllGuiTextures.COMPUTER; + + private final Supplier displayTitle; + private final RenderWindowFunction additional; + private final Screen previousScreen; + private final Supplier hasAttachedComputer; + + private AbstractSimiWidget computerWidget; + private IconButton confirmButton; + + public ComputerScreen(Component title, @Nullable RenderWindowFunction additional, Screen previousScreen, Supplier hasAttachedComputer) { + this(title, () -> title, additional, previousScreen, hasAttachedComputer); + } + + public ComputerScreen(Component title, Supplier displayTitle, @Nullable RenderWindowFunction additional, Screen previousScreen, Supplier hasAttachedComputer) { + super(title); + this.displayTitle = displayTitle; + this.additional = additional; + this.previousScreen = previousScreen; + this.hasAttachedComputer = hasAttachedComputer; + } + + @Override + public void tick() { + if (!hasAttachedComputer.get()) + minecraft.setScreen(previousScreen); + + super.tick(); + } + + @Override + protected void init() { + setWindowSize(background.width, background.height); + super.init(); + + int x = guiLeft; + int y = guiTop; + + Mods.COMPUTERCRAFT.executeIfInstalled(() -> () -> { + computerWidget = new ElementWidget(x + 33, y + 38) + .showingElement(GuiGameElement.of(Mods.COMPUTERCRAFT.getBlock("computer_advanced"))); + computerWidget.getToolTip().add(Lang.translate("gui.attached_computer.hint").component()); + addRenderableWidget(computerWidget); + }); + + confirmButton = new IconButton(x + background.width - 33, y + background.height - 24, AllIcons.I_CONFIRM); + confirmButton.withCallback(this::onClose); + addRenderableWidget(confirmButton); + } + + + + @Override + protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) { + int x = guiLeft; + int y = guiTop; + + background.render(ms, x, y, this); + + font.draw(ms, displayTitle.get(), x + background.width / 2.0F - font.width(displayTitle.get()) / 2.0F, y + 4, 0x442000); + font.drawWordWrap(Lang.translate("gui.attached_computer.controlled").component(), x + 55, y + 32, 111, 0x7A7A7A); + + if (additional != null) + additional.render(ms, mouseX, mouseY, partialTicks, x, y, background); + } + + @FunctionalInterface + public interface RenderWindowFunction { + + void render(PoseStack ms, int mouseX, int mouseY, float partialTicks, int guiLeft, int guiTop, AllGuiTextures background); + + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/FallbackComputerBehaviour.java b/src/main/java/com/simibubi/create/compat/computercraft/FallbackComputerBehaviour.java new file mode 100644 index 000000000..2e1a3da77 --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/FallbackComputerBehaviour.java @@ -0,0 +1,16 @@ +package com.simibubi.create.compat.computercraft; + +import com.simibubi.create.foundation.tileEntity.SmartTileEntity; + +public class FallbackComputerBehaviour extends AbstractComputerBehaviour { + + public FallbackComputerBehaviour(SmartTileEntity te) { + super(te); + } + + @Override + public boolean hasAttachedComputer() { + return false; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/ComputerBehaviour.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/ComputerBehaviour.java new file mode 100644 index 000000000..dfe1f504d --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/ComputerBehaviour.java @@ -0,0 +1,74 @@ +package com.simibubi.create.compat.computercraft.implementation; + +import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour; +import com.simibubi.create.compat.computercraft.implementation.peripherals.DisplayLinkPeripheral; +import com.simibubi.create.compat.computercraft.implementation.peripherals.SequencedGearshiftPeripheral; +import com.simibubi.create.compat.computercraft.implementation.peripherals.SpeedControllerPeripheral; +import com.simibubi.create.compat.computercraft.implementation.peripherals.SpeedGaugePeripheral; +import com.simibubi.create.compat.computercraft.implementation.peripherals.StationPeripheral; +import com.simibubi.create.compat.computercraft.implementation.peripherals.StressGaugePeripheral; +import com.simibubi.create.content.contraptions.relays.advanced.SpeedControllerTileEntity; +import com.simibubi.create.content.contraptions.relays.advanced.sequencer.SequencedGearshiftTileEntity; +import com.simibubi.create.content.contraptions.relays.gauge.SpeedGaugeTileEntity; +import com.simibubi.create.content.contraptions.relays.gauge.StressGaugeTileEntity; +import com.simibubi.create.content.logistics.block.display.DisplayLinkTileEntity; +import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationTileEntity; +import com.simibubi.create.foundation.tileEntity.SmartTileEntity; + +import dan200.computercraft.api.peripheral.IPeripheral; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.CapabilityToken; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.common.util.NonNullSupplier; + +public class ComputerBehaviour extends AbstractComputerBehaviour { + + protected static final Capability PERIPHERAL_CAPABILITY = + CapabilityManager.get(new CapabilityToken<>() { + }); + LazyOptional peripheral; + NonNullSupplier peripheralSupplier; + + public ComputerBehaviour(SmartTileEntity te) { + super(te); + this.peripheralSupplier = getPeripheralFor(te); + } + + public static NonNullSupplier getPeripheralFor(SmartTileEntity te) { + if (te instanceof SpeedControllerTileEntity scte) + return () -> new SpeedControllerPeripheral(scte, scte.targetSpeed); + if (te instanceof DisplayLinkTileEntity dlte) + return () -> new DisplayLinkPeripheral(dlte); + if (te instanceof SequencedGearshiftTileEntity sgte) + return () -> new SequencedGearshiftPeripheral(sgte); + if (te instanceof SpeedGaugeTileEntity sgte) + return () -> new SpeedGaugePeripheral(sgte); + if (te instanceof StressGaugeTileEntity sgte) + return () -> new StressGaugePeripheral(sgte); + if (te instanceof StationTileEntity ste) + return () -> new StationPeripheral(ste); + + throw new IllegalArgumentException("No peripheral available for " + te.getType() + .getRegistryName()); + } + + @Override + public boolean isPeripheralCap(Capability cap) { + return cap == PERIPHERAL_CAPABILITY; + } + + @Override + public LazyOptional getPeripheralCapability() { + if (peripheral == null || !peripheral.isPresent()) + peripheral = LazyOptional.of(peripheralSupplier); + return peripheral.cast(); + } + + @Override + public void removePeripheral() { + if (peripheral != null) + peripheral.invalidate(); + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/CreateLuaTable.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/CreateLuaTable.java new file mode 100644 index 000000000..3c957274e --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/CreateLuaTable.java @@ -0,0 +1,172 @@ +package com.simibubi.create.compat.computercraft.implementation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.lua.LuaTable; +import dan200.computercraft.api.lua.LuaValues; + +public class CreateLuaTable implements LuaTable { + + private final Map map; + + public CreateLuaTable() { + this.map = new HashMap<>(); + } + + public CreateLuaTable(Map map) { + this.map = new HashMap<>(map); + } + + public boolean getBoolean(String key) throws LuaException { + Object value = get(key); + + if (!(value instanceof Boolean)) + throw LuaValues.badField(key, "boolean", LuaValues.getType(value)); + + return (Boolean) value; + } + + public String getString(String key) throws LuaException { + Object value = get(key); + + if (!(value instanceof String)) + throw LuaValues.badField(key, "string", LuaValues.getType(value)); + + return (String) value; + } + + public CreateLuaTable getTable(String key) throws LuaException { + Object value = get(key); + + if (!(value instanceof Map)) + throw LuaValues.badField(key, "table", LuaValues.getType(value)); + + return new CreateLuaTable((Map) value); + } + + public Optional getOptBoolean(String key) throws LuaException { + Object value = get(key); + + if (value == null) + return Optional.empty(); + + if (!(value instanceof Boolean)) + throw LuaValues.badField(key, "boolean", LuaValues.getType(value)); + + return Optional.of((Boolean) value); + } + + public Set stringKeySet() throws LuaException { + Set stringSet = new HashSet<>(); + + for (Object key : keySet()) { + if (!(key instanceof String)) + throw new LuaException("key " + key + " is not string (got " + LuaValues.getType(key) + ")"); + + stringSet.add((String) key); + } + + return Collections.unmodifiableSet(stringSet); + } + + public Collection tableValues() throws LuaException { + List tables = new ArrayList<>(); + + for (int i = 1; i <= size(); i++) { + Object value = get((double) i); + + if (!(value instanceof Map)) + throw new LuaException("value " + value + " is not table (got " + LuaValues.getType(value) + ")"); + + tables.add(new CreateLuaTable((Map) value)); + } + + return Collections.unmodifiableList(tables); + } + + public Map getMap() { + return map; + } + + @Nullable + @Override + public Object put(Object key, Object value) { + return map.put(key, value); + } + + public void putBoolean(String key, boolean value) { + map.put(key, value); + } + + public void putDouble(String key, double value) { + map.put(key, value); + } + + public void putString(String key, String value) { + map.put(key, value); + } + + public void putTable(String key, CreateLuaTable value) { + map.put(key, value); + } + + public void putTable(int i, CreateLuaTable value) { + map.put(i, value); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object o) { + return map.containsKey(o); + } + + @Override + public boolean containsValue(Object o) { + return map.containsValue(o); + } + + @Override + public Object get(Object o) { + return map.get(o); + } + + @NotNull + @Override + public Set keySet() { + return map.keySet(); + } + + @NotNull + @Override + public Collection values() { + return map.values(); + } + + @NotNull + @Override + public Set> entrySet() { + return map.entrySet(); + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/DisplayLinkPeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/DisplayLinkPeripheral.java new file mode 100644 index 000000000..0e9275227 --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/DisplayLinkPeripheral.java @@ -0,0 +1,108 @@ +package com.simibubi.create.compat.computercraft.implementation.peripherals; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.jetbrains.annotations.NotNull; + +import com.simibubi.create.content.logistics.block.display.DisplayLinkContext; +import com.simibubi.create.content.logistics.block.display.DisplayLinkTileEntity; +import com.simibubi.create.content.logistics.block.display.target.DisplayTargetStats; + +import dan200.computercraft.api.lua.LuaFunction; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; + +public class DisplayLinkPeripheral extends SyncedPeripheral { + + public static final String TAG_KEY = "ComputerSourceList"; + private final AtomicInteger cursorX = new AtomicInteger(); + private final AtomicInteger cursorY = new AtomicInteger(); + + public DisplayLinkPeripheral(DisplayLinkTileEntity tile) { + super(tile); + } + + @LuaFunction + public final void setCursorPos(int x, int y) { + cursorX.set(x - 1); + cursorY.set(y - 1); + } + + @LuaFunction + public final Object[] getCursorPos() { + return new Object[] {cursorX.get() + 1, cursorY.get() + 1}; + } + + @LuaFunction(mainThread = true) + public final Object[] getSize() { + DisplayTargetStats stats = tile.activeTarget.provideStats(new DisplayLinkContext(tile.getLevel(), tile)); + return new Object[]{stats.maxRows(), stats.maxColumns()}; + } + + @LuaFunction + public final boolean isColor() { + return false; + } + + @LuaFunction + public final boolean isColour() { + return false; + } + + @LuaFunction + public final void write(String text) { + ListTag tag = tile.getSourceConfig().getList(TAG_KEY, Tag.TAG_STRING); + + int x = cursorX.get(); + int y = cursorY.get(); + + for (int i = tag.size(); i <= y; i++) { + tag.add(StringTag.valueOf("")); + } + + StringBuilder builder = new StringBuilder(tag.getString(y)); + + builder.append(" ".repeat(Math.max(0, x - builder.length()))); + builder.replace(x, x + text.length(), text); + + tag.set(y, StringTag.valueOf(builder.toString())); + + synchronized (tile) { + tile.getSourceConfig().put(TAG_KEY, tag); + } + + cursorX.set(x + text.length()); + } + + @LuaFunction + public final void clearLine() { + ListTag tag = tile.getSourceConfig().getList(TAG_KEY, Tag.TAG_STRING); + + if (tag.size() > cursorY.get()) + tag.set(cursorY.get(), StringTag.valueOf("")); + + synchronized (tile) { + tile.getSourceConfig().put(TAG_KEY, tag); + } + } + + @LuaFunction + public final void clear() { + synchronized (tile) { + tile.getSourceConfig().put(TAG_KEY, new ListTag()); + } + } + + @LuaFunction(mainThread = true) + public final void update() { + tile.tickSource(); + } + + @NotNull + @Override + public String getType() { + return "Create_DisplayLink"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SequencedGearshiftPeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SequencedGearshiftPeripheral.java new file mode 100644 index 000000000..8addb181e --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SequencedGearshiftPeripheral.java @@ -0,0 +1,54 @@ +package com.simibubi.create.compat.computercraft.implementation.peripherals; + +import org.jetbrains.annotations.NotNull; + +import com.simibubi.create.content.contraptions.relays.advanced.sequencer.Instruction; +import com.simibubi.create.content.contraptions.relays.advanced.sequencer.InstructionSpeedModifiers; +import com.simibubi.create.content.contraptions.relays.advanced.sequencer.SequencedGearshiftTileEntity; +import com.simibubi.create.content.contraptions.relays.advanced.sequencer.SequencerInstructions; + +import dan200.computercraft.api.lua.IArguments; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.lua.LuaFunction; + +public class SequencedGearshiftPeripheral extends SyncedPeripheral { + + public SequencedGearshiftPeripheral(SequencedGearshiftTileEntity tile) { + super(tile); + } + + @LuaFunction(mainThread = true) + public final void rotate(IArguments arguments) throws LuaException { + runInstruction(arguments, SequencerInstructions.TURN_ANGLE); + } + + @LuaFunction(mainThread = true) + public final void move(IArguments arguments) throws LuaException { + runInstruction(arguments, SequencerInstructions.TURN_DISTANCE); + } + + @LuaFunction + public final boolean isRunning() { + return !this.tile.isIdle(); + } + + private void runInstruction(IArguments arguments, SequencerInstructions instructionType) throws LuaException { + int speedModifier = arguments.count() > 1 ? arguments.getInt(1) : 1; + this.tile.getInstructions().clear(); + + this.tile.getInstructions().add(new Instruction( + instructionType, + InstructionSpeedModifiers.getByModifier(speedModifier), + Math.abs(arguments.getInt(0)))); + this.tile.getInstructions().add(new Instruction(SequencerInstructions.END)); + + this.tile.run(0); + } + + @NotNull + @Override + public String getType() { + return "Create_SequencedGearshift"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedControllerPeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedControllerPeripheral.java new file mode 100644 index 000000000..2aba811f0 --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedControllerPeripheral.java @@ -0,0 +1,35 @@ +package com.simibubi.create.compat.computercraft.implementation.peripherals; + +import org.jetbrains.annotations.NotNull; + +import com.simibubi.create.content.contraptions.relays.advanced.SpeedControllerTileEntity; +import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollValueBehaviour; + +import dan200.computercraft.api.lua.LuaFunction; + +public class SpeedControllerPeripheral extends SyncedPeripheral { + + private final ScrollValueBehaviour targetSpeed; + + public SpeedControllerPeripheral(SpeedControllerTileEntity tile, ScrollValueBehaviour targetSpeed) { + super(tile); + this.targetSpeed = targetSpeed; + } + + @LuaFunction(mainThread = true) + public final void setTargetSpeed(int speed) { + this.targetSpeed.setValue(speed); + } + + @LuaFunction + public final float getTargetSpeed() { + return this.targetSpeed.getValue(); + } + + @NotNull + @Override + public String getType() { + return "Create_RotationSpeedController"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedGaugePeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedGaugePeripheral.java new file mode 100644 index 000000000..a13b02cd5 --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedGaugePeripheral.java @@ -0,0 +1,26 @@ +package com.simibubi.create.compat.computercraft.implementation.peripherals; + +import org.jetbrains.annotations.NotNull; + +import com.simibubi.create.content.contraptions.relays.gauge.SpeedGaugeTileEntity; + +import dan200.computercraft.api.lua.LuaFunction; + +public class SpeedGaugePeripheral extends SyncedPeripheral { + + public SpeedGaugePeripheral(SpeedGaugeTileEntity tile) { + super(tile); + } + + @LuaFunction + public final float getSpeed() { + return this.tile.getSpeed(); + } + + @NotNull + @Override + public String getType() { + return "Create_Speedometer"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StationPeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StationPeripheral.java new file mode 100644 index 000000000..3de8e979b --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StationPeripheral.java @@ -0,0 +1,269 @@ +package com.simibubi.create.compat.computercraft.implementation.peripherals; + +import java.util.Map; + +import javax.annotation.Nullable; + +import org.jetbrains.annotations.NotNull; + +import com.simibubi.create.compat.computercraft.implementation.CreateLuaTable; +import com.simibubi.create.content.logistics.trains.entity.Train; +import com.simibubi.create.content.logistics.trains.management.edgePoint.station.GlobalStation; +import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationTileEntity; +import com.simibubi.create.content.logistics.trains.management.edgePoint.station.TrainEditPacket; +import com.simibubi.create.content.logistics.trains.management.schedule.Schedule; +import com.simibubi.create.foundation.networking.AllPackets; +import com.simibubi.create.foundation.utility.Components; +import com.simibubi.create.foundation.utility.StringHelper; + +import dan200.computercraft.api.lua.IArguments; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.lua.LuaFunction; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.CollectionTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NumericTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import net.minecraftforge.network.PacketDistributor; + +public class StationPeripheral extends SyncedPeripheral { + + public StationPeripheral(StationTileEntity tile) { + super(tile); + } + + @LuaFunction(mainThread = true) + public final void assemble() throws LuaException { + if (!tile.isAssembling()) + throw new LuaException("station must be in assembly mode"); + + tile.assemble(null); + + if (tile.getStation() == null || tile.getStation().getPresentTrain() == null) + throw new LuaException("failed to assemble train"); + + if (!tile.exitAssemblyMode()) + throw new LuaException("failed to exit assembly mode"); + } + + @LuaFunction(mainThread = true) + public final void disassemble() throws LuaException { + if (tile.isAssembling()) + throw new LuaException("station must not be in assembly mode"); + + getTrainOrThrow(); + + if (!tile.enterAssemblyMode(null)) + throw new LuaException("could not disassemble train"); + } + + @LuaFunction(mainThread = true) + public final void setAssemblyMode(boolean assemblyMode) throws LuaException { + if (assemblyMode) { + if (!tile.enterAssemblyMode(null)) + throw new LuaException("failed to enter assembly mode"); + } else { + if (!tile.exitAssemblyMode()) + throw new LuaException("failed to exit assembly mode"); + } + } + + @LuaFunction + public final boolean isInAssemblyMode() { + return tile.isAssembling(); + } + + @LuaFunction + public final String getStationName() throws LuaException { + GlobalStation station = tile.getStation(); + if (station == null) + throw new LuaException("station is not connected to a track"); + + return station.name; + } + + @LuaFunction(mainThread = true) + public final void setStationName(String name) throws LuaException { + if (!tile.updateName(name)) + throw new LuaException("could not set station name"); + } + + @LuaFunction + public final boolean isTrainPresent() throws LuaException { + GlobalStation station = tile.getStation(); + if (station == null) + throw new LuaException("station is not connected to a track"); + + return station.getPresentTrain() != null; + } + + @LuaFunction + public final boolean isTrainImminent() throws LuaException { + GlobalStation station = tile.getStation(); + if (station == null) + throw new LuaException("station is not connected to a track"); + + return station.getImminentTrain() != null; + } + + @LuaFunction + public final boolean isTrainEnroute() throws LuaException { + GlobalStation station = tile.getStation(); + if (station == null) + throw new LuaException("station is not connected to a track"); + + return station.getNearestTrain() != null; + } + + @LuaFunction + public final String getTrainName() throws LuaException { + Train train = getTrainOrThrow(); + return train.name.getString(); + } + + @LuaFunction(mainThread = true) + public final void setTrainName(String name) throws LuaException { + Train train = getTrainOrThrow(); + train.name = Components.literal(name); + AllPackets.channel.send(PacketDistributor.ALL.noArg(), new TrainEditPacket.TrainEditReturnPacket(train.id, name, train.icon.getId())); + } + + @LuaFunction + public final boolean hasSchedule() throws LuaException { + Train train = getTrainOrThrow(); + return train.runtime.getSchedule() != null; + } + + @LuaFunction + public final CreateLuaTable getSchedule() throws LuaException { + Train train = getTrainOrThrow(); + + Schedule schedule = train.runtime.getSchedule(); + if (schedule == null) + throw new LuaException("train doesn't have a schedule"); + + return fromCompoundTag(schedule.write()); + } + + @LuaFunction(mainThread = true) + public final void setSchedule(IArguments arguments) throws LuaException { + Train train = getTrainOrThrow(); + Schedule schedule = Schedule.fromTag(toCompoundTag(new CreateLuaTable(arguments.getTable(0)))); + boolean autoSchedule = train.runtime.getSchedule() == null || train.runtime.isAutoSchedule; + train.runtime.setSchedule(schedule, autoSchedule); + } + + private @NotNull Train getTrainOrThrow() throws LuaException { + GlobalStation station = tile.getStation(); + if (station == null) + throw new LuaException("station is not connected to a track"); + + Train train = station.getPresentTrain(); + if (train == null) + throw new LuaException("there is no train present"); + + return train; + } + + private static @NotNull CreateLuaTable fromCompoundTag(CompoundTag tag) throws LuaException { + return (CreateLuaTable) fromNBTTag(null, tag); + } + + private static @NotNull Object fromNBTTag(@Nullable String key, Tag tag) throws LuaException { + byte type = tag.getId(); + + if (type == Tag.TAG_BYTE && key != null && key.equals("Count")) + return ((NumericTag) tag).getAsByte(); + else if (type == Tag.TAG_BYTE) + return ((NumericTag) tag).getAsByte() != 0; + else if (type == Tag.TAG_SHORT || type == Tag.TAG_INT || type == Tag.TAG_LONG) + return ((NumericTag) tag).getAsLong(); + else if (type == Tag.TAG_FLOAT || type == Tag.TAG_DOUBLE) + return ((NumericTag) tag).getAsDouble(); + else if (type == Tag.TAG_STRING) + return tag.getAsString(); + else if (type == Tag.TAG_LIST || type == Tag.TAG_BYTE_ARRAY || type == Tag.TAG_INT_ARRAY || type == Tag.TAG_LONG_ARRAY) { + CreateLuaTable list = new CreateLuaTable(); + CollectionTag listTag = (CollectionTag) tag; + + for (int i = 0; i < listTag.size(); i++) { + list.put(i + 1, fromNBTTag(null, listTag.get(i))); + } + + return list; + + } else if (type == Tag.TAG_COMPOUND) { + CreateLuaTable table = new CreateLuaTable(); + CompoundTag compoundTag = (CompoundTag) tag; + + for (String compoundKey : compoundTag.getAllKeys()) { + table.put( + StringHelper.camelCaseToSnakeCase(compoundKey), + fromNBTTag(compoundKey, compoundTag.get(compoundKey)) + ); + } + + return table; + } + + throw new LuaException("unknown tag type " + tag.getType().getName()); + } + + private static @NotNull CompoundTag toCompoundTag(CreateLuaTable table) throws LuaException { + return (CompoundTag) toNBTTag(null, table.getMap()); + } + + private static @NotNull Tag toNBTTag(@Nullable String key, Object value) throws LuaException { + if (value instanceof Boolean v) + return ByteTag.valueOf(v); + else if (value instanceof Byte || (key != null && key.equals("count"))) + return ByteTag.valueOf(((Number) value).byteValue()); + else if (value instanceof Number v) { + // If number is numerical integer + if (v.intValue() == v.doubleValue()) + return IntTag.valueOf(v.intValue()); + else + return DoubleTag.valueOf(v.doubleValue()); + + } else if (value instanceof String v) + return StringTag.valueOf(v); + else if (value instanceof Map v && v.containsKey(1.0)) { // List + ListTag list = new ListTag(); + for (Object o : v.values()) { + list.add(toNBTTag(null, o)); + } + + return list; + + } else if (value instanceof Map v) { // Table/Map + CompoundTag compound = new CompoundTag(); + for (Object objectKey : v.keySet()) { + if (!(objectKey instanceof String compoundKey)) + throw new LuaException("table key is not of type string"); + + compound.put( + // Items serialize their resource location as "id" and not as "Id". + // This check is needed to see if the 'i' should be left lowercase or not. + // Items store "count" in the same compound tag, so we can check for its presence to see if this is a serialized item + compoundKey.equals("id") && v.containsKey("count") ? "id" : StringHelper.snakeCaseToCamelCase(compoundKey), + toNBTTag(compoundKey, v.get(compoundKey)) + ); + } + + return compound; + } + + throw new LuaException("unknown object type " + value.getClass().getName()); + } + + @NotNull + @Override + public String getType() { + return "Create_Station"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StressGaugePeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StressGaugePeripheral.java new file mode 100644 index 000000000..b712bd6da --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StressGaugePeripheral.java @@ -0,0 +1,31 @@ +package com.simibubi.create.compat.computercraft.implementation.peripherals; + +import org.jetbrains.annotations.NotNull; + +import com.simibubi.create.content.contraptions.relays.gauge.StressGaugeTileEntity; + +import dan200.computercraft.api.lua.LuaFunction; + +public class StressGaugePeripheral extends SyncedPeripheral { + + public StressGaugePeripheral(StressGaugeTileEntity tile) { + super(tile); + } + + @LuaFunction + public final float getStress() { + return this.tile.getNetworkStress(); + } + + @LuaFunction + public final float getStressCapacity() { + return this.tile.getNetworkCapacity(); + } + + @NotNull + @Override + public String getType() { + return "Create_Stressometer"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SyncedPeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SyncedPeripheral.java new file mode 100644 index 000000000..a6371063d --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SyncedPeripheral.java @@ -0,0 +1,50 @@ +package com.simibubi.create.compat.computercraft.implementation.peripherals; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.simibubi.create.compat.computercraft.AttachedComputerPacket; +import com.simibubi.create.compat.computercraft.implementation.ComputerBehaviour; +import com.simibubi.create.foundation.networking.AllPackets; +import com.simibubi.create.foundation.tileEntity.SmartTileEntity; + +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import net.minecraftforge.network.PacketDistributor; + +public abstract class SyncedPeripheral implements IPeripheral { + + protected final T tile; + private final AtomicInteger computers = new AtomicInteger(); + + public SyncedPeripheral(T tile) { + this.tile = tile; + } + + @Override + public void attach(@NotNull IComputerAccess computer) { + computers.incrementAndGet(); + updateTile(); + } + + @Override + public void detach(@NotNull IComputerAccess computer) { + computers.decrementAndGet(); + updateTile(); + } + + private void updateTile() { + boolean hasAttachedComputer = computers.get() > 0; + + tile.getBehaviour(ComputerBehaviour.TYPE).setHasAttachedComputer(hasAttachedComputer); + AllPackets.channel.send(PacketDistributor.ALL.noArg(), new AttachedComputerPacket(tile.getBlockPos(), hasAttachedComputer)); + } + + @Override + public boolean equals(@Nullable IPeripheral other) { + return this == other; + } + +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerTileEntity.java index 9238ed52e..37385e92b 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerTileEntity.java @@ -2,6 +2,11 @@ package com.simibubi.create.content.contraptions.relays.advanced; import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour; +import com.simibubi.create.compat.computercraft.ComputerCraftProxy; import com.simibubi.create.content.contraptions.RotationPropagator; import com.simibubi.create.content.contraptions.base.KineticTileEntity; import com.simibubi.create.content.contraptions.components.motor.CreativeMotorTileEntity; @@ -20,11 +25,14 @@ import net.minecraft.core.Direction; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; public class SpeedControllerTileEntity extends KineticTileEntity { public static final int DEFAULT_SPEED = 16; - protected ScrollValueBehaviour targetSpeed; + public ScrollValueBehaviour targetSpeed; + public AbstractComputerBehaviour computerBehaviour; boolean hasBracket; @@ -53,7 +61,8 @@ public class SpeedControllerTileEntity extends KineticTileEntity { targetSpeed.withCallback(i -> this.updateTargetRotation()); targetSpeed.withStepFunction(CreativeMotorTileEntity::step); behaviours.add(targetSpeed); - + behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this)); + registerAwardables(behaviours, AllAdvancements.SPEED_CONTROLLER); } @@ -63,7 +72,7 @@ public class SpeedControllerTileEntity extends KineticTileEntity { RotationPropagator.handleRemoved(level, worldPosition, this); removeSource(); attachKinetics(); - + if (isCogwheelPresent() && getSpeed() != 0) award(AllAdvancements.SPEED_CONTROLLER); } @@ -127,6 +136,20 @@ public class SpeedControllerTileEntity extends KineticTileEntity { && stateAbove.getValue(CogWheelBlock.AXIS).isHorizontal(); } + @NotNull + @Override + public LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { + if (computerBehaviour.isPeripheralCap(cap)) + return computerBehaviour.getPeripheralCapability(); + return super.getCapability(cap, side); + } + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + computerBehaviour.removePeripheral(); + } + private class ControllerValueBoxTransform extends ValueBoxTransform.Sided { @Override diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/ConfigureSequencedGearshiftPacket.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/ConfigureSequencedGearshiftPacket.java index 75fb4ca84..fa77415c4 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/ConfigureSequencedGearshiftPacket.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/ConfigureSequencedGearshiftPacket.java @@ -35,6 +35,9 @@ public class ConfigureSequencedGearshiftPacket extends TileEntityConfigurationPa @Override protected void applySettings(SequencedGearshiftTileEntity te) { + if (te.computerBehaviour.hasAttachedComputer()) + return; + te.run(-1); te.instructions = Instruction.deserializeAll(instructions); te.sendData(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/Instruction.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/Instruction.java index 9335285d1..a7b91a04a 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/Instruction.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/Instruction.java @@ -19,8 +19,12 @@ public class Instruction { } public Instruction(SequencerInstructions instruction, int value) { + this(instruction, InstructionSpeedModifiers.FORWARD, value); + } + + public Instruction(SequencerInstructions instruction, InstructionSpeedModifiers speedModifier, int value) { this.instruction = instruction; - speedModifier = InstructionSpeedModifiers.FORWARD; + this.speedModifier = speedModifier; this.value = value; } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/InstructionSpeedModifiers.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/InstructionSpeedModifiers.java index 93713d075..b1a1f236a 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/InstructionSpeedModifiers.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/InstructionSpeedModifiers.java @@ -1,6 +1,7 @@ package com.simibubi.create.content.contraptions.relays.advanced.sequencer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import com.simibubi.create.foundation.utility.Components; @@ -36,4 +37,11 @@ public enum InstructionSpeedModifiers { return options; } + public static InstructionSpeedModifiers getByModifier(int modifier) { + return Arrays.stream(InstructionSpeedModifiers.values()) + .filter(speedModifier -> speedModifier.value == modifier) + .findAny() + .orElse(InstructionSpeedModifiers.FORWARD); + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftScreen.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftScreen.java index 7f2c86ab4..1cf8e7259 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftScreen.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftScreen.java @@ -4,6 +4,7 @@ import java.util.Vector; import com.mojang.blaze3d.vertex.PoseStack; import com.simibubi.create.AllBlocks; +import com.simibubi.create.compat.computercraft.ComputerScreen; import com.simibubi.create.foundation.gui.AbstractSimiScreen; import com.simibubi.create.foundation.gui.AllGuiTextures; import com.simibubi.create.foundation.gui.AllIcons; @@ -15,7 +16,6 @@ import com.simibubi.create.foundation.networking.AllPackets; import com.simibubi.create.foundation.utility.Components; import com.simibubi.create.foundation.utility.Lang; -import net.minecraft.core.BlockPos; import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; @@ -25,22 +25,26 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen { private final ItemStack renderedItem = AllBlocks.SEQUENCED_GEARSHIFT.asStack(); private final AllGuiTextures background = AllGuiTextures.SEQUENCER; private IconButton confirmButton; + private SequencedGearshiftTileEntity te; private ListTag compareTag; private Vector instructions; - private BlockPos pos; private Vector> inputs; public SequencedGearshiftScreen(SequencedGearshiftTileEntity te) { super(Lang.translateDirect("gui.sequenced_gearshift.title")); this.instructions = te.instructions; - this.pos = te.getBlockPos(); + this.te = te; compareTag = Instruction.serializeAll(instructions); } @Override protected void init() { + if (te.computerBehaviour.hasAttachedComputer()) + minecraft.setScreen(new ComputerScreen(title, this::renderAdditional, + this, te.computerBehaviour::hasAttachedComputer)); + setWindowSize(background.width, background.height); setWindowOffset(-20, 0); super.init(); @@ -127,6 +131,15 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen { modifier.setState(instruction.speedModifier.ordinal()); } + @Override + public void tick() { + super.tick(); + + if (te.computerBehaviour.hasAttachedComputer()) + minecraft.setScreen(new ComputerScreen(title, this::renderAdditional, + this, te.computerBehaviour::hasAttachedComputer)); + } + @Override protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) { int x = guiLeft; @@ -134,6 +147,13 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen { background.render(ms, x, y, this); + for (int row = 0; row < instructions.capacity(); row++) { + AllGuiTextures toDraw = AllGuiTextures.SEQUENCER_EMPTY; + int yOffset = toDraw.height * row; + + toDraw.render(ms, x, y + 14 + yOffset, this); + } + for (int row = 0; row < instructions.capacity(); row++) { AllGuiTextures toDraw = AllGuiTextures.SEQUENCER_EMPTY; int yOffset = toDraw.height * row; @@ -156,10 +176,13 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen { label(ms, 127, yOffset - 3, instruction.speedModifier.label); } + renderAdditional(ms, mouseX, mouseY, partialTicks, x, y, background); drawCenteredString(ms, font, title, x + (background.width - 8) / 2, y + 3, 0xFFFFFF); + } + private void renderAdditional(PoseStack ms, int mouseX, int mouseY, float partialTicks, int guiLeft, int guiTop, AllGuiTextures background) { GuiGameElement.of(renderedItem) - .at(x + background.width + 6, y + background.height - 56, -200) + .at(guiLeft + background.width + 6, guiTop + background.height - 56, 100) .scale(5) .render(ms); } @@ -172,7 +195,7 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen { ListTag serialized = Instruction.serializeAll(instructions); if (serialized.equals(compareTag)) return; - AllPackets.channel.sendToServer(new ConfigureSequencedGearshiftPacket(pos, serialized)); + AllPackets.channel.sendToServer(new ConfigureSequencedGearshiftPacket(te.getBlockPos(), serialized)); } @Override diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftTileEntity.java index 9c595af72..2fd02b095 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftTileEntity.java @@ -1,8 +1,15 @@ package com.simibubi.create.content.contraptions.relays.advanced.sequencer; +import java.util.List; import java.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour; +import com.simibubi.create.compat.computercraft.ComputerCraftProxy; import com.simibubi.create.content.contraptions.relays.encased.SplitShaftTileEntity; +import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -10,6 +17,8 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; public class SequencedGearshiftTileEntity extends SplitShaftTileEntity { @@ -20,6 +29,8 @@ public class SequencedGearshiftTileEntity extends SplitShaftTileEntity { int timer; boolean poweredPreviously; + public AbstractComputerBehaviour computerBehaviour; + public SequencedGearshiftTileEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); instructions = Instruction.createDefault(); @@ -30,6 +41,12 @@ public class SequencedGearshiftTileEntity extends SplitShaftTileEntity { poweredPreviously = false; } + @Override + public void addBehaviours(List behaviours) { + super.addBehaviours(behaviours); + behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this)); + } + @Override public void tick() { super.tick(); @@ -72,6 +89,8 @@ public class SequencedGearshiftTileEntity extends SplitShaftTileEntity { } public void onRedstoneUpdate(boolean isPowered, boolean isRunning) { + if (computerBehaviour.hasAttachedComputer()) + return; if (!poweredPreviously && isPowered) risingFlank(); poweredPreviously = isPowered; @@ -105,7 +124,7 @@ public class SequencedGearshiftTileEntity extends SplitShaftTileEntity { } } - protected void run(int instructionIndex) { + public void run(int instructionIndex) { Instruction instruction = getInstruction(instructionIndex); if (instruction == null || instruction.instruction == SequencerInstructions.END) { if (getModifier() != 0) @@ -156,6 +175,20 @@ public class SequencedGearshiftTileEntity extends SplitShaftTileEntity { super.read(compound, clientPacket); } + @NotNull + @Override + public LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { + if (computerBehaviour.isPeripheralCap(cap)) + return computerBehaviour.getPeripheralCapability(); + return super.getCapability(cap, side); + } + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + computerBehaviour.removePeripheral(); + } + @Override public float getRotationSpeedModifier(Direction face) { if (isVirtual()) @@ -171,4 +204,8 @@ public class SequencedGearshiftTileEntity extends SplitShaftTileEntity { .getSpeedModifier(); } + public Vector getInstructions() { + return this.instructions; + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/GaugeTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/GaugeTileEntity.java index bdd77ba69..bc320e85d 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/GaugeTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/GaugeTileEntity.java @@ -12,7 +12,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -public class GaugeTileEntity extends KineticTileEntity implements IHaveGoggleInformation { +public abstract class GaugeTileEntity extends KineticTileEntity implements IHaveGoggleInformation { public float dialTarget; public float dialState; @@ -52,4 +52,5 @@ public class GaugeTileEntity extends KineticTileEntity implements IHaveGoggleInf return true; } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/SpeedGaugeTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/SpeedGaugeTileEntity.java index fb48c7209..5f853e7d9 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/SpeedGaugeTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/SpeedGaugeTileEntity.java @@ -2,24 +2,41 @@ package com.simibubi.create.content.contraptions.relays.gauge; import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour; +import com.simibubi.create.compat.computercraft.ComputerCraftProxy; import com.simibubi.create.content.contraptions.base.IRotate.SpeedLevel; import com.simibubi.create.foundation.config.AllConfigs; +import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import com.simibubi.create.foundation.utility.Color; import com.simibubi.create.foundation.utility.Lang; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; public class SpeedGaugeTileEntity extends GaugeTileEntity { + public AbstractComputerBehaviour computerBehaviour; + public SpeedGaugeTileEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); } + @Override + public void addBehaviours(List behaviours) { + super.addBehaviours(behaviours); + behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this)); + } + @Override public void onSpeedChanged(float prevSpeed) { super.onSpeedChanged(prevSpeed); @@ -62,4 +79,19 @@ public class SpeedGaugeTileEntity extends GaugeTileEntity { .forGoggles(tooltip); return true; } + + @NotNull + @Override + public LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { + if (computerBehaviour.isPeripheralCap(cap)) + return computerBehaviour.getPeripheralCapability(); + return super.getCapability(cap, side); + } + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + computerBehaviour.removePeripheral(); + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/StressGaugeTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/StressGaugeTileEntity.java index 665226cb1..8eb5c9d0e 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/StressGaugeTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/StressGaugeTileEntity.java @@ -2,6 +2,11 @@ package com.simibubi.create.content.contraptions.relays.gauge; import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour; +import com.simibubi.create.compat.computercraft.ComputerCraftProxy; import com.simibubi.create.content.contraptions.base.IRotate.StressImpact; import com.simibubi.create.foundation.advancement.AllAdvancements; import com.simibubi.create.foundation.item.ItemDescription; @@ -13,14 +18,19 @@ import com.simibubi.create.foundation.utility.LangBuilder; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; public class StressGaugeTileEntity extends GaugeTileEntity { + public AbstractComputerBehaviour computerBehaviour; + static BlockPos lastSent; public StressGaugeTileEntity(BlockEntityType type, BlockPos pos, BlockState state) { @@ -30,6 +40,7 @@ public class StressGaugeTileEntity extends GaugeTileEntity { @Override public void addBehaviours(List behaviours) { super.addBehaviours(behaviours); + behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this)); registerAwardables(behaviours, AllAdvancements.STRESSOMETER, AllAdvancements.STRESSOMETER_MAXED); } @@ -141,4 +152,18 @@ public class StressGaugeTileEntity extends GaugeTileEntity { award(AllAdvancements.STRESSOMETER_MAXED); } + @NotNull + @Override + public LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { + if (computerBehaviour.isPeripheralCap(cap)) + return computerBehaviour.getPeripheralCapability(); + return super.getCapability(cap, side); + } + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + computerBehaviour.removePeripheral(); + } + } diff --git a/src/main/java/com/simibubi/create/content/logistics/block/display/AllDisplayBehaviours.java b/src/main/java/com/simibubi/create/content/logistics/block/display/AllDisplayBehaviours.java index 55637bae2..5f73512f2 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/display/AllDisplayBehaviours.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/display/AllDisplayBehaviours.java @@ -9,6 +9,8 @@ import java.util.Map; import javax.annotation.Nullable; import com.simibubi.create.Create; +import com.simibubi.create.compat.Mods; +import com.simibubi.create.content.logistics.block.display.source.ComputerDisplaySource; import com.simibubi.create.content.logistics.block.display.source.DeathCounterDisplaySource; import com.simibubi.create.content.logistics.block.display.source.DisplaySource; import com.simibubi.create.content.logistics.block.display.source.EnchantPowerDisplaySource; @@ -237,5 +239,14 @@ public class AllDisplayBehaviours { assignTile(register(Create.asResource("scoreboard_display_source"), new ScoreboardDisplaySource()), BlockEntityType.COMMAND_BLOCK); assignTile(register(Create.asResource("enchant_power_display_source"), new EnchantPowerDisplaySource()), BlockEntityType.ENCHANTING_TABLE); assignBlock(register(Create.asResource("redstone_power_display_source"), new RedstonePowerDisplaySource()), Blocks.TARGET); + + Mods.COMPUTERCRAFT.executeIfInstalled(() -> () -> { + DisplayBehaviour computerDisplaySource = register(Create.asResource("computer_display_source"), new ComputerDisplaySource()); + + assignTile(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "wired_modem_full")); + assignTile(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_normal")); + assignTile(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_advanced")); + assignTile(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_command")); + }); } } diff --git a/src/main/java/com/simibubi/create/content/logistics/block/display/DisplayLinkTileEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/display/DisplayLinkTileEntity.java index 9d9a5f606..448afed55 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/display/DisplayLinkTileEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/display/DisplayLinkTileEntity.java @@ -2,6 +2,11 @@ package com.simibubi.create.content.logistics.block.display; import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour; +import com.simibubi.create.compat.computercraft.ComputerCraftProxy; import com.simibubi.create.content.logistics.block.display.source.DisplaySource; import com.simibubi.create.content.logistics.block.display.target.DisplayTarget; import com.simibubi.create.foundation.advancement.AllAdvancements; @@ -18,6 +23,8 @@ import net.minecraft.nbt.NbtUtils; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; public class DisplayLinkTileEntity extends SmartTileEntity { @@ -31,9 +38,11 @@ public class DisplayLinkTileEntity extends SmartTileEntity { public LerpedFloat glow; private boolean sendPulse; - + public int refreshTicks; + public AbstractComputerBehaviour computerBehaviour; + public DisplayLinkTileEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); targetOffset = BlockPos.ZERO; @@ -44,10 +53,16 @@ public class DisplayLinkTileEntity extends SmartTileEntity { glow.chase(0, 0.5f, Chaser.EXP); } + @Override + public void addBehaviours(List behaviours) { + behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this)); + registerAwardables(behaviours, AllAdvancements.DISPLAY_LINK, AllAdvancements.DISPLAY_BOARD); + } + @Override public void tick() { super.tick(); - + if (isVirtual()) { glow.tickChaser(); return; @@ -59,9 +74,9 @@ public class DisplayLinkTileEntity extends SmartTileEntity { glow.tickChaser(); return; } - + refreshTicks++; - if (refreshTicks < activeSource.getPassiveRefreshTicks()) + if (refreshTicks < activeSource.getPassiveRefreshTicks() || !activeSource.shouldPassiveReset()) return; tickSource(); } @@ -114,13 +129,8 @@ public class DisplayLinkTileEntity extends SmartTileEntity { activeSource.transferData(context, activeTarget, targetLine); sendPulse = true; sendData(); - - award(AllAdvancements.DISPLAY_LINK); - } - @Override - public void addBehaviours(List behaviours) { - registerAwardables(behaviours, AllAdvancements.DISPLAY_LINK, AllAdvancements.DISPLAY_BOARD); + award(AllAdvancements.DISPLAY_LINK); } @Override @@ -133,7 +143,7 @@ public class DisplayLinkTileEntity extends SmartTileEntity { protected void write(CompoundTag tag, boolean clientPacket) { super.write(tag, clientPacket); writeGatheredData(tag); - if (clientPacket && activeTarget != null) + if (clientPacket && activeTarget != null) tag.putString("TargetType", activeTarget.id.toString()); if (clientPacket && sendPulse) { sendPulse = false; @@ -173,6 +183,21 @@ public class DisplayLinkTileEntity extends SmartTileEntity { sourceConfig = data.copy(); } + @NotNull + @Override + public LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { + if (computerBehaviour.isPeripheralCap(cap)) + return computerBehaviour.getPeripheralCapability(); + + return super.getCapability(cap, side); + } + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + computerBehaviour.removePeripheral(); + } + public void target(BlockPos targetPosition) { this.targetOffset = targetPosition.subtract(worldPosition); } diff --git a/src/main/java/com/simibubi/create/content/logistics/block/display/source/ComputerDisplaySource.java b/src/main/java/com/simibubi/create/content/logistics/block/display/source/ComputerDisplaySource.java new file mode 100644 index 000000000..e3e988143 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/block/display/source/ComputerDisplaySource.java @@ -0,0 +1,33 @@ +package com.simibubi.create.content.logistics.block.display.source; + +import java.util.ArrayList; +import java.util.List; + +import com.simibubi.create.content.logistics.block.display.DisplayLinkContext; +import com.simibubi.create.content.logistics.block.display.target.DisplayTargetStats; +import com.simibubi.create.foundation.utility.Components; + +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.MutableComponent; + +public class ComputerDisplaySource extends DisplaySource { + + @Override + public List provideText(DisplayLinkContext context, DisplayTargetStats stats) { + List components = new ArrayList<>(); + ListTag tag = context.sourceConfig().getList("ComputerSourceList", Tag.TAG_STRING); + + for (int i = 0; i < tag.size(); i++) { + components.add(Components.literal(tag.getString(i))); + } + + return components; + } + + @Override + public boolean shouldPassiveReset() { + return false; + } + +} diff --git a/src/main/java/com/simibubi/create/content/logistics/block/display/source/DisplaySource.java b/src/main/java/com/simibubi/create/content/logistics/block/display/source/DisplaySource.java index 5b9877273..d844a1ed1 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/display/source/DisplaySource.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/display/source/DisplaySource.java @@ -49,6 +49,10 @@ public abstract class DisplaySource extends DisplayBehaviour { return 100; }; + public boolean shouldPassiveReset() { + return true; + } + protected String getTranslationKey() { return id.getPath(); } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java index a5a371013..8c237ceaf 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java @@ -15,8 +15,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.Consumer; -import javax.annotation.Nullable; - import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.commons.lang3.mutable.MutableObject; @@ -51,7 +49,7 @@ import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.NBTHelper; import com.simibubi.create.foundation.utility.Pair; import com.simibubi.create.foundation.utility.VecHelper; - +import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Direction.Axis; @@ -67,6 +65,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Explosion.BlockInteraction; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; + import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.capability.IFluidHandler; @@ -86,6 +85,7 @@ public class Train { public boolean honk = false; public UUID id; + @Nullable public UUID owner; public TrackGraph graph; public Navigation navigation; @@ -124,7 +124,7 @@ public class Train { public int honkPitch; public float accumulatedSteamRelease; - + int tickOffset; double[] stress; @@ -277,7 +277,7 @@ public class Train { int carriageCount = carriages.size(); boolean stalled = false; double maxStress = 0; - + if (carriageWaitingForChunks != -1) distance = 0; @@ -317,7 +317,7 @@ public class Train { entries++; } } - + if (entries > 0) actual = total / entries; @@ -369,7 +369,7 @@ public class Train { .getLeadingPoint(); double totalStress = derailed ? 0 : leadingStress + trailingStress; - + boolean first = i == 0; boolean last = i == carriageCount - 1; int carriageType = first ? last ? Carriage.BOTH : Carriage.FIRST : last ? Carriage.LAST : Carriage.MIDDLE; @@ -1087,7 +1087,8 @@ public class Train { public CompoundTag write(DimensionPalette dimensions) { CompoundTag tag = new CompoundTag(); tag.putUUID("Id", id); - tag.putUUID("Owner", owner); + if (owner != null) + tag.putUUID("Owner", owner); if (graph != null) tag.putUUID("Graph", graph.id); tag.put("Carriages", NBTHelper.writeCompoundList(carriages, c -> c.write(dimensions))); @@ -1133,7 +1134,7 @@ public class Train { public static Train read(CompoundTag tag, Map trackNetworks, DimensionPalette dimensions) { UUID id = tag.getUUID("Id"); - UUID owner = tag.getUUID("Owner"); + UUID owner = tag.contains("Owner") ? tag.getUUID("Owner") : null; UUID graphId = tag.contains("Graph") ? tag.getUUID("Graph") : null; TrackGraph graph = graphId == null ? null : trackNetworks.get(graphId); List carriages = new ArrayList<>(); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainPacket.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainPacket.java index 479b2699e..ec43cd9f9 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainPacket.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainPacket.java @@ -12,7 +12,6 @@ import com.simibubi.create.foundation.networking.SimplePacketBase; import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.RegisteredObjects; - import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.world.level.block.Block; @@ -37,7 +36,10 @@ public class TrainPacket extends SimplePacketBase { if (!add) return; - UUID owner = buffer.readUUID(); + UUID owner = null; + if (buffer.readBoolean()) + owner = buffer.readUUID(); + List carriages = new ArrayList<>(); List carriageSpacing = new ArrayList<>(); @@ -73,7 +75,9 @@ public class TrainPacket extends SimplePacketBase { if (!add) return; - buffer.writeUUID(train.owner); + buffer.writeBoolean(train.owner != null); + if (train.owner != null) + buffer.writeUUID(train.owner); buffer.writeVarInt(train.carriages.size()); for (Carriage carriage : train.carriages) { diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AbstractStationScreen.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AbstractStationScreen.java index ed32075ae..a1d5ea049 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AbstractStationScreen.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AbstractStationScreen.java @@ -7,6 +7,7 @@ import com.jozufozu.flywheel.core.PartialModel; import com.jozufozu.flywheel.util.transform.TransformStack; import com.mojang.blaze3d.vertex.PoseStack; import com.simibubi.create.CreateClient; +import com.simibubi.create.compat.computercraft.ComputerScreen; import com.simibubi.create.content.logistics.trains.entity.Carriage; import com.simibubi.create.content.logistics.trains.entity.Train; import com.simibubi.create.content.logistics.trains.entity.TrainIconType; @@ -15,6 +16,7 @@ import com.simibubi.create.foundation.gui.AllGuiTextures; import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.gui.element.GuiGameElement; import com.simibubi.create.foundation.gui.widget.IconButton; +import com.simibubi.create.foundation.utility.Components; import net.minecraft.world.level.block.state.properties.BlockStateProperties; @@ -39,6 +41,10 @@ public abstract class AbstractStationScreen extends AbstractSimiScreen { @Override protected void init() { + if (te.computerBehaviour.hasAttachedComputer()) + minecraft.setScreen(new ComputerScreen(title, () -> Components.literal(station.name), + this::renderAdditional, this, te.computerBehaviour::hasAttachedComputer)); + setWindowSize(background.width, background.height); super.init(); clearWidgets(); @@ -71,17 +77,29 @@ public abstract class AbstractStationScreen extends AbstractSimiScreen { return w; } + @Override + public void tick() { + super.tick(); + + if (te.computerBehaviour.hasAttachedComputer()) + minecraft.setScreen(new ComputerScreen(title, () -> Components.literal(station.name), + this::renderAdditional, this, te.computerBehaviour::hasAttachedComputer)); + } + @Override protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) { int x = guiLeft; int y = guiTop; background.render(ms, x, y, this); + renderAdditional(ms, mouseX, mouseY, partialTicks, x, y, background); + } + private void renderAdditional(PoseStack ms, int mouseX, int mouseY, float partialTicks, int guiLeft, int guiTop, AllGuiTextures background) { ms.pushPose(); TransformStack msr = TransformStack.cast(ms); msr.pushPose() - .translate(x + background.width + 4, y + background.height + 4, 100) + .translate(guiLeft + background.width + 4, guiTop + background.height + 4, 100) .scale(40) .rotateX(-22) .rotateY(63); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java index e1ec084c7..2028aca9d 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java @@ -1,19 +1,11 @@ package com.simibubi.create.content.logistics.trains.management.edgePoint.station; -import com.simibubi.create.Create; -import com.simibubi.create.content.logistics.trains.GraphLocation; -import com.simibubi.create.content.logistics.trains.entity.Train; import com.simibubi.create.foundation.networking.TileEntityConfigurationPacket; -import com.simibubi.create.foundation.utility.VecHelper; - import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.Vec3; public class StationEditPacket extends TileEntityConfigurationPacket { @@ -92,18 +84,12 @@ public class StationEditPacket extends TileEntityConfigurationPacket station.assembling = true); + GlobalStation station = getStation(); + if (station != null) { + for (Train train : Create.RAILWAYS.sided(level).trains.values()) { + if (train.navigation.destination != station) + continue; + + GlobalStation preferredDestination = train.runtime.startCurrentInstruction(); + train.navigation.startNavigation(preferredDestination != null ? preferredDestination : station, Double.MAX_VALUE, false); + } + } + + return true; + } + + public boolean exitAssemblyMode() { + if (!isAssembling()) + return false; + + cancelAssembly(); + BlockState newState = getBlockState().setValue(StationBlock.ASSEMBLING, false); + level.setBlock(getBlockPos(), newState, 3); + refreshBlockState(); + + return updateStationState(station -> station.assembling = false); + } + + public boolean tryDisassembleTrain(@Nullable ServerPlayer sender) { + GlobalStation station = getStation(); + if (station == null) + return false; + + Train train = station.getPresentTrain(); + if (train == null) + return false; + + BlockPos trackPosition = edgePoint.getGlobalPosition(); + if (!train.disassemble(getAssemblyDirection(), trackPosition.above())) + return false; + + dropSchedule(sender); + return true; + } + public boolean isAssembling() { BlockState state = getBlockState(); return state.hasProperty(StationBlock.ASSEMBLING) && state.getValue(StationBlock.ASSEMBLING); @@ -341,6 +409,42 @@ public class StationTileEntity extends SmartTileEntity implements ITransformable return true; } + public void dropSchedule(@Nullable ServerPlayer sender) { + GlobalStation station = getStation(); + if (station == null) + return; + + Train train = station.getPresentTrain(); + if (train == null) + return; + + ItemStack schedule = train.runtime.returnSchedule(); + if (schedule.isEmpty()) + return; + if (sender != null && sender.getMainHandItem().isEmpty()) { + sender.getInventory() + .placeItemBackInInventory(schedule); + return; + } + + Vec3 v = VecHelper.getCenterOf(getBlockPos()); + ItemEntity itemEntity = new ItemEntity(getLevel(), v.x, v.y, v.z, schedule); + itemEntity.setDeltaMovement(Vec3.ZERO); + getLevel().addFreshEntity(itemEntity); + } + + private boolean updateStationState(Consumer updateState) { + GlobalStation station = getStation(); + GraphLocation graphLocation = edgePoint.determineGraphLocation(); + if (station == null || graphLocation == null) + return false; + + updateState.accept(station); + Create.RAILWAYS.sync.pointAdded(graphLocation.graph, station); + Create.RAILWAYS.markTracksDirty(); + return true; + } + public void refreshAssemblyInfo() { if (!edgePoint.hasValidTrack()) return; @@ -409,6 +513,14 @@ public class StationTileEntity extends SmartTileEntity implements ITransformable map.put(worldPosition, BoundingBox.fromCorners(startPosition, trackEnd)); } + public boolean updateName(String name) { + if (!updateStationState(station -> station.name = name)) + return false; + notifyUpdate(); + + return true; + } + public boolean isValidBogeyOffset(int i) { if ((i < 3 || bogeyCount == 0) && i != 0) return false; @@ -698,12 +810,20 @@ public class StationTileEntity extends SmartTileEntity implements ITransformable } @Override - public LazyOptional getCapability(Capability cap, Direction side) { + public @NotNull LazyOptional getCapability(@NotNull Capability cap, Direction side) { if (isItemHandlerCap(cap)) return depotBehaviour.getItemCapability(cap, side); + if (computerBehaviour.isPeripheralCap(cap)) + return computerBehaviour.getPeripheralCapability(); return super.getCapability(cap, side); } + @Override + public void invalidateCaps() { + super.invalidateCaps(); + computerBehaviour.removePeripheral(); + } + private void applyAutoSchedule() { ItemStack stack = getAutoSchedule(); if (!AllItems.SCHEDULE.isIn(stack)) diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/IScheduleInput.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/IScheduleInput.java index 4858e0974..41392f608 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/IScheduleInput.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/IScheduleInput.java @@ -25,6 +25,8 @@ public interface IScheduleInput { public abstract CompoundTag getData(); + public abstract void setData(CompoundTag data); + public default int slotsTargeted() { return 0; } @@ -40,7 +42,7 @@ public interface IScheduleInput { } public default void setItem(int slot, ItemStack stack) {} - + public default ItemStack getItem(int slot) { return ItemStack.EMPTY; } @@ -58,4 +60,4 @@ public interface IScheduleInput { return false; } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/ScheduleDataEntry.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/ScheduleDataEntry.java index 58b2eae7a..c2c7144c7 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/ScheduleDataEntry.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/ScheduleDataEntry.java @@ -3,33 +3,39 @@ package com.simibubi.create.content.logistics.trains.management.schedule; import net.minecraft.nbt.CompoundTag; public abstract class ScheduleDataEntry implements IScheduleInput { - + protected CompoundTag data; - + public ScheduleDataEntry() { data = new CompoundTag(); } - + @Override public CompoundTag getData() { return data; } - + + @Override + public void setData(CompoundTag data) { + this.data = data; + readAdditional(data); + } + protected void writeAdditional(CompoundTag tag) {}; protected void readAdditional(CompoundTag tag) {}; - + protected T enumData(String key, Class enumClass) { T[] enumConstants = enumClass.getEnumConstants(); return enumConstants[data.getInt(key) % enumConstants.length]; } - + protected String textData(String key) { return data.getString(key); } - + protected int intData(String key) { return data.getInt(key); } - + } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/FluidThresholdCondition.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/FluidThresholdCondition.java index 0c3f25f55..f9ab12a18 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/FluidThresholdCondition.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/FluidThresholdCondition.java @@ -68,7 +68,8 @@ public class FluidThresholdCondition extends CargoThresholdCondition { @Override protected void readAdditional(CompoundTag tag) { super.readAdditional(tag); - compareStack = ItemStack.of(tag.getCompound("Bucket")); + if (tag.contains("Bucket")) + compareStack = ItemStack.of(tag.getCompound("Bucket")); } @Override @@ -139,4 +140,4 @@ public class FluidThresholdCondition extends CargoThresholdCondition { Math.max(0, getThreshold() + offset), Lang.translateDirect("schedule.condition.threshold.buckets")); } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ItemThresholdCondition.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ItemThresholdCondition.java index 78905d201..81122ad73 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ItemThresholdCondition.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ItemThresholdCondition.java @@ -69,7 +69,8 @@ public class ItemThresholdCondition extends CargoThresholdCondition { @Override protected void readAdditional(CompoundTag tag) { super.readAdditional(tag); - stack = ItemStack.of(tag.getCompound("Item")); + if (tag.contains("Item")) + stack = ItemStack.of(tag.getCompound("Item")); } @Override @@ -131,4 +132,4 @@ public class ItemThresholdCondition extends CargoThresholdCondition { Math.max(0, getThreshold() + offset), Lang.translateDirect("schedule.condition.threshold." + (inStacks() ? "stacks" : "items"))); } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/RedstoneLinkCondition.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/RedstoneLinkCondition.java index 9bb8f4190..896c9a23f 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/RedstoneLinkCondition.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/RedstoneLinkCondition.java @@ -107,7 +107,8 @@ public class RedstoneLinkCondition extends ScheduleWaitCondition { @Override protected void readAdditional(CompoundTag tag) { - freq = Couple.deserializeEach(tag.getList("Frequency", Tag.TAG_COMPOUND), c -> Frequency.of(ItemStack.of(c))); + if (tag.contains("Frequency")) + freq = Couple.deserializeEach(tag.getList("Frequency", Tag.TAG_COMPOUND), c -> Frequency.of(ItemStack.of(c))); } @Override @@ -118,7 +119,7 @@ public class RedstoneLinkCondition extends ScheduleWaitCondition { .titled(Lang.translateDirect("schedule.condition.redstone_link.frequency_state")), "Inverted"); } - + @Override public MutableComponent getWaitingStatus(Level level, Train train, CompoundTag tag) { return Lang.translateDirect("schedule.condition.redstone_link.status"); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ScheduleWaitCondition.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ScheduleWaitCondition.java index a8e307d6a..7da150cce 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ScheduleWaitCondition.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ScheduleWaitCondition.java @@ -16,16 +16,17 @@ import net.minecraft.world.level.Level; public abstract class ScheduleWaitCondition extends ScheduleDataEntry { public abstract boolean tickCompletion(Level level, Train train, CompoundTag context); - + protected void requestStatusToUpdate(CompoundTag context) { context.putInt("StatusVersion", context.getInt("StatusVersion") + 1); } - + public final CompoundTag write() { CompoundTag tag = new CompoundTag(); + CompoundTag dataCopy = data.copy(); + writeAdditional(dataCopy); tag.putString("Id", getId().toString()); - tag.put("Data", data.copy()); - writeAdditional(tag); + tag.put("Data", dataCopy); return tag; } @@ -43,11 +44,14 @@ public abstract class ScheduleWaitCondition extends ScheduleDataEntry { } ScheduleWaitCondition condition = supplier.get(); - condition.data = tag.getCompound("Data"); + // Left around for migration purposes. Data added in writeAdditional has moved into the "Data" tag condition.readAdditional(tag); + CompoundTag data = tag.getCompound("Data"); + condition.readAdditional(data); + condition.data = data; return condition; } public abstract MutableComponent getWaitingStatus(Level level, Train train, CompoundTag tag); -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/destination/ScheduleInstruction.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/destination/ScheduleInstruction.java index cc3554094..6e1e56354 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/destination/ScheduleInstruction.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/destination/ScheduleInstruction.java @@ -16,9 +16,10 @@ public abstract class ScheduleInstruction extends ScheduleDataEntry { public final CompoundTag write() { CompoundTag tag = new CompoundTag(); + CompoundTag dataCopy = data.copy(); + writeAdditional(dataCopy); tag.putString("Id", getId().toString()); - tag.put("Data", data.copy()); - writeAdditional(tag); + tag.put("Data", dataCopy); return tag; } @@ -36,9 +37,12 @@ public abstract class ScheduleInstruction extends ScheduleDataEntry { } ScheduleInstruction scheduleDestination = supplier.get(); - scheduleDestination.data = tag.getCompound("Data"); + // Left around for migration purposes. Data added in writeAdditional has moved into the "Data" tag scheduleDestination.readAdditional(tag); + CompoundTag data = tag.getCompound("Data"); + scheduleDestination.readAdditional(data); + scheduleDestination.data = data; return scheduleDestination; } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java index 53f9571e8..71946bbe2 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java +++ b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java @@ -163,7 +163,7 @@ public enum AllGuiTextures implements ScreenElement { SPEECH_TOOLTIP_BACKGROUND("widgets", 0, 24, 8, 8), SPEECH_TOOLTIP_COLOR("widgets", 8, 24, 8, 8), - + TRAIN_HUD_SPEED_BG("widgets", 0, 190, 182, 5), TRAIN_HUD_SPEED("widgets", 0, 185, 182, 5), TRAIN_HUD_THROTTLE("widgets", 0, 195, 182, 5), @@ -175,7 +175,10 @@ public enum AllGuiTextures implements ScreenElement { TRAIN_PROMPT("widgets", 0, 230, 256, 16), // PlacementIndicator - PLACEMENT_INDICATOR_SHEET("placement_indicator", 0, 0, 16, 256); + PLACEMENT_INDICATOR_SHEET("placement_indicator", 0, 0, 16, 256), + + // ComputerCraft + COMPUTER("computer", 200, 102); ; diff --git a/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java b/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java index 10c6a635f..634fd8ec0 100644 --- a/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java +++ b/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java @@ -8,6 +8,7 @@ import java.util.function.Function; import java.util.function.Supplier; import com.simibubi.create.Create; +import com.simibubi.create.compat.computercraft.AttachedComputerPacket; import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionBlockChangedPacket; import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionDisassemblyPacket; import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionRelocationPacket; @@ -184,7 +185,7 @@ public enum AllPackets { S_TRAIN_PROMPT(TrainPromptPacket.class, TrainPromptPacket::new, PLAY_TO_CLIENT), CONTRAPTION_RELOCATION(ContraptionRelocationPacket.class, ContraptionRelocationPacket::new, PLAY_TO_CLIENT), TRACK_GRAPH_ROLL_CALL(TrackGraphRollCallPacket.class, TrackGraphRollCallPacket::new, PLAY_TO_CLIENT), - + ATTACHED_COMPUTER(AttachedComputerPacket.class, AttachedComputerPacket::new, PLAY_TO_CLIENT), ; public static final ResourceLocation CHANNEL_NAME = Create.asResource("main"); 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 ed44673e8..5abcce458 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 @@ -3,6 +3,7 @@ package com.simibubi.create.foundation.ponder.content; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllItems; import com.simibubi.create.Create; +import com.simibubi.create.compat.Mods; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.ponder.PonderRegistrationHelper; import com.simibubi.create.foundation.ponder.PonderRegistry; @@ -20,8 +21,11 @@ import com.simibubi.create.foundation.ponder.content.trains.TrainScenes; import com.simibubi.create.foundation.ponder.content.trains.TrainSignalScenes; import com.simibubi.create.foundation.ponder.content.trains.TrainStationScenes; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.DyeColor; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; +import net.minecraftforge.registries.ForgeRegistries; public class PonderIndex { @@ -548,6 +552,12 @@ public class PonderIndex { .add(Blocks.COMMAND_BLOCK) .add(Blocks.TARGET); + Mods.COMPUTERCRAFT.executeIfInstalled(() -> () -> { + Block computer = ForgeRegistries.BLOCKS.getValue(new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_advanced")); + if (computer != null) + PonderRegistry.TAGS.forTag(PonderTag.DISPLAY_SOURCES).add(computer); + }); + PonderRegistry.TAGS.forTag(PonderTag.DISPLAY_TARGETS) .add(AllBlocks.ORANGE_NIXIE_TUBE) .add(AllBlocks.DISPLAY_BOARD) diff --git a/src/main/java/com/simibubi/create/foundation/utility/StringHelper.java b/src/main/java/com/simibubi/create/foundation/utility/StringHelper.java new file mode 100644 index 000000000..78521b4e5 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/StringHelper.java @@ -0,0 +1,46 @@ +package com.simibubi.create.foundation.utility; + +import java.util.Locale; + +public class StringHelper { + + public static String snakeCaseToCamelCase(String text) { + StringBuilder builder = new StringBuilder(); + builder.append(text.substring(0, 1).toUpperCase(Locale.ROOT)); + + for (int i = 1; i < text.length(); i++) { + int j = text.indexOf('_', i); + + if (j == -1) { + builder.append(text.substring(i)); + break; + } + + builder.append(text.substring(i, j).toLowerCase(Locale.ROOT)); + builder.append(text.substring(j + 1, j + 2).toUpperCase(Locale.ROOT)); + + i = j + 1; + } + + return builder.toString(); + } + + public static String camelCaseToSnakeCase(String text) { + StringBuilder builder = new StringBuilder(); + + for (char c : text.toCharArray()) { + if (Character.isUpperCase(c)) { + builder.append('_'); + builder.append(Character.toLowerCase(c)); + } else { + builder.append(c); + } + } + + if (builder.length() > 0 && builder.charAt(0) == '_') + builder.deleteCharAt(0); + + return builder.toString(); + } + +} diff --git a/src/main/resources/assets/create/textures/gui/computer.png b/src/main/resources/assets/create/textures/gui/computer.png new file mode 100644 index 0000000000000000000000000000000000000000..293bedad784f171501f1983784abcf863ba62ae0 GIT binary patch literal 1016 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;K+f|3pAc6d z{r~^}M~@!exN-BwjT_gmUq5r^%%_97$BrG_v17-QB}*1BUc7&n%Y+FNy1ToZnwlyq zDoRR9^7Hf4($d1i!$U(u9UL6uj2JB~En^KCVhtE#fJmRg+}u1`pCMY0Aqt3e8KQI; zB6S!%^~EE#86tp4iy<6{G#LyG3|zED_4M@YHH7Wdg>BS@t<{9ARD~>6fJo3nS`oN+#hRG+_7|fh9KU{vh z{rl>?&(*(cqIT_^>9>47cX@fdSN#5|`~GX(z5ie5kJg`gfA9agr6nGCn5oH||AXF^ zC?=l6QvIwR`~`)Ed}s6zK4F;q=g%+yE5|E&pX)Pisq1T&H)YNEE5mNDbATy}bHn>| z`NeAD9|~H&iBxQ6jj^w>vEo0gx8UvLcbgcvH^5Lv7322M$ZW=2kq2_WI<1qk{T7wY zy&}$|nh|IUCYlxHP!@VMs-aA*z+{hF2BTO47+tu+)EBUr+rn*k_)B}&zspy!UJ+XG zwOHm4(4G~_2cEWX(0g5RI-uYs9d3@9zXff`dYdd7&r}=IWDX{bt>Io(;;l) z^z(gg=_gs8*d^x8{Tjr5gwNm&kYeGV5T}sT@F@I2al;cPM&^?3OgzjYh4EYm z#!bDS*E27<=c5tSkW>5bIm1cwU@p;w@A?P7%=SFW7jXFO7AN)!-+aKopOC}t+<|