diff --git a/api/src/main/java/org/allaymc/api/block/component/BlockBaseComponent.java b/api/src/main/java/org/allaymc/api/block/component/BlockBaseComponent.java index 9c2e1d7ffe..5317f895b4 100644 --- a/api/src/main/java/org/allaymc/api/block/component/BlockBaseComponent.java +++ b/api/src/main/java/org/allaymc/api/block/component/BlockBaseComponent.java @@ -53,11 +53,20 @@ default boolean canKeepExisting(BlockStateWithPos current, BlockStateWithPos nei void onNeighborUpdate(BlockStateWithPos current, BlockStateWithPos neighbor, BlockFace face); /** - * Called when the block encounters random tick update. + * Called when the block encounters random update. * - * @param blockState the block. + * @param blockStateWithPos the block that encountered the random update. */ - void onRandomUpdate(BlockStateWithPos blockState); + default void onRandomUpdate(BlockStateWithPos blockStateWithPos) {} + + /** + * Check if the block can receive random updates. + * + * @return {@code true} if the block can receive random updates, {@code false} otherwise. + */ + default boolean canRandomUpdate() { + return false; + } /** * Try to place a block. @@ -116,22 +125,27 @@ default boolean canKeepExisting(BlockStateWithPos current, BlockStateWithPos nei * For example, if a water block is in layer 1 and layer 0 is replaced with air, * then the water block's onNeighborLayerReplace() method will be called. * - * @param currentBlockState The block that is being replaced. - * @param newBlockState The block that is replacing the current block. - * @param placementInfo The player placement info, can be null. + * @param currentBlockState the block that is being replaced. + * @param newBlockState the block that is replacing the current block. + * @param placementInfo the player placement info, can be null. */ void afterNeighborLayerReplace(BlockStateWithPos currentBlockState, BlockState newBlockState, PlayerInteractInfo placementInfo); /** * Called when a block is broken by non-creative game mode player. * - * @param blockState The block that was broken. - * @param usedItem The item that was used to break the block, can be {@code null}. - * @param entity The player who broke the block, can be {@code null}. + * @param blockState the block that was broken. + * @param usedItem the item that was used to break the block, can be {@code null}. + * @param entity the player who broke the block, can be {@code null}. */ void onBreak(BlockStateWithPos blockState, ItemStack usedItem, Entity entity); - void onScheduledUpdate(BlockStateWithPos blockState); + /** + * Called when a block receives a scheduled update. + * + * @param blockStateWithPos the block that received the scheduled update. + */ + default void onScheduledUpdate(BlockStateWithPos blockStateWithPos) {} /** * Get the block's drops when it is broke by item normally. diff --git a/api/src/main/java/org/allaymc/api/eventbus/event/block/BlockRandomUpdateEvent.java b/api/src/main/java/org/allaymc/api/eventbus/event/block/BlockRandomUpdateEvent.java new file mode 100644 index 0000000000..b2252efec0 --- /dev/null +++ b/api/src/main/java/org/allaymc/api/eventbus/event/block/BlockRandomUpdateEvent.java @@ -0,0 +1,13 @@ +package org.allaymc.api.eventbus.event.block; + +import org.allaymc.api.block.dto.BlockStateWithPos; +import org.allaymc.api.eventbus.event.CancellableEvent; + +/** + * @author daoge_cmd + */ +public class BlockRandomUpdateEvent extends BlockEvent implements CancellableEvent { + public BlockRandomUpdateEvent(BlockStateWithPos block) { + super(block); + } +} diff --git a/server/src/main/java/org/allaymc/server/block/component/BlockBaseComponentImpl.java b/server/src/main/java/org/allaymc/server/block/component/BlockBaseComponentImpl.java index 27ee5e93b3..6c6b1a57ad 100644 --- a/server/src/main/java/org/allaymc/server/block/component/BlockBaseComponentImpl.java +++ b/server/src/main/java/org/allaymc/server/block/component/BlockBaseComponentImpl.java @@ -53,12 +53,6 @@ public void onNeighborUpdate(BlockStateWithPos current, BlockStateWithPos neighb } } - @Override - public void onRandomUpdate(BlockStateWithPos blockState) {} - - @Override - public void onScheduledUpdate(BlockStateWithPos blockState) {} - @Override public Set getDrops(BlockStateWithPos blockState, ItemStack usedItem, Entity entity) { var vanillaBlockId = BlockId.fromIdentifier(blockType.getIdentifier()); diff --git a/server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java b/server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java index a16ee24cb8..cde1915c97 100644 --- a/server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java +++ b/server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java @@ -22,6 +22,7 @@ import org.allaymc.api.world.DimensionInfo; import org.allaymc.api.world.biome.BiomeType; import org.allaymc.api.world.chunk.*; +import org.allaymc.api.world.gamerule.GameRule; import org.allaymc.api.world.storage.WorldStorage; import org.allaymc.server.world.service.AllayLightService; import org.cloudburstmc.nbt.NbtUtils; @@ -31,6 +32,7 @@ import org.jetbrains.annotations.UnmodifiableView; import java.util.*; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.locks.StampedLock; import java.util.function.Consumer; import java.util.function.Predicate; @@ -79,6 +81,7 @@ public void tick(long currentTick, Dimension dimension, WorldStorage worldStorag unsafeChunk.getBlockEntitiesUnsafe().values().forEach(blockEntity -> blockEntity.tick(currentTick)); unsafeChunk.getEntitiesUnsafe().values().forEach(entity -> entity.tick(currentTick)); tickScheduledUpdates(dimension); + tickRandomUpdates(dimension); checkAutoSave(worldStorage); } @@ -104,7 +107,7 @@ protected void tickScheduledUpdates(Dimension dimension) { var blockState = getBlockState(localX, y, localZ, layer); var blockStateWithPos = new BlockStateWithPos(blockState, new Position3i(localX + (unsafeChunk.x << 4), y, localZ + (unsafeChunk.z << 4), dimension), layer); - if (!callScheduleUpdateEvent(blockStateWithPos)) { + if (!new BlockScheduleUpdateEvent(blockStateWithPos).call()) { return; } @@ -112,8 +115,27 @@ protected void tickScheduledUpdates(Dimension dimension) { }); } - protected boolean callScheduleUpdateEvent(BlockStateWithPos block) { - return new BlockScheduleUpdateEvent(block).call(); + protected void tickRandomUpdates(Dimension dimension) { + int randomTickSpeed = dimension.getWorld().getWorldData().getGameRuleValue(GameRule.RANDOM_TICK_SPEED); + if (randomTickSpeed == 0) { + return; + } + + for (var section : unsafeChunk.getSections()) { + int sectionY = section.sectionY(); + for (int i = 0; i < randomTickSpeed; i++) { + int n = ThreadLocalRandom.current().nextInt(); + int localX = n & 0xF; + int localZ = n >> 8 & 0xF; + int localY = n >> 16 & 0xF; + + var blockState = section.getBlockState(localX, localY, localZ, 0); + if (blockState.getBehavior().canRandomUpdate()) { + var blockStateWithPos = new BlockStateWithPos(blockState, new Position3i(localX + (unsafeChunk.x << 4), localY + (sectionY << 4), localZ + (unsafeChunk.z << 4), dimension), 0); + blockState.getBehavior().onRandomUpdate(blockStateWithPos); + } + } + } } protected void checkAutoSave(WorldStorage worldStorage) {