Merge remote-tracking branch 'origin/mc1.20.1/feature-dev' into mc1.21.1/dev
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -21,7 +21,6 @@ public enum Mods {
|
|||
AETHER_II,
|
||||
BETTEREND,
|
||||
COMPUTERCRAFT,
|
||||
CONNECTIVITY,
|
||||
CURIOS,
|
||||
DYNAMICTREES,
|
||||
FUNCTIONALSTORAGE,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
c.put("Data", block.nbt());
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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`.
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -29,6 +29,9 @@ public record RadialWrenchMenuSubmitPacket(BlockPos blockPos, BlockState newStat
|
|||
@Override
|
||||
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);
|
||||
|
|
|
@ -81,6 +81,12 @@ public abstract class CopycatModel extends BakedModelWrapperWithData {
|
|||
occlusionData.occlude(face);
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
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 (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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,12 +729,20 @@ 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)
|
||||
|
@ -874,6 +882,7 @@ public class FactoryPanelBehaviour extends FilteringBehaviour implements MenuPro
|
|||
blockEntity.setChanged();
|
||||
blockEntity.sendData();
|
||||
playFeedbackSound(this);
|
||||
resetTimerSlightly();
|
||||
if (!getWorld().isClientSide)
|
||||
notifyRedstoneOutputs();
|
||||
}
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -50,6 +50,7 @@ public class ItemVaultBlockEntity extends SmartBlockEntity implements IMultiBloc
|
|||
protected void onContentsChanged(int slot) {
|
||||
super.onContentsChanged(slot);
|
||||
updateComparators();
|
||||
level.blockEntityChanged(worldPosition);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
|
|
|
@ -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()));
|
||||
|
||||
|
||||
;
|
||||
|
|
|
@ -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.";
|
||||
|
|
Before Width: | Height: | Size: 878 B After Width: | Height: | Size: 870 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 400 B After Width: | Height: | Size: 768 B |
Before Width: | Height: | Size: 424 B After Width: | Height: | Size: 1 KiB |