limbo decay block change effects

This commit is contained in:
CreepyCre 2021-10-14 23:24:34 +02:00
parent 2dd8e2b9a3
commit 7dbc3c715e
10 changed files with 296 additions and 32 deletions

View file

@ -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<BlockPos, BreakBlockInfo> customBreakBlockMap = new HashMap();
public static Map<BlockPos, BreakBlockInfo> getCustomBreakBlockMap(int ticks) {
Set<BlockPos> 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;
}
}
}

View file

@ -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);
}
}

View file

@ -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<BlockPos, CustomBreakBlockHandler.BreakBlockInfo> breakBlocks = CustomBreakBlockHandler.getCustomBreakBlockMap(this.ticks);
// stolen from WorldRenderer#render
for (Map.Entry<BlockPos, CustomBreakBlockHandler.BreakBlockInfo> 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();
}
}
}
}

View file

@ -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();
}

View file

@ -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();

View file

@ -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());
});
}
}

View file

@ -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);
}

View file

@ -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<ClientPacketListener> {
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<ClientPacketListener> 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;
}
}

View file

@ -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<RegistryKey<World>, Set<DecayTask>> 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<Direction> 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<DecayPattern> 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<World> key = world.getRegistryKey();
if (DECAY_QUEUE.containsKey(key)) {
Set<DecayTask> tasks = DECAY_QUEUE.get(key);
Set<DecayTask> 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));
}
}
}
}

View file

@ -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",