Skip to content

Commit b49fdba

Browse files
committed
fix client chunk blockentity duplicated tickers
1 parent 6f8f9bc commit b49fdba

File tree

6 files changed

+232
-30
lines changed

6 files changed

+232
-30
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.github.litermc.vschunkloader.accessor;
2+
3+
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
4+
import net.minecraft.world.level.chunk.LevelChunk;
5+
6+
public interface ClientChunkCacheAccessor {
7+
Long2ObjectMap<LevelChunk> vsc$getShipChunks();
8+
}

common/src/main/java/com/github/litermc/vschunkloader/command/VSCCommands.java

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,6 @@ public static void register(final CommandDispatcher<CommandSourceStack> dispatch
5858
.executes(VSCCommands::queryForceLoadTokens)
5959
)
6060
)
61-
.then(Commands.literal("delete")
62-
.then(Commands.argument("ships", ShipArgument.Companion.ships())
63-
.executes(VSCCommands::delete)
64-
)
65-
)
6661
);
6762
}
6863

@@ -174,28 +169,4 @@ private static int queryForceLoadTokens(final CommandContext<CommandSourceStack>
174169
}, false);
175170
return count + 1;
176171
}
177-
178-
private static int delete(final CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
179-
final CommandSourceStack source = context.getSource();
180-
final MinecraftServer server = source.getServer();
181-
final Set<Ship> ships = ShipArgument.Companion.getShips((CommandContext<VSCommandSource>)((CommandContext<?>)(context)), "ships");
182-
int successCount = 0;
183-
for (final Ship ship : ships) {
184-
if (!(ship instanceof ServerShip serverShip)) {
185-
continue;
186-
}
187-
final ServerLevel level = Utils.getLevel(serverShip.getChunkClaimDimension());
188-
if (level == null) {
189-
continue;
190-
}
191-
ShipAllocator.get(level).putShip(serverShip);
192-
successCount++;
193-
}
194-
final int finalSuccessCount = successCount;
195-
source.sendSuccess(() ->
196-
Component.translatable("command.valkyrienskies.delete.success", finalSuccessCount),
197-
true
198-
);
199-
return finalSuccessCount;
200-
}
201172
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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 net.minecraft.client.multiplayer.ClientLevel;
23+
import net.minecraft.client.renderer.LevelRenderer;
24+
import net.minecraft.client.renderer.ViewArea;
25+
import net.minecraft.world.level.ChunkPos;
26+
27+
import org.spongepowered.asm.mixin.Mixin;
28+
import org.spongepowered.asm.mixin.Shadow;
29+
import org.spongepowered.asm.mixin.injection.At;
30+
import org.spongepowered.asm.mixin.injection.Inject;
31+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
32+
33+
/**
34+
* This mixin allows {@link LevelRenderer} to render ship chunks.
35+
*/
36+
@Mixin(LevelRenderer.class)
37+
public abstract class MixinLevelRenderer {
38+
@Shadow
39+
private ClientLevel level;
40+
@Shadow
41+
private ViewArea viewArea;
42+
43+
/**
44+
* Prevents ships from disappearing on f3+a
45+
*/
46+
@Inject(
47+
method = "allChanged",
48+
at = @At(
49+
value = "INVOKE",
50+
target = "Lnet/minecraft/client/renderer/ViewArea;repositionCamera(DD)V"
51+
)
52+
)
53+
private void afterRefresh(final CallbackInfo ci) {
54+
((ClientChunkCacheAccessor) this.level.getChunkSource()).vsc$getShipChunks().forEach((pos, chunk) -> {
55+
final int x = ChunkPos.getX(pos);
56+
final int z = ChunkPos.getZ(pos);
57+
for (int y = this.level.getMinSection(); y < this.level.getMaxSection(); y++) {
58+
this.viewArea.setDirty(x, y, z, false);
59+
}
60+
});
61+
}
62+
}

common/src/main/resources/vschunkloader.mixins.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
"MixinSleepStatus"
1414
],
1515
"client": [
16+
"client.MixinClientChunkCache",
17+
"client.MixinLevelRenderer"
1618
],
1719
"server": [
1820
],

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ forge_loader_version_range=[47,)
3737
architectury_version=9.1.12
3838
cloth_config_version=6.4.90
3939
kff_version=4.10.0
40-
vs2_version=2.3.0-beta.9
40+
vs2_version=2.3.0-beta.12
4141
vs_core_version=1.1.0+cf6990a85d
4242

4343
# Soft dependencies

0 commit comments

Comments
 (0)