diff --git a/src/main/java/org/dimdev/dimdoors/client/CustomBreakBlockHandler.java b/src/main/java/org/dimdev/dimdoors/client/CustomBreakBlockHandler.java new file mode 100644 index 00000000..0240ba0b --- /dev/null +++ b/src/main/java/org/dimdev/dimdoors/client/CustomBreakBlockHandler.java @@ -0,0 +1,62 @@ +package org.dimdev.dimdoors.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.util.math.BlockPos; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Environment(EnvType.CLIENT) +public class CustomBreakBlockHandler { + private static final Map customBreakBlockMap = new HashMap(); + + public static Map getCustomBreakBlockMap(int ticks) { + Set expired = customBreakBlockMap.entrySet().stream().filter(entry -> ticks - entry.getValue().getLastUpdateTick() > 400).map(entry -> entry.getKey()).collect(Collectors.toSet()); + expired.forEach(customBreakBlockMap::remove); + return customBreakBlockMap; + } + + public static void customBreakBlock(BlockPos pos, int stage, int ticks) { + if (stage < 0 || stage > 10) { + customBreakBlockMap.remove(pos); + } else { + if (customBreakBlockMap.containsKey(pos)) { + + BreakBlockInfo info = customBreakBlockMap.get(pos); + info.setStage(stage); + info.setLastUpdateTick(ticks); + } else { + customBreakBlockMap.put(pos, new BreakBlockInfo(stage, ticks)); + } + } + } + + public static class BreakBlockInfo { + private int stage; + private int lastUpdateTick; + + private BreakBlockInfo(int stage, int lastUpdateTick) { + this.stage = stage; + this.lastUpdateTick = lastUpdateTick; + } + + public void setStage(int stage) { + this.stage = stage; + } + + public int getStage() { + return stage; + } + + public void setLastUpdateTick(int lastUpdateTick) { + this.lastUpdateTick = lastUpdateTick; + } + + public int getLastUpdateTick() { + return lastUpdateTick; + } + } +} diff --git a/src/main/java/org/dimdev/dimdoors/mixin/ServerWorldMixin.java b/src/main/java/org/dimdev/dimdoors/mixin/ServerWorldMixin.java new file mode 100644 index 00000000..1d4e0e61 --- /dev/null +++ b/src/main/java/org/dimdev/dimdoors/mixin/ServerWorldMixin.java @@ -0,0 +1,19 @@ +package org.dimdev.dimdoors.mixin; + +import net.minecraft.server.world.ServerWorld; +import org.dimdev.dimdoors.world.decay.LimboDecay; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.function.BooleanSupplier; + +@Mixin(ServerWorld.class) +public abstract class ServerWorldMixin { + + @Inject(method = "tick(Ljava/util/function/BooleanSupplier;)V", at = @At(target = "Lnet/minecraft/server/world/ServerWorld;fluidTickScheduler:Lnet/minecraft/server/world/ServerTickScheduler;", value = "FIELD", ordinal = 0, shift = At.Shift.AFTER)) + public void afterScheduledTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci) { + LimboDecay.tick((ServerWorld) (Object) this); + } +} diff --git a/src/main/java/org/dimdev/dimdoors/mixin/client/WorldRendererMixin.java b/src/main/java/org/dimdev/dimdoors/mixin/client/WorldRendererMixin.java index 1c982a06..8f904809 100644 --- a/src/main/java/org/dimdev/dimdoors/mixin/client/WorldRendererMixin.java +++ b/src/main/java/org/dimdev/dimdoors/mixin/client/WorldRendererMixin.java @@ -1,6 +1,11 @@ package org.dimdev.dimdoors.mixin.client; import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.render.*; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import org.dimdev.dimdoors.client.CustomBreakBlockHandler; import org.dimdev.dimdoors.listener.pocket.PocketListenerUtil; import org.dimdev.dimdoors.world.ModDimensions; import org.dimdev.dimdoors.world.pocket.type.addon.SkyAddon; @@ -13,19 +18,11 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.render.BufferBuilder; -import net.minecraft.client.render.BufferRenderer; -import net.minecraft.client.render.GameRenderer; -import net.minecraft.client.render.Tessellator; -import net.minecraft.client.render.VertexFormat; -import net.minecraft.client.render.VertexFormats; -import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.world.ClientWorld; import net.minecraft.util.Identifier; import net.minecraft.util.math.Matrix4f; import net.minecraft.util.math.Vec3f; -import net.minecraft.util.registry.Registry; import net.minecraft.util.registry.RegistryKey; import net.minecraft.world.World; @@ -33,6 +30,7 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import java.util.List; +import java.util.Map; @Mixin(WorldRenderer.class) @Environment(EnvType.CLIENT) @@ -48,6 +46,13 @@ public abstract class WorldRendererMixin { @Shadow private MinecraftClient client; + @Shadow + @Final + private BufferBuilderStorage bufferBuilders; + + @Shadow + private int ticks; + @Shadow @Final private static Identifier END_SKY; @@ -207,4 +212,32 @@ public abstract class WorldRendererMixin { RenderSystem.enableTexture(); RenderSystem.disableBlend(); } + + @Inject(method = "render(Lnet/minecraft/client/util/math/MatrixStack;FJZLnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/GameRenderer;Lnet/minecraft/client/render/LightmapTextureManager;Lnet/minecraft/util/math/Matrix4f;)V", + at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/WorldRenderer;blockBreakingProgressions:Lit/unimi/dsi/fastutil/longs/Long2ObjectMap;", ordinal = 1)) // bytecode order is flipped from java code order, notice the ordinal + public void renderCustomBreakBlockAnimation(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f, CallbackInfo ci) { + Vec3d vec3d = camera.getPos(); + double d = vec3d.getX(); + double e = vec3d.getY(); + double f = vec3d.getZ(); + + Map breakBlocks = CustomBreakBlockHandler.getCustomBreakBlockMap(this.ticks); + + // stolen from WorldRenderer#render + for (Map.Entry entry : breakBlocks.entrySet()) { + BlockPos pos = entry.getKey(); + double h = (double) pos.getX() - d; + double x = (double) pos.getY() - e; + double y = (double) pos.getZ() - f; + if (!(h * h + x * x + y * y > 1024.0D)) { + int stage = entry.getValue().getStage(); + matrices.push(); + matrices.translate((double) pos.getX() - d, (double) pos.getY() - e, (double) pos.getZ() - f); + MatrixStack.Entry entry3 = matrices.peek(); + VertexConsumer vertexConsumer2 = new OverlayVertexConsumer(this.bufferBuilders.getEffectVertexConsumers().getBuffer((RenderLayer) ModelLoader.BLOCK_DESTRUCTION_RENDER_LAYERS.get(stage)), entry3.getModel(), entry3.getNormal()); + this.client.getBlockRenderManager().renderDamage(this.world.getBlockState(pos), pos, this.world, matrices, vertexConsumer2); + matrices.pop(); + } + } + } } diff --git a/src/main/java/org/dimdev/dimdoors/mixin/client/accessor/WorldRendererAccessor.java b/src/main/java/org/dimdev/dimdoors/mixin/client/accessor/WorldRendererAccessor.java new file mode 100644 index 00000000..2c55758e --- /dev/null +++ b/src/main/java/org/dimdev/dimdoors/mixin/client/accessor/WorldRendererAccessor.java @@ -0,0 +1,11 @@ +package org.dimdev.dimdoors.mixin.client.accessor; + +import net.minecraft.client.render.WorldRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(WorldRenderer.class) +public interface WorldRendererAccessor { + @Accessor + int getTicks(); +} diff --git a/src/main/java/org/dimdev/dimdoors/network/ExtendedServerPlayNetworkHandler.java b/src/main/java/org/dimdev/dimdoors/network/ExtendedServerPlayNetworkHandler.java index 59835c2e..544396e3 100644 --- a/src/main/java/org/dimdev/dimdoors/network/ExtendedServerPlayNetworkHandler.java +++ b/src/main/java/org/dimdev/dimdoors/network/ExtendedServerPlayNetworkHandler.java @@ -1,8 +1,13 @@ package org.dimdev.dimdoors.network; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; public interface ExtendedServerPlayNetworkHandler { + static ExtendedServerPlayNetworkHandler get(ServerPlayNetworkHandler networkHandler) { + return (ExtendedServerPlayNetworkHandler) networkHandler; + } + ServerPacketHandler getDimDoorsPacketHandler(); MinecraftServer dimdoorsGetServer(); diff --git a/src/main/java/org/dimdev/dimdoors/network/client/ClientPacketHandler.java b/src/main/java/org/dimdev/dimdoors/network/client/ClientPacketHandler.java index 9e75c9b2..6a9debdb 100644 --- a/src/main/java/org/dimdev/dimdoors/network/client/ClientPacketHandler.java +++ b/src/main/java/org/dimdev/dimdoors/network/client/ClientPacketHandler.java @@ -7,21 +7,20 @@ import java.util.List; import java.util.Set; import java.util.function.Supplier; +import net.minecraft.client.render.WorldRenderer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dimdev.dimdoors.client.CustomBreakBlockHandler; import org.dimdev.dimdoors.entity.MonolithEntity; +import org.dimdev.dimdoors.mixin.client.accessor.WorldRendererAccessor; import org.dimdev.dimdoors.network.SimplePacket; import org.dimdev.dimdoors.network.packet.c2s.NetworkHandlerInitializedC2SPacket; -import org.dimdev.dimdoors.network.packet.s2c.MonolithAggroParticlesPacket; -import org.dimdev.dimdoors.network.packet.s2c.MonolithTeleportParticlesPacket; -import org.dimdev.dimdoors.network.packet.s2c.PlayerInventorySlotUpdateS2CPacket; -import org.dimdev.dimdoors.network.packet.s2c.SyncPocketAddonsS2CPacket; +import org.dimdev.dimdoors.network.packet.s2c.*; import org.dimdev.dimdoors.particle.client.MonolithParticle; import org.dimdev.dimdoors.world.pocket.type.addon.AutoSyncedAddon; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.network.PacketByteBuf; import net.minecraft.util.Identifier; import net.minecraft.util.registry.RegistryKey; import net.minecraft.world.World; @@ -30,7 +29,6 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -import net.fabricmc.fabric.api.networking.v1.PacketSender; @Environment(EnvType.CLIENT) public class ClientPacketHandler implements ClientPacketListener { @@ -54,6 +52,7 @@ public class ClientPacketHandler implements ClientPacketListener { registerReceiver(SyncPocketAddonsS2CPacket.ID, SyncPocketAddonsS2CPacket::new); registerReceiver(MonolithAggroParticlesPacket.ID, MonolithAggroParticlesPacket::new); registerReceiver(MonolithTeleportParticlesPacket.ID, MonolithTeleportParticlesPacket::new); + registerReceiver(RenderBreakBlockS2CPacket.ID, RenderBreakBlockS2CPacket::new); sendPacket(new NetworkHandlerInitializedC2SPacket()); } @@ -141,4 +140,11 @@ public class ClientPacketHandler implements ClientPacketListener { //noinspection ConstantConditions client.execute(() -> client.particleManager.addParticle(new MonolithParticle(client.world, client.player.getX(), client.player.getY(), client.player.getZ()))); } + + @Override + public void onRenderBreakBlock(RenderBreakBlockS2CPacket packet) { + MinecraftClient.getInstance().executeTask(() -> { + CustomBreakBlockHandler.customBreakBlock(packet.getPos(), packet.getStage(), ((WorldRendererAccessor) MinecraftClient.getInstance().worldRenderer).getTicks()); + }); + } } diff --git a/src/main/java/org/dimdev/dimdoors/network/client/ClientPacketListener.java b/src/main/java/org/dimdev/dimdoors/network/client/ClientPacketListener.java index 9d0cf46f..a01c159c 100644 --- a/src/main/java/org/dimdev/dimdoors/network/client/ClientPacketListener.java +++ b/src/main/java/org/dimdev/dimdoors/network/client/ClientPacketListener.java @@ -1,9 +1,6 @@ package org.dimdev.dimdoors.network.client; -import org.dimdev.dimdoors.network.packet.s2c.MonolithAggroParticlesPacket; -import org.dimdev.dimdoors.network.packet.s2c.MonolithTeleportParticlesPacket; -import org.dimdev.dimdoors.network.packet.s2c.PlayerInventorySlotUpdateS2CPacket; -import org.dimdev.dimdoors.network.packet.s2c.SyncPocketAddonsS2CPacket; +import org.dimdev.dimdoors.network.packet.s2c.*; public interface ClientPacketListener { void onPlayerInventorySlotUpdate(PlayerInventorySlotUpdateS2CPacket packet); @@ -13,4 +10,6 @@ public interface ClientPacketListener { void onMonolithAggroParticles(MonolithAggroParticlesPacket packet); void onMonolithTeleportParticles(MonolithTeleportParticlesPacket packet); + + void onRenderBreakBlock(RenderBreakBlockS2CPacket packet); } diff --git a/src/main/java/org/dimdev/dimdoors/network/packet/s2c/RenderBreakBlockS2CPacket.java b/src/main/java/org/dimdev/dimdoors/network/packet/s2c/RenderBreakBlockS2CPacket.java new file mode 100644 index 00000000..88708655 --- /dev/null +++ b/src/main/java/org/dimdev/dimdoors/network/packet/s2c/RenderBreakBlockS2CPacket.java @@ -0,0 +1,60 @@ +package org.dimdev.dimdoors.network.packet.s2c; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import org.dimdev.dimdoors.network.SimplePacket; +import org.dimdev.dimdoors.network.client.ClientPacketListener; + +import java.io.IOException; + +public class RenderBreakBlockS2CPacket implements SimplePacket { + public static final Identifier ID = new Identifier("dimdoors:render_break_block"); + + private BlockPos pos; + private int stage; + + @Environment(EnvType.CLIENT) + public RenderBreakBlockS2CPacket() { + + } + + public RenderBreakBlockS2CPacket(BlockPos pos, int stage) { + this.pos = pos; + this.stage = stage; + } + + @Override + public SimplePacket read(PacketByteBuf buf) throws IOException { + pos = buf.readBlockPos(); + stage = buf.readInt(); + return this; + } + + @Override + public PacketByteBuf write(PacketByteBuf buf) throws IOException { + buf.writeBlockPos(pos); + buf.writeInt(stage); + return buf; + } + + @Override + public void apply(ClientPacketListener listener) { + listener.onRenderBreakBlock(this); + } + + @Override + public Identifier channelId() { + return ID; + } + + public BlockPos getPos() { + return pos; + } + + public int getStage() { + return stage; + } +} diff --git a/src/main/java/org/dimdev/dimdoors/world/decay/LimboDecay.java b/src/main/java/org/dimdev/dimdoors/world/decay/LimboDecay.java index 858a40f2..f031da0c 100644 --- a/src/main/java/org/dimdev/dimdoors/world/decay/LimboDecay.java +++ b/src/main/java/org/dimdev/dimdoors/world/decay/LimboDecay.java @@ -1,21 +1,30 @@ package org.dimdev.dimdoors.world.decay; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Random; +import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener; import net.minecraft.block.BlockState; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; +import net.minecraft.network.packet.s2c.play.BlockBreakingProgressS2CPacket; +import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.resource.ResourceManager; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.math.Direction; +import net.minecraft.util.registry.RegistryKey; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dimdev.dimdoors.DimensionalDoorsInitializer; +import org.dimdev.dimdoors.network.ExtendedServerPlayNetworkHandler; +import org.dimdev.dimdoors.network.packet.s2c.RenderBreakBlockS2CPacket; +import org.dimdev.dimdoors.sound.ModSoundEvents; import org.dimdev.dimdoors.util.ResourceUtil; import org.jetbrains.annotations.NotNull; @@ -29,6 +38,9 @@ import net.minecraft.world.World; */ public final class LimboDecay { private static final Logger LOGGER = LogManager.getLogger(); + private static final Map, Set> DECAY_QUEUE = new HashMap<>(); + // TODO: config + private static final int DECAY_DELAY = 40; private static final Random RANDOM = new Random(); @@ -36,33 +48,58 @@ public final class LimboDecay { * Checks the blocks orthogonally around a given location (presumably the location of an Unraveled Fabric block) * and applies Limbo decay to them. This gives the impression that decay spreads outward from Unraveled Fabric. */ - public static void applySpreadDecay(World world, BlockPos pos) { + public static void applySpreadDecay(ServerWorld world, BlockPos pos) { //Check if we randomly apply decay spread or not. This can be used to moderate the frequency of //full spread decay checks, which can also shift its performance impact on the game. if (RANDOM.nextDouble() < DimensionalDoorsInitializer.getConfig().getLimboConfig().decaySpreadChance) { BlockState origin = world.getBlockState(pos); //Apply decay to the blocks above, below, and on all four sides. - decayBlock(world, pos.up(), origin); - decayBlock(world, pos.down(), origin); - decayBlock(world, pos.north(), origin); - decayBlock(world, pos.south(), origin); - decayBlock(world, pos.west(), origin); - decayBlock(world, pos.east(), origin); + // TODO: make max amount configurable + int decayAmount = RANDOM.nextInt(5) + 1; + List directions = new ArrayList<>(Arrays.asList(Direction.values())); + for (int i = 0; i < decayAmount; i++) { + decayBlock(world, pos.offset(directions.remove(RANDOM.nextInt(5 - i))), origin); + } } } /** * Checks if a block can be decayed and, if so, changes it to the next block ID along the decay sequence. */ - private static void decayBlock(World world, BlockPos pos, BlockState origin) { + private static void decayBlock(ServerWorld world, BlockPos pos, BlockState origin) { @NotNull Collection patterns = DecayLoader.getInstance().getPatterns(); if(patterns.isEmpty()) return; BlockState target = world.getBlockState(pos); - patterns.stream().filter(decayPattern -> decayPattern.test(world, pos, origin, target)).findAny().ifPresent(pattern -> pattern.process(world, pos, origin, target)); + patterns.stream().filter(decayPattern -> decayPattern.test(world, pos, origin, target)).findAny().ifPresent(pattern -> { + world.getPlayers(EntityPredicates.maxDistance(pos.getX(), pos.getY(), pos.getZ(), 100)).forEach(player -> { + ExtendedServerPlayNetworkHandler.get(player.networkHandler).getDimDoorsPacketHandler().sendPacket(new RenderBreakBlockS2CPacket(pos, 5)); + }); + world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), ModSoundEvents.TEARING, SoundCategory.BLOCKS, 0.5f, 1f); + queueDecay(world, pos, origin, pattern, DECAY_DELAY); + }); + } + + public static void queueDecay(ServerWorld world, BlockPos pos, BlockState origin, DecayPattern pattern, int delay) { + DecayTask task = new DecayTask(pos, origin, pattern, delay); + if (delay <= 0) { + task.process(world); + } else { + DECAY_QUEUE.computeIfAbsent(world.getRegistryKey(), k -> new HashSet<>()).add(task); + } + } + + public static void tick(ServerWorld world) { + RegistryKey key = world.getRegistryKey(); + if (DECAY_QUEUE.containsKey(key)) { + Set tasks = DECAY_QUEUE.get(key); + Set tasksToRun = tasks.stream().filter(DecayTask::reduceDelayIsDone).collect(Collectors.toSet()); + tasks.removeAll(tasksToRun); + tasksToRun.forEach(task -> task.process(world)); + } } public static class DecayLoader implements SimpleSynchronousResourceReloadListener { @@ -97,4 +134,34 @@ public final class LimboDecay { return new Identifier("dimdoors", "decay_pattern"); } } + + private static class DecayTask { + private final BlockPos pos; + private final BlockState origin; + private final DecayPattern processor; + private int delay; + + + public DecayTask(BlockPos pos, BlockState origin, DecayPattern processor, int delay) { + this.pos = pos; + this.origin = origin; + this.processor = processor; + this.delay = delay; + } + + public boolean reduceDelayIsDone() { + return --delay <= 0; + } + + public void process(ServerWorld world) { + BlockState target = world.getBlockState(pos); + if (world.isChunkLoaded(pos) && processor.test(world, pos, origin, target)) { + world.getPlayers(EntityPredicates.maxDistance(pos.getX(), pos.getY(), pos.getZ(), 100)).forEach(player -> { + ExtendedServerPlayNetworkHandler.get(player.networkHandler).getDimDoorsPacketHandler().sendPacket(new RenderBreakBlockS2CPacket(pos, -1)); + }); + world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), target.getSoundGroup().getBreakSound(), SoundCategory.BLOCKS, 0.5f, 1f); + processor.process(world, pos, origin, world.getBlockState(pos)); + } + } + } } diff --git a/src/main/resources/dimdoors.mixins.json b/src/main/resources/dimdoors.mixins.json index c82f23b5..d883fa5d 100644 --- a/src/main/resources/dimdoors.mixins.json +++ b/src/main/resources/dimdoors.mixins.json @@ -14,6 +14,7 @@ "ServerPlayerEntityMixin", "ServerPlayerInteractionManagerMixin", "ServerPlayNetworkHandlerMixin", + "ServerWorldMixin", "StructurePoolMixin", "WorldMixin", "accessor.BuiltinBiomesAccessor", @@ -26,7 +27,8 @@ "accessor.ListTagAccessor", "accessor.RecipesProviderAccessor", "accessor.RedstoneWireBlockAccessor", - "accessor.StatsAccessor" + "accessor.StatsAccessor", + "client.accessor.WorldRendererAccessor" ], "client": [ "client.ClientPlayerInteractionManagerMixin",