Merge remote-tracking branch 'origin/mc1.20.1/feature-dev' into mc1.21.1/dev

This commit is contained in:
simibubi 2025-02-11 14:58:48 +01:00
commit ee4414f149
34 changed files with 363 additions and 157 deletions

View file

@ -128,6 +128,7 @@ _Now using Flywheel 1.0_
- Fixed stations voiding schedules when disassembling the train
- Fixed lighting on signal block indicators
- Fixed vaults and tanks rotated in place not updating their multiblock correctly
- Hose pulley now deletes lilypads and other surface foliage
#### API Changes

View file

@ -23,7 +23,7 @@ registrate_version = MC1.21-1.3.0+62
# Dependency Versions
flywheel_minecraft_version = 1.21.1
flywheel_version = 1.0.0-beta-4
flywheel_version = 1.0.0-beta-6
flywheel_version_range = [1.0.0-alpha,2.0)
ponder_version = 1.0.32
jei_minecraft_version = 1.21.1

View file

@ -0,0 +1,20 @@
{
"type": "create:crushing",
"conditions": [
{
"type": "forge:mod_loaded",
"modid": "immersiveengineering"
}
],
"ingredients": [
{
"item": "immersiveengineering:coal_coke"
}
],
"processingTime": 200,
"results": [
{
"item": "immersiveengineering:dust_coke"
}
]
}

View file

@ -0,0 +1,21 @@
{
"type": "create:crushing",
"conditions": [
{
"type": "forge:mod_loaded",
"modid": "immersiveengineering"
}
],
"ingredients": [
{
"item": "immersiveengineering:coke"
}
],
"processingTime": 200,
"results": [
{
"count": 9,
"item": "immersiveengineering:dust_coke"
}
]
}

View file

@ -0,0 +1,20 @@
{
"type": "create:crushing",
"conditions": [
{
"type": "forge:mod_loaded",
"modid": "immersiveengineering"
}
],
"ingredients": [
{
"item": "immersiveengineering:slag"
}
],
"processingTime": 200,
"results": [
{
"item": "immersiveengineering:slag_gravel"
}
]
}

View file

@ -21,7 +21,6 @@ public enum Mods {
AETHER_II,
BETTEREND,
COMPUTERCRAFT,
CONNECTIVITY,
CURIOS,
DYNAMICTREES,
FUNCTIONALSTORAGE,

View file

@ -9,6 +9,7 @@ import com.simibubi.create.infrastructure.config.AllConfigs;
import dev.ftb.mods.ftbchunks.client.gui.LargeMapScreen;
import dev.ftb.mods.ftbchunks.client.gui.RegionMapPanel;
import dev.ftb.mods.ftblibrary.ui.BaseScreen;
import dev.ftb.mods.ftblibrary.ui.ScreenWrapper;
import dev.ftb.mods.ftblibrary.ui.Widget;
import net.minecraft.client.Minecraft;
@ -152,7 +153,7 @@ public class FTBChunksTrainMap {
private static LargeMapScreen getAsLargeMapScreen(Screen screen) {
if (!(screen instanceof ScreenWrapper screenWrapper))
return null;
Object wrapped = ObfuscationReflectionHelper.getPrivateValue(ScreenWrapper.class, screenWrapper, "wrappedGui");
BaseScreen wrapped = screenWrapper.getGui();
if (!(wrapped instanceof LargeMapScreen largeMapScreen))
return null;
return largeMapScreen;

View file

@ -18,13 +18,13 @@ import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.AllItems;
import com.simibubi.create.AllMovementBehaviours;
import com.simibubi.create.AllSoundEvents;
import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.actors.psi.PortableStorageInterfaceMovement;
import com.simibubi.create.content.contraptions.actors.seat.SeatBlock;
import com.simibubi.create.content.contraptions.actors.seat.SeatEntity;
import com.simibubi.create.content.contraptions.actors.trainControls.ControlsStopControllingPacket;
import com.simibubi.create.content.contraptions.behaviour.MovementBehaviour;
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
import com.simibubi.create.content.contraptions.data.ContraptionSyncLimiting;
import com.simibubi.create.content.contraptions.elevator.ElevatorContraption;
import com.simibubi.create.content.contraptions.glue.SuperGlueEntity;
import com.simibubi.create.content.contraptions.mounted.MountedContraption;
@ -605,11 +605,8 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
CompoundTag compound = new CompoundTag();
writeAdditional(compound, registryFriendlyByteBuf.registryAccess(), true);
if (!CatnipServices.PLATFORM.getLoader().isNeoForge() && ContraptionData.isTooLargeForSync(compound)) {
String info = getContraption().getType().id + " @" + position() + " (" + getStringUUID() + ")";
Create.LOGGER.warn("Could not send Contraption Spawn Data (Packet too big): {}", info);
compound = null;
}
if (!CatnipServices.PLATFORM.getLoader().isNeoForge() && ContraptionSyncLimiting.isTooLargeForSync(compound))
compound = null; // don't sync contraption data
registryFriendlyByteBuf.writeNbt(compound);
}

View file

@ -92,6 +92,7 @@ import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.network.protocol.game.DebugPackets;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ai.village.poi.PoiTypes;
@ -107,6 +108,7 @@ import net.minecraft.world.level.block.PressurePlateBlock;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
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.block.state.properties.ChestType;
@ -139,6 +141,7 @@ public abstract class Contraption {
public boolean disassembled;
protected Map<BlockPos, StructureBlockInfo> blocks;
protected Map<BlockPos, CompoundTag> updateTags;
protected List<MutablePair<StructureBlockInfo, MovementContext>> actors;
protected Map<BlockPos, MovingInteractionBehaviour> interactors;
protected List<ItemStack> disabledActors;
@ -166,6 +169,7 @@ public abstract class Contraption {
public Contraption() {
blocks = new HashMap<>();
updateTags = new HashMap<>();
seats = new ArrayList<>();
actors = new ArrayList<>();
disabledActors = new ArrayList<>();
@ -646,6 +650,15 @@ public abstract class Contraption {
bounds = bounds.minmax(new AABB(localPos));
BlockEntity be = pair.getValue();
if (be != null) {
CompoundTag updateTag = be.getUpdateTag(level.registryAccess());
// the ID needs to be in the tag so the client can properly add the BlockEntity
ResourceLocation id = Objects.requireNonNull(BlockEntityType.getKey(be.getType()));
updateTag.putString("id", id.toString());
updateTags.put(localPos, updateTag);
}
storage.addBlock(level, state, pos, localPos, be);
captureMultiblock(localPos, structureBlockInfo, be);
@ -793,7 +806,7 @@ public abstract class Contraption {
CompoundTag nbt = new CompoundTag();
nbt.putString("Type", getType().id);
CompoundTag blocksNBT = writeBlocksCompound();
CompoundTag blocksNBT = writeBlocksCompound(spawnPacket);
ListTag multiblocksNBT = new ListTag();
capturedMultiblocks.keySet().forEach(controllerPos -> {
@ -886,7 +899,7 @@ public abstract class Contraption {
storage.write(nbt, registries, spawnPacket);
}
private CompoundTag writeBlocksCompound() {
private CompoundTag writeBlocksCompound(boolean spawnPacket) {
CompoundTag compound = new CompoundTag();
HashMapPalette<BlockState> palette = new HashMapPalette<>(GameData.getBlockStateIDMap(), 16, (i, s) -> {
throw new IllegalStateException("Palette Map index exceeded maximum");
@ -895,11 +908,30 @@ public abstract class Contraption {
for (StructureBlockInfo block : this.blocks.values()) {
int id = palette.idFor(block.state());
BlockPos pos = block.pos();
CompoundTag c = new CompoundTag();
c.putLong("Pos", block.pos().asLong());
c.putLong("Pos", pos.asLong());
c.putInt("State", id);
if (block.nbt() != null)
CompoundTag updateTag = updateTags.get(pos);
if (spawnPacket) {
// for client sync, treat the updateTag as the data
if (updateTag != null) {
c.put("Data", updateTag);
}
// legacy: use full data if update tag is not available
if (updateTag == null && block.nbt() != null) {
c.put("Data", block.nbt());
}
} else {
// otherwise, write actual data as the data, save updateTag on its own
if (block.nbt() != null) {
c.put("Data", block.nbt());
}
if (updateTag != null) {
c.put("UpdateTag", updateTag);
}
}
blockList.add(c);
}
@ -942,6 +974,13 @@ public abstract class Contraption {
this.blocks.put(info.pos(), info);
if (c.contains("UpdateTag", Tag.TAG_COMPOUND)) {
CompoundTag updateTag = c.getCompound("UpdateTag");
if (!updateTag.isEmpty()) {
this.updateTags.put(info.pos(), updateTag);
}
}
if (!world.isClientSide)
return;

View file

@ -1,86 +0,0 @@
package com.simibubi.create.content.contraptions;
import com.simibubi.create.compat.Mods;
import com.simibubi.create.foundation.mixin.accessor.NbtAccounterAccessor;
import com.simibubi.create.infrastructure.config.AllConfigs;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
public class ContraptionData {
/**
* A sane, default maximum for contraption data size.
*/
public static final int DEFAULT_LIMIT = 2_000_000;
/**
* Connectivity expands the NBT packet limit to 2 GB.
*/
public static final int CONNECTIVITY_LIMIT = Integer.MAX_VALUE;
/**
* Packet Fixer expands the NBT packet limit to 200 MB.
*/
public static final int PACKET_FIXER_LIMIT = 209_715_200;
/**
* XL Packets expands the NBT packet limit to 2 GB.
*/
public static final int XL_PACKETS_LIMIT = 2_000_000_000;
/**
* Minecart item sizes are limited by the vanilla slot change packet ({@link ClientboundContainerSetSlotPacket}).
* {@link #DEFAULT_LIMIT} is used as the default.
* Connectivity, PacketFixer, and XL Packets expand the size limit.
* If one of these mods is loaded, we take advantage of it and use the higher limit.
*/
public static final int PICKUP_LIMIT;
static {
int limit = DEFAULT_LIMIT;
// Check from largest to smallest to use the smallest limit if multiple mods are loaded.
// It is necessary to use the smallest limit because even if multiple mods are loaded,
// not all of their mixins may be applied. Therefore, it is safest to only assume that
// the mod with the smallest limit is actually active.
if (Mods.CONNECTIVITY.isLoaded()) {
limit = CONNECTIVITY_LIMIT;
}
if (Mods.XLPACKETS.isLoaded()) {
limit = XL_PACKETS_LIMIT;
}
if (Mods.PACKETFIXER.isLoaded()) {
limit = PACKET_FIXER_LIMIT;
}
PICKUP_LIMIT = limit;
}
/**
* @return true if the given NBT is too large for a contraption to be synced to clients.
*/
public static boolean isTooLargeForSync(CompoundTag data) {
int max = AllConfigs.server().kinetics.maxDataSize.get();
return max != 0 && packetSize(data) > max;
}
/**
* @return true if the given NBT is too large for a contraption to be picked up with a wrench.
*/
public static boolean isTooLargeForPickup(Tag data) {
return packetSize(data) > PICKUP_LIMIT;
}
/**
* @return the size of the given NBT when put through a packet, in bytes.
*/
public static long packetSize(Tag data) {
FriendlyByteBuf test = new FriendlyByteBuf(Unpooled.buffer());
test.writeNbt(data);
NbtAccounter sizeTracker = NbtAccounter.unlimitedHeap();
test.readNbt(sizeTracker);
long size = ((NbtAccounterAccessor) sizeTracker).create$getUsage();
test.release();
return size;
}
}

View file

@ -0,0 +1,55 @@
package com.simibubi.create.content.contraptions.data;
import com.simibubi.create.compat.Mods;
import com.simibubi.create.foundation.mixin.accessor.NbtAccounterAccessor;
import io.netty.buffer.Unpooled;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.network.FriendlyByteBuf;
public class ContraptionPickupLimiting {
/// The default NBT limit, defined by {@link FriendlyByteBuf#readNbt()}.
public static final int NBT_LIMIT = 2_097_152;
// increased nbt limits provided by other mods.
public static final int PACKET_FIXER_LIMIT = NBT_LIMIT * 100;
public static final int XL_PACKETS_LIMIT = Integer.MAX_VALUE;
// leave some space for the rest of the packet.
public static final int BUFFER = 20_000;
// the actual limit to be used
public static final int LIMIT = Util.make(() -> {
// the smallest limit needs to be used, as we can't guarantee that all mixins are applied if multiple are present.
if (Mods.PACKETFIXER.isLoaded()) {
return PACKET_FIXER_LIMIT;
} else if (Mods.XLPACKETS.isLoaded()) {
return XL_PACKETS_LIMIT;
}
// none are present, use vanilla default
return NBT_LIMIT;
}) - BUFFER;
/**
* @return true if the given NBT is too large for a contraption to be picked up with a wrench.
*/
public static boolean isTooLargeForPickup(CompoundTag data) {
return nbtSize(data) > LIMIT;
}
/**
* @return the size of the given NBT when read by the client according to {@link NbtAccounter}
*/
private static long nbtSize(CompoundTag data) {
FriendlyByteBuf test = new FriendlyByteBuf(Unpooled.buffer());
test.writeNbt(data);
NbtAccounter sizeTracker = new NbtAccounter(Long.MAX_VALUE);
test.readNbt(sizeTracker);
long size = ((NbtAccounterAccessor) sizeTracker).create$getUsage();
test.release();
return size;
}
}

View file

@ -0,0 +1,52 @@
package com.simibubi.create.content.contraptions.data;
import com.simibubi.create.compat.Mods;
import io.netty.buffer.Unpooled;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
public class ContraptionSyncLimiting {
/**
* Contraption entity sync is limited by the clientbound custom payload limit, since that's what Forge's
* extended spawn packet uses. The NBT limit is irrelevant since it's bypassed on deserialization.
*/
public static final int SIZE_LIMIT = 1_048_576;
// increased packet limits provided by other mods.
public static final int PACKET_FIXER_LIMIT = SIZE_LIMIT * 100;
public static final int XL_PACKETS_LIMIT = Integer.MAX_VALUE;
// leave some room for the rest of the packet.
public static final int BUFFER = 20_000;
// the actual limit to be used
public static final int LIMIT = Util.make(() -> {
// the smallest limit needs to be used, as we can't guarantee that all mixins are applied if multiple are present.
if (Mods.PACKETFIXER.isLoaded()) {
return PACKET_FIXER_LIMIT;
} else if (Mods.XLPACKETS.isLoaded()) {
return XL_PACKETS_LIMIT;
}
// none are present, use vanilla default
return SIZE_LIMIT;
}) - BUFFER;
/**
* @return true if the given NBT is too large for a contraption to be synced to clients.
*/
public static boolean isTooLargeForSync(CompoundTag data) {
return byteSize(data) > LIMIT;
}
/**
* @return the size of the given NBT when encoded, in bytes
*/
private static long byteSize(CompoundTag data) {
FriendlyByteBuf test = new FriendlyByteBuf(Unpooled.buffer());
test.writeNbt(data);
return test.writerIndex();
}
}

View file

@ -0,0 +1,37 @@
# Data Limiting
This pair of classes prevents clients from getting chunkbanned when contraptions are too large.
This information is up-to-date as of 1.20.1.
There's a few different packet limits in play:
- the NBT limit: `2_097_152`, enforced by `FriendlyByteBuf.readNbt()`
- the clientbound custom payload limit: `1_048_576` bytes, applies to `ClientboundCustomPayloadPacket`
- the serverbound custom payload limit: `32767` bytes, applies to `ServerboundCustomPayloadPacket`
- the packet limit: `8_388_608` bytes, applies to all packets
# NBT Size vs Bytes
There's two units in play as well - NBT Size and Bytes. The NBT limit uses NBT size, while the other
three use bytes. I'm (TropheusJ) not sure what exactly an NBT Size unit is - it's not bits, but it's
close.
Because of this discrepancy, the NBT limit is actually much lower than it seems. It will usually be
the first limit hit.
Bytes are found by writing a tag to a buffer and getting its `writerIndex`. NBT Size is found using
an `NbtAccounter`.
# Sync
Sync is pretty straightforward.
The only limit relevant here is the clientbound custom payload limit. The NBT limit would be relevant, but
client-side deserialization bypasses it.
Sync is much less of an issue compared to pickup, since a lot of data can be skipped when syncing.
# Pickup
Two limits are relevant for pickup: the NBT limit and the packet limit.
The NBT limit is hit way sooner, and is usually the limiting factor. Other mods may increase it, in
which case the packet limit may become relevant.
The custom payload limit is not relevant since item sync goes through the vanilla `ClientboundContainerSetSlotPacket`.

View file

@ -11,11 +11,11 @@ import com.simibubi.create.AllItems;
import com.simibubi.create.AllMovementBehaviours;
import com.simibubi.create.content.contraptions.AbstractContraptionEntity;
import com.simibubi.create.content.contraptions.Contraption;
import com.simibubi.create.content.contraptions.ContraptionData;
import com.simibubi.create.content.contraptions.ContraptionMovementSetting;
import com.simibubi.create.content.contraptions.OrientedContraptionEntity;
import com.simibubi.create.content.contraptions.actors.psi.PortableStorageInterfaceMovement;
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
import com.simibubi.create.content.contraptions.data.ContraptionPickupLimiting;
import com.simibubi.create.content.kinetics.deployer.DeployerFakePlayer;
import com.simibubi.create.foundation.advancement.AllAdvancements;
import com.simibubi.create.foundation.utility.CreateLang;
@ -253,7 +253,7 @@ public class MinecartContraptionItem extends Item {
ItemStack generatedStack = create(type, oce);
generatedStack.set(DataComponents.CUSTOM_NAME, entity.getCustomName());
if (ContraptionData.isTooLargeForPickup(generatedStack.saveOptional(event.getLevel().registryAccess()))) {
if (ContraptionPickupLimiting.isTooLargeForPickup(generatedStack.saveOptional(event.getLevel().registryAccess()))) {
MutableComponent message = CreateLang.translateDirect("contraption.minecart_contraption_too_big")
.withStyle(ChatFormatting.RED);
player.displayClientMessage(message, true);

View file

@ -174,7 +174,8 @@ public class RadialWrenchMenu extends AbstractSimiScreen {
@Override
public void tick() {
ticksOpen++;
if (!level.getBlockState(pos).is(state.getBlock()))
Minecraft.getInstance().setScreen(null);
super.tick();
}
@ -185,8 +186,6 @@ public class RadialWrenchMenu extends AbstractSimiScreen {
PoseStack ms = graphics.pose();
LocalPlayer player = Minecraft.getInstance().player;
ms.pushPose();
ms.translate(x, y, 0);
@ -272,6 +271,8 @@ public class RadialWrenchMenu extends AbstractSimiScreen {
.translateY(-(sectorWidth / 2f + innerRadius))
.rotateZDegrees(-i * sectorAngle);
poseStack.translate(0, 0, 100);
try {
GuiGameElement.of(blockState, blockEntity)
.rotateBlock(player.getXRot(), player.getYRot() + 180, 0f)

View file

@ -30,6 +30,9 @@ public record RadialWrenchMenuSubmitPacket(BlockPos blockPos, BlockState newStat
public void handle(ServerPlayer player) {
Level level = player.level();
if (!level.getBlockState(blockPos).is(newState.getBlock()))
return;
BlockState updatedState = Block.updateFromNeighbourShapes(newState, level, blockPos);
KineticBlockEntity.switchToBlockState(level, blockPos, updatedState);

View file

@ -82,6 +82,12 @@ public abstract class CopycatModel extends BakedModelWrapperWithData {
}
}
@Override
public List<BakedQuad> getQuads(BlockState state, Direction side, RandomSource rand) {
return getCroppedQuads(state, side, rand, getMaterial(ModelData.EMPTY), ModelData.EMPTY,
RenderType.cutoutMipped());
}
@Override
public List<BakedQuad> getQuads(BlockState state, Direction side, RandomSource rand, ModelData data, RenderType renderType) {

View file

@ -149,8 +149,14 @@ public class FluidDrainingBehaviour extends FluidManipulationBehaviour {
playEffect(world, currentPos, fluid, true);
blockEntity.award(AllAdvancements.HOSE_PULLEY);
if (!blockEntity.isVirtual())
if (!blockEntity.isVirtual()) {
world.setBlock(currentPos, emptied, 2 | 16);
BlockState stateAbove = world.getBlockState(currentPos.above());
if (stateAbove.getFluidState()
.getType() == Fluids.EMPTY && !stateAbove.canSurvive(world, currentPos.above()))
world.setBlock(currentPos.above(), Blocks.AIR.defaultBlockState(), 2 | 16);
}
affectedArea = BBHelper.encapsulate(affectedArea, currentPos);
queue.dequeue();

View file

@ -3,6 +3,7 @@ package com.simibubi.create.content.kinetics.drill;
import com.simibubi.create.content.kinetics.base.BlockBreakingKineticBlockEntity;
import com.simibubi.create.content.kinetics.belt.behaviour.DirectBeltInputBehaviour;
import com.simibubi.create.content.kinetics.drill.CobbleGenOptimisation.CobbleGenBlockConfiguration;
import com.simibubi.create.content.logistics.chute.ChuteBlockEntity;
import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour;
import net.minecraft.core.BlockPos;
@ -41,6 +42,15 @@ public class DrillBlockEntity extends BlockBreakingKineticBlockEntity {
}
public boolean optimiseCobbleGen(BlockState stateToBreak) {
DirectBeltInputBehaviour inv =
BlockEntityBehaviour.get(level, breakingPos.below(), DirectBeltInputBehaviour.TYPE);
BlockEntity blockEntityBelow = level.getBlockEntity(breakingPos.below());
BlockEntity blockEntityAbove = level.getBlockEntity(breakingPos.above());
if (inv == null && !(blockEntityBelow instanceof HopperBlockEntity)
&& !(blockEntityAbove instanceof ChuteBlockEntity chute && chute.getItemMotion() > 0))
return false;
CobbleGenBlockConfiguration config =
CobbleGenOptimisation.getConfig(level, worldPosition, getBlockState().getValue(DrillBlock.FACING));
if (config == null)
@ -57,20 +67,19 @@ public class DrillBlockEntity extends BlockBreakingKineticBlockEntity {
if (currentOutput.isAir() || !currentOutput.equals(stateToBreak))
return false;
DirectBeltInputBehaviour inv =
BlockEntityBehaviour.get(level, breakingPos.below(), DirectBeltInputBehaviour.TYPE);
if (inv != null)
for (ItemStack stack : Block.getDrops(stateToBreak, sl, breakingPos, null))
inv.handleInsertion(stack, Direction.UP, false);
else {
BlockEntity blockEntity = level.getBlockEntity(breakingPos.below());
if (blockEntity instanceof HopperBlockEntity hbe) {
else if (blockEntityBelow instanceof HopperBlockEntity hbe) {
IItemHandler handler = level.getCapability(ItemHandler.BLOCK, hbe.getBlockPos(), null);
if (handler != null)
for (ItemStack stack : Block.getDrops(stateToBreak, sl, breakingPos, null))
ItemHandlerHelper.insertItemStacked(handler, stack, false);
}
} else if (blockEntityAbove instanceof ChuteBlockEntity chute && chute.getItemMotion() > 0) {
for (ItemStack stack : Block.getDrops(stateToBreak, sl, breakingPos, null))
if (chute.getItem()
.isEmpty())
chute.setItem(stack, 0);
}
level.levelEvent(2001, breakingPos, Block.getId(stateToBreak));

View file

@ -26,6 +26,7 @@ public class SteamEngineVisual extends AbstractBlockEntityVisual<SteamEngineBloc
protected final TransformedInstance connector;
private Float lastAngle = Float.NaN;
private Axis lastAxis = null;
public SteamEngineVisual(VisualizationContext context, SteamEngineBlockEntity blockEntity, float partialTick) {
super(context, blockEntity, partialTick);
@ -47,11 +48,18 @@ public class SteamEngineVisual extends AbstractBlockEntityVisual<SteamEngineBloc
private void animate() {
Float angle = blockEntity.getTargetAngle();
Axis axis = Axis.Y;
if (Objects.equals(angle, lastAngle)) {
PoweredShaftBlockEntity shaft = blockEntity.getShaft();
if (shaft != null)
axis = KineticBlockEntityRenderer.getRotationAxisOf(shaft);
if (Objects.equals(angle, lastAngle) && lastAxis == axis) {
return;
}
lastAngle = angle;
lastAxis = axis;
if (angle == null) {
piston.setVisible(false);
@ -66,11 +74,6 @@ public class SteamEngineVisual extends AbstractBlockEntityVisual<SteamEngineBloc
Direction facing = SteamEngineBlock.getFacing(blockState);
Axis facingAxis = facing.getAxis();
Axis axis = Axis.Y;
PoweredShaftBlockEntity shaft = blockEntity.getShaft();
if (shaft != null)
axis = KineticBlockEntityRenderer.getRotationAxisOf(shaft);
boolean roll90 = facingAxis.isHorizontal() && axis == Axis.Y || facingAxis.isVertical() && axis == Axis.Z;
float sine = Mth.sin(angle);

View file

@ -65,6 +65,13 @@ public class AddressEditBox extends EditBox {
@Override
public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) {
if (pButton == GLFW.GLFW_MOUSE_BUTTON_RIGHT) {
if (isMouseOver(pMouseX, pMouseY)) {
setValue("");
return true;
}
}
boolean wasFocused = isFocused();
if (super.mouseClicked(pMouseX, pMouseY, pButton)) {
if (!wasFocused) {

View file

@ -388,7 +388,7 @@ public class FactoryPanelBehaviour extends FilteringBehaviour implements MenuPro
return;
}
timer = REQUEST_INTERVAL;
resetTimer();
if (recipeAddress.isBlank())
return;
@ -707,7 +707,7 @@ public class FactoryPanelBehaviour extends FilteringBehaviour implements MenuPro
PackagerBlockEntity packager = panelBE.getRestockedPackager();
if (packager == null)
return InventorySummary.EMPTY;
return packager.getAvailableItems();
return packager.getAvailableItems(true);
}
public int getPromised() {
@ -720,7 +720,7 @@ public class FactoryPanelBehaviour extends FilteringBehaviour implements MenuPro
if (panelBE().restocker) {
if (forceClearPromises) {
restockerPromises.forceClear(item);
timer = 0;
resetTimerSlightly();
}
forceClearPromises = false;
return restockerPromises.getTotalPromisedAndRemoveExpired(item, getPromiseExpiryTimeInTicks());
@ -729,13 +729,21 @@ public class FactoryPanelBehaviour extends FilteringBehaviour implements MenuPro
RequestPromiseQueue promises = Create.LOGISTICS.getQueuedPromises(network);
if (forceClearPromises) {
promises.forceClear(item);
timer = 0;
resetTimerSlightly();
}
forceClearPromises = false;
return promises == null ? 0 : promises.getTotalPromisedAndRemoveExpired(item, getPromiseExpiryTimeInTicks());
}
public void resetTimer() {
timer = REQUEST_INTERVAL;
}
public void resetTimerSlightly() {
timer = REQUEST_INTERVAL / 2;
}
private int getPromiseExpiryTimeInTicks() {
if (promiseClearingInterval == -1)
return -1;
@ -874,6 +882,7 @@ public class FactoryPanelBehaviour extends FilteringBehaviour implements MenuPro
blockEntity.setChanged();
blockEntity.sendData();
playFeedbackSound(this);
resetTimerSlightly();
if (!getWorld().isClientSide)
notifyRedstoneOutputs();
}

View file

@ -45,7 +45,6 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingRecipe;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.item.crafting.ShapedRecipe;
@ -673,8 +672,6 @@ public class FactoryPanelScreen extends AbstractSimiScreen {
availableCraftingRecipe = level.getRecipeManager()
.getAllRecipesFor(RecipeType.CRAFTING)
.parallelStream()
.filter(r -> r.value().getSerializer() == RecipeSerializer.SHAPED_RECIPE
|| r.value().getSerializer() == RecipeSerializer.SHAPELESS_RECIPE)
.filter(r -> output.getItem() == r.value().getResultItem(level.registryAccess())
.getItem())
.filter(r -> {

View file

@ -167,6 +167,10 @@ public class PackagerBlockEntity extends SmartBlockEntity {
}
public InventorySummary getAvailableItems() {
return getAvailableItems(false);
}
public InventorySummary getAvailableItems(boolean scanInputSlots) {
if (availableItems != null && invVersionTracker.stillWaiting(targetInventory.getInventory()))
return availableItems;
@ -186,9 +190,7 @@ public class PackagerBlockEntity extends SmartBlockEntity {
for (int slot = 0; slot < targetInv.getSlots(); slot++) {
int slotLimit = targetInv.getSlotLimit(slot);
@NotNull
ItemStack extractItem = targetInv.extractItem(slot, slotLimit, true);
availableItems.add(extractItem);
availableItems.add(scanInputSlots ? targetInv.getStackInSlot(slot) : targetInv.extractItem(slot, slotLimit, true));
}
invVersionTracker.awaitNewVersion(targetInventory.getInventory());

View file

@ -50,6 +50,7 @@ public class ItemVaultBlockEntity extends SmartBlockEntity implements IMultiBloc
protected void onContentsChanged(int slot) {
super.onContentsChanged(slot);
updateComparators();
level.blockEntityChanged(worldPosition);
}
};

View file

@ -63,9 +63,12 @@ public class SchematicAndQuillHandler {
if (bb.contains(projectedView))
delta *= -1;
int x = (int) (vec.getX() * delta);
int y = (int) (vec.getY() * delta);
int z = (int) (vec.getZ() * delta);
// Round away from zero to avoid an implicit floor
int intDelta = (int) (delta > 0 ? Math.ceil(delta) : Math.floor(delta));
int x = vec.getX() * intDelta;
int y = vec.getY() * intDelta;
int z = vec.getZ() * intDelta;
AxisDirection axisDirection = selectedFace.getAxisDirection();
if (axisDirection == AxisDirection.NEGATIVE)

View file

@ -95,8 +95,6 @@ public class SchematicHandler implements LayeredDraw.Layer {
if (activeSchematicItem != null && transformation != null)
transformation.tick();
renderers.forEach(SchematicRenderer::tick);
LocalPlayer player = mc.player;
ItemStack stack = findBlueprintInHand(player);
if (stack == null) {
@ -316,7 +314,7 @@ public class SchematicHandler implements LayeredDraw.Layer {
return false;
if (selectionScreen.focused) {
selectionScreen.cycle((int) delta);
selectionScreen.cycle((int) Math.signum(delta));
return true;
}
if (AllKeys.ctrlDown())

View file

@ -53,20 +53,17 @@ public class SchematicRenderer {
changed = true;
}
public void tick() {
if (!active)
return;
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || mc.player == null || !changed)
return;
redraw();
changed = false;
}
public void render(PoseStack ms, SuperRenderTypeBuffer buffers) {
if (!active)
return;
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || mc.player == null)
return;
if (changed)
redraw();
changed = false;
bufferCache.forEach((layer, buffer) -> {
buffer.renderInto(ms, buffers.getBuffer(layer));
});

View file

@ -406,7 +406,21 @@ public class CrushingRecipeGen extends ProcessingRecipeGen {
.output(0.75f, Mods.AET, "ambrosium_shard", 1)
.output(0.125f, Mods.AET, "holystone", 1)
.output(0.75f, AllItems.EXP_NUGGET.get())
.whenModLoaded(Mods.AET.getId()))
.whenModLoaded(Mods.AET.getId())),
// IE
IE_COKE_DUST = create(Mods.IE.recipeId("coal_coke"), b -> b.duration(200)
.require(Mods.IE, "coal_coke").output(Mods.IE, "dust_coke")
.whenModLoaded(Mods.IE.getId())),
IE_COKE_BLOCK = create(Mods.IE.recipeId("coke_block"), b -> b.duration(200)
.require(Mods.IE, "coke").output(1, Mods.IE.asResource("dust_coke"), 9)
.whenModLoaded(Mods.IE.getId())),
IE_SLAG_GRAVEL = create(Mods.IE.recipeId("slag"), b -> b.duration(200)
.require(Mods.IE, "slag").output(Mods.IE, "slag_gravel")
.whenModLoaded(Mods.IE.getId()));
;

View file

@ -1,6 +1,5 @@
package com.simibubi.create.infrastructure.config;
import com.simibubi.create.content.contraptions.ContraptionData;
import com.simibubi.create.content.contraptions.ContraptionMovementSetting;
import net.createmod.catnip.config.ConfigBase;
@ -32,8 +31,6 @@ public class CKinetics extends ConfigBase {
public final ConfigGroup contraptions = group(1, "contraptions", "Moving Contraptions");
public final ConfigInt maxBlocksMoved = i(2048, 1, "maxBlocksMoved", Comments.maxBlocksMoved);
public final ConfigInt maxDataSize =
i(ContraptionData.DEFAULT_LIMIT, 0, "maxDataSize", Comments.bytes, Comments.maxDataDisable, Comments.maxDataSize, Comments.maxDataSize2);
public final ConfigInt maxChassisRange = i(16, 1, "maxChassisRange", Comments.maxChassisRange);
public final ConfigInt maxPistonPoles = i(64, 1, "maxPistonPoles", Comments.maxPistonPoles);
public final ConfigInt maxRopeLength = i(384, 1, "maxRopeLength", Comments.maxRopeLength);
@ -86,9 +83,6 @@ public class CKinetics extends ConfigBase {
"multiplier used for calculating exhaustion from speed when a crank is turned.";
static String maxBlocksMoved =
"Maximum amount of blocks in a structure movable by Pistons, Bearings or other means.";
static String maxDataSize = "Maximum amount of data a contraption can have before it can't be synced with players.";
static String maxDataSize2 = "Un-synced contraptions will not be visible and will not have collision.";
static String maxDataDisable = "[0 to disable this limit]";
static String maxChassisRange = "Maximum value of a chassis attachment range.";
static String maxPistonPoles = "Maximum amount of extension poles behind a Mechanical Piston.";
static String maxRopeLength = "Max length of rope available off a Rope Pulley.";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 878 B

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 B

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 B

After

Width:  |  Height:  |  Size: 1 KiB