diff --git a/src/main/java/com/ldtteam/blockui/BOGuiGraphics.java b/src/main/java/com/ldtteam/blockui/BOGuiGraphics.java index 0046281..3159102 100644 --- a/src/main/java/com/ldtteam/blockui/BOGuiGraphics.java +++ b/src/main/java/com/ldtteam/blockui/BOGuiGraphics.java @@ -9,6 +9,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.MultiBufferSource.BufferSource; @@ -184,4 +185,9 @@ public void popMvPose() RenderSystem.getModelViewStack().popMatrix(); RenderSystem.applyModelViewMatrix(); } + + public static double getAltSpeedFactor() + { + return Screen.hasAltDown() ? 5 : 1; + } } diff --git a/src/main/java/com/ldtteam/blockui/mod/ClientEventSubscriber.java b/src/main/java/com/ldtteam/blockui/mod/ClientEventSubscriber.java index 4f5205a..496e468 100644 --- a/src/main/java/com/ldtteam/blockui/mod/ClientEventSubscriber.java +++ b/src/main/java/com/ldtteam/blockui/mod/ClientEventSubscriber.java @@ -12,7 +12,6 @@ import com.ldtteam.blockui.mod.container.ContainerHook; import com.ldtteam.blockui.util.resloc.OutOfJarResourceLocation; import com.ldtteam.blockui.views.BOWindow; -import com.ldtteam.common.util.BlockToItemHelper; import com.mojang.blaze3d.platform.InputConstants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; @@ -22,7 +21,6 @@ import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.ModList; -import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; import net.neoforged.neoforge.client.event.ClientTickEvent; import net.neoforged.neoforge.client.event.RenderGuiLayerEvent; import net.neoforged.neoforge.client.event.InputEvent.MouseScrollingEvent; @@ -175,19 +173,4 @@ public static void renderOverlay(final RenderGuiLayerEvent.Pre event) event.setCanceled(true); } } - - @SubscribeEvent - public static void onClientPlayerLoggingOut(final ClientPlayerNetworkEvent.LoggingOut event) - { - BlockToItemHelper.releaseFakeLevelInstance(); - } - - @SubscribeEvent - public static void onClientPlayerDimChange(final ClientPlayerNetworkEvent.Clone event) - { - if (event.getNewPlayer().level() != event.getOldPlayer().level()) - { - BlockToItemHelper.releaseFakeLevelInstance(); - } - } } diff --git a/src/main/java/com/ldtteam/blockui/views/ScrollingView.java b/src/main/java/com/ldtteam/blockui/views/ScrollingView.java index 9805209..622cb28 100644 --- a/src/main/java/com/ldtteam/blockui/views/ScrollingView.java +++ b/src/main/java/com/ldtteam/blockui/views/ScrollingView.java @@ -1,5 +1,6 @@ package com.ldtteam.blockui.views; +import com.ldtteam.blockui.BOGuiGraphics; import com.ldtteam.blockui.Pane; import com.ldtteam.blockui.PaneParams; import com.ldtteam.blockui.controls.Scrollbar; @@ -75,7 +76,7 @@ public void setSize(final int w, final int h) @Override public boolean scrollInput(final double horizontalWheel, final double verticalWheel, final double mx, final double my) { - return setScrollY(getScrollY() - verticalWheel); + return setScrollY(getScrollY() - verticalWheel * BOGuiGraphics.getAltSpeedFactor()); } public ScrollingContainer getContainer() diff --git a/src/main/java/com/ldtteam/blockui/views/ZoomDragView.java b/src/main/java/com/ldtteam/blockui/views/ZoomDragView.java index 2a60aab..9476f1c 100644 --- a/src/main/java/com/ldtteam/blockui/views/ZoomDragView.java +++ b/src/main/java/com/ldtteam/blockui/views/ZoomDragView.java @@ -233,8 +233,8 @@ public boolean onMouseDrag(final double startX, final double startY, final int s final boolean childResult = super.onMouseDrag(startX, startY, speed, calcRelativeX(x), calcRelativeY(y)); if (!childResult && dragEnabled) { - setScrollX(scrollX - x * dragFactor); - setScrollY(scrollY - y * dragFactor); + setScrollX(scrollX - x * dragFactor * BOGuiGraphics.getAltSpeedFactor()); + setScrollY(scrollY - y * dragFactor * BOGuiGraphics.getAltSpeedFactor()); return true; } return childResult; @@ -250,6 +250,7 @@ public boolean scrollInput(final double horizontalWheel, final double verticalWh final double childY = my - y; final double oldX = (childX + scrollX) / scale; final double oldY = (childY + scrollY) / scale; + final double zoomFactor = this.zoomFactor * BOGuiGraphics.getAltSpeedFactor(); scale = verticalWheel < 0 ? scale / zoomFactor : scale * zoomFactor; // try to round if around whole number (cuz of text texture) diff --git a/src/main/java/com/ldtteam/common/fakelevel/FakeChunk.java b/src/main/java/com/ldtteam/common/fakelevel/FakeChunk.java index dd2ab73..57d958a 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/FakeChunk.java +++ b/src/main/java/com/ldtteam/common/fakelevel/FakeChunk.java @@ -53,10 +53,19 @@ public class FakeChunk extends LevelChunk { private final FakeLevel fakeLevel; + // section cache + int lastY; + LevelChunkSection lastSection = null; + public FakeChunk(final FakeLevel worldIn, final int x, final int z) { super(worldIn, new ChunkPos(x, z)); this.fakeLevel = worldIn; + + // set itself to cache + fakeLevel.lastX = x; + fakeLevel.lastZ = z; + fakeLevel.lastChunk = this; } // ======================================== @@ -220,6 +229,10 @@ public LevelChunkSection[] getSections() @Override public LevelChunkSection getSection(int yIdx) { + if (lastY == yIdx && lastSection != null) + { + return lastSection; + } return new FakeLevelChunkSection(this, yIdx); } diff --git a/src/main/java/com/ldtteam/common/fakelevel/FakeLevel.java b/src/main/java/com/ldtteam/common/fakelevel/FakeLevel.java index 07523a2..d0a89a9 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/FakeLevel.java +++ b/src/main/java/com/ldtteam/common/fakelevel/FakeLevel.java @@ -109,6 +109,10 @@ public class FakeLevel extends Level */ protected BlockPos worldPos = BlockPos.ZERO; + // chunk cache + int lastX, lastZ; + ChunkAccess lastChunk = null; + /** * @param levelSource data source, also try to set block entities/entities collections * @param lightProvider light source @@ -131,7 +135,7 @@ public FakeLevel(final SOURCE levelSource, realLevel.registryAccess(), realLevel.dimensionTypeRegistration(), realLevel.getProfilerSupplier(), - true, + realLevel.isClientSide(), false, 0, 0); @@ -158,6 +162,11 @@ public void setRealLevel(final Level realLevel) return; } + if (realLevel != null && realLevel.isClientSide != this.isClientSide) + { + throw new IllegalArgumentException("Received wrong sided realLevel - fakeLevel.isClientSide = " + this.isClientSide); + } + this.realLevel = realLevel; ((FakeLevelData) this.getLevelData()).vanillaLevelData = realLevel == null ? null : realLevel.getLevelData(); } @@ -201,7 +210,7 @@ public BlockPos getWorldPos() /** * For better block entity handling in chunk methods. If set then {@link IFakeLevelBlockGetter#getBlockEntity(BlockPos) - * levelSource.getBlockEntity(BlockPos)} is not used + * levelSource.getBlockEntity(BlockPos)} is not used. Reset with empty collection * * @param blockEntities all block entities, should be data equivalent to levelSource */ @@ -211,11 +220,11 @@ public void setBlockEntities(final Map blockEntities) } /** - * @param entities all entities, their level should be this fake level instance + * @param entities all entities, their level should be this fake level instance. Reset with empty collection */ public void setEntities(final Collection entities) { - levelEntityGetter = FakeLevelEntityGetterAdapter.ofEntities(entities); + levelEntityGetter = entities.isEmpty() ? FakeLevelEntityGetterAdapter.EMPTY : FakeLevelEntityGetterAdapter.ofEntities(entities); } // ======================================== @@ -296,6 +305,10 @@ public BlockState getBlockState(final BlockPos pos) @Override public ChunkAccess getChunk(int x, int z, ChunkStatus requiredStatus, boolean nonnull) { + if (lastX == x && lastZ == z && lastChunk != null) + { + return lastChunk; + } return nonnull || hasChunk(x, z) ? new FakeChunk(this, x, z) : null; } @@ -368,7 +381,7 @@ public boolean isInWorldBounds(final BlockPos pos) @Override public CrashReportCategory fillReportDetails(CrashReport report) { - CrashReportCategory crashreportcategory = report.addCategory("Structurize fake level"); + CrashReportCategory crashreportcategory = report.addCategory("BlockUI fake level"); levelSource.describeSelfInCrashReport(crashreportcategory); return crashreportcategory; } diff --git a/src/main/java/com/ldtteam/common/fakelevel/FakeLevelChunkSection.java b/src/main/java/com/ldtteam/common/fakelevel/FakeLevelChunkSection.java index 4b15c72..a0b3e10 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/FakeLevelChunkSection.java +++ b/src/main/java/com/ldtteam/common/fakelevel/FakeLevelChunkSection.java @@ -30,6 +30,10 @@ public FakeLevelChunkSection(final FakeChunk fakeChunk, final int yIdx) super(null, null); this.fakeChunk = fakeChunk; this.yIdx = yIdx; + + // set itself to cache + fakeChunk.lastY = yIdx; + fakeChunk.lastSection = this; } private BlockPos formGlobalPos(int x, int y, int z) diff --git a/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelBlockGetter.java b/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelBlockGetter.java index 7aefae3..8066728 100644 --- a/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelBlockGetter.java +++ b/src/main/java/com/ldtteam/common/fakelevel/IFakeLevelBlockGetter.java @@ -3,7 +3,6 @@ import net.minecraft.CrashReportCategory; import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.Fluids; @@ -126,40 +125,4 @@ default AABB getAABB() { return new AABB(getMinX(), getMinBuildHeight(), getMinZ(), getMaxX(), getMaxBuildHeight(), getMaxZ()); } - - public static class SingleBlockFakeLevelGetter implements IFakeLevelBlockGetter - { - public BlockState blockState = null; - public BlockEntity blockEntity = null; - - @Override - public BlockEntity getBlockEntity(final BlockPos pos) - { - return blockEntity; - } - - @Override - public BlockState getBlockState(final BlockPos pos) - { - return blockState; - } - - @Override - public int getHeight() - { - return 1; - } - - @Override - public int getSizeX() - { - return 1; - } - - @Override - public int getSizeZ() - { - return 1; - } - } } diff --git a/src/main/java/com/ldtteam/common/fakelevel/SingleBlockFakeLevel.java b/src/main/java/com/ldtteam/common/fakelevel/SingleBlockFakeLevel.java new file mode 100644 index 0000000..87b5e3b --- /dev/null +++ b/src/main/java/com/ldtteam/common/fakelevel/SingleBlockFakeLevel.java @@ -0,0 +1,176 @@ +package com.ldtteam.common.fakelevel; + +import net.minecraft.CrashReportCategory; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Simple implementation of {@link IFakeLevelBlockGetter} mostly for usage in methods where {@link Level} is needed for virtual + * BE/entities etc. + */ +public class SingleBlockFakeLevel extends FakeLevel +{ + /** + * Creates simple fakeLevel instance + * + * @param realLevel actual valid vanilla instance to provide eg. registries + */ + public SingleBlockFakeLevel(final Level realLevel) + { + super(new SingleBlockFakeLevelGetter(), IFakeLevelLightProvider.USE_CLIENT_LEVEL, realLevel, null, true); + } + + /** + * Do not forget to unset to prevent potential memory leaks + * + * @param blockState related to blockEntity + * @param blockEntity related to blockState + * @param realLevel actual valid vanilla instance to provide eg. registries + * @see #unset(FakeLevel, BlockEntity) + * @see FakeLevel#setEntities(Collection) FakeLevel#setEntities(Collection) if you want to add entities, do not forget to reset + */ + public void prepare(final BlockState blockState, @Nullable final BlockEntity blockEntity, final Level realLevel) + { + getLevelSource().blockEntity = blockEntity; + getLevelSource().blockState = blockState; + setRealLevel(realLevel); + + if (blockEntity != null) + { + blockEntity.setLevel(this); + } + } + + /** + * @param blockEntity to unlink level if needed + * @see #prepare(FakeLevel, BlockState, BlockEntity, Level) + */ + public void unset(@Nullable final BlockEntity blockEntity) + { + getLevelSource().blockEntity = null; + getLevelSource().blockState = null; + setRealLevel(null); + + if (blockEntity != null) + { + try + { + blockEntity.setLevel(null); + } + catch (final NullPointerException e) + { + // setLevel impls sometimes violates nullability of level field + } + } + } + + /** + * See related methods for more information. + * + * @param blockState related to blockEntity + * @param blockEntity related to blockState + * @param realLevel actual valid vanilla instance to provide eg. registries + * @param action context action + * @see #prepare(FakeLevel, BlockState, BlockEntity, Level) + * @see #unset(FakeLevel, BlockEntity) + */ + public void withFakeLevelContext(final BlockState blockState, + @Nullable final BlockEntity blockEntity, + final Level realLevel, + final Consumer action) + { + prepare(blockState, blockEntity, realLevel); + action.accept(this); + unset(blockEntity); + } + + /** + * See related methods for more information. + * + * @param blockState related to blockEntity + * @param blockEntity related to blockState + * @param realLevel actual valid vanilla instance to provide eg. registries + * @param action context action + * @see #prepare(FakeLevel, BlockState, BlockEntity, Level) + * @see #unset(FakeLevel, BlockEntity) + */ + public T useFakeLevelContext(final BlockState blockState, + @Nullable final BlockEntity blockEntity, + final Level realLevel, + final Function action) + { + prepare(blockState, blockEntity, realLevel); + final T result = action.apply(this); + unset(blockEntity); + return result; + } + + public static class SingleBlockFakeLevelGetter implements IFakeLevelBlockGetter + { + public BlockState blockState = null; + public BlockEntity blockEntity = null; + + @Override + public BlockEntity getBlockEntity(final BlockPos pos) + { + return blockEntity; + } + + @Override + public BlockState getBlockState(final BlockPos pos) + { + return blockState; + } + + @Override + public int getHeight() + { + return 1; + } + + @Override + public int getSizeX() + { + return 1; + } + + @Override + public int getSizeZ() + { + return 1; + } + + @Override + public void describeSelfInCrashReport(final CrashReportCategory category) + { + category.setDetail("Single block", blockState::toString); + category.setDetail("Single block entity type", + () -> blockEntity == null ? null : BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(blockEntity.getType()).toString()); + } + } + + public static class SidedSingleBlockFakeLevel + { + private SingleBlockFakeLevel client; + private SingleBlockFakeLevel server; + + public SingleBlockFakeLevel get(final Level realLevel) + { + if (realLevel.isClientSide()) + { + return client != null ? client : (client = new SingleBlockFakeLevel(realLevel)); + } + else + { + return server != null ? server : (server = new SingleBlockFakeLevel(realLevel)); + } + } + } +} diff --git a/src/main/java/com/ldtteam/common/util/BlockToItemHelper.java b/src/main/java/com/ldtteam/common/util/BlockToItemHelper.java index 515596c..dc85834 100644 --- a/src/main/java/com/ldtteam/common/util/BlockToItemHelper.java +++ b/src/main/java/com/ldtteam/common/util/BlockToItemHelper.java @@ -1,9 +1,7 @@ package com.ldtteam.common.util; import com.ldtteam.blockui.mod.item.BlockStateRenderingData; -import com.ldtteam.common.fakelevel.FakeLevel; -import com.ldtteam.common.fakelevel.IFakeLevelBlockGetter.SingleBlockFakeLevelGetter; -import com.ldtteam.common.fakelevel.IFakeLevelLightProvider; +import com.ldtteam.common.fakelevel.SingleBlockFakeLevel.SidedSingleBlockFakeLevel; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.server.level.ServerLevel; @@ -31,21 +29,12 @@ */ public class BlockToItemHelper { - private static final HitResult ZERO_POS_HIT_RESULT = new BlockHitResult(Vec3.atCenterOf(BlockPos.ZERO), Direction.NORTH, BlockPos.ZERO, true); - private static FakeLevel fakeLevel; - - public static void releaseFakeLevelInstance() - { - if (fakeLevel != null) - { - fakeLevel.setRealLevel(null); - fakeLevel = null; - } - } + public static final HitResult ZERO_POS_HIT_RESULT = new BlockHitResult(Vec3.atCenterOf(BlockPos.ZERO), Direction.NORTH, BlockPos.ZERO, true); + private static final SidedSingleBlockFakeLevel fakeLevel = new SidedSingleBlockFakeLevel(); /** * Mostly for use in UI where you dont have level instance (eg. player selects block, from xml, but not when displaying real world - * info - see {@link BlockStateRenderingData#of(Level, BlockPos, Player)}). NOT thread safe! + * info - see {@link BlockStateRenderingData#of(Level, BlockPos, Player)}). * * @return result of player middle-mouse-button click with more sensible defaults (liquids -> buckets, fire -> flint+steel), might * be {@link ItemStack#isEmpty()} in case of error @@ -58,16 +47,12 @@ public static ItemStack getItemStack(final BlockState blockState, final BlockEnt return ItemStack.EMPTY; } - if (fakeLevel == null) - { - fakeLevel = new FakeLevel<>(new SingleBlockFakeLevelGetter(), IFakeLevelLightProvider.USE_CLIENT_LEVEL, player.level(), null, true); - } + // client vs server concurrency - we dont care if create two instances, the other should just disappear - fakeLevel.setRealLevel(player.level()); - fakeLevel.getLevelSource().blockState = blockState; - fakeLevel.getLevelSource().blockEntity = blockEntity; - - return getItemStackUsingPlayerPick(fakeLevel, BlockPos.ZERO, player, ZERO_POS_HIT_RESULT); + return fakeLevel.get(player.level()).useFakeLevelContext(blockState, + blockEntity, + player.level(), + level -> getItemStackUsingPlayerPick(level, BlockPos.ZERO, player, ZERO_POS_HIT_RESULT)); } /** @@ -104,7 +89,7 @@ public static ItemStack getItemStackUsingPlayerPick(final Level level, final Blo } final BlockState blockState = level.getBlockState(pos); - ItemStack result = blockState.getCloneItemStack(hitResult, fakeLevel, pos, player); + ItemStack result = blockState.getCloneItemStack(hitResult, level, pos, player); if (result.isEmpty()) { @@ -118,7 +103,7 @@ public static ItemStack getItemStackUsingPlayerPick(final Level level, final Blo * @param blockState source for item * @return vanilla result with few fixes */ - private static Item getItem(final BlockState blockState) + public static Item getItem(final BlockState blockState) { final Block block = blockState.getBlock(); if (block instanceof final LiquidBlock liquid)