diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/MixinMinecraftServer.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/MixinMinecraftServer.java index 62ef75ef..f6df2275 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/MixinMinecraftServer.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/MixinMinecraftServer.java @@ -1,28 +1,43 @@ package io.github.opencubicchunks.cubicchunks.mixin.core.common.server; +import com.llamalad7.mixinextras.injector.WrapWithCondition; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Share; import com.llamalad7.mixinextras.sugar.ref.LocalRef; import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cc_core.api.CubicConstants; +import io.github.opencubicchunks.cc_core.world.SpawnPlaceFinder; import io.github.opencubicchunks.cubicchunks.CanBeCubic; -import io.github.opencubicchunks.cubicchunks.MarkableAsCubic; import net.minecraft.core.BlockPos; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; +import net.minecraft.tags.BlockTags; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.storage.ServerLevelData; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(MinecraftServer.class) public abstract class MixinMinecraftServer { + @Shadow public abstract ServerLevel overworld(); + + @Shadow public abstract boolean isRunning(); + + @Shadow protected long nextTickTimeNanos; + + @Shadow protected abstract void waitUntilNextTick(); + @Inject(method = "setInitialSpawn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/ChunkPos;(Lnet/minecraft/core/BlockPos;)V")) private static void cc_replaceChunkPosInSetInitialSpawn(ServerLevel serverLevel, ServerLevelData serverLevelData, boolean generateBonusChest, boolean debug, CallbackInfo ci, @Share( "cubePos") LocalRef cubePosLocalRef) { - if(((CanBeCubic) serverLevel).cc_isCubic()) { + if (((CanBeCubic) serverLevel).cc_isCubic()) { CubePos cubePos = new CubePos(serverLevel.getChunkSource().randomState().sampler().findSpawnPosition()); cubePosLocalRef.set(cubePos); } @@ -31,16 +46,53 @@ private static void cc_replaceChunkPosInSetInitialSpawn(ServerLevel serverLevel, @WrapOperation(method = "setInitialSpawn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/ChunkPos;getWorldPosition()Lnet/minecraft/core/BlockPos;")) private static BlockPos cc_replaceGetWorldPositionInSetInitialSpawn(ChunkPos chunkPos, Operation original, ServerLevel serverLevel, @Share( "cubePos") LocalRef cubePosLocalRef) { - if(((CanBeCubic) serverLevel).cc_isCubic()) { + if (((CanBeCubic) serverLevel).cc_isCubic()) { return cubePosLocalRef.get().asChunkPos().getWorldPosition(); } return null; } - // change getheight to funny algorithm that barteks wrote + @WrapOperation(method = "setInitialSpawn", + at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;getHeight(Lnet/minecraft/world/level/levelgen/Heightmap$Types;II)I")) + private static int cc_replaceGetHeightWithSpawnPlaceFinder(ServerLevel serverLevel, Heightmap.Types heightmapType, int x, int z, Operation original, + @Share("cubePos") LocalRef cubePosLocalRef) { + if (((CanBeCubic) serverLevel).cc_isCubic()) { + BlockPos topBlockBisect = SpawnPlaceFinder.getTopBlockBisect(serverLevel, cubePosLocalRef.get().asBlockPos(), false, + pos -> serverLevel.getBlockState(pos).is(BlockTags.VALID_SPAWN), + pos -> serverLevel.getBlockState(pos).getCollisionShape(serverLevel, pos).isEmpty()); + if (topBlockBisect != null) { + return topBlockBisect.getY(); + } else { + return serverLevel.getSeaLevel() + 1; + } + } + return original.call(serverLevel, heightmapType, x, z); + } - // setInitialSpawn - mixin + // TODO: Fix this when ServerChunkCache exists + @WrapWithCondition(method = "prepareLevels", at = @At(value = "INVOKE", + target = "Lnet/minecraft/server/level/ServerChunkCache;addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) + private boolean cc_replaceAddRegionTicketInPrepareLevels(ServerChunkCache serverChunkCache, TicketType ticketType, ChunkPos chunkPos, int originalSpawnRadius, T unit, + @Share("spawnRadius") LocalRef spawnRadiusRef) { + if (((CanBeCubic) serverChunkCache).cc_isCubic()) { + int spawnRadius = (int) Math.ceil(10 * (16 / (float) CubicConstants.DIAMETER_IN_BLOCKS)); //vanilla is 10, 32: 5, 64: 3 + spawnRadiusRef.set(spawnRadius); + //(CubicServerChunkCache)serverChunkCache.addRegionTicket(CubicTicketType.START, CloPos.cube(overworld().getSharedSpawnPos()), spawnRadius + 1, unit); + return false; + } + return true; + } - // prepareLevels - mixin + @Inject(method = "prepareLevels", at = @At(value = "FIELD", target = "Lnet/minecraft/server/MinecraftServer;nextTickTimeNanos:J")) + private void cc_waitUntilCubicGenerationComplete(CallbackInfo ci, @Share("spawnRadius") LocalRef spawnRadiusRef) { + if (((CanBeCubic) overworld().getChunkSource()).cc_isCubic()) { + int d = spawnRadiusRef.get() * 2 + 1; + //while (this.isRunning() && ((CubicServerChunkCache) overworld().getChunkSource()).getTickingGeneratedCubes() < d * d * d) { + // this.nextTickTimeNanos = Util.getMillis() + 10L; + // this.waitUntilNextTick(); + //} + } + } + // TODO: forced cubes will need to be implemented for prepareLevels as well in the same way as above } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinPlayerRespawnLogic.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinPlayerRespawnLogic.java index fc1596fb..f7ab0146 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinPlayerRespawnLogic.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinPlayerRespawnLogic.java @@ -21,7 +21,7 @@ private static void getOverworldRespawnPos(ServerLevel level, int posX, int posZ return; } cir.setReturnValue(SpawnPlaceFinder.getTopBlockBisect(level, new BlockPos(posX, 0, posZ), false, - pos -> level.getBlockState((BlockPos) pos).is(BlockTags.VALID_SPAWN), - pos -> level.getBlockState((BlockPos) pos).getCollisionShape(level, (BlockPos) pos).isEmpty())); + pos -> level.getBlockState(pos).is(BlockTags.VALID_SPAWN), + pos -> level.getBlockState(pos).getCollisionShape(level, pos).isEmpty())); } } diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/MinecraftServerTestAccess.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/MinecraftServerTestAccess.java new file mode 100644 index 00000000..bac8adb4 --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/MinecraftServerTestAccess.java @@ -0,0 +1,18 @@ +package io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.world.level.storage.ServerLevelData; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(MinecraftServer.class) +public interface MinecraftServerTestAccess { + + @Invoker(value = "prepareLevels") + void invoke_prepareLevels(ChunkProgressListener chunkProgressListener); + + @Invoker(value = "setInitialSpawn") + void invoke_setInitialSpawn(ServerLevel serverLevel, ServerLevelData serverLevelData, boolean p_177899_, boolean p_177900_); +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/TestMinecraftServer.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/TestMinecraftServer.java index 6bd83a8f..3f7fc00a 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/TestMinecraftServer.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/TestMinecraftServer.java @@ -2,9 +2,26 @@ import static io.github.opencubicchunks.cubicchunks.testutils.Setup.setupTests; +import static io.github.opencubicchunks.cubicchunks.testutils.Misc.setupServerLevel; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import io.github.opencubicchunks.cubicchunks.MarkableAsCubic; +import io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level.MinecraftServerTestAccess; +import io.github.opencubicchunks.cubicchunks.testutils.CloseableReference; +import net.minecraft.client.server.IntegratedServer; +import net.minecraft.core.registries.Registries; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldStem; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.dimension.LevelStem; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.mockito.Answers; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class TestMinecraftServer { @@ -13,11 +30,24 @@ public static void setup() { setupTests(); } - @Test public void testSetInitialSpawnVanilla() { - // TODO + private MinecraftServer setupServer() { + WorldStem worldStemMock = mock(WorldStem.class, withSettings().defaultAnswer(Answers.RETURNS_DEEP_STUBS)); + when(worldStemMock.registries().compositeAccess().registryOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)).thenReturn(true); + return new IntegratedServer(mock(RETURNS_DEEP_STUBS), mock(RETURNS_DEEP_STUBS), mock(RETURNS_DEEP_STUBS), mock(RETURNS_DEEP_STUBS), worldStemMock, mock(RETURNS_DEEP_STUBS), + mock(RETURNS_DEEP_STUBS)); + } + + //TODO: These tests are disabled for now since MinecraftServer creates a ServerFunctionManager which eventually tries to copy a null array. Not sure how to mock that right now. + @Test @Disabled public void testSetInitialSpawnVanilla() throws Exception { + try (CloseableReference serverLevelReference = setupServerLevel()) { + ((MarkableAsCubic)serverLevelReference.value()).cc_setCubic(); + ((MinecraftServerTestAccess)setupServer()).invoke_setInitialSpawn(serverLevelReference.value(), mock(RETURNS_DEEP_STUBS), false, false); + } } - @Test public void testPrepareLevelsVanilla() { - // TODO + @Test @Disabled public void testPrepareLevelsVanilla() throws Exception { + MinecraftServer server = setupServer(); + ((MarkableAsCubic)server.overworld()).cc_setCubic(); + ((MinecraftServerTestAccess)setupServer()).invoke_prepareLevels(mock()); } } diff --git a/src/test/resources/cubicchunks.mixins.test.json b/src/test/resources/cubicchunks.mixins.test.json index ff9c8a6a..6475faca 100644 --- a/src/test/resources/cubicchunks.mixins.test.json +++ b/src/test/resources/cubicchunks.mixins.test.json @@ -13,7 +13,8 @@ }, "mixins": [ "common.server.level.ChunkTrackerTestAccess", - "common.server.level.CubicDistanceManagerTestAccess" + "common.server.level.CubicDistanceManagerTestAccess", + "common.server.level.MinecraftServerTestAccess" ], "client": [], "server": []