|
| 1 | +/** |
| 2 | + * Copyright (C) 2025 the authors of ValkyrienSkies mod |
| 3 | + * Modified by zyxkad 2025 |
| 4 | + * |
| 5 | + * This program is free software: you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU Lesser General Public License as published by |
| 7 | + * the Free Software Foundation, either version 3 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * This program is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU Lesser General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU Lesser General Public License |
| 16 | + * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 17 | + */ |
| 18 | +package com.github.litermc.vschunkloader.mixin.client; |
| 19 | + |
| 20 | +import com.github.litermc.vschunkloader.accessor.ClientChunkCacheAccessor; |
| 21 | + |
| 22 | +import io.netty.util.collection.LongObjectHashMap; |
| 23 | +import io.netty.util.collection.LongObjectMap; |
| 24 | +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; |
| 25 | +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; |
| 26 | +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; |
| 27 | +import net.minecraft.client.Minecraft; |
| 28 | +import net.minecraft.client.multiplayer.ClientChunkCache; |
| 29 | +import net.minecraft.client.multiplayer.ClientLevel; |
| 30 | +import net.minecraft.nbt.CompoundTag; |
| 31 | +import net.minecraft.network.FriendlyByteBuf; |
| 32 | +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData.BlockEntityTagOutput; |
| 33 | +import net.minecraft.world.level.ChunkPos; |
| 34 | +import net.minecraft.world.level.chunk.ChunkStatus; |
| 35 | +import net.minecraft.world.level.chunk.LevelChunk; |
| 36 | + |
| 37 | +import org.valkyrienskies.core.api.ships.ClientShip; |
| 38 | +import org.valkyrienskies.core.api.ships.properties.ChunkClaim; |
| 39 | +import org.valkyrienskies.mod.common.VSGameUtilsKt; |
| 40 | +import org.valkyrienskies.mod.compat.SodiumCompat; |
| 41 | +import org.valkyrienskies.mod.compat.VSRenderer; |
| 42 | +import org.valkyrienskies.mod.mixin.ValkyrienCommonMixinConfigPlugin; |
| 43 | +import org.valkyrienskies.mod.mixin.accessors.client.multiplayer.ClientLevelAccessor; |
| 44 | +import org.valkyrienskies.mod.mixin.accessors.client.render.LevelRendererAccessor; |
| 45 | +import org.valkyrienskies.mod.mixinducks.client.render.IVSViewAreaMethods; |
| 46 | +import org.valkyrienskies.mod.mixinducks.client.world.ClientChunkCacheDuck; |
| 47 | +import org.valkyrienskies.mod.mixinducks.mod_compat.vanilla_renderer.LevelRendererDuck; |
| 48 | + |
| 49 | +import org.spongepowered.asm.mixin.Final; |
| 50 | +import org.spongepowered.asm.mixin.Mixin; |
| 51 | +import org.spongepowered.asm.mixin.Shadow; |
| 52 | +import org.spongepowered.asm.mixin.Unique; |
| 53 | +import org.spongepowered.asm.mixin.injection.At; |
| 54 | +import org.spongepowered.asm.mixin.injection.Inject; |
| 55 | +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; |
| 56 | +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; |
| 57 | + |
| 58 | +import java.util.function.Consumer; |
| 59 | + |
| 60 | +/** |
| 61 | + * The purpose of this mixin is to allow {@link ClientChunkCache} to store ship chunks. |
| 62 | + */ |
| 63 | +@Mixin(value = ClientChunkCache.class, priority = 2000) |
| 64 | +public abstract class MixinClientChunkCache implements ClientChunkCacheAccessor, ClientChunkCacheDuck { |
| 65 | + @Unique |
| 66 | + private static final LongObjectMap<LevelChunk> EMPTY_NETTY_MAP = new LongObjectHashMap<>(); |
| 67 | + |
| 68 | + @Shadow |
| 69 | + @Final |
| 70 | + ClientLevel level; |
| 71 | + |
| 72 | + @Unique |
| 73 | + private final Long2ObjectMap<LevelChunk> shipChunks = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); |
| 74 | + |
| 75 | + @Override |
| 76 | + public LongObjectMap<LevelChunk> vs$getShipChunks() { |
| 77 | + return EMPTY_NETTY_MAP; |
| 78 | + } |
| 79 | + |
| 80 | + @Override |
| 81 | + public Long2ObjectMap<LevelChunk> vsc$getShipChunks() { |
| 82 | + return this.shipChunks; |
| 83 | + } |
| 84 | + |
| 85 | + @Inject(method = "replaceWithPacketData", at = @At("HEAD"), cancellable = true) |
| 86 | + private void preReplaceWithPacketData( |
| 87 | + final int x, |
| 88 | + final int z, |
| 89 | + final FriendlyByteBuf buf, |
| 90 | + final CompoundTag tag, |
| 91 | + final Consumer<BlockEntityTagOutput> consumer, |
| 92 | + final CallbackInfoReturnable<LevelChunk> cir |
| 93 | + ) { |
| 94 | + if (!VSGameUtilsKt.isChunkInShipyard(this.level, x, z)) { |
| 95 | + return; |
| 96 | + } |
| 97 | + if (Minecraft.getInstance().levelRenderer instanceof final LevelRendererDuck levelRenderer) { |
| 98 | + levelRenderer.vs$setNeedsFrustumUpdate(); |
| 99 | + } |
| 100 | + final ChunkPos pos = new ChunkPos(x, z); |
| 101 | + final long chunkPosLong = pos.toLong(); |
| 102 | + final LevelChunk oldChunk = this.shipChunks.get(chunkPosLong); |
| 103 | + final LevelChunk worldChunk; |
| 104 | + if (oldChunk != null) { |
| 105 | + worldChunk = oldChunk; |
| 106 | + worldChunk.replaceWithPacketData(buf, tag, consumer); |
| 107 | + } else { |
| 108 | + worldChunk = new LevelChunk(this.level, pos); |
| 109 | + worldChunk.replaceWithPacketData(buf, tag, consumer); |
| 110 | + this.shipChunks.put(chunkPosLong, worldChunk); |
| 111 | + } |
| 112 | + |
| 113 | + this.level.onChunkLoaded(pos); |
| 114 | + SodiumCompat.onChunkAdded(this.level, x, z); |
| 115 | + cir.setReturnValue(worldChunk); |
| 116 | + } |
| 117 | + |
| 118 | + @Override |
| 119 | + public void vs$removeShip(final ClientShip ship) { |
| 120 | + final ChunkClaim chunks = ship.getChunkClaim(); |
| 121 | + for (int x = chunks.getXStart(); x <= chunks.getXEnd(); x++) { |
| 122 | + for (int z = chunks.getZStart(); z <= chunks.getZEnd(); z++) { |
| 123 | + this.removeShipChunk(x, z); |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + @Unique |
| 129 | + private void removeShipChunk(final int chunkX, final int chunkZ) { |
| 130 | + final LevelChunk chunk = this.shipChunks.remove(ChunkPos.asLong(chunkX, chunkZ)); |
| 131 | + if (chunk == null) { |
| 132 | + return; |
| 133 | + } |
| 134 | + this.level.unload(chunk); |
| 135 | + if (ValkyrienCommonMixinConfigPlugin.getVSRenderer() != VSRenderer.SODIUM) { |
| 136 | + ((IVSViewAreaMethods) ((LevelRendererAccessor) ((ClientLevelAccessor) this.level).getLevelRenderer()).getViewArea()) |
| 137 | + .unloadChunk(chunkX, chunkZ); |
| 138 | + } |
| 139 | + SodiumCompat.onChunkRemoved(this.level, chunkX, chunkZ); |
| 140 | + } |
| 141 | + |
| 142 | + @Inject( |
| 143 | + method = "getChunk(IILnet/minecraft/world/level/chunk/ChunkStatus;Z)Lnet/minecraft/world/level/chunk/LevelChunk;", |
| 144 | + at = @At("HEAD"), |
| 145 | + cancellable = true |
| 146 | + ) |
| 147 | + public void preGetChunk( |
| 148 | + final int chunkX, |
| 149 | + final int chunkZ, |
| 150 | + final ChunkStatus chunkStatus, |
| 151 | + final boolean bl, |
| 152 | + final CallbackInfoReturnable<LevelChunk> cir |
| 153 | + ) { |
| 154 | + final LevelChunk shipChunk = this.shipChunks.get(ChunkPos.asLong(chunkX, chunkZ)); |
| 155 | + if (shipChunk != null) { |
| 156 | + cir.setReturnValue(shipChunk); |
| 157 | + } |
| 158 | + } |
| 159 | +} |
0 commit comments