diff --git a/build.gradle b/build.gradle index 31a9511a..34ba7e0c 100644 --- a/build.gradle +++ b/build.gradle @@ -213,7 +213,7 @@ dependencies { targetConfiguration = "testArchivesOutput" } - libraries("com.github.OpenCubicChunks:dasm:cd078a82cd") { + libraries("com.github.OpenCubicChunks:dasm:4ff381c5a3") { transitive = false } libraries("io.github.opencubicchunks:regionlib:0.63.0-SNAPSHOT") diff --git a/buildSrc/src/main/java/io/github/opencubicchunks/gradle/DasmPlugin.java b/buildSrc/src/main/java/io/github/opencubicchunks/gradle/DasmPlugin.java index f26a5a2f..a706e866 100644 --- a/buildSrc/src/main/java/io/github/opencubicchunks/gradle/DasmPlugin.java +++ b/buildSrc/src/main/java/io/github/opencubicchunks/gradle/DasmPlugin.java @@ -383,6 +383,9 @@ private static String remapMethodName(Mappings mappings, String methodOwner, Met private static String remapFieldName(Mappings mappings, String fieldOwner, Type type, String name) { Mappings.ClassMapping mapEntry = mappings.getClass(fieldOwner.replace('.', '/')); + if (mapEntry == null) { + return name; + } Mappings.FieldMapping field = mapEntry.getField(name, type.getDescriptor()); if (field == null) { return name; diff --git a/gradle.properties b/gradle.properties index 09d8b89a..6dc41bf2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,8 +19,8 @@ neo_version_range=[20.4,) loader_version_range=[2,) # Disabled due to local variables being named incorrectly (e.g. in DynamicGraphMinFixedPoint) -#neogradle.subsystems.parchment.minecraftVersion=1.20.2 -#neogradle.subsystems.parchment.mappingsVersion=2023.12.10 +#neogradle.subsystems.parchment.minecraftVersion=1.20.3 +#neogradle.subsystems.parchment.mappingsVersion=2023.12.31 ## Mod Properties diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/CubicChunks.java b/src/main/java/io/github/opencubicchunks/cubicchunks/CubicChunks.java index 2e548924..49b9b72a 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/CubicChunks.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/CubicChunks.java @@ -19,6 +19,8 @@ @Mod("cubicchunks") public class CubicChunks extends CubicChunksBase { protected static CommonConfig config = null; + // For hardcoding height in P1 + public static final int SUPERFLAT_HEIGHT = 5; public CubicChunks(IEventBus modEventBus) { ChunkMap.class.getName(); diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/ASMConfigPlugin.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/ASMConfigPlugin.java index ef840769..a3c54636 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/ASMConfigPlugin.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/ASMConfigPlugin.java @@ -3,8 +3,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; @@ -12,10 +10,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -27,7 +23,6 @@ import io.github.opencubicchunks.dasm.RedirectsParseException; import io.github.opencubicchunks.dasm.RedirectsParser; import io.github.opencubicchunks.dasm.Transformer; -import io.github.opencubicchunks.dasm.TypeRedirect; import net.neoforged.fml.loading.FMLEnvironment; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; @@ -35,12 +30,10 @@ import org.objectweb.asm.tree.MethodNode; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; -import org.spongepowered.asm.mixin.transformer.ClassInfo; import org.spongepowered.asm.service.MixinService; public class ASMConfigPlugin implements IMixinConfigPlugin { - private final ConcurrentHashMap classesToDuplicateSrc = new ConcurrentHashMap<>(); - private final Map classDuplicationDummyTargets = new HashMap<>(); + private final Map dasmTransformedInPreApply = new ConcurrentHashMap<>(); private final Map redirectSetByName = new HashMap<>(); private final Throwable constructException; @@ -84,19 +77,6 @@ public ASMConfigPlugin() { for (RedirectsParser.RedirectSet redirectSet : redirectSets) { redirectSetByName.put(redirectSet.getName(), redirectSet); } - // TODO: whole-class redirects -// for (RedirectsParser.ClassTarget target : targetClasses) { -// List sets = new ArrayList<>(); -// for (String set : target.getSets()) { -// sets.add(redirectSetByName.get(set)); -// } -// redirectSetsByClassTarget.put(target, sets); -// if (target.isWholeClass()) { -// classDuplicationDummyTargets.put( -// mappings.mapClassName(findWholeClassTypeRedirectFor(target, redirectSetByName)), -// target.getClassName()); -// } -// } } catch (Throwable e) { constructException = e; // Annoying because mixin catches Throwable for creating a config plugin >:( return; @@ -104,18 +84,6 @@ public ASMConfigPlugin() { constructException = null; } - private String findWholeClassTypeRedirectFor(RedirectsParser.ClassTarget target, Map redirects) { - List sets = target.getSets(); - for (String set : sets) { - for (TypeRedirect typeRedirect : redirects.get(set).getTypeRedirects()) { - if (typeRedirect.srcClassName().equals(target.getClassName())) { - return typeRedirect.dstClassName(); - } - } - } - throw new IllegalStateException("No type redirect for whole class redirect " + target.getClassName()); - } - @Override public void onLoad(String mixinPackage) { if (this.constructException != null) { throw new Error(this.constructException); // throw error because Mixin catches Exception for onLoad @@ -130,9 +98,7 @@ private String findWholeClassTypeRedirectFor(RedirectsParser.ClassTarget target, return true; } - @Override public void acceptTargets(Set myTargets, Set otherTargets) { - - } + @Override public void acceptTargets(Set myTargets, Set otherTargets) { } @Nullable @Override public List getMixins() { @@ -140,81 +106,17 @@ private String findWholeClassTypeRedirectFor(RedirectsParser.ClassTarget target, } @Override public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { - try { - var mixinClass = MixinService.getService().getBytecodeProvider().getClassNode(mixinClassName); - var target = new RedirectsParser.ClassTarget(targetClassName); - Set redirectSets = new HashSet<>(); - - findRedirectSets(targetClassName, mixinClass, redirectSets); - buildClassTarget(mixinClass, target, TransformFrom.ApplicationStage.PRE_APPLY); - findRedirectSets(targetClassName, targetClass, redirectSets); - buildClassTarget(targetClass, target, TransformFrom.ApplicationStage.PRE_APPLY); - - if (classDuplicationDummyTargets.containsKey(targetClassName)) { - if (!classesToDuplicateSrc.containsKey(targetClassName)) { - throw new IllegalStateException("Class " + targetClassName + " has been loaded before " + classDuplicationDummyTargets.get(targetClassName)); - } - replaceClassContent(targetClass, classesToDuplicateSrc.get(targetClassName)); - return; - } - - //TODO: untangle the mess of some methods accepting the/class/name, and others accepting the.class.name - //Ideally the input json would all have the same, and we'd just figure it out here - if (target.getTargetMethods().isEmpty()) { - return; - } - if (target.isWholeClass()) { - throw new RuntimeException("Whole-class DASM duplication not supported yet"); - // TODO: whole-class redirects -// ClassNode duplicate = new ClassNode(); -// targetClass.accept(duplicate); -// -// this.transformer.transformClass(duplicate, target, redirectSetsByClassTarget.get(target)); -// classesToDuplicateSrc.put(duplicate.name.replace('/', '.'), duplicate); -// return; - } - - this.transformer.transformClass(targetClass, target, redirectSets.stream().toList()); - try { - // ugly hack to add class metadata to mixin - // based on https://github.com/Chocohead/OptiFabric/blob/54fc2ef7533e43d1982e14bc3302bcf156f590d8/src/main/java/me/modmuss50/optifabric/compat/fabricrendererapi - // /RendererMixinPlugin.java#L25:L44 - Method addMethod = ClassInfo.class.getDeclaredMethod("addMethod", MethodNode.class, boolean.class); - addMethod.setAccessible(true); - - ClassInfo ci = ClassInfo.forName(targetClassName); - Set existingMethods = ci.getMethods().stream().map(x -> x.getName() + x.getDesc()).collect(Collectors.toSet()); - for (MethodNode method : targetClass.methods) { - if (!existingMethods.contains(method.name + method.desc)) { - addMethod.invoke(ci, method, false); - } - } - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - throw new IllegalStateException(e); - } - } catch (Throwable t) { - t.printStackTrace(); - throw new RuntimeException(t); - } + var wasTransformed = transformClass(targetClassName, targetClass, mixinClassName, TransformFrom.ApplicationStage.PRE_APPLY); + dasmTransformedInPreApply.put(mixinClassName + "|" + targetClassName, wasTransformed); } @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { // Apply POST_APPLY dasm transforms - ClassNode mixinClass = null; - try { - mixinClass = MixinService.getService().getBytecodeProvider().getClassNode(mixinClassName); - } catch (ClassNotFoundException | IOException e) { - throw new RuntimeException(e); - } - var target = new RedirectsParser.ClassTarget(targetClassName); - Set redirectSets = new HashSet<>(); + boolean wasTransformed = transformClass(targetClassName, targetClass, mixinClassName, TransformFrom.ApplicationStage.POST_APPLY); - findRedirectSets(targetClassName, mixinClass, redirectSets); - buildClassTarget(mixinClass, target, TransformFrom.ApplicationStage.POST_APPLY); - findRedirectSets(targetClassName, targetClass, redirectSets); - buildClassTarget(targetClass, target, TransformFrom.ApplicationStage.POST_APPLY); - - this.transformer.transformClass(targetClass, target, redirectSets.stream().toList()); + // If no DASM transformation happened to this class, we can skip removing the prefixed methods + if (!(wasTransformed | dasmTransformedInPreApply.get(mixinClassName + "|" + targetClassName))) + return; // Find all DASM-added method nodes and their corresponding MixinMerged method nodes record PrefixMethodPair(MethodNode dasmAddedMethod, MethodNode mixinAddedMethod) { } @@ -227,7 +129,7 @@ record PrefixMethodPair(MethodNode dasmAddedMethod, MethodNode mixinAddedMethod) .findFirst(); if (mixinAddedMethod.isEmpty()) { - CubicChunks.LOGGER.warn("Found DASM added method `%s` without a corresponding MixinMerged method", methodNameWithoutPrefix); + CubicChunks.LOGGER.info(String.format("Found DASM added method `%s` without a corresponding MixinMerged method", methodNameWithoutPrefix)); } methodPairs.add(new PrefixMethodPair(methodNode, mixinAddedMethod.orElse(null))); } @@ -244,6 +146,37 @@ record PrefixMethodPair(MethodNode dasmAddedMethod, MethodNode mixinAddedMethod) }); } + /** + * @return Whether any transformation was done to the targetClass + */ + private boolean transformClass(String targetClassName, ClassNode targetClass, String mixinClassName, TransformFrom.ApplicationStage stage) { + ClassNode mixinClass; + try { + mixinClass = MixinService.getService().getBytecodeProvider().getClassNode(mixinClassName); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + + var target = new RedirectsParser.ClassTarget(targetClassName); + Set redirectSets = new HashSet<>(); + + findRedirectSets(targetClassName, mixinClass, redirectSets); + buildClassTarget(mixinClass, target, stage, "cc_dasm$"); + findRedirectSets(targetClassName, targetClass, redirectSets); + buildClassTarget(targetClass, target, stage, "cc_dasm$"); + + if (target.getTargetMethods().isEmpty() && target.wholeClass() == null) { + return false; + } + + if (target.wholeClass() != null) { + this.transformer.transformClass(targetClass, target, redirectSets.stream().toList()); + } else { + this.transformer.transformClass(targetClass, target, redirectSets.stream().toList()); + } + return true; + } + private void findRedirectSets(String targetClassName, ClassNode targetClass, Set redirectSets) { if (targetClass.invisibleAnnotations == null) { return; @@ -270,6 +203,10 @@ private void findRedirectSets(String targetClassName, ClassNode targetClass, Set useSets = (List) value; } } + if (useSets == null) { + redirectSets.add(redirectSetByName.get("general")); + continue; + } for (String useSet : useSets) { RedirectsParser.RedirectSet redirectSet = redirectSetByName.get(useSet); if (redirectSet == null) { @@ -280,13 +217,40 @@ private void findRedirectSets(String targetClassName, ClassNode targetClass, Set } } - private static void buildClassTarget(ClassNode targetClass, RedirectsParser.ClassTarget classTarget, TransformFrom.ApplicationStage stage) { + private static void buildClassTarget(ClassNode targetClass, RedirectsParser.ClassTarget classTarget, TransformFrom.ApplicationStage stage, String methodPrefix) { + if (targetClass.invisibleAnnotations == null) { + return; + } + for (AnnotationNode ann : targetClass.invisibleAnnotations) { + if (!ann.desc.equals("Lio/github/opencubicchunks/cubicchunks/mixin/TransformFromClass;") || ann.values == null) { + continue; + } + + List values = ann.values; + Type srcClass = null; + TransformFrom.ApplicationStage requestedStage = TransformFrom.ApplicationStage.PRE_APPLY; + for (int i = 0, valuesSize = values.size(); i < valuesSize; i += 2) { + String name = (String) values.get(i); + Object value = values.get(i + 1); + if (name.equals("value")) { + srcClass = parseCopyFromAnnotation((AnnotationNode) value); + } else if (name.equals("stage")) { + var parts = ((String[]) value); + requestedStage = TransformFrom.ApplicationStage.valueOf(parts[1]); + } + } + if (stage != requestedStage) { + continue; + } + classTarget.targetWholeClass(srcClass); + } + for (Iterator iterator = targetClass.methods.iterator(); iterator.hasNext(); ) { MethodNode method = iterator.next(); if (method.invisibleAnnotations == null) { continue; } - differentStage: + for (AnnotationNode ann : method.invisibleAnnotations) { if (!ann.desc.equals("Lio/github/opencubicchunks/cubicchunks/mixin/TransformFrom;")) { continue; @@ -307,20 +271,15 @@ private static void buildClassTarget(ClassNode targetClass, RedirectsParser.Clas for (int i = 0, valuesSize = values.size(); i < valuesSize; i += 2) { String name = (String) values.get(i); Object value = values.get(i + 1); - if (name.equals("value")) { - targetName = (String) value; - } else if (name.equals("makeSyntheticAccessor")) { - makeSyntheticAccessor = (Boolean) value; - } else if (name.equals("signature")) { - desc = parseMethodDescriptor((AnnotationNode) value); - } else if (name.equals("stage")) { - var parts = ((String[]) value); - requestedStage = TransformFrom.ApplicationStage.valueOf(parts[1]); - } else if (name.equals("copyFrom")) { - var val = (Type) value; - if (!Objects.equals(val, Type.getObjectType(Object.class.getName()))) { // Special case the default - srcOwner = val; + switch (name) { + case "value" -> targetName = (String) value; + case "makeSyntheticAccessor" -> makeSyntheticAccessor = (Boolean) value; + case "signature" -> desc = parseMethodDescriptor((AnnotationNode) value); + case "stage" -> { + var parts = ((String[]) value); + requestedStage = TransformFrom.ApplicationStage.valueOf(parts[1]); } + case "copyFrom" -> srcOwner = parseCopyFromAnnotation((AnnotationNode) value); } } if (stage != requestedStage) { @@ -336,19 +295,19 @@ private static void buildClassTarget(ClassNode targetClass, RedirectsParser.Clas if (srcOwner == null) { targetMethod = new RedirectsParser.ClassTarget.TargetMethod( new Transformer.ClassMethod(Type.getObjectType(targetClass.name), new org.objectweb.asm.commons.Method(targetName, desc)), - "cc_dasm$" + method.name, // Name is modified here to prevent mixin from overwriting it. We remove this prefix in postApply. + methodPrefix + method.name, // Name is modified here to prevent mixin from overwriting it. We remove this prefix in postApply. true, makeSyntheticAccessor ); } else { targetMethod = new RedirectsParser.ClassTarget.TargetMethod( srcOwner, new Transformer.ClassMethod(Type.getObjectType(targetClass.name), new org.objectweb.asm.commons.Method(targetName, desc)), - "cc_dasm$" + method.name, // Name is modified here to prevent mixin from overwriting it. We remove this prefix in postApply. + methodPrefix + method.name, // Name is modified here to prevent mixin from overwriting it. We remove this prefix in postApply. true, makeSyntheticAccessor ); } if (classTarget.getTargetMethods().stream().anyMatch(t -> t.method().method.equals(targetMethod.method().method))) { - throw new RuntimeException(String.format("Trying to add duplicate TargetMethod to ClassTarget:\n\t\t\t\t%s | %s", targetMethod.method().owner, + throw new RuntimeException(String.format("Trying to add duplicate TargetMethod to %s:\n\t\t\t\t%s | %s", classTarget.getClassName(), targetMethod.method().owner, targetMethod.method().method)); } classTarget.addTarget(targetMethod); @@ -356,6 +315,17 @@ private static void buildClassTarget(ClassNode targetClass, RedirectsParser.Clas } } + private static Type parseCopyFromAnnotation(AnnotationNode copyFromAnnotation) { + assert copyFromAnnotation.values.size() == 2 : "CopyFrom annotation has multiple targeting fields"; + + if ((copyFromAnnotation.values.get(0)).equals("clazz")) { + return (Type) copyFromAnnotation.values.get(1); + } else if ((copyFromAnnotation.values.get(0)).equals("string")) { + return Type.getObjectType((String) copyFromAnnotation.values.get(1)); + } + return Type.getType(Object.class); + } + private static String parseMethodDescriptor(AnnotationNode ann) { if (ann == null) { return null; @@ -368,12 +338,10 @@ private static String parseMethodDescriptor(AnnotationNode ann) { for (int i = 0, valuesSize = values.size(); i < valuesSize; i += 2) { String name = (String) values.get(i); Object value = values.get(i + 1); - if (name.equals("ret")) { - ret = (Type) value; - } else if (name.equals("args")) { - args = (List) value; - } else if (name.equals("useFromString")) { - useFromString = (Boolean) value; + switch (name) { + case "ret" -> ret = (Type) value; + case "args" -> args = (List) value; + case "useFromString" -> useFromString = (Boolean) value; } } if (useFromString) { @@ -382,34 +350,6 @@ private static String parseMethodDescriptor(AnnotationNode ann) { return Type.getMethodDescriptor(ret, args.toArray(new Type[0])); } - private void replaceClassContent(ClassNode node, ClassNode replaceWith) { - node.access = 0; - node.name = null; - node.signature = null; - node.superName = null; - node.interfaces.clear(); - node.sourceFile = null; - node.sourceDebug = null; - node.module = null; - node.outerClass = null; - node.outerMethod = null; - node.outerMethodDesc = null; - node.visibleAnnotations = null; - node.invisibleAnnotations = null; - node.visibleTypeAnnotations = null; - node.invisibleTypeAnnotations = null; - node.attrs = null; - node.innerClasses.clear(); - node.nestHostClass = null; - node.nestMembers = null; - node.permittedSubclasses = null; - node.recordComponents = null; - node.fields.clear(); - node.methods.clear(); - - replaceWith.accept(node); - } - private JsonElement parseFileAsJson(String fileName) { ClassLoader classloader = Thread.currentThread().getContextClassLoader(); try (InputStream is = classloader.getResourceAsStream(fileName)) { diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/CopyFrom.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/CopyFrom.java new file mode 100644 index 00000000..c38b7952 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/CopyFrom.java @@ -0,0 +1,7 @@ +package io.github.opencubicchunks.cubicchunks.mixin; + +public @interface CopyFrom { + Class clazz() default Object.class; + + String string() default ""; +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/TransformFrom.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/TransformFrom.java index 06cacdd3..02f38d68 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/TransformFrom.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/TransformFrom.java @@ -11,7 +11,7 @@ String value(); - Class copyFrom() default Object.class; + CopyFrom copyFrom() default @CopyFrom(); Signature signature() default @Signature(fromString = true); diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/TransformFromClass.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/TransformFromClass.java new file mode 100644 index 00000000..2d849285 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/TransformFromClass.java @@ -0,0 +1,7 @@ +package io.github.opencubicchunks.cubicchunks.mixin; + +public @interface TransformFromClass { + CopyFrom value(); + + TransformFrom.ApplicationStage stage() default TransformFrom.ApplicationStage.PRE_APPLY; +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/TestMixin.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/TestMixin.java deleted file mode 100644 index 6fba1ba2..00000000 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/TestMixin.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.opencubicchunks.cubicchunks.mixin.core.common; - -import io.github.opencubicchunks.cc_core.api.CubePos; -import io.github.opencubicchunks.cubicchunks.mixin.DasmRedirect; -import io.github.opencubicchunks.cubicchunks.mixin.TransformFrom; -import io.github.opencubicchunks.cubicchunks.mixin.TransformFrom.Signature; -import net.minecraft.server.level.ChunkMap; -import net.minecraft.world.level.ChunkPos; -import org.spongepowered.asm.mixin.Mixin; - -@Mixin(ChunkMap.class) -@DasmRedirect -public abstract class TestMixin { - @TransformFrom(value = "getChunkDebugData", signature = @Signature( - args = { ChunkPos.class }, - ret = String.class - )) - public abstract String getCubeDebugData(CubePos pos); - - @TransformFrom(value = "markPositionReplaceable(Lnet/minecraft/world/level/ChunkPos;)V") - public abstract void markCubePositionReplaceable(CubePos pos); -} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkAccess.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkAccess.java new file mode 100644 index 00000000..87083c13 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkAccess.java @@ -0,0 +1,30 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk; + +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; +import net.minecraft.core.Registry; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.blending.BlendingData; +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; + +@Mixin(ChunkAccess.class) +public abstract class MixinChunkAccess implements CloAccess { + private CloPos cc_cloPos; + + @Inject(method = "", at = @At("RETURN")) + private void onInit(ChunkPos chunkPos, UpgradeData p_187622_, LevelHeightAccessor p_187623_, Registry p_187624_, long p_187625_, LevelChunkSection[] p_187626_, BlendingData p_187627_, + CallbackInfo ci) { + cc_cloPos = CloPos.chunk(chunkPos); + } + + public CloPos cc_getCloPos() { + return cc_cloPos; + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinImposterProtoChunk.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinImposterProtoChunk.java new file mode 100644 index 00000000..2b0dcde5 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinImposterProtoChunk.java @@ -0,0 +1,17 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk; + +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ImposterProtoClo; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import net.minecraft.world.level.chunk.ImposterProtoChunk; +import net.minecraft.world.level.chunk.LevelChunk; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(ImposterProtoChunk.class) +public abstract class MixinImposterProtoChunk implements ImposterProtoClo { + @Override public LevelClo cc_getWrappedClo() { + return (LevelClo) this.getWrapped(); + } + + @Shadow public abstract LevelChunk getWrapped(); +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinLevelChunk.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinLevelChunk.java new file mode 100644 index 00000000..9c6a4d6a --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinLevelChunk.java @@ -0,0 +1,9 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk; + +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import net.minecraft.world.level.chunk.LevelChunk; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(LevelChunk.class) +public abstract class MixinLevelChunk implements LevelClo { +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinCubeAccess.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinCubeAccess.java new file mode 100644 index 00000000..65a4ebeb --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinCubeAccess.java @@ -0,0 +1,9 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube; + +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; +import org.spongepowered.asm.mixin.Mixin; + +// Needed for DASM to apply +@Mixin(CubeAccess.class) +public class MixinCubeAccess { +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinLevelCube$BoundTickingBlockEntity.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinLevelCube$BoundTickingBlockEntity.java new file mode 100644 index 00000000..a5c43588 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinLevelCube$BoundTickingBlockEntity.java @@ -0,0 +1,8 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube; + +import org.spongepowered.asm.mixin.Mixin; + +// Needed for DASM to apply +@Mixin(targets = "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$BoundTickingBlockEntity") +public class MixinLevelCube$BoundTickingBlockEntity { +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinLevelCube$RebindableTickingBlockEntityWrapper.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinLevelCube$RebindableTickingBlockEntityWrapper.java new file mode 100644 index 00000000..2794c6fa --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinLevelCube$RebindableTickingBlockEntityWrapper.java @@ -0,0 +1,8 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube; + +import org.spongepowered.asm.mixin.Mixin; + +// Needed for DASM to apply +@Mixin(targets = "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$RebindableTickingBlockEntityWrapper") +public class MixinLevelCube$RebindableTickingBlockEntityWrapper { +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinLevelCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinLevelCube.java new file mode 100644 index 00000000..be7c74b8 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinLevelCube.java @@ -0,0 +1,30 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube; + +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; +import net.minecraft.core.BlockPos; +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +// Needed for DASM to apply +@Mixin(LevelCube.class) +public abstract class MixinLevelCube extends MixinCubeAccess { + /** + * Redirect to use cube section indexing instead of chunk section indexing + */ + @Dynamic @Redirect(method = "cc_dasm$getBlockState", at = @At(value = "INVOKE", target = "Lio/github/opencubicchunks/cubicchunks/world/level/cube/LevelCube;getSectionIndex(I)I")) + private int cc_onGetBlockState_SectionIndex(LevelCube instance, int i, BlockPos pos) { + return Coords.blockToIndex(pos); + } + + /** + * Redirect to use cube section indexing instead of chunk section indexing + */ + @Dynamic @Redirect(method = "cc_dasm$getFluidState(III)Lnet/minecraft/world/level/material/FluidState;", at = @At(value = "INVOKE", target = "Lio/github/opencubicchunks/cubicchunks" + + "/world/level/cube/LevelCube;getSectionIndex(I)I")) + private int cc_onGetFluidState_SectionIndex(LevelCube instance, int i, int x, int y, int z) { + return Coords.blockToIndex(x, y, z); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/CloAccess.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/CloAccess.java index 92b4a1ef..c5cfd1a9 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/CloAccess.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/CloAccess.java @@ -1,4 +1,154 @@ package io.github.opencubicchunks.cubicchunks.world.level.chunklike; -public interface CloAccess { +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +import it.unimi.dsi.fastutil.shorts.ShortList; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.BiomeGenerationSettings; +import net.minecraft.world.level.biome.BiomeManager; +import net.minecraft.world.level.biome.BiomeResolver; +import net.minecraft.world.level.biome.Climate; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.LightChunk; +import net.minecraft.world.level.chunk.StructureAccess; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.gameevent.GameEventListenerRegistry; +import net.minecraft.world.level.levelgen.BelowZeroRetrogen; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.NoiseChunk; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.ticks.TickContainerAccess; + +public interface CloAccess extends BlockGetter, BiomeManager.NoiseBiomeSource, LightChunk, StructureAccess { + GameEventListenerRegistry getListenerRegistry(int p_251437_); + + @Nullable BlockState setBlockState(BlockPos p_62087_, BlockState p_62088_, boolean p_62089_); + + void setBlockEntity(BlockEntity p_156114_); + + void addEntity(Entity p_62078_); + + int getHighestFilledSectionIndex(); + + // Deprecated + int getHighestSectionPosition(); + + Set getBlockEntitiesPos(); + + LevelChunkSection[] getSections(); + + LevelChunkSection getSection(int p_187657_); + + Collection> getHeightmaps(); + + void setHeightmap(Heightmap.Types p_62083_, long[] p_62084_); + + Heightmap getOrCreateHeightmapUnprimed(Heightmap.Types p_62079_); + + boolean hasPrimedHeightmap(Heightmap.Types p_187659_); + + int getHeight(Heightmap.Types p_62080_, int p_62081_, int p_62082_); + + // replacement of ChunkPos getPos() + CloPos cc_getCloPos(); + + Map getAllStarts(); + + void setAllStarts(Map p_62090_); + + boolean isYSpaceEmpty(int p_62075_, int p_62076_); + + void setUnsaved(boolean p_62094_); + + boolean isUnsaved(); + + ChunkStatus getStatus(); + + ChunkStatus getHighestGeneratedStatus(); + + void removeBlockEntity(BlockPos p_62101_); + + void markPosForPostprocessing(BlockPos p_62102_); + + ShortList[] getPostProcessing(); + + void addPackedPostProcess(short p_62092_, int p_62093_); + + void setBlockEntityNbt(CompoundTag p_62091_); + + @Nullable CompoundTag getBlockEntityNbt(BlockPos p_62103_); + + @Nullable CompoundTag getBlockEntityNbtForSaving(BlockPos p_62104_); + + void findBlocks(Predicate p_285343_, BiConsumer p_285030_); + + void findBlocks(java.util.function.BiPredicate p_285343_, BiConsumer p_285030_); + + TickContainerAccess getBlockTicks(); + + TickContainerAccess getFluidTicks(); + + ChunkAccess.TicksToSave getTicksForSerialization(); + + UpgradeData getUpgradeData(); + + boolean isOldNoiseGeneration(); + + @Nullable BlendingData getBlendingData(); + + void setBlendingData(BlendingData p_187646_); + + long getInhabitedTime(); + + void incrementInhabitedTime(long p_187633_); + + void setInhabitedTime(long p_62099_); + + boolean isLightCorrect(); + + void setLightCorrect(boolean p_62100_); + + NoiseChunk getOrCreateNoiseChunk(Function p_223013_); + + @Deprecated BiomeGenerationSettings carverBiome(Supplier p_223015_); + + void fillBiomesFromNoise(BiomeResolver p_187638_, Climate.Sampler p_187639_); + + boolean hasAnyStructureReferences(); + + @Nullable BelowZeroRetrogen getBelowZeroRetrogen(); + + boolean isUpgrading(); + + LevelHeightAccessor getHeightAccessorForGeneration(); + + void initializeLightSources(); + + // TODO static methods +// static ShortList getOrCreateOffsetList(ShortList[] p_62096_, int p_62097_); +// +// static record TicksToSave(SerializableTickContainer blocks, SerializableTickContainer fluids); + + // TODO forge method +// @Nullable public net.minecraft.world.level.LevelAccessor getWorldForge() { return null; } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ImposterProtoClo.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ImposterProtoClo.java index b34ce141..eee89974 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ImposterProtoClo.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ImposterProtoClo.java @@ -1,4 +1,5 @@ package io.github.opencubicchunks.cubicchunks.world.level.chunklike; public interface ImposterProtoClo extends ProtoClo { + LevelClo cc_getWrappedClo(); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/LevelClo.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/LevelClo.java index c1c61549..bf82a65c 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/LevelClo.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/LevelClo.java @@ -1,4 +1,61 @@ package io.github.opencubicchunks.cubicchunks.world.level.chunklike; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.material.FluidState; + public interface LevelClo extends CloAccess { + FluidState getFluidState(int p_62815_, int p_62816_, int p_62817_); + + @Nullable + BlockEntity getBlockEntity(BlockPos p_62868_, LevelChunk.EntityCreationType p_62869_); + + void addAndRegisterBlockEntity(BlockEntity p_156391_); + + boolean isTicking(BlockPos p_156411_); + + void runPostLoad(); + + boolean isEmpty(); + + void replaceWithPacketData( + FriendlyByteBuf p_187972_, CompoundTag p_187973_, Consumer p_187974_ + ); + + void replaceBiomes(FriendlyByteBuf p_275574_); + + void setLoaded(boolean p_62914_); + + Level getLevel(); + + Map getBlockEntities(); + + void postProcessGeneration(); + + void unpackTicks(long p_187986_); + + void registerTickContainerInLevel(ServerLevel p_187959_); + + void unregisterTickContainerFromLevel(ServerLevel p_187980_); + + FullChunkStatus getFullStatus(); + + void setFullStatus(Supplier p_62880_); + + void clearAllBlockEntities(); + + void registerAllBlockEntitiesAfterLevelLoad(); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ProtoClo.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ProtoClo.java index 33f7371d..cbaa3312 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ProtoClo.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/chunklike/ProtoClo.java @@ -1,4 +1,50 @@ package io.github.opencubicchunks.cubicchunks.world.level.chunklike; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.CarvingMask; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.levelgen.BelowZeroRetrogen; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.lighting.LevelLightEngine; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.ticks.LevelChunkTicks; + public interface ProtoClo extends CloAccess { + Map getBlockEntities(); + + void addEntity(CompoundTag p_63243_); + + List getEntities(); + + void setStatus(ChunkStatus p_63187_); + + Map getBlockEntityNbts(); + + @Nullable + CarvingMask getCarvingMask(GenerationStep.Carving p_188185_); + + CarvingMask getOrCreateCarvingMask(GenerationStep.Carving p_188191_); + + void setCarvingMask(GenerationStep.Carving p_188187_, CarvingMask p_188188_); + + void setLightEngine(LevelLightEngine p_63210_); + + void setBelowZeroRetrogen(@Nullable BelowZeroRetrogen p_188184_); + + LevelChunkTicks unpackBlockTicks(); + + LevelChunkTicks unpackFluidTicks(); + + // TODO statics +// public static short packOffsetCoordinates(BlockPos p_63281_); +// +// public static BlockPos unpackOffsetCoordinates(short p_63228_, int p_63229_, ChunkPos p_63230_); } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java index 08ae746d..b2038e31 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java @@ -1,6 +1,339 @@ package io.github.opencubicchunks.cubicchunks.world.level.cube; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import com.google.common.collect.Maps; +import io.github.opencubicchunks.cc_core.api.CubicConstants; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.CubicChunks; +import io.github.opencubicchunks.cubicchunks.mixin.CopyFrom; +import io.github.opencubicchunks.cubicchunks.mixin.DasmRedirect; +import io.github.opencubicchunks.cubicchunks.mixin.TransformFrom; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.shorts.ShortList; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeGenerationSettings; +import net.minecraft.world.level.biome.BiomeResolver; +import net.minecraft.world.level.biome.Climate; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.BelowZeroRetrogen; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.NoiseChunk; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; +import net.minecraft.world.level.lighting.ChunkSkyLightSources; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.ticks.TickContainerAccess; +import org.jetbrains.annotations.Nullable; + +@DasmRedirect({ "cubeAccessAndDescendants" }) +public abstract class CubeAccess implements CloAccess { + // Fields copied from ChunkAccess, except ChunkPos -> CloPos + protected final ShortList[] postProcessing; + protected volatile boolean unsaved; + private volatile boolean isLightCorrect; + protected final CloPos cloPos; + private long inhabitedTime; + @Nullable + @Deprecated + private BiomeGenerationSettings carverBiomeSettings; + // TODO (P3) NoiseChunk might need to be different + @Nullable protected NoiseChunk noiseChunk; + + protected final UpgradeData upgradeData; + @Nullable + protected BlendingData blendingData; + protected final Map heightmaps = Maps.newEnumMap(Heightmap.Types.class); + protected ChunkSkyLightSources skyLightSources; + private final Map structureStarts = Maps.newHashMap(); + private final Map structuresRefences = Maps.newHashMap(); + protected final Map pendingBlockEntities = Maps.newHashMap(); + protected final Map blockEntities = Maps.newHashMap(); + protected final LevelHeightAccessor levelHeightAccessor; + protected final LevelChunkSection[] sections; + + // Constructor signature matches ChunkAccess for DASM redirect purposes + public CubeAccess( + CloPos cloPos, + UpgradeData upgradeData, + LevelHeightAccessor levelHeightAccessor, + Registry biomeRegistry, + long inhabitedTime, + @Nullable LevelChunkSection[] chunkSections, + @Nullable BlendingData blendingData + ) { + this.cloPos = cloPos; + this.upgradeData = upgradeData; + this.levelHeightAccessor = levelHeightAccessor; + this.sections = new LevelChunkSection[CubicConstants.SECTION_COUNT]; + this.inhabitedTime = inhabitedTime; + this.postProcessing = new ShortList[CubicConstants.SECTION_COUNT]; + this.blendingData = blendingData; + this.skyLightSources = new ChunkSkyLightSources(levelHeightAccessor); + if (chunkSections != null) { + if (this.sections.length == chunkSections.length) { + System.arraycopy(chunkSections, 0, this.sections, 0, this.sections.length); + } else { + CubicChunks.LOGGER.warn("Could not set level cube sections, array length is {} instead of {}", chunkSections.length, this.sections.length); + } + } + + replaceMissingSections(biomeRegistry, this.sections); + } + + private static void replaceMissingSections(Registry p_281389_, LevelChunkSection[] p_282796_) { + for(int i = 0; i < p_282796_.length; ++i) { + if (p_282796_[i] == null) { + p_282796_[i] = new LevelChunkSection(p_281389_); + } + } + } + + @Override @Nullable public abstract BlockState setBlockState(BlockPos p_62087_, BlockState p_62088_, boolean p_62089_); + + @Override public abstract void setBlockEntity(BlockEntity p_156114_); + + @Override public abstract void addEntity(Entity p_62078_); + + // Next two methods are used for vanilla heightmaps/lighting/end gateways. We shouldn't need these + @Override public int getHighestFilledSectionIndex() { + // TODO is there a better dummy value to return? + return -1; + } + + @Override public int getHighestSectionPosition() { + return this.getMinBuildHeight(); + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getBlockEntitiesPos()Ljava/util/Set;") + @Override public native Set getBlockEntitiesPos(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getSections()[Lnet/minecraft/world/level/chunk/LevelChunkSection;") + @Override public native LevelChunkSection[] getSections(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getSection(I)Lnet/minecraft/world/level/chunk/LevelChunkSection;") + @Override public native LevelChunkSection getSection(int p_187657_); + + // TODO (P2) heightmap methods on cubes + @Override public Collection> getHeightmaps() { + throw new UnsupportedOperationException(); + } + + @Override public void setHeightmap(Heightmap.Types p_62083_, long[] p_62084_) { + } + + @Override public Heightmap getOrCreateHeightmapUnprimed(Heightmap.Types p_62079_) { + throw new UnsupportedOperationException(); + } + + @Override public boolean hasPrimedHeightmap(Heightmap.Types p_187659_) { + return false; + } + + @Override public int getHeight(Heightmap.Types p_62080_, int p_62081_, int p_62082_) { + return CubicChunks.SUPERFLAT_HEIGHT; + } + // end heightmaps + + @Override public CloPos cc_getCloPos() { + return cloPos; + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getStartForStructure(Lnet/minecraft/world/level/levelgen/structure/Structure;)" + + "Lnet/minecraft/world/level/levelgen/structure/StructureStart;") + @Override @Nullable public native StructureStart getStartForStructure(Structure p_223005_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "setStartForStructure(Lnet/minecraft/world/level/levelgen/structure/Structure;" + + "Lnet/minecraft/world/level/levelgen/structure/StructureStart;)V") + @Override public native void setStartForStructure(Structure p_223010_, StructureStart p_223011_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getAllStarts()Ljava/util/Map;") + @Override public native Map getAllStarts(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "setAllStarts(Ljava/util/Map;)V") + @Override public native void setAllStarts(Map p_62090_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getReferencesForStructure(Lnet/minecraft/world/level/levelgen/structure/Structure;)" + + "Lit/unimi/dsi/fastutil/longs/LongSet;") + @Override public native LongSet getReferencesForStructure(Structure p_223017_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "addReferenceForStructure(Lnet/minecraft/world/level/levelgen/structure/Structure;J)V") + @Override public native void addReferenceForStructure(Structure p_223007_, long p_223008_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getAllReferences()Ljava/util/Map;") + @Override public native Map getAllReferences(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "setAllReferences(Ljava/util/Map;)V") + @Override public native void setAllReferences(Map p_187663_); + + @Override public boolean isYSpaceEmpty(int p_62075_, int p_62076_) { + // TODO + return false; + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "setUnsaved(Z)V") + @Override public native void setUnsaved(boolean p_62094_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "isUnsaved()Z") + @Override public native boolean isUnsaved(); + + @Override public abstract ChunkStatus getStatus(); + + @Override public ChunkStatus getHighestGeneratedStatus() { + // In ChunkAccess this method is only used for below-zero retrogen; with no retrogen it does this + return this.getStatus(); + } + + @Override public abstract void removeBlockEntity(BlockPos p_62101_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "markPosForPostprocessing(Lnet/minecraft/core/BlockPos;)V") + @Override public native void markPosForPostprocessing(BlockPos p_62102_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getPostProcessing()[Lit/unimi/dsi/fastutil/shorts/ShortList;") + @Override public native ShortList[] getPostProcessing(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "addPackedPostProcess(SI)V") + @Override public native void addPackedPostProcess(short p_62092_, int p_62093_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "setBlockEntityNbt(Lnet/minecraft/nbt/CompoundTag;)V") + @Override public native void setBlockEntityNbt(CompoundTag p_62091_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getBlockEntityNbt(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/nbt/CompoundTag;") + @Override @Nullable public native CompoundTag getBlockEntityNbt(BlockPos p_62103_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getBlockEntityNbtForSaving(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/nbt/CompoundTag;") + @Override @Nullable public native CompoundTag getBlockEntityNbtForSaving(BlockPos p_62104_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "findBlockLightSources(Ljava/util/function/BiConsumer;)V") + @Override public native void findBlockLightSources(BiConsumer p_285269_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "findBlocks(Ljava/util/function/Predicate;Ljava/util/function/BiConsumer;)V") + @Override public native void findBlocks(Predicate p_285343_, BiConsumer p_285030_); + + @Override public void findBlocks(BiPredicate p_285343_, BiConsumer p_285030_) { + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + + for (int y = 0; y < CubicConstants.DIAMETER_IN_SECTIONS; y++) { + for (int z = 0; z < CubicConstants.DIAMETER_IN_SECTIONS; z++) { + for (int x = 0; x < CubicConstants.DIAMETER_IN_SECTIONS; x++) { + LevelChunkSection levelchunksection = this.getSection(Coords.sectionToIndex(x, y, z)); + if (levelchunksection.maybeHas((state) -> p_285343_.test(state, BlockPos.ZERO))) { + BlockPos blockpos = this.cloPos.cubePos().asSectionPos().offset(x, y, z).origin(); + + for(int sectionLocalY = 0; sectionLocalY < SectionPos.SECTION_SIZE; ++sectionLocalY) { + for(int sectionLocalZ = 0; sectionLocalZ < SectionPos.SECTION_SIZE; ++sectionLocalZ) { + for(int sectionLocalX = 0; sectionLocalX < SectionPos.SECTION_SIZE; ++sectionLocalX) { + BlockState blockstate = levelchunksection.getBlockState(sectionLocalX, sectionLocalY, sectionLocalZ); + mutableBlockPos.setWithOffset(blockpos, sectionLocalX, sectionLocalY, sectionLocalZ); + if (p_285343_.test(blockstate, mutableBlockPos.immutable())) { + p_285030_.accept(mutableBlockPos, blockstate); + } + } + } + } + } + } + } + } + } + + @Override public abstract TickContainerAccess getBlockTicks(); + + @Override public abstract TickContainerAccess getFluidTicks(); + + @Override public abstract ChunkAccess.TicksToSave getTicksForSerialization(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getUpgradeData()Lnet/minecraft/world/level/chunk/UpgradeData;") + @Override public native UpgradeData getUpgradeData(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "isOldNoiseGeneration()Z") + @Override public native boolean isOldNoiseGeneration(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getBlendingData()Lnet/minecraft/world/level/levelgen/blending/BlendingData;") + @Override @Nullable public native BlendingData getBlendingData(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "setBlendingData(Lnet/minecraft/world/level/levelgen/blending/BlendingData;)V") + @Override public native void setBlendingData(BlendingData p_187646_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getInhabitedTime()J") + @Override public native long getInhabitedTime(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "incrementInhabitedTime(J)V") + @Override public native void incrementInhabitedTime(long p_187633_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "setInhabitedTime(J)V") + @Override public native void setInhabitedTime(long p_62099_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "isLightCorrect()Z") + @Override public native boolean isLightCorrect(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "setLightCorrect(Z)V") + @Override public native void setLightCorrect(boolean p_62100_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getMinBuildHeight()I") + @Override public native int getMinBuildHeight(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getHeight()I") + @Override public native int getHeight(); + + @Override public NoiseChunk getOrCreateNoiseChunk(Function p_223013_) { + throw new UnsupportedOperationException(); // TODO P3 + } + + @Override public BiomeGenerationSettings carverBiome(Supplier p_223015_) { + throw new UnsupportedOperationException(); // TODO P3 + } + + @Override public Holder getNoiseBiome(int p_204347_, int p_204348_, int p_204349_) { + throw new UnsupportedOperationException(); // TODO P3 + } + + @Override public void fillBiomesFromNoise(BiomeResolver p_187638_, Climate.Sampler p_187639_) { + throw new UnsupportedOperationException(); // TODO P3 + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "hasAnyStructureReferences()Z") + @Override public native boolean hasAnyStructureReferences(); + + @Override @Nullable public BelowZeroRetrogen getBelowZeroRetrogen() { + return null; // No below-zero retrogen in cubic worlds :) + } + + @Override public boolean isUpgrading() { + return false; // Used for below-zero retrogen; not applicable to cubes + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getHeightAccessorForGeneration()Lnet/minecraft/world/level/LevelHeightAccessor;") + @Override public native LevelHeightAccessor getHeightAccessorForGeneration(); + + @Override public void initializeLightSources() { + // TODO P2 + } -public class CubeAccess implements CloAccess { + @Override public ChunkSkyLightSources getSkyLightSources() { + throw new UnsupportedOperationException(); // TODO P2 + } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java index e73d9ad9..cc8988d9 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java @@ -2,5 +2,6 @@ import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ImposterProtoClo; -public class ImposterProtoCube extends ProtoCube implements ImposterProtoClo { +// not yet implemented - stub class +public abstract class ImposterProtoCube extends ProtoCube implements ImposterProtoClo { } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/LevelCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/LevelCube.java index 940cc458..5242cc22 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/LevelCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/LevelCube.java @@ -1,6 +1,434 @@ package io.github.opencubicchunks.cubicchunks.world.level.cube; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.mojang.logging.LogUtils; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.mixin.CopyFrom; +import io.github.opencubicchunks.cubicchunks.mixin.DasmRedirect; +import io.github.opencubicchunks.cubicchunks.mixin.TransformFrom; +import io.github.opencubicchunks.cubicchunks.mixin.TransformFromClass; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.TickingBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.gameevent.GameEventListenerRegistry; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.ticks.LevelChunkTicks; +import net.minecraft.world.ticks.TickContainerAccess; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +@DasmRedirect({ "cubeAccessAndDescendants" }) public class LevelCube extends CubeAccess implements LevelClo { + // Fields matching LevelChunk + static final Logger LOGGER = LogUtils.getLogger(); + private static final TickingBlockEntity NULL_TICKER = new TickingBlockEntity() { + @Override public void tick() { + } + + @Override public boolean isRemoved() { + return true; + } + + @Override public BlockPos getPos() { + return BlockPos.ZERO; + } + + @Override public String getType() { + return ""; + } + }; + private final Map tickersInLevel = Maps.newHashMap(); + private boolean loaded; + final Level level; + @Nullable private Supplier fullStatus; + @Nullable private PostLoadProcessor postLoad; + private final Int2ObjectMap gameEventListenerRegistrySections; + private final LevelChunkTicks blockTicks; + private final LevelChunkTicks fluidTicks; + + // Constructors mirroring vanilla signatures + public LevelCube(Level p_187945_, CloPos p_187946_) { + this(p_187945_, p_187946_, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null); + } + + public LevelCube( + Level p_196854_, + CloPos p_196855_, + UpgradeData p_196856_, + LevelChunkTicks p_196857_, + LevelChunkTicks p_196858_, + long p_196859_, + @Nullable LevelChunkSection[] p_196860_, + @Nullable PostLoadProcessor p_196861_, + @Nullable BlendingData p_196862_ + ) { + super(p_196855_, p_196856_, p_196854_, p_196854_.registryAccess().registryOrThrow(Registries.BIOME), p_196859_, p_196860_, p_196862_); + this.level = p_196854_; + this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap<>(); + + for(Heightmap.Types heightmap$types : Heightmap.Types.values()) { + if (ChunkStatus.FULL.heightmapsAfter().contains(heightmap$types)) { + // TODO (P2) heightmaps +// this.heightmaps.put(heightmap$types, new Heightmap(this, heightmap$types)); + } + } + + this.postLoad = p_196861_; + this.blockTicks = p_196857_; + this.fluidTicks = p_196858_; + } + + public LevelCube(ServerLevel p_196850_, ProtoCube p_196851_, @Nullable PostLoadProcessor p_196852_) { + this( + p_196850_, + p_196851_.cc_getCloPos(), + p_196851_.getUpgradeData(), + p_196851_.unpackBlockTicks(), + p_196851_.unpackFluidTicks(), + p_196851_.getInhabitedTime(), + p_196851_.getSections(), + p_196852_, + p_196851_.getBlendingData() + ); + + for(BlockEntity blockentity : p_196851_.getBlockEntities().values()) { + this.setBlockEntity(blockentity); + } + + this.pendingBlockEntities.putAll(p_196851_.getBlockEntityNbts()); + + for(int i = 0; i < p_196851_.getPostProcessing().length; ++i) { + this.postProcessing[i] = p_196851_.getPostProcessing()[i]; + } + + this.setAllStarts(p_196851_.getAllStarts()); + this.setAllReferences(p_196851_.getAllReferences()); + + for(Map.Entry entry : p_196851_.getHeightmaps()) { + if (ChunkStatus.FULL.heightmapsAfter().contains(entry.getKey())) { + // TODO (P2) heightmaps +// this.setHeightmap(entry.getKey(), entry.getValue().getRawData()); + } + } + + this.skyLightSources = p_196851_.skyLightSources; + this.setLightCorrect(p_196851_.isLightCorrect()); + this.unsaved = true; + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getBlockTicks()Lnet/minecraft/world/ticks/TickContainerAccess;") + @Override public native TickContainerAccess getBlockTicks(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getFluidTicks()Lnet/minecraft/world/ticks/TickContainerAccess;") + @Override public native TickContainerAccess getFluidTicks(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getTicksForSerialization()Lnet/minecraft/world/level/chunk/ChunkAccess$TicksToSave;") + @Override public native ChunkAccess.TicksToSave getTicksForSerialization(); + + // TODO should this actually be dasm'd? + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getListenerRegistry(I)Lnet/minecraft/world/level/gameevent/GameEventListenerRegistry;") + @Override public native GameEventListenerRegistry getListenerRegistry(int p_251193_); + + // dasm + mixin + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;") + @Override public native @NotNull BlockState getBlockState(BlockPos p_62923_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getFluidState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/material/FluidState;") + @Override public native @NotNull FluidState getFluidState(BlockPos p_62895_); + + // dasm + mixin + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getFluidState(III)Lnet/minecraft/world/level/material/FluidState;") + @Override public native FluidState getFluidState(int p_62815_, int p_62816_, int p_62817_); + + // TODO might be dasm-able eventually, if we get more powerful mixin tools + @Nullable @Override public BlockState setBlockState(BlockPos pos, BlockState state, boolean p_62867_) { + var chunkSection = this.getSection(Coords.blockToIndex(pos)); + boolean isOnlyAir = chunkSection.hasOnlyAir(); + if (isOnlyAir && state.isAir()) { + return null; + } else { + int sectionLocalX = pos.getX() & 15; + int sectionLocalY = pos.getY() & 15; + int sectionLocalZ = pos.getZ() & 15; + var previousState = chunkSection.setBlockState(sectionLocalX, sectionLocalY, sectionLocalZ, state); + if (previousState == state) { + return null; + } else { + var block = state.getBlock(); + // TODO (P2) heightmaps + lighting - see vanilla equivalent to this method + + boolean flag2 = previousState.hasBlockEntity(); + if (!this.level.isClientSide) { + previousState.onRemove(this.level, pos, state, p_62867_); + } else if ((!previousState.is(block) || !state.hasBlockEntity()) && flag2) { + this.removeBlockEntity(pos); + } + + if (!chunkSection.getBlockState(sectionLocalX, sectionLocalY, sectionLocalZ).is(block)) { + return null; + } else { + if (!this.level.isClientSide && !this.level.captureBlockSnapshots) { + state.onPlace(this.level, pos, previousState, p_62867_); + } + + if (state.hasBlockEntity()) { + BlockEntity blockentity = this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); + if (blockentity == null) { + blockentity = ((EntityBlock)block).newBlockEntity(pos, state); + if (blockentity != null) { + this.addAndRegisterBlockEntity(blockentity); + } + } else { + blockentity.setBlockState(state); + this.updateBlockEntityTicker(blockentity); + } + } + + this.unsaved = true; + return previousState; + } + } + } + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "addEntity(Lnet/minecraft/world/entity/Entity;)V") + @Deprecated @Override public native void addEntity(Entity p_62826_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "createBlockEntity(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/entity/BlockEntity;") + @Nullable private native BlockEntity createBlockEntity(BlockPos p_62935_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getBlockEntity(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/entity/BlockEntity;") + @Override @Nullable public native BlockEntity getBlockEntity(BlockPos p_62912_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getBlockEntity(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/chunk/LevelChunk$EntityCreationType;)" + + "Lnet/minecraft/world/level/block/entity/BlockEntity;") + @Nullable public native BlockEntity getBlockEntity(BlockPos p_62868_, LevelChunk.EntityCreationType p_62869_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "addAndRegisterBlockEntity(Lnet/minecraft/world/level/block/entity/BlockEntity;)V") + public native void addAndRegisterBlockEntity(BlockEntity p_156391_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "isInLevel()Z") + private native boolean isInLevel(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "isTicking(Lnet/minecraft/core/BlockPos;)Z") + public native boolean isTicking(BlockPos p_156411_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "setBlockEntity(Lnet/minecraft/world/level/block/entity/BlockEntity;)V") + @Override public native void setBlockEntity(BlockEntity p_156374_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getBlockEntityNbtForSaving(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/nbt/CompoundTag;") + @Override @Nullable public native CompoundTag getBlockEntityNbtForSaving(BlockPos p_62932_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "removeBlockEntity(Lnet/minecraft/core/BlockPos;)V") + @Override public native void removeBlockEntity(BlockPos p_62919_); + + // TODO maybe shouldn't be dasm + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "removeGameEventListener(Lnet/minecraft/world/level/block/entity/BlockEntity;" + + "Lnet/minecraft/server/level/ServerLevel;" + + ")V") + private native void removeGameEventListener(T p_223413_, ServerLevel p_223414_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "removeGameEventListenerRegistry(I)V") + private native void removeGameEventListenerRegistry(int p_283355_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "removeBlockEntityTicker(Lnet/minecraft/core/BlockPos;)V") + private native void removeBlockEntityTicker(BlockPos p_156413_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "runPostLoad()V") + public native void runPostLoad(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "isEmpty()Z") + public native boolean isEmpty(); + + public void replaceWithPacketData( + FriendlyByteBuf p_187972_, CompoundTag p_187973_, Consumer p_187974_ + ) { + this.clearAllBlockEntities(); + + for(LevelChunkSection levelchunksection : this.sections) { + levelchunksection.read(p_187972_); + } + + // TODO (P2) heightmaps - see vanilla equivalent + + // TODO (P2) lighting +// this.initializeLightSources(); + p_187974_.accept((p_187968_, p_187969_, p_187970_) -> { + BlockEntity blockentity = this.getBlockEntity(p_187968_, LevelChunk.EntityCreationType.IMMEDIATE); + if (blockentity != null && p_187970_ != null && blockentity.getType() == p_187969_) { + blockentity.handleUpdateTag(p_187970_); + } + }); + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "replaceBiomes(Lnet/minecraft/network/FriendlyByteBuf;)V") + public native void replaceBiomes(FriendlyByteBuf p_275574_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "setLoaded(Z)V") + public native void setLoaded(boolean p_62914_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getLevel()Lnet/minecraft/world/level/Level;") + public native Level getLevel(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getBlockEntities()Ljava/util/Map;") + public native Map getBlockEntities(); + + // TODO P2 or P3 figure this out later - stub method for now + public void postProcessGeneration() { + for (int i = 0; i < this.postProcessing.length; ++i) { + if (this.postProcessing[i] != null) { + this.postProcessing[i].clear(); + } + } + + for(BlockPos blockpos1 : ImmutableList.copyOf(this.pendingBlockEntities.keySet())) { + this.getBlockEntity(blockpos1); + } + + this.pendingBlockEntities.clear(); + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "promotePendingBlockEntity(Lnet/minecraft/core/BlockPos;Lnet/minecraft/nbt/CompoundTag;)" + + "Lnet/minecraft/world/level/block/entity/BlockEntity;") + @Nullable private native BlockEntity promotePendingBlockEntity(BlockPos p_62871_, CompoundTag p_62872_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "unpackTicks(J)V") + public native void unpackTicks(long p_187986_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "registerTickContainerInLevel(Lnet/minecraft/server/level/ServerLevel;)V") + public native void registerTickContainerInLevel(ServerLevel p_187959_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "unregisterTickContainerFromLevel(Lnet/minecraft/server/level/ServerLevel;)V") + public native void unregisterTickContainerFromLevel(ServerLevel p_187980_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getStatus()Lnet/minecraft/world/level/chunk/ChunkStatus;") + @Override public native ChunkStatus getStatus(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "getFullStatus()Lnet/minecraft/server/level/FullChunkStatus;") + public native FullChunkStatus getFullStatus(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "setFullStatus(Ljava/util/function/Supplier;)V") + public native void setFullStatus(Supplier p_62880_); + + // TODO a bit concerning + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "clearAllBlockEntities()V") + public native void clearAllBlockEntities(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "registerAllBlockEntitiesAfterLevelLoad()V") + public native void registerAllBlockEntitiesAfterLevelLoad(); + + // TODO (P3): GameEvent stuff is a bit concerning + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "addGameEventListener(Lnet/minecraft/world/level/block/entity/BlockEntity;" + + "Lnet/minecraft/server/level/ServerLevel;)V") + private native void addGameEventListener(T p_223416_, ServerLevel p_223417_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "updateBlockEntityTicker(Lnet/minecraft/world/level/block/entity/BlockEntity;)V") + private native void updateBlockEntityTicker(T p_156407_); + + @TransformFrom(copyFrom = @CopyFrom(clazz = LevelChunk.class), value = "createTicker(Lnet/minecraft/world/level/block/entity/BlockEntity;" + + "Lnet/minecraft/world/level/block/entity/BlockEntityTicker;)" + + "Lnet/minecraft/world/level/block/entity/TickingBlockEntity;") + private native TickingBlockEntity createTicker(T p_156376_, BlockEntityTicker p_156377_); + + // TODO forge stuff + // FORGE START +// private final net.neoforged.neoforge.attachment.AttachmentHolder.AsField attachmentHolder = new net.neoforged.neoforge.attachment.AttachmentHolder.AsField(); +// +// @Override +// public boolean hasData(net.neoforged.neoforge.attachment.AttachmentType type) { +// return attachmentHolder.hasData(type); +// } +// +// @Override +// public T getData(net.neoforged.neoforge.attachment.AttachmentType type) { +// return attachmentHolder.getData(type); +// } +// +// @Override +// @Nullable +// public T setData(net.neoforged.neoforge.attachment.AttachmentType type, T data) { +// setUnsaved(true); +// return attachmentHolder.setData(type, data); +// } + // FORGE END + + @DasmRedirect({ "cubeAccessAndDescendants" }) + @TransformFromClass(@CopyFrom(string = "net.minecraft.world.level.chunk.LevelChunk$BoundTickingBlockEntity")) + class BoundTickingBlockEntity implements TickingBlockEntity { + private final T blockEntity; + private final BlockEntityTicker ticker; + private boolean loggedInvalidBlockState; + + BoundTickingBlockEntity(T p_156433_, BlockEntityTicker p_156434_) { + throw new IllegalStateException("DASM failed to apply"); + } + + @Override public native void tick(); + + @Override public native boolean isRemoved(); + + @Override public native BlockPos getPos(); + + @Override public native String getType(); + + @Override public native String toString(); + } + + @FunctionalInterface + public interface PostLoadProcessor { + void run(LevelCube p_196867_); + } + + @DasmRedirect({ "cubeAccessAndDescendants" }) + @TransformFromClass(@CopyFrom(string = "net.minecraft.world.level.chunk.LevelChunk$RebindableTickingBlockEntityWrapper")) + public class RebindableTickingBlockEntityWrapper implements TickingBlockEntity { + private TickingBlockEntity ticker; + + RebindableTickingBlockEntityWrapper(TickingBlockEntity p_156447_) { + throw new IllegalStateException("DASM failed to apply"); + } + + native void rebind(TickingBlockEntity p_156450_); + + @Override public native void tick(); + + @Override public native boolean isRemoved(); + + @Override public native BlockPos getPos(); + + @Override public native String getType(); + + @Override public native String toString(); + } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java index cf1e255e..fa5c8382 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java @@ -2,5 +2,9 @@ import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ProtoClo; -public class ProtoCube extends CubeAccess implements ProtoClo { +// not yet implemented - stub class +public abstract class ProtoCube extends CubeAccess implements ProtoClo { + public ProtoCube() { + super(null, null, null, null, 0L, null, null); + } } diff --git a/src/main/resources/cubicchunks.mixins.core.json b/src/main/resources/cubicchunks.mixins.core.json index 7ff4a573..b9626519 100644 --- a/src/main/resources/cubicchunks.mixins.core.json +++ b/src/main/resources/cubicchunks.mixins.core.json @@ -25,7 +25,13 @@ "common.server.level.MixinServerPlayer", "common.server.level.MixinTickingTracker", "common.server.MixinMinecraftServer", - "common.TestMixin", + "common.world.level.chunk.MixinChunkAccess", + "common.world.level.chunk.MixinImposterProtoChunk", + "common.world.level.chunk.MixinLevelChunk", + "common.world.level.cube.MixinCubeAccess", + "common.world.level.cube.MixinLevelCube", + "common.world.level.cube.MixinLevelCube$BoundTickingBlockEntity", + "common.world.level.cube.MixinLevelCube$RebindableTickingBlockEntityWrapper", "common.world.level.MixinLevel" ], "client": [], diff --git a/src/main/resources/dasm/sets/sets.dasm b/src/main/resources/dasm/sets/sets.dasm index 668ac721..bbb3bd25 100644 --- a/src/main/resources/dasm/sets/sets.dasm +++ b/src/main/resources/dasm/sets/sets.dasm @@ -4,20 +4,45 @@ "io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos", "net.minecraft.world.level.ChunkPos", "net.minecraft.world.level.chunk.LevelChunk", + "net.minecraft.world.level.chunk.LevelChunk$BoundTickingBlockEntity", + "net.minecraft.world.level.chunk.LevelChunk$PostLoadProcessor", + "net.minecraft.world.level.chunk.LevelChunk$RebindableTickingBlockEntityWrapper", "net.minecraft.world.level.chunk.ChunkAccess", + "io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess", + "io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo", + "io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess", "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube", - "io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess" + "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$BoundTickingBlockEntity", + "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$PostLoadProcessor", + "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$RebindableTickingBlockEntityWrapper" ], "sets": { "general": { "typeRedirects": { "ChunkPos": "CloPos", + "ChunkAccess": "CloAccess", + "LevelChunk": "LevelClo" + }, + "fieldRedirects": { + }, + "methodRedirects": { + "ChunkAccess | ChunkPos getPos()": "cc_getCloPos" + } + }, + "cubeAccessAndDescendants": { + "typeRedirects": { + "ChunkPos": "CloPos", + "ChunkAccess": "CubeAccess", "LevelChunk": "LevelCube", - "ChunkAccess": "CubeAccess" + "LevelChunk$BoundTickingBlockEntity": "LevelCube$BoundTickingBlockEntity", + "LevelChunk$PostLoadProcessor": "LevelCube$PostLoadProcessor", + "LevelChunk$RebindableTickingBlockEntityWrapper": "LevelCube$RebindableTickingBlockEntityWrapper" }, "fieldRedirects": { + "ChunkAccess | ChunkPos chunkPos": "cloPos" }, "methodRedirects": { + "ChunkAccess | ChunkPos getPos()": "cc_getCloPos" } } } diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestInterfacesMatchVanillaClasses.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestInterfacesMatchVanillaClasses.java new file mode 100644 index 00000000..35cc6bd3 --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestInterfacesMatchVanillaClasses.java @@ -0,0 +1,122 @@ +package io.github.opencubicchunks.cubicchunks.test.misc; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ImposterProtoClo; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ProtoClo; +import net.minecraft.SharedConstants; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.Bootstrap; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ImposterProtoChunk; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.neoforged.neoforge.attachment.AttachmentType; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestInterfacesMatchVanillaClasses { + @BeforeAll + public static void setup() { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + SharedConstants.IS_RUNNING_IN_IDE = true; + } + + private static String stringifyMethod(Method method) { + return method.getName() + "(" + Arrays.stream(method.getParameterTypes()).map(Class::getName).collect(Collectors.joining(", ")) + ") -> " + method.getReturnType().getName(); + } + + private static boolean isStatic(Method method) { + return Modifier.isStatic(method.getModifiers()); + } + + private void testParityIncludingAncestors(Class vanillaClass, Class cubicClass, Method... excludes) { + var excludesSet = Arrays.stream(excludes) + .map(TestInterfacesMatchVanillaClasses::stringifyMethod) + .collect(Collectors.toSet()); + var vanillaMethods = Arrays.stream(vanillaClass.getMethods()) + .filter(method -> method.getDeclaringClass() != Object.class && !isStatic(method)) + .map(TestInterfacesMatchVanillaClasses::stringifyMethod) + .filter(s -> !excludesSet.contains(s)) + .collect(Collectors.toSet()); + var cubicMethods = Arrays.stream(cubicClass.getMethods()) + .filter(method -> method.getDeclaringClass() != Object.class && !isStatic(method)) + .map(TestInterfacesMatchVanillaClasses::stringifyMethod) + .filter(methodString -> !vanillaMethods.remove(methodString)) // Filter methodStrings that are NOT in vanillaMethods + .toList(); + assertTrue(vanillaMethods.isEmpty() && cubicMethods.isEmpty(), () -> String.format(""" + Expected parity between %s %s and %s %s. + Extra methods in %s: + %s + Extra methods in %s: + %s + + """, + vanillaClass.isInterface() ? "interface" : "class", + vanillaClass.getName(), + cubicClass.isInterface() ? "interface" : "class", + cubicClass.getName(), + vanillaClass.getSimpleName(), + vanillaMethods.isEmpty() ? "[none]" : String.join("\n ", vanillaMethods), + cubicClass.getSimpleName(), + cubicMethods.isEmpty() ? "[none]" : String.join("\n ", cubicMethods))); + } + + @Test public void testChunkAccessCloAccessParity() throws NoSuchMethodException { + testParityIncludingAncestors( + ChunkAccess.class, + CloAccess.class, + ChunkAccess.class.getMethod("getPos"), + ChunkAccess.class.getMethod("getWorldForge") // TODO need to check existence; this would fail on Fabric + ); + } + + @Test public void testLevelChunkLevelCloParity() throws NoSuchMethodException { + testParityIncludingAncestors( + LevelChunk.class, + LevelClo.class, + ChunkAccess.class.getMethod("getPos"), + // TODO need to check existence; these would fail on Fabric + ChunkAccess.class.getMethod("getWorldForge"), + LevelChunk.class.getMethod("getWorldForge"), + LevelChunk.class.getMethod("readAttachmentsFromNBT", CompoundTag.class), + LevelChunk.class.getMethod("hasData", AttachmentType.class), + LevelChunk.class.getMethod("getData", AttachmentType.class), + LevelChunk.class.getMethod("writeAttachmentsToNBT"), + LevelChunk.class.getMethod("setData", AttachmentType.class, Object.class), + LevelChunk.class.getMethod("hasData", Supplier.class), + LevelChunk.class.getMethod("getData", Supplier.class), + LevelChunk.class.getMethod("setData", Supplier.class, Object.class) + ); + } + + @Test public void testProtoChunkProtoCloParity() throws NoSuchMethodException { + testParityIncludingAncestors( + ProtoChunk.class, + ProtoClo.class, + ChunkAccess.class.getMethod("getPos"), + ChunkAccess.class.getMethod("getWorldForge") // TODO need to check existence; this would fail on Fabric + ); + } + + @Test public void testImposterProtoChunkImposterProtoCloParity() throws NoSuchMethodException { + testParityIncludingAncestors( + ImposterProtoChunk.class, + ImposterProtoClo.class, + ChunkAccess.class.getMethod("getPos"), + ImposterProtoChunk.class.getMethod("getWrapped"), + ChunkAccess.class.getMethod("getWorldForge") // TODO need to check existence; this would fail on Fabric + ); + } +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestCubeAccess.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestCubeAccess.java new file mode 100644 index 00000000..a32c4581 --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestCubeAccess.java @@ -0,0 +1,135 @@ +package io.github.opencubicchunks.cubicchunks.test.world.level.cube; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +import io.github.opencubicchunks.cc_core.api.CubicConstants; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; +import net.minecraft.SharedConstants; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.server.Bootstrap; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.gameevent.GameEventListenerRegistry; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.ticks.TickContainerAccess; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestCubeAccess { + @BeforeAll + public static void setup() { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + SharedConstants.IS_RUNNING_IN_IDE = true; + } + + static class CubeAccessTestImpl extends CubeAccess { + public CubeAccessTestImpl(CloPos cloPos, UpgradeData upgradeData, + LevelHeightAccessor levelHeightAccessor, Registry biomeRegistry, + long inhabitedTime, @Nullable LevelChunkSection[] chunkSections, + @Nullable BlendingData blendingData) { + super(cloPos, upgradeData, levelHeightAccessor, biomeRegistry, inhabitedTime, chunkSections, blendingData); + } + + @Override public GameEventListenerRegistry getListenerRegistry(int p_251437_) { + return null; + } + + @Override public @Nullable BlockState setBlockState(BlockPos blockPos, BlockState state, boolean unused) { + int sectionIndex = Coords.blockToIndex(blockPos); + int localX = Coords.blockToSectionLocal(blockPos.getX()); + int localY = Coords.blockToSectionLocal(blockPos.getY()); + int localZ = Coords.blockToSectionLocal(blockPos.getZ()); + return this.sections[sectionIndex].setBlockState(localX, localY, localZ, state); + } + + @Override public void setBlockEntity(BlockEntity p_156114_) { + + } + + @Override public void addEntity(Entity p_62078_) { + + } + + @Override public ChunkStatus getStatus() { + return null; + } + + @Override public void removeBlockEntity(BlockPos p_62101_) { + + } + + @Override public TickContainerAccess getBlockTicks() { + return null; + } + + @Override public TickContainerAccess getFluidTicks() { + return null; + } + + @Override public ChunkAccess.TicksToSave getTicksForSerialization() { + return null; + } + + @Nullable @Override public BlockEntity getBlockEntity(BlockPos p_45570_) { + return null; + } + + @Override public BlockState getBlockState(BlockPos p_45571_) { + return null; + } + + @Override public FluidState getFluidState(BlockPos p_45569_) { + return null; + } + } + + private void findBlocks(Random random) { + CloPos cubePos = CloPos.cube(random.nextInt(20000)-10000, random.nextInt(20000)-10000, random.nextInt(20000)-10000); + var cubeAccess = new CubeAccessTestImpl(cubePos, mock(), mock(), mock(), 0L, new LevelChunkSection[CubicConstants.SECTION_COUNT], mock()); + Set visitedPositions = new HashSet<>(); + Set expectedPositions = new HashSet<>(); + for (int i = 0; i < 1000; i++) { + BlockPos pos = cubePos.cubePos().asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); + if (!visitedPositions.add(pos)) continue; + if (random.nextBoolean()) { + cubeAccess.setBlockState(pos, Blocks.STONE.defaultBlockState(), false); + expectedPositions.add(pos); + } else { + cubeAccess.setBlockState(pos, Blocks.DIRT.defaultBlockState(), false); + } + } + Set foundPositions = new HashSet<>(); + cubeAccess.findBlocks((state, pos) -> state == Blocks.STONE.defaultBlockState(), (pos, state) -> foundPositions.add(new BlockPos(pos))); + assertEquals(expectedPositions, foundPositions); + } + + @Test public void testFindBlocks() { + var random = new Random(-99); + for (int i = 0; i < 100; i++) { + findBlocks(random); + } + } +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestLevelCube.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestLevelCube.java new file mode 100644 index 00000000..7ce62f88 --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestLevelCube.java @@ -0,0 +1,109 @@ +package io.github.opencubicchunks.cubicchunks.test.world.level.cube; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import io.github.opencubicchunks.cc_core.api.CubicConstants; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; +import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; +import net.minecraft.SharedConstants; +import net.minecraft.core.BlockPos; +import net.minecraft.server.Bootstrap; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.material.Fluids; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.mockito.Answers; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestLevelCube { + @BeforeAll + public static void setup() { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + SharedConstants.IS_RUNNING_IN_IDE = true; + } + + // TODO replaceWithPacketData - probably needs to be an integration test + // TODO (P2 or P3) postProcessGeneration - currently a method stub + + private void simpleGetSetBlockState(Random random) { + CloPos cubePos = CloPos.cube(random.nextInt(20000)-10000, random.nextInt(20000)-10000, random.nextInt(20000)-10000); + var cube = new LevelCube(mock(Answers.RETURNS_DEEP_STUBS), cubePos); + Map states = new HashMap<>(); + for (int i = 0; i < 1000; i++) { + var pos = cubePos.cubePos() + .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); + var state = random.nextBoolean() ? Blocks.STONE.defaultBlockState() : Blocks.DIRT.defaultBlockState(); + states.put(pos, state); + cube.setBlockState(pos, state, false); + } + + for (var pos : states.keySet()) { + assertEquals(states.get(pos), cube.getBlockState(pos)); + } + } + + // Mojang's fluid stuff is so jank and half-implemented + private void fluidState(Random random) { + CloPos cubePos = CloPos.cube(random.nextInt(20000)-10000, random.nextInt(20000)-10000, random.nextInt(20000)-10000); + var cube = new LevelCube(mock(Answers.RETURNS_DEEP_STUBS), cubePos); + Set positions = new HashSet<>(); + var state = Blocks.ANDESITE_SLAB.defaultBlockState().setValue(BlockStateProperties.WATERLOGGED, true); + for (int i = 0; i < 100; i++) { + var pos = cubePos.cubePos() + .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); + positions.add(pos); + cube.setBlockState(pos, state, false); + } + + for (var pos : positions) { + assertEquals(state, cube.getBlockState(pos)); + assertEquals(Fluids.WATER.getSource(false), cube.getFluidState(pos)); + } + } + + private void methodCallsAndBlockEntities(Random random) { + CloPos cubePos = CloPos.cube(random.nextInt(20000)-10000, random.nextInt(20000)-10000, random.nextInt(20000)-10000); + var pos = cubePos.cubePos() + .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); + var cube = new LevelCube(mock(Answers.RETURNS_DEEP_STUBS), cubePos); + cube.setLoaded(true); // required for removing block entities to work + BlockState state1 = spy(Blocks.FURNACE.defaultBlockState()); + BlockState state2 = spy(Blocks.STONE.defaultBlockState()); + + cube.setBlockState(pos, state1, false); + verify(state1, times(1)).onPlace(any(), eq(pos), eq(Blocks.AIR.defaultBlockState()), eq(false)); + assertNotNull(cube.getBlockEntity(pos)); + + cube.setBlockState(pos, state2, false); + verify(state1, times(1)).onRemove(any(), eq(pos), eq(state2), eq(false)); + verify(state2, times(1)).onPlace(any(), eq(pos), eq(state1), eq(false)); + // We don't check block entity is gone, since this requires more complex mocking of the Level, + // and it is handled by BlockState.onRemove, which we check is called + } + + @Test public void testGetSetBlockStateAndFluidState() { + var random = new Random(-102); + for (int i = 0; i < 100; i++) { + simpleGetSetBlockState(random); + fluidState(random); + methodCallsAndBlockEntities(random); + } + } +}