diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/WrapperFactory.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/WrapperFactory.java index 85fee0a84..911a2378a 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/WrapperFactory.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/WrapperFactory.java @@ -22,8 +22,8 @@ package com.seibel.distanthorizons.common.wrappers; import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper; import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper; -import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhServerLevel; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; @@ -35,6 +35,7 @@ import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.chunk.ChunkAccess; import java.io.IOException; +import java.util.HashMap; /** * This handles creating abstract wrapper objects. @@ -71,6 +72,9 @@ public class WrapperFactory implements IWrapperFactory @Override public IBlockStateWrapper getAirBlockStateWrapper() { return BlockStateWrapper.AIR; } + @Override + public HashMap getRendererIgnoredBlocks() { return BlockStateWrapper.RENDERER_IGNORED_BLOCKS; } + /** * Note: when this is updated for different MC versions, make sure you also update the documentation in diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/BlockStateWrapper.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/BlockStateWrapper.java index e19d0d9fb..b54fd8a1c 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/BlockStateWrapper.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/BlockStateWrapper.java @@ -3,16 +3,15 @@ package com.seibel.distanthorizons.common.wrappers.block; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; -import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; #if MC_1_16_5 || MC_1_17_1 @@ -39,8 +38,11 @@ public class BlockStateWrapper implements IBlockStateWrapper // must be defined before AIR, otherwise a null pointer will be thrown private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - public static final BlockStateWrapper AIR = new BlockStateWrapper(null); - public static final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + public static final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + public static final BlockStateWrapper AIR = fromBlockState(BuiltInRegistries.BLOCK.get(ResourceLocation.tryParse("minecraft:air")).defaultBlockState(), false); + public static final String[] RENDERER_IGNORED_BLOCKS_RESOURCE_LOCATIONS = {"minecraft:air", "minecraft:barrier", "minecraft:structure_void"}; + public static final HashMap RENDERER_IGNORED_BLOCKS_INTERNAL = getRendererIgnoredBlocksInternal(RENDERER_IGNORED_BLOCKS_RESOURCE_LOCATIONS); + public static final HashMap RENDERER_IGNORED_BLOCKS = getRendererIgnoredBlocks(RENDERER_IGNORED_BLOCKS_INTERNAL); /** * Cached so it can be quickly used as a semi-stable hashing method.
@@ -53,21 +55,75 @@ public class BlockStateWrapper implements IBlockStateWrapper // constructors // //==============// - public static BlockStateWrapper fromBlockState(BlockState blockState) + public static BlockStateWrapper fromBlockState(BlockState blockState) { - if (blockState == null || blockState.isAir()) - { + return fromBlockState(blockState, true); + } + + private static BlockStateWrapper fromBlockState(BlockState blockState, boolean nullCheck) + { + if (Objects.requireNonNull(blockState).isAir() && AIR != null) return AIR; - } - return cache.computeIfAbsent(blockState, BlockStateWrapper::new); + return cache.computeIfAbsent(blockState, blockState1 -> new BlockStateWrapper(blockState1, nullCheck)); } - public final BlockState blockState; - BlockStateWrapper(BlockState blockState) + /** + * Only meant for use in the {@code RENDERER_IGNORED_BLOCKS_INTERNAL} list. Do not use elsewhere, since the {@code levelWrapper} parameter of each {@code IBlockStateWrapper} will be {@code null}, causing issues. + * @param resourceLocations The resource location(s) of the block(s) that should be ignored by the renderer, may only contain {@code [a-z0-9/._-)} characters. + * @return The default blockstate(s) of the block(s), paired with the serialized resource location(s) of the block(s), which should be passed into the {@code RENDERER_IGNORED_BLOCKS_INTERNAL} map and the {@code getRendererIgnoredBlocks} method. + */ + @SuppressWarnings("SameParameterValue") + private static @NotNull HashMap getRendererIgnoredBlocksInternal(String @NotNull ... resourceLocations) { + HashMap blockStates = new HashMap<>(); + + for (String resourceLocation : resourceLocations) + { + ResourceLocation fetchedResourceLocation = Objects.requireNonNull(ResourceLocation.tryParse(resourceLocation), String.format("Supplied a resource location that couldn't be parsed by Minecraft: %s", resourceLocation)); + var splitResourceLocation = resourceLocation.split(":"); + + if (splitResourceLocation.length == 0) { + LOGGER.warn("A resource location that should be ignored by the renderer was in an invalid format: {}", resourceLocation); + continue; + } + + blockStates.put(BuiltInRegistries.BLOCK.get(fetchedResourceLocation).defaultBlockState(), splitResourceLocation[1].toUpperCase(Locale.ROOT)); + } + + return blockStates; + } + + /** + * Only meant for use in the {@code RENDERER_IGNORED_BLOCKS} list. Do not use elsewhere, since the {@code levelWrapper} parameter of each {@code IBlockStateWrapper} will be {@code null}, causing issues. + * @param rendererIgnoredBlocks A map containing the blockstate(s) of the block(s), paired with the resource location(s) of the block(s) that should be ignored by the renderer. + * @return The blockstate wrapper(s) of the blockstate(s), which should be passed into the {@code RENDERER_IGNORED_BLOCKS} list. + */ + @SuppressWarnings("SameParameterValue") + private static @NotNull HashMap getRendererIgnoredBlocks(@NotNull Map rendererIgnoredBlocks) + { + HashMap blockStateWrappers = new HashMap<>(); + + for (Map.Entry blockStateResourceLocations : rendererIgnoredBlocks.entrySet()) + { + blockStateWrappers.put(blockStateResourceLocations.getValue(), fromBlockState(blockStateResourceLocations.getKey(), false)); + } + + return blockStateWrappers; + } + + public final BlockState blockState; + + BlockStateWrapper(BlockState blockState) + { + this(blockState, true); + } + + // TODO: Pass in levelwrapper so nullCheck has a use + private BlockStateWrapper(BlockState blockState, boolean nullCheck) { this.blockState = blockState; - LOGGER.trace("Created BlockStateWrapper for [" + blockState + "]"); + + LOGGER.trace("Created BlockStateWrapper for [{}]", blockState); } @@ -95,49 +151,50 @@ public class BlockStateWrapper implements IBlockStateWrapper @Override public int getLightEmission() { return (this.blockState != null) ? this.blockState.getLightEmission() : 0; } - @Override public String serialize() // FIXME pass in level to prevent null pointers (or maybe just RegistryAccess?) { - // cache the serialization result so it can be quickly used as a semi-stable hashing method - if (this.serializationResult == null) + // the result can be quickly used as a semi-stable hashing method, so it's going to be cached + if (this.serializationResult != null) + return this.serializationResult; + + if (this.blockState == null) { - if (this.blockState == null) - { - return "AIR"; - } - - ResourceLocation resourceLocation; + return "AIR"; + } + + if (RENDERER_IGNORED_BLOCKS_INTERNAL.containsKey(this.blockState)) + return this.serializationResult = RENDERER_IGNORED_BLOCKS_INTERNAL.get(this.blockState); + + ResourceLocation resourceLocation; #if MC_1_16_5 || MC_1_17_1 resourceLocation = Registry.BLOCK.getKey(this.blockState.getBlock()); #elif MC_1_18_2 || MC_1_19_2 net.minecraft.core.RegistryAccess registryAccess = Minecraft.getInstance().level.registryAccess(); resourceLocation = registryAccess.registryOrThrow(Registry.BLOCK_REGISTRY).getKey(this.blockState.getBlock()); #else - net.minecraft.core.RegistryAccess registryAccess = Minecraft.getInstance().level.registryAccess(); - resourceLocation = registryAccess.registryOrThrow(Registries.BLOCK).getKey(this.blockState.getBlock()); + net.minecraft.core.RegistryAccess registryAccess = Minecraft.getInstance().level.registryAccess(); + resourceLocation = registryAccess.registryOrThrow(Registries.BLOCK).getKey(this.blockState.getBlock()); #endif - - if (resourceLocation == null) - { - LOGGER.warn("unable to serialize: " + this.blockState); - } - - this.serializationResult = resourceLocation.getNamespace() + RESOURCE_LOCATION_SEPARATOR + resourceLocation.getPath() - + STATE_STRING_SEPARATOR + serializeBlockStateProperties(this.blockState); + + if (resourceLocation == null) + { + LOGGER.warn("unable to serialize: " + this.blockState); } + this.serializationResult = resourceLocation.getNamespace() + RESOURCE_LOCATION_SEPARATOR + resourceLocation.getPath() + + STATE_STRING_SEPARATOR + serializeBlockStateProperties(this.blockState); return this.serializationResult; } - public static BlockStateWrapper deserialize(String resourceStateString) throws IOException // FIXME pass in level to prevent null pointers (or maybe just RegistryAccess?) + public static IBlockStateWrapper deserialize(String resourceStateString) throws IOException // FIXME pass in level to prevent null pointers (or maybe just RegistryAccess?) { - if (resourceStateString.equals("AIR") || resourceStateString.equals("")) // the empty string shouldn't normally happen, but just in case - { - return AIR; - } + if (resourceStateString.isEmpty()) + throw new IOException("resourceStateString is empty"); + if (RENDERER_IGNORED_BLOCKS_INTERNAL.containsValue(resourceStateString)) + return RENDERER_IGNORED_BLOCKS.get(resourceStateString); // parse the BlockState diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/BatchGenerationEnvironment.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/BatchGenerationEnvironment.java index caacbd061..e54490bbb 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/BatchGenerationEnvironment.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/BatchGenerationEnvironment.java @@ -484,7 +484,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv ChunkAccess chunk = totalChunks.get(x, z); if (chunk != null) { - chunkWrapperList.set(x, z, new ChunkWrapper(chunk, region, null)); + chunkWrapperList.set(x, z, new ChunkWrapper(chunk, region, serverlevel.getLevelWrapper())); } });