diff --git a/src/main/java/com/minelittlepony/unicopia/BlockDestructionManager.java b/src/main/java/com/minelittlepony/unicopia/BlockDestructionManager.java new file mode 100644 index 00000000..45a842bc --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/BlockDestructionManager.java @@ -0,0 +1,87 @@ +package com.minelittlepony.unicopia; + +import com.minelittlepony.unicopia.network.Channel; +import com.minelittlepony.unicopia.network.MsgBlockDestruction; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class BlockDestructionManager { + + public static final int UNSET_DAMAGE = -1; + public static final int MAX_DAMAGE = 10; + + private final Destruction emptyDestruction = new Destruction(BlockPos.ORIGIN); + + private final World world; + + private final Long2ObjectMap destructions = new Long2ObjectOpenHashMap<>(); + + private final Object locker = new Object(); + + public BlockDestructionManager(World world) { + this.world = world; + } + + public int getBlockDestruction(BlockPos pos) { + return destructions.getOrDefault(pos.asLong(), emptyDestruction).amount; + } + + public void clearBlockDestruction(BlockPos pos) { + setBlockDestruction(pos, -1); + } + + public void setBlockDestruction(BlockPos pos, int amount) { + synchronized (locker) { + destructions.computeIfAbsent(pos.asLong(), p -> new Destruction(pos)).set(amount); + } + } + + public int damageBlock(BlockPos pos, int amount) { + amount = Math.max(getBlockDestruction(pos), 0) + amount; + setBlockDestruction(pos, amount); + return amount; + } + + public void tick() { + synchronized (locker) { + destructions.long2ObjectEntrySet().removeIf(entry -> entry.getValue().tick()); + } + } + + private class Destruction { + BlockPos pos; + int amount = -1; + int age = 50; + + Destruction(BlockPos pos) { + this.pos = pos; + } + + boolean tick() { + if (age-- > 0) { + return false; + } + + if (amount >= 0) { + set(amount - 1); + } + return amount < 0 || age-- <= 0; + } + + void set(int amount) { + this.age = 50; + this.amount = amount >= 0 && amount < MAX_DAMAGE ? amount : UNSET_DAMAGE; + if (world instanceof ServerWorld) { + Channel.SERVER_BLOCK_DESTRUCTION.send(world, new MsgBlockDestruction(pos, this.amount)); + } + } + } + + public interface Source { + BlockDestructionManager getDestructionManager(); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/Unicopia.java b/src/main/java/com/minelittlepony/unicopia/Unicopia.java index 902addf4..9de5a878 100644 --- a/src/main/java/com/minelittlepony/unicopia/Unicopia.java +++ b/src/main/java/com/minelittlepony/unicopia/Unicopia.java @@ -33,7 +33,10 @@ public class Unicopia implements ModInitializer { UTags.bootstrap(); Commands.bootstrap(); - ServerTickEvents.END_WORLD_TICK.register(AwaitTickQueue::tick); + ServerTickEvents.END_WORLD_TICK.register(w -> { + AwaitTickQueue.tick(w); + ((BlockDestructionManager.Source)w).getDestructionManager().tick(); + }); UItems.bootstrap(); UPotions.bootstrap(); diff --git a/src/main/java/com/minelittlepony/unicopia/WorldTribeManager.java b/src/main/java/com/minelittlepony/unicopia/WorldTribeManager.java index 248f8a39..440ac112 100644 --- a/src/main/java/com/minelittlepony/unicopia/WorldTribeManager.java +++ b/src/main/java/com/minelittlepony/unicopia/WorldTribeManager.java @@ -10,7 +10,7 @@ public class WorldTribeManager extends PersistentState { private Race defaultRace = Unicopia.getConfig().getPrefferedRace(); public WorldTribeManager(ServerWorld world) { - super("unicopia:tribes" + world.getDimension().getSuffix()); + super(nameFor(world.getDimension())); } public Race getDefaultRace() { diff --git a/src/main/java/com/minelittlepony/unicopia/client/ClientBlockDestructionManager.java b/src/main/java/com/minelittlepony/unicopia/client/ClientBlockDestructionManager.java new file mode 100644 index 00000000..8960b7cc --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/ClientBlockDestructionManager.java @@ -0,0 +1,83 @@ +package com.minelittlepony.unicopia.client; + +import java.util.SortedSet; +import com.google.common.collect.Sets; +import com.minelittlepony.unicopia.BlockDestructionManager; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.client.render.BlockBreakingInfo; +import net.minecraft.util.math.BlockPos; + +public class ClientBlockDestructionManager { + + private final Long2ObjectMap destructions = new Long2ObjectOpenHashMap<>(); + + private final Long2ObjectMap> combined = new Long2ObjectOpenHashMap<>(); + + private final Object locker = new Object(); + + public void setBlockDestruction(BlockPos pos, int amount) { + synchronized (locker) { + if (amount <= 0 || amount > BlockDestructionManager.MAX_DAMAGE) { + destructions.remove(pos.asLong()); + } else { + destructions.computeIfAbsent(pos.asLong(), p -> new Destruction(pos)).set(amount); + } + } + } + + public void tick(Long2ObjectMap> vanilla) { + synchronized (locker) { + destructions.long2ObjectEntrySet().removeIf(entry -> entry.getValue().tick()); + + combined.clear(); + + if (!destructions.isEmpty()) { + destructions.forEach((pos, value) -> { + combined.computeIfAbsent(pos.longValue(), p -> Sets.newTreeSet()).add(value.info); + }); + vanilla.forEach((pos, value) -> { + combined.computeIfAbsent(pos.longValue(), p -> Sets.newTreeSet()).addAll(value); + }); + } + } + } + + public Long2ObjectMap> getCombinedDestructions(Long2ObjectMap> vanilla) { + return destructions.isEmpty() ? vanilla : combined; + } + + private class Destruction { + int age = 50; + + BlockBreakingInfo info; + + Destruction(BlockPos pos) { + this.info = new BlockBreakingInfo(0, pos); + } + + boolean tick() { + if (age-- > 0) { + return false; + } + int amount = info.getStage(); + + if (amount >= 0) { + amount--; + set(amount); + } + return amount < 0 || age-- <= 0; + } + + void set(int amount) { + this.age = 50; + info.setStage(amount >= 0 && amount < BlockDestructionManager.MAX_DAMAGE ? amount : BlockDestructionManager.UNSET_DAMAGE); + } + } + + public interface Source { + ClientBlockDestructionManager getDestructionManager(); + + int getTicks(); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java index b03149a1..b623ed1c 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -179,7 +179,7 @@ public class Pony implements Caster, Equine, Transmi if (entity instanceof ServerPlayerEntity) { MsgOtherPlayerCapabilities packet = new MsgOtherPlayerCapabilities(full, this); Channel.SERVER_PLAYER_CAPABILITIES.send(entity, packet); - Channel.SERVER_OTHER_PLAYER_CAPABILITIES.send(entity, packet); + Channel.SERVER_OTHER_PLAYER_CAPABILITIES.send(entity.world, packet); } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinWorld.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinWorld.java index 4a981b2a..dce1739f 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinWorld.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinWorld.java @@ -7,6 +7,8 @@ import java.util.stream.Stream; import javax.annotation.Nullable; import org.spongepowered.asm.mixin.Mixin; + +import com.minelittlepony.unicopia.BlockDestructionManager; import com.minelittlepony.unicopia.entity.behaviour.Disguise; import net.minecraft.entity.Entity; @@ -16,7 +18,15 @@ import net.minecraft.world.World; import net.minecraft.world.WorldAccess; @Mixin(World.class) -abstract class MixinWorld implements WorldAccess { +abstract class MixinWorld implements WorldAccess, BlockDestructionManager.Source { + + private final BlockDestructionManager destructions = new BlockDestructionManager((World)(Object)this); + + @Override + public BlockDestructionManager getDestructionManager() { + return destructions; + } + @Override public Stream getEntityCollisions(@Nullable Entity entity, Box box, Predicate predicate) { if (box.getAverageSideLength() >= 1.0E-7D) { diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinWorldRenderer.java b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinWorldRenderer.java new file mode 100644 index 00000000..6b63849d --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinWorldRenderer.java @@ -0,0 +1,56 @@ +package com.minelittlepony.unicopia.mixin.client; + +import java.util.SortedSet; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.minelittlepony.unicopia.client.ClientBlockDestructionManager; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import net.minecraft.client.render.BlockBreakingInfo; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.resource.SynchronousResourceReloadListener; + +@Mixin(WorldRenderer.class) +abstract class MixinWorldRenderer implements SynchronousResourceReloadListener, AutoCloseable, ClientBlockDestructionManager.Source { + + private final ClientBlockDestructionManager destructions = new ClientBlockDestructionManager(); + + @Shadow + private @Final Long2ObjectMap> blockBreakingProgressions; + + @Override + public ClientBlockDestructionManager getDestructionManager() { + return destructions; + } + + @Override + @Accessor("ticks") + public abstract int getTicks(); + + @Redirect(method = "render(" + + "Lnet/minecraft/client/util/math/MatrixStack;" + + "FLZ" + + "Lnet/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;") + ) + private Long2ObjectMap> redirectGetDamagesMap(WorldRenderer sender) { + return destructions.getCombinedDestructions(blockBreakingProgressions); + } + + @Inject(method = "tick()V", at = @At("RETURN")) + private void onTick(CallbackInfo info) { + destructions.tick(blockBreakingProgressions); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/Channel.java b/src/main/java/com/minelittlepony/unicopia/network/Channel.java index 90ade34c..5f051647 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/Channel.java +++ b/src/main/java/com/minelittlepony/unicopia/network/Channel.java @@ -10,6 +10,7 @@ import net.fabricmc.fabric.api.network.ServerSidePacketRegistry; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.Identifier; +import net.minecraft.world.World; import net.minecraft.network.PacketByteBuf; public interface Channel { @@ -21,6 +22,8 @@ public interface Channel { MPacketType SERVER_OTHER_PLAYER_CAPABILITIES = serverToClients(new Identifier("unicopia", "other_player_capabilities"), MsgOtherPlayerCapabilities::new); CPacketType SERVER_SPAWN_PROJECTILE = serverToClient(new Identifier("unicopia", "projectile_entity"), MsgSpawnProjectile::new); + MPacketType SERVER_BLOCK_DESTRUCTION = serverToClients(new Identifier("unicopia", "block_destruction"), MsgBlockDestruction::new); + static void bootstrap() { } static SPacketType clientToServer(Identifier id, Function factory) { @@ -45,8 +48,8 @@ public interface Channel { interface MPacketType { Identifier getId(); - default void send(PlayerEntity sender, T packet) { - sender.world.getPlayers().forEach(player -> { + default void send(World world, T packet) { + world.getPlayers().forEach(player -> { if (player != null) { ServerSidePacketRegistry.INSTANCE.sendToPlayer(player, getId(), packet.toBuffer()); } diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgBlockDestruction.java b/src/main/java/com/minelittlepony/unicopia/network/MsgBlockDestruction.java new file mode 100644 index 00000000..0cd2a1ef --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgBlockDestruction.java @@ -0,0 +1,36 @@ +package com.minelittlepony.unicopia.network; + +import com.minelittlepony.unicopia.client.ClientBlockDestructionManager; + +import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.math.BlockPos; +import net.minecraft.client.MinecraftClient; + +public class MsgBlockDestruction implements Channel.Packet { + + private final BlockPos pos; + + private final int amount; + + MsgBlockDestruction(PacketByteBuf buffer) { + pos = buffer.readBlockPos(); + amount = buffer.readByte(); + } + + public MsgBlockDestruction(BlockPos pos, int amount) { + this.pos = pos; + this.amount = amount; + } + + @Override + public void toBuffer(PacketByteBuf buffer) { + buffer.writeBlockPos(pos); + buffer.writeByte(amount); + } + + @Override + public void handle(PacketContext context) { + ((ClientBlockDestructionManager.Source)MinecraftClient.getInstance().worldRenderer).getDestructionManager().setBlockDestruction(pos, amount); + } +} diff --git a/src/main/resources/unicopia.mixin.json b/src/main/resources/unicopia.mixin.json index d1a43078..abf8d34c 100644 --- a/src/main/resources/unicopia.mixin.json +++ b/src/main/resources/unicopia.mixin.json @@ -31,7 +31,8 @@ "client.MixinItemModels", "client.MixinKeyboardInput", "client.MixinLightmapTextureManager", - "client.MixinMouse" + "client.MixinMouse", + "client.MixinWorldRenderer" ], "injectors": { "defaultRequire": 1