Compare commits

...

36 Commits

Author SHA1 Message Date
s809 7c37a5c370 Run prepare only when needed 2025-04-27 00:40:12 +05:00
s809 b495ac4799 remove python dependency for building with correct sqlite natives 2025-04-27 00:13:24 +05:00
Ran 3721ebea6e Improve LodDataBuilder.java
- Use bitwise modulo
- Don't compute certain things 256 times when they can be computed once.
- Removed expressions that are always false
- Improved comments
2025-04-11 11:24:17 +10:00
Ran 98f8a87362 Improve LodDataBuilder.java
- Use bitwise modulo
- Don't compute certain things 256 times when they can be computed once.
- Removed expressions that are always false
- Improved comments
2025-04-11 11:20:05 +10:00
Ran 10a743ddef Don't check for Indium for Sodium version >= 0.6 2025-04-07 23:53:02 +10:00
James Seibel 95c896f964 maybe break n-sized rendering but fix LOD loading getting stuck 2025-04-07 06:56:58 -05:00
James Seibel 040bc16874 re-add comment to getWorldFolderName() 2025-04-07 06:55:45 -05:00
James Seibel 35d3fdb473 Revert "bandaid fix for Forge 1.20.1 UI crashing"
This reverts commit 2b519a826f.
2025-04-07 06:55:01 -05:00
Ran 549f7510f7 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/TintWithoutLevelOverrider.java
2025-04-07 13:52:25 +10:00
Ran fab64d8477 Fix white foliage issue 2025-04-07 13:52:02 +10:00
Ran 4a6a35f617 Fix white foliage issue 2025-04-07 13:47:42 +10:00
James Seibel 2b519a826f bandaid fix for Forge 1.20.1 UI crashing 2025-04-05 09:19:36 -05:00
James Seibel 445c01b5ae up version number 2.3.2 -> 2.3.3 2025-04-05 09:11:45 -05:00
James Seibel 0f08bd540e remove dev from the version number 2025-04-05 09:10:23 -05:00
James Seibel 77088465f9 Improve DH world gen progress message 2025-04-02 07:25:19 -05:00
James Seibel affe014433 Fix auto updater for MC 1.21.4 and 1.21.5 2025-03-31 06:56:00 -05:00
James Seibel 4c06bf6dbd change iris incompat MC 1.21.5 1.8.12 -> 1.8.10 2025-03-30 17:31:51 -05:00
James Seibel dcab616385 Fix memory leaks due to un-closed thread pools and worlds 2025-03-30 17:31:04 -05:00
James Seibel 89b7d08e9b Fix the sun/moon and stars not rendering
Closes #986
2025-03-30 16:50:43 -05:00
James Seibel 724e318221 Fix beacon beams now going through some blocks 2025-03-30 15:23:27 -05:00
James Seibel a40d62d46a Fix flashing on MC 1.21.5 in non-overworld dimensions 2025-03-30 14:36:56 -05:00
James Seibel 2f6b4c079b don't log InterruptedException during threadPool shutdown 2025-03-29 20:11:36 -05:00
James Seibel 951e3c0271 Fix accidentally removing required imports 2025-03-29 20:09:41 -05:00
James Seibel 84825c2d09 Add MC 1.21.5 to the auto build script 2025-03-29 19:23:53 -05:00
James Seibel ec627e2eba Fix fog for MC 1.16.5 2025-03-29 19:22:59 -05:00
James Seibel 06bc9a349f Fix MC 1.16.5 compiling 2025-03-29 18:48:00 -05:00
James Seibel ff6c4e227b level wrapper weak refs to fix leak on bad shutdown 2025-03-29 18:18:23 -05:00
James Seibel a4d46ffe94 Mark Iris 1.8.12 and lower broken for MC 1.21.5
Older MC versions are unaffected and function correctly
2025-03-29 16:51:46 -05:00
James Seibel bd5c140782 Fix import for fabric mixin texture 2025-03-29 16:45:32 -05:00
James Seibel 229c3f7c91 Add neoforge 1.21.5 2025-03-29 16:11:04 -05:00
James Seibel 693369bc08 MC 1.21.5 changes, lighting fix, and world gen dup fix 2025-03-29 15:45:26 -05:00
James Seibel d109fe6c43 comment out LOD bias option for MC 1.21.5+ 2025-03-29 15:44:54 -05:00
James Seibel 3c9d3707cf update world gen chunk loading for MC 1.21.5 2025-03-29 15:38:42 -05:00
James Seibel 6e53564835 Fix getting block colors for MC 1.21.5 2025-03-29 12:34:35 -05:00
James Seibel 2480fe0d86 Add basic MC 1.21.5 rendering (block colors and world gen broken) 2025-03-29 10:40:36 -05:00
James Seibel 691c9d3f45 up version number 2.3.1 -> 2.3.2 2025-03-25 07:17:34 -05:00
52 changed files with 919 additions and 271 deletions
-1
View File
@@ -29,7 +29,6 @@ buildAllJars/
relocate_natives/.venv/ relocate_natives/.venv/
relocate_natives/__pycache__/ relocate_natives/__pycache__/
relocate_natives/apple-codesign/ relocate_natives/apple-codesign/
relocate_natives/cache/
# file from notepad++ # file from notepad++
*.bak *.bak
+1 -4
View File
@@ -17,9 +17,6 @@ variables:
# These can be extended so code is a bit less duplicated # These can be extended so code is a bit less duplicated
.build_java: .build_java:
#image: eclipse-temurin:17 #image: eclipse-temurin:17
before_script:
- apt-get update
- apt-get install python3 python3-pip python-is-python3 python3-venv -y --no-install-recommends
cache: cache:
key: "gradleCache_$CI_JOB_NAME_SLUG" key: "gradleCache_$CI_JOB_NAME_SLUG"
policy: pull-push policy: pull-push
@@ -38,7 +35,7 @@ build:
stage: build stage: build
parallel: parallel:
matrix: matrix:
- MC_VER: ["1.16.5", "1.17.1", "1.18.2", "1.19.2", "1.19.4", "1.20.1", "1.20.2", "1.20.4", "1.20.6", "1.21.1", "1.21.3", "1.21.4"] - MC_VER: ["1.16.5", "1.17.1", "1.18.2", "1.19.2", "1.19.4", "1.20.1", "1.20.2", "1.20.4", "1.20.6", "1.21.1", "1.21.3", "1.21.4", "1.21.5"]
script: script:
# this both runs the unit tests and assembles the code # this both runs the unit tests and assembles the code
- ./gradlew clean -PmcVer="${MC_VER}" -PinfoGitCommit="${CI_COMMIT_SHA}" -PinfoGitBranch="${CI_COMMIT_BRANCH}" -PinfoBuildSource="GitLab CI (${CI_PIPELINE_ID})" --gradle-user-home cache/; - ./gradlew clean -PmcVer="${MC_VER}" -PinfoGitCommit="${CI_COMMIT_SHA}" -PinfoGitBranch="${CI_COMMIT_BRANCH}" -PinfoBuildSource="GitLab CI (${CI_PIPELINE_ID})" --gradle-user-home cache/;
+4 -26
View File
@@ -19,7 +19,7 @@ plugins {
id "systems.manifold.manifold-gradle-plugin" version "0.0.2-alpha" id "systems.manifold.manifold-gradle-plugin" version "0.0.2-alpha"
// Architectury is used here only as a replacement for forge's own loom // Architectury is used here only as a replacement for forge's own loom
id "dev.architectury.loom" version "1.7-SNAPSHOT" apply false id "dev.architectury.loom" version "1.10-SNAPSHOT" apply false
} }
@@ -106,24 +106,12 @@ forgix {
class NativeTransformer implements Transformer { class NativeTransformer implements Transformer {
private boolean enabled = false
private final HashMap<String, String> replacements = new HashMap() private final HashMap<String, String> replacements = new HashMap()
private final HashMap<String, byte[]> rewrittenFiles = new HashMap() private final HashMap<String, byte[]> rewrittenFiles = new HashMap()
private var nativeRelocator private var nativeRelocator
public File rootDir public File rootDir
NativeTransformer() {
try {
int exitCode = Runtime.getRuntime().exec(new String[]{"python", "--version"}).waitFor()
if (exitCode == 0) {
enabled = true
}
} catch (IOException e) {
println(e)
}
}
void relocateNative(String target, String replacement) { void relocateNative(String target, String replacement) {
if (replacement.length() > target.length()) { if (replacement.length() > target.length()) {
throw new GradleException("Length of value \"${replacement}\" exceeds the length of \"${target}\": ${replacement.length()} > ${target.length()}") throw new GradleException("Length of value \"${replacement}\" exceeds the length of \"${target}\": ${replacement.length()} > ${target.length()}")
@@ -132,22 +120,15 @@ class NativeTransformer implements Transformer {
replacements.put(target, replacement) replacements.put(target, replacement)
} }
void before(Closure closure) {
if (enabled)
closure.run()
}
@Override @Override
boolean canTransformResource(@Nonnull FileTreeElement element) { boolean canTransformResource(@Nonnull FileTreeElement element) {
return enabled && replacements.keySet().stream().anyMatch { return replacements.keySet().stream().anyMatch {
element.name.startsWith(it as String) element.name.startsWith(it as String)
} }
} }
@Override @Override
void transform(@Nonnull TransformerContext context) { void transform(@Nonnull TransformerContext context) {
println("Transforming $context.path...")
byte[] content = context.is.readAllBytes() byte[] content = context.is.readAllBytes()
if (nativeRelocator == null) { if (nativeRelocator == null) {
@@ -395,16 +376,13 @@ subprojects { p ->
// Sqlite Database // Sqlite Database
// librariesLocation isn't used because it's too long for replacing paths in native libraries // librariesLocation isn't used because it's too long for replacing paths in native libraries
// Allowing strings larger than the original string would require shifting the entire binary's contents // Allowing strings larger than the original string would require shifting the entire binary's contents
transform(NativeTransformer) {
rootDir = project.rootDir
before {
relocate "org.sqlite", "dh_sqlite", { relocate "org.sqlite", "dh_sqlite", {
exclude "org/sqlite/native/**" exclude "org/sqlite/native/**"
} }
relocate "jdbc:sqlite", "jdbc:dh_sqlite" relocate "jdbc:sqlite", "jdbc:dh_sqlite"
}
transform(NativeTransformer) {
rootDir = project.rootDir
relocateNative "org/sqlite", "dh_sqlite" relocateNative "org/sqlite", "dh_sqlite"
relocateNative "org_sqlite", "dh_1sqlite" relocateNative "org_sqlite", "dh_1sqlite"
} }
+7 -3
View File
@@ -12,14 +12,15 @@ class NativeRelocator
/** /**
* Initializes the NativeRelocator by preparing the environment if necessary. * Initializes the NativeRelocator by preparing the environment if necessary.
* Executes the appropriate preparation script based on the OS. * Executes the appropriate preparation script based on the OS.
*
* @throws Exception if the preparation script fails or an unsupported OS is detected.
*/ */
NativeRelocator(Path rootDirectory) throws Exception NativeRelocator(Path rootDirectory)
{ {
this.rootDirectory = rootDirectory; this.rootDirectory = rootDirectory;
this.cacheRoot = this.rootDirectory.resolve("cache"); this.cacheRoot = this.rootDirectory.resolve("cache");
}
private void prepare() throws Exception
{
if (this.rootDirectory.resolve(".venv").toFile().exists()) if (this.rootDirectory.resolve(".venv").toFile().exists())
{ {
return; return;
@@ -196,6 +197,9 @@ class NativeRelocator
return Files.readAllBytes(outputFilePath); return Files.readAllBytes(outputFilePath);
} }
System.out.println("Relocating to " + outputPath + "...");
this.prepare();
for (Map.Entry<String, String> replacement : replacements.entrySet()) for (Map.Entry<String, String> replacement : replacements.entrySet())
{ {
this.replaceInNullTerminatedStrings(content, replacement.getKey(), replacement.getValue()); this.replaceInNullTerminatedStrings(content, replacement.getKey(), replacement.getValue());
@@ -71,7 +71,9 @@ public class VersionConstants implements IVersionConstants
#elif MC_VER == MC_1_21_3 #elif MC_VER == MC_1_21_3
return "1.21.3"; return "1.21.3";
#elif MC_VER == MC_1_21_4 #elif MC_VER == MC_1_21_4
return "1.21_4"; return "1.21.4";
#elif MC_VER == MC_1_21_5
return "1.21.5";
#else #else
ERROR MC version constant missing ERROR MC version constant missing
#endif #endif
@@ -97,16 +97,14 @@ public class BlockStateWrapper implements IBlockStateWrapper
/** technically final, but since it requires a method call to generate it can't be marked as such */ /** technically final, but since it requires a method call to generate it can't be marked as such */
private String serialString; private String serialString;
private final int hashCode; private final int hashCode;
/** /** Should be between {@link LodUtil#BLOCK_FULLY_OPAQUE} and {@link LodUtil#BLOCK_FULLY_OPAQUE} */
* Cached opacity value, -1 if not populated. <br> private final int opacity;
* Should be between {@link LodUtil#BLOCK_FULLY_OPAQUE} and {@link LodUtil#BLOCK_FULLY_OPAQUE}
*/
private int opacity = -1;
/** used by the Iris shader mod to determine how each LOD should be rendered */ /** used by the Iris shader mod to determine how each LOD should be rendered */
private byte blockMaterialId = 0; private byte blockMaterialId = 0;
private final boolean isBeaconBlock; private final boolean isBeaconBlock;
private final boolean isBeaconBaseBlock; private final boolean isBeaconBaseBlock;
private final boolean allowsBeaconBeamPassage;
/** null if this block can't tint beacons */ /** null if this block can't tint beacons */
private final Color beaconTintColor; private final Color beaconTintColor;
private final Color mapColor; private final Color mapColor;
@@ -163,9 +161,12 @@ public class BlockStateWrapper implements IBlockStateWrapper
this.serialString = this.serialize(levelWrapper); this.serialString = this.serialize(levelWrapper);
this.hashCode = Objects.hash(this.serialString); this.hashCode = Objects.hash(this.serialString);
this.blockMaterialId = this.calculateEDhApiBlockMaterialId().index; this.blockMaterialId = this.calculateEDhApiBlockMaterialId().index;
this.opacity = this.calculateOpacity();
String lowercaseSerial = this.serialString.toLowerCase();
// beacon blocks // beacon blocks
String lowercaseSerial = this.serialString.toLowerCase();
boolean isBeaconBaseBlock = false; boolean isBeaconBaseBlock = false;
for (int i = 0; i < LodUtil.BEACON_BASE_BLOCK_NAME_LIST.size(); i++) for (int i = 0; i < LodUtil.BEACON_BASE_BLOCK_NAME_LIST.size(); i++)
{ {
@@ -201,6 +202,39 @@ public class BlockStateWrapper implements IBlockStateWrapper
this.beaconTintColor = beaconTintColor; this.beaconTintColor = beaconTintColor;
// allow/deny beacon beam passage
boolean allowsBeaconBeamPassage;
if (this.blockState != null)
{
// get block properties (defaults to the values used by air)
boolean canOcclude = this.getCanOcclude();
boolean propagatesSkyLightDown = this.getPropagatesSkyLightDown();
if (lowercaseSerial.contains("minecraft:bedrock"))
{
// bedrock is a special case fully opaque block that does allow beacons through
allowsBeaconBeamPassage = true;
}
else if (propagatesSkyLightDown || !canOcclude)
{
// stairs, cake, fences, etc.
allowsBeaconBeamPassage = true;
}
else
{
// non-opaque blocks (glass, mob spawners, etc.)
// all allow beacons through
allowsBeaconBeamPassage = (this.opacity != LodUtil.BLOCK_FULLY_OPAQUE);
}
}
else
{
// air allows beacons through
allowsBeaconBeamPassage = true;
}
this.allowsBeaconBeamPassage = allowsBeaconBeamPassage;
int mcColor = 0; int mcColor = 0;
if (this.blockState != null) if (this.blockState != null)
{ {
@@ -344,28 +378,12 @@ public class BlockStateWrapper implements IBlockStateWrapper
//=================// //=================//
@Override @Override
public int getOpacity() public int getOpacity() { return this.opacity; }
private int calculateOpacity()
{ {
// use the cached opacity value if possible // get block properties (defaults to the values used by air)
if (this.opacity != -1) boolean canOcclude = this.getCanOcclude();
{ boolean propagatesSkyLightDown = this.getPropagatesSkyLightDown();
return this.opacity;
}
// get block properties (default to the values used by air)
boolean canOcclude = false;
boolean propagatesSkyLightDown = true;
if (this.blockState != null)
{
canOcclude = this.blockState.canOcclude();
#if MC_VER < MC_1_21_3
propagatesSkyLightDown = this.blockState.propagatesSkylightDown(EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
#else
propagatesSkyLightDown = this.blockState.propagatesSkylightDown();
#endif
}
@@ -398,9 +416,37 @@ public class BlockStateWrapper implements IBlockStateWrapper
} }
this.opacity = opacity; return opacity;
return this.opacity;
} }
private boolean getCanOcclude()
{
// defaults to the value used by air
boolean canOcclude = false;
if (this.blockState != null)
{
canOcclude = this.blockState.canOcclude();
}
return canOcclude;
}
private boolean getPropagatesSkyLightDown()
{
// defaults to the value used by air
boolean propagatesSkyLightDown = true;
if (this.blockState != null)
{
#if MC_VER < MC_1_21_3
propagatesSkyLightDown = this.blockState.propagatesSkylightDown(EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
#else
propagatesSkyLightDown = this.blockState.propagatesSkylightDown();
#endif
}
return propagatesSkyLightDown;
}
@Override @Override
public int getLightEmission() { return (this.blockState != null) ? this.blockState.getLightEmission() : 0; } public int getLightEmission() { return (this.blockState != null) ? this.blockState.getLightEmission() : 0; }
@@ -473,6 +519,8 @@ public class BlockStateWrapper implements IBlockStateWrapper
public boolean isBeaconBaseBlock() { return this.isBeaconBaseBlock; } public boolean isBeaconBaseBlock() { return this.isBeaconBaseBlock; }
@Override @Override
public boolean isBeaconTintBlock() { return this.beaconTintColor != null; } public boolean isBeaconTintBlock() { return this.beaconTintColor != null; }
@Override
public boolean allowsBeaconBeamPassage() { return this.allowsBeaconBeamPassage; }
@Override @Override
public Color getMapColor() { return this.mapColor; } public Color getMapColor() { return this.mapColor; }
@@ -40,11 +40,18 @@ import java.util.Random;
#endif #endif
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
#if MC_VER < MC_1_21_5
#else
import net.minecraft.client.renderer.block.model.BlockModelPart;
#endif
/** /**
* This stores and calculates the colors * This stores and calculates the colors
* the given {@link BlockState} should have based * the given {@link BlockState} should have based
@@ -196,9 +203,7 @@ public class ClientBlockStateColorCache
List<BakedQuad> quads = null; List<BakedQuad> quads = null;
for (Direction direction : COLOR_RESOLUTION_DIRECTION_ORDER) for (Direction direction : COLOR_RESOLUTION_DIRECTION_ORDER)
{ {
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper(). quads = this.getQuadsForDirection(direction);
getBlockModel(this.blockState).getQuads(this.blockState, direction, RANDOM);
if (quads != null && !quads.isEmpty() if (quads != null && !quads.isEmpty()
&& !( && !(
this.blockState.getBlock() instanceof RotatedPillarBlock this.blockState.getBlock() instanceof RotatedPillarBlock
@@ -212,19 +217,37 @@ public class ClientBlockStateColorCache
if (quads == null || quads.isEmpty()) if (quads == null || quads.isEmpty())
{ {
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper(). quads = this.getUnculledQuads();
getBlockModel(this.blockState).getQuads(this.blockState, null, RANDOM);
} }
if (quads != null && !quads.isEmpty()) if (quads != null
&& !quads.isEmpty()
&& quads.get(0) != null)
{ {
this.needPostTinting = quads.get(0).isTinted(); BakedQuad firstQuad = quads.get(0);
this.needShade = quads.get(0).isShade();
this.tintIndex = quads.get(0).getTintIndex(); this.needPostTinting = firstQuad.isTinted();
#if MC_VER <= MC_1_21_4
this.needShade = firstQuad.isShade();
this.tintIndex = firstQuad.getTintIndex();
#else
this.needShade = firstQuad.shade();
this.tintIndex = firstQuad.tintIndex();
#endif
#if MC_VER < MC_1_17_1
this.baseColor = calculateColorFromTexture( this.baseColor = calculateColorFromTexture(
#if MC_VER < MC_1_17_1 quads.get(0).sprite, firstQuad.sprite,
#else quads.get(0).getSprite(), #endif
ColorMode.getColorMode(this.blockState.getBlock())); ColorMode.getColorMode(this.blockState.getBlock()));
#elif MC_VER < MC_1_21_5
this.baseColor = calculateColorFromTexture(
firstQuad.getSprite(),
ColorMode.getColorMode(this.blockState.getBlock()));
#else
this.baseColor = calculateColorFromTexture(
firstQuad.sprite(),
ColorMode.getColorMode(this.blockState.getBlock()));
#endif
} }
else else
{ {
@@ -232,8 +255,7 @@ public class ClientBlockStateColorCache
this.needPostTinting = false; this.needPostTinting = false;
this.needShade = false; this.needShade = false;
this.tintIndex = 0; this.tintIndex = 0;
this.baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(this.blockState), this.baseColor = this.getParticleIconColor();
ColorMode.getColorMode(this.blockState.getBlock()));
} }
} }
else else
@@ -242,8 +264,7 @@ public class ClientBlockStateColorCache
this.needPostTinting = true; this.needPostTinting = true;
this.needShade = false; this.needShade = false;
this.tintIndex = 0; this.tintIndex = 0;
this.baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(this.blockState), this.baseColor = this.getParticleIconColor();
ColorMode.getColorMode(this.blockState.getBlock()));
} }
this.isColorResolved = true; this.isColorResolved = true;
@@ -253,6 +274,35 @@ public class ClientBlockStateColorCache
RESOLVE_LOCK.unlock(); RESOLVE_LOCK.unlock();
} }
} }
@Nullable
private List<BakedQuad> getUnculledQuads() { return this.getQuadsForDirection(null); }
@Nullable
private List<BakedQuad> getQuadsForDirection(@Nullable Direction direction)
{
List<BakedQuad> quads = null;
#if MC_VER < MC_1_21_5
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(this.blockState).getQuads(this.blockState, direction, RANDOM);
#else
List<BlockModelPart> blockModelPartList = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(this.blockState).collectParts(RANDOM);
quads = new ArrayList<>();
if (blockModelPartList != null)
{
for (int i = 0; i < blockModelPartList.size(); i++)
{
// if direction is null this will return the unculled quads
quads.addAll(blockModelPartList.get(i).getQuads(direction));
}
}
#endif
return quads;
}
//TODO: Perhaps make this not just use the first frame? //TODO: Perhaps make this not just use the first frame?
private static int calculateColorFromTexture(TextureAtlasSprite texture, ColorMode colorMode) private static int calculateColorFromTexture(TextureAtlasSprite texture, ColorMode colorMode)
{ {
@@ -383,6 +433,13 @@ public class ClientBlockStateColorCache
return (bias + (scale * t)) >>> 16; return (bias + (scale * t)) >>> 16;
} }
private int getParticleIconColor()
{
return calculateColorFromTexture(
Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(this.blockState),
ColorMode.getColorMode(this.blockState.getBlock()));
}
//===============// //===============//
@@ -19,19 +19,16 @@
package com.seibel.distanthorizons.common.wrappers.block; package com.seibel.distanthorizons.common.wrappers.block;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.world.level.*; import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.lighting.LevelLightEngine; import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.FluidState;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -46,30 +43,61 @@ public class TintWithoutLevelOverrider implements BlockAndTintGetter
* but {@link Nullable} is there just in case. * but {@link Nullable} is there just in case.
*/ */
@Nullable @Nullable
private final Biome biome; #if MC_VER >= MC_1_18_2
public final Holder<Biome> biome;
#else
public final Biome biome;
#endif
/**
* Constructs the TintWithoutLevelOverrider, storing the provided Biome Holder for late-binding access.
*
* <p>Previously, this class might have immediately unwrapped the Holder like this:</p>
* <pre>{@code
* // Inside constructor (OLD WAY - PROBLEMATIC):
* Holder<Biome> biomeHolder = getTheHolderFromSomewhere();
* this.biome = biomeHolder.value(); // <-- PROBLEM HERE
* }</pre>
*
* <p>This approach is problematic because the {@link net.minecraft.core.Holder} system,
* particularly {@code Holder.Reference}, is designed for <strong>late binding</strong>. Here's why storing
* the Holder itself is now necessary:</p>
* <ol>
* <li>A {@code Holder.Reference<Biome>} might be created initially just with a
* {@link net.minecraft.resources.ResourceKey} (like {@code minecraft:plains}), but its actual
* {@link net.minecraft.core.Holder#value() value()} (the {@code Biome} object itself) might be {@code null}
* at construction time.</li>
* <li>Later, during game loading, registry population, or potentially due to modifications by other mods
* (e.g., Polytone), the system calls internal binding methods (like {@code bindValue(Biome)})
* on the {@code Holder} instance. This sets or <strong>updates</strong> the internal reference to the
* actual {@code Biome} object.</li>
* <li>Crucially, the binding process might assign a completely <strong>new</strong> {@code Biome} object
* instance to the {@code Holder} reference, replacing any previous one.</li>
* </ol>
*
* <p>If we unwrapped the {@code Holder} using {@code .value()} within the constructor (the old way),
* our class's internal {@code biome} field would permanently store a reference to whatever {@code Biome}
* object the {@code Holder} pointed to *at that exact moment*. It would have no link back to the
* {@code Holder} and would be unaware if the {@code Holder} was later updated to point to a different
* (or the initially missing) {@code Biome} object. This would lead to using stale or even {@code null} data.</p>
*
* <p>By storing the {@code Holder<Biome>} itself, this class can call {@link net.minecraft.core.Holder#value()}
* whenever the biome information is needed, ensuring it always retrieves the most current {@code Biome}
* instance associated with the holder at that time.</p>
*/
public TintWithoutLevelOverrider(BiomeWrapper biomeWrapper, IClientLevelWrapper clientLevelWrapper) public TintWithoutLevelOverrider(BiomeWrapper biomeWrapper, IClientLevelWrapper clientLevelWrapper)
{ {
// try to get the wrapped biome #if MC_VER >= MC_1_18_2 Holder<Biome> #else Biome #endif biome = biomeWrapper.biome;
Biome unwrappedBiome = null; if (biome == null) // We are looking at the empty biome wrapper
if (biomeWrapper.biome != null)
{ {
unwrappedBiome = unwrap(biomeWrapper.biome);
}
if(unwrappedBiome == null)
{
// we are looking at the empty biome wrapper, try using plains as a backup
BiomeWrapper plainsBiomeWrapper = ((BiomeWrapper) clientLevelWrapper.getPlainsBiomeWrapper()); BiomeWrapper plainsBiomeWrapper = ((BiomeWrapper) clientLevelWrapper.getPlainsBiomeWrapper());
if (plainsBiomeWrapper != null) if (plainsBiomeWrapper != null)
{ {
unwrappedBiome = unwrap(plainsBiomeWrapper.biome); biome = plainsBiomeWrapper.biome;
} }
} }
this.biome = unwrappedBiome; this.biome = biome;
} }
@@ -77,15 +105,12 @@ public class TintWithoutLevelOverrider implements BlockAndTintGetter
@Override @Override
public int getBlockTint(@NotNull BlockPos blockPos, @NotNull ColorResolver colorResolver) public int getBlockTint(@NotNull BlockPos blockPos, @NotNull ColorResolver colorResolver)
{ {
if (this.biome != null) if (this.biome == null)
{
return colorResolver.getColor(this.biome, blockPos.getX(), blockPos.getZ());
}
else
{ {
// hopefully unneeded debug color // hopefully unneeded debug color
return ColorUtil.CYAN; return ColorUtil.CYAN;
} }
return colorResolver.getColor(unwrap(biome), blockPos.getX(), blockPos.getZ());
} }
private static Biome unwrap(#if MC_VER >= MC_1_18_2 Holder<Biome> #else Biome #endif biome) private static Biome unwrap(#if MC_VER >= MC_1_18_2 Holder<Biome> #else Biome #endif biome)
@@ -19,7 +19,11 @@
package com.seibel.distanthorizons.common.wrappers.minecraft; package com.seibel.distanthorizons.common.wrappers.minecraft;
#if MC_VER < MC_1_21_5
import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.platform.GlStateManager;
#elif MC_VER == MC_1_21_5
import com.mojang.blaze3d.opengl.GlStateManager;
#endif
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
@@ -148,7 +152,10 @@ public class MinecraftGLWrapper implements IMinecraftGLWrapper
public void glBlendFunc(int sfactor, int dfactor) public void glBlendFunc(int sfactor, int dfactor)
{ {
GL32.glBlendFunc(sfactor, dfactor); GL32.glBlendFunc(sfactor, dfactor);
#if MC_VER < MC_1_21_5
GlStateManager._blendFunc(sfactor, dfactor); GlStateManager._blendFunc(sfactor, dfactor);
#endif
} }
/** @see GL32#glBlendFuncSeparate */ /** @see GL32#glBlendFuncSeparate */
@Override @Override
@@ -71,6 +71,10 @@ import net.minecraft.world.phys.Vec3;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.joml.Vector4f; import org.joml.Vector4f;
#if MC_VER >= MC_1_21_5
import com.mojang.blaze3d.opengl.GlTexture;
import org.lwjgl.opengl.GL32;
#endif
/** /**
* A singleton that contains everything * A singleton that contains everything
@@ -102,8 +106,15 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
*/ */
public int finalLevelFrameBufferId = -1; public int finalLevelFrameBufferId = -1;
public boolean colorTextureCastFailLogged = false;
public boolean depthTextureCastFailLogged = false;
//=========//
// methods //
//=========//
@Override @Override
public Vec3f getLookAtVector() public Vec3f getLookAtVector()
{ {
@@ -250,6 +261,26 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
private RenderTarget getRenderTarget() { return MC.getMainRenderTarget(); } private RenderTarget getRenderTarget() { return MC.getMainRenderTarget(); }
@Override
public boolean mcRendersToFrameBuffer()
{
#if MC_VER < MC_1_21_5
return true;
#else
return false;
#endif
}
@Override
public boolean runningLegacyOpenGL()
{
#if MC_VER <= MC_1_16_5
return true;
#else
return false;
#endif
}
@Override @Override
public int getTargetFrameBuffer() public int getTargetFrameBuffer()
{ {
@@ -259,27 +290,87 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
return this.finalLevelFrameBufferId; return this.finalLevelFrameBufferId;
} }
#if MC_VER < MC_1_21_5
return this.getRenderTarget().frameBufferId; return this.getRenderTarget().frameBufferId;
#else
// MC renders to a texture and then directly to the default FBO now
// we need to draw to their texture instead of the FBO
return 0; // 0 is the ID for the default frame buffer
#endif
} }
@Override @Override
public void clearTargetFrameBuffer() { this.finalLevelFrameBufferId = -1; } public void clearTargetFrameBuffer() { this.finalLevelFrameBufferId = -1; }
@Override @Override
public int getDepthTextureId() { return this.getRenderTarget().getDepthTextureId(); } public int getDepthTextureId()
{
#if MC_VER < MC_1_21_5
return this.getRenderTarget().getDepthTextureId();
#else
try
{
GlTexture glTexture = (GlTexture) this.getRenderTarget().getDepthTexture();
if (glTexture == null)
{
// shouldn't happen, but just in case
return 0;
}
return glTexture.glId();
}
catch (ClassCastException e)
{
// only log this error once per session
if (!this.depthTextureCastFailLogged)
{
this.depthTextureCastFailLogged = true;
LOGGER.error("Unable to cast render Target depth texture to GlTexture. MC or a rendering mod may have changed the object type.", e);
}
return 0;
}
#endif
}
@Override @Override
public int getColorTextureId() { return this.getRenderTarget().getColorTextureId(); } public int getColorTextureId()
{
#if MC_VER < MC_1_21_5
return this.getRenderTarget().getColorTextureId();
#else
try
{
GlTexture glTexture = (GlTexture) this.getRenderTarget().getColorTexture();
if (glTexture == null)
{
// shouldn't happen, but just in case
return 0;
}
return glTexture.glId();
}
catch (ClassCastException e)
{
// only log this error once per session
if (!this.colorTextureCastFailLogged)
{
this.colorTextureCastFailLogged = true;
LOGGER.error("Unable to cast render Target color texture to GlTexture. MC or a rendering mod may have changed the object type.", e);
}
return 0;
}
#endif
}
@Override @Override
public int getTargetFrameBufferViewportWidth() public int getTargetFrameBufferViewportWidth()
{ {
return getRenderTarget().viewWidth; return this.getRenderTarget().viewWidth;
} }
@Override @Override
public int getTargetFrameBufferViewportHeight() public int getTargetFrameBufferViewportHeight()
{ {
return getRenderTarget().viewHeight; return this.getRenderTarget().viewHeight;
} }
@Override @Override
@@ -52,6 +52,7 @@ public class LightMapWrapper implements ILightMapWrapper
public void uploadLightmap(NativeImage image) public void uploadLightmap(NativeImage image)
{ {
#if MC_VER < MC_1_21_5
int currentTexture = GLMC.getActiveTexture(); int currentTexture = GLMC.getActiveTexture();
if (this.textureId == 0) if (this.textureId == 0)
{ {
@@ -69,13 +70,20 @@ public class LightMapWrapper implements ILightMapWrapper
{ {
GLMC.glBindTexture(currentTexture); GLMC.glBindTexture(currentTexture);
} }
#else
throw new UnsupportedOperationException("setLightmapId should be used for MC versions after 1.21.5"); // TODO that MC version number is wrong, when did we actually start using setLightmapId()?
#endif
} }
private void createLightmap(NativeImage image) private void createLightmap(NativeImage image)
{ {
#if MC_VER < MC_1_21_5
this.textureId = GLMC.glGenTextures(); this.textureId = GLMC.glGenTextures();
GLMC.glBindTexture(this.textureId); GLMC.glBindTexture(this.textureId);
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, image.format().glFormat(), image.getWidth(), image.getHeight(), GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, image.format().glFormat(), image.getWidth(), image.getHeight(),
0, image.format().glFormat(), GL32.GL_UNSIGNED_BYTE, (ByteBuffer) null); 0, image.format().glFormat(), GL32.GL_UNSIGNED_BYTE, (ByteBuffer) null);
#else
throw new UnsupportedOperationException("setLightmapId should be used for MC versions after 1.21.5"); // TODO that MC version number is wrong, when did we actually start using setLightmapId()?
#endif
} }
public void setLightmapId(int minecraftLightmapTetxureId) public void setLightmapId(int minecraftLightmapTetxureId)
@@ -25,7 +25,6 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource; import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.phys.Vec3;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -33,6 +32,10 @@ import org.jetbrains.annotations.Nullable;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
#if MC_VER <= MC_1_20_4 #if MC_VER <= MC_1_20_4
@@ -42,6 +45,7 @@ import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif #endif
#if MC_VER < MC_1_21_3 #if MC_VER < MC_1_21_3
import net.minecraft.world.phys.Vec3;
#else #else
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
#endif #endif
@@ -49,7 +53,12 @@ import com.seibel.distanthorizons.core.util.ColorUtil;
public class ClientLevelWrapper implements IClientLevelWrapper public class ClientLevelWrapper implements IClientLevelWrapper
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(ClientLevelWrapper.class.getSimpleName()); private static final Logger LOGGER = DhLoggerBuilder.getLogger(ClientLevelWrapper.class.getSimpleName());
private static final ConcurrentHashMap<ClientLevel, ClientLevelWrapper> LEVEL_WRAPPER_BY_CLIENT_LEVEL = new ConcurrentHashMap<>(); // TODO can leak /**
* weak references are to prevent rare issues
* where, upon world closure, some levels aren't shutdown/removed properly
* and/or for servers were the level object isn't consistent
*/
private static final Map<ClientLevel, WeakReference<ClientLevelWrapper>> LEVEL_WRAPPER_REF_BY_CLIENT_LEVEL = Collections.synchronizedMap(new WeakHashMap<>());
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class); private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
private static final Minecraft MINECRAFT = Minecraft.getInstance(); private static final Minecraft MINECRAFT = Minecraft.getInstance();
@@ -72,9 +81,9 @@ public class ClientLevelWrapper implements IClientLevelWrapper
//===============// //==================//
// wrapper logic // // instance methods //
//===============// //==================//
public static IClientLevelWrapper getWrapper(@NotNull ClientLevel level) { return getWrapper(level, false); } public static IClientLevelWrapper getWrapper(@NotNull ClientLevel level) { return getWrapper(level, false); }
@@ -96,7 +105,19 @@ public class ClientLevelWrapper implements IClientLevelWrapper
} }
} }
return LEVEL_WRAPPER_BY_CLIENT_LEVEL.computeIfAbsent(level, ClientLevelWrapper::new); return LEVEL_WRAPPER_REF_BY_CLIENT_LEVEL.compute(level, (newLevel, levelRef) ->
{
if (levelRef != null)
{
ClientLevelWrapper oldLevelWrapper = levelRef.get();
if (oldLevelWrapper != null)
{
return levelRef;
}
}
return new WeakReference<>(new ClientLevelWrapper(newLevel));
}).get();
} }
@Nullable @Nullable
@@ -265,7 +286,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
@Override @Override
public void onUnload() public void onUnload()
{ {
LEVEL_WRAPPER_BY_CLIENT_LEVEL.remove(this.level); LEVEL_WRAPPER_REF_BY_CLIENT_LEVEL.remove(this.level);
this.parentDhLevel = null; this.parentDhLevel = null;
} }
@@ -20,6 +20,10 @@
package com.seibel.distanthorizons.common.wrappers.world; package com.seibel.distanthorizons.common.wrappers.world;
import java.io.File; import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType;
@@ -57,7 +61,11 @@ import org.apache.logging.log4j.Logger;
public class ServerLevelWrapper implements IServerLevelWrapper public class ServerLevelWrapper implements IServerLevelWrapper
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final ConcurrentHashMap<ServerLevel, ServerLevelWrapper> LEVEL_WRAPPER_BY_SERVER_LEVEL = new ConcurrentHashMap<>(); /**
* weak references are to prevent rare issues
* where, upon world closure, some levels aren't shutdown/removed properly
*/
private static final Map<ServerLevel, WeakReference<ServerLevelWrapper>> LEVEL_WRAPPER_REF_BY_SERVER_LEVEL = Collections.synchronizedMap(new WeakHashMap<>());
private final ServerLevel level; private final ServerLevel level;
@Deprecated // TODO circular references are bad @Deprecated // TODO circular references are bad
@@ -70,15 +78,29 @@ public class ServerLevelWrapper implements IServerLevelWrapper
//==============// //==============//
public static ServerLevelWrapper getWrapper(ServerLevel level) public static ServerLevelWrapper getWrapper(ServerLevel level)
{ return LEVEL_WRAPPER_BY_SERVER_LEVEL.computeIfAbsent(level, ServerLevelWrapper::new); } {
return LEVEL_WRAPPER_REF_BY_SERVER_LEVEL.compute(level, (newLevel, levelRef) ->
{
if (levelRef != null)
{
ServerLevelWrapper oldLevelWrapper = levelRef.get();
if (oldLevelWrapper != null)
{
return levelRef;
}
}
return new WeakReference<>(new ServerLevelWrapper(newLevel));
}).get();
}
public ServerLevelWrapper(ServerLevel level) { this.level = level; } public ServerLevelWrapper(ServerLevel level) { this.level = level; }
//=========// //==================//
// methods // // instance methods //
//=========// //==================//
@Override @Override
public File getMcSaveFolder() public File getMcSaveFolder()
@@ -93,6 +115,8 @@ public class ServerLevelWrapper implements IServerLevelWrapper
@Override @Override
public String getWorldFolderName() public String getWorldFolderName()
{ {
// TODO can we just replace this with getMcSaveFolder()? Why are we using the screenshot file anyway?
// this can have issues when the screenshot file is null/missing
#if MC_VER >= MC_1_17_1 #if MC_VER >= MC_1_17_1
return this.level.getServer().getWorldScreenshotFile().get().getParent().getFileName().toString(); return this.level.getServer().getWorldScreenshotFile().get().getParent().getFileName().toString();
#else // <= 1.16.5 #else // <= 1.16.5
@@ -179,7 +203,7 @@ public class ServerLevelWrapper implements IServerLevelWrapper
public ServerLevel getWrappedMcObject() { return this.level; } public ServerLevel getWrappedMcObject() { return this.level; }
@Override @Override
public void onUnload() { LEVEL_WRAPPER_BY_SERVER_LEVEL.remove(this.level); } public void onUnload() { LEVEL_WRAPPER_REF_BY_SERVER_LEVEL.remove(this.level); }
@Override @Override
@@ -113,7 +113,11 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
new ConfigBasedLogger(LogManager.getLogger("LodWorldGen"), new ConfigBasedLogger(LogManager.getLogger("LodWorldGen"),
() -> Config.Common.Logging.logWorldGenLoadEvent.get()); () -> Config.Common.Logging.logWorldGenLoadEvent.get());
#if MC_VER < MC_1_21_5
private static final TicketType<ChunkPos> DH_SERVER_GEN_TICKET = TicketType.create("dh_server_gen_ticket", Comparator.comparingLong(ChunkPos::toLong)); private static final TicketType<ChunkPos> DH_SERVER_GEN_TICKET = TicketType.create("dh_server_gen_ticket", Comparator.comparingLong(ChunkPos::toLong));
#else
private static final TicketType DH_SERVER_GEN_TICKET = new TicketType(/* timeout, 0 = disabled*/0L, /* persist */ false, TicketType.TicketUse.LOADING);
#endif
private static final IModChecker MOD_CHECKER = SingletonInjector.INSTANCE.get(IModChecker.class); private static final IModChecker MOD_CHECKER = SingletonInjector.INSTANCE.get(IModChecker.class);
@@ -878,7 +882,11 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
chunkLevel = generateUpToFeatures ? ChunkLevel.byStatus(ChunkStatus.FEATURES) : 33; chunkLevel = generateUpToFeatures ? ChunkLevel.byStatus(ChunkStatus.FEATURES) : 33;
#endif #endif
#if MC_VER < MC_1_21_5
level.getChunkSource().distanceManager.addTicket(DH_SERVER_GEN_TICKET, pos, chunkLevel, pos); level.getChunkSource().distanceManager.addTicket(DH_SERVER_GEN_TICKET, pos, chunkLevel, pos);
#else
level.getChunkSource().addTicketWithRadius(DH_SERVER_GEN_TICKET, pos, 0);
#endif
level.getChunkSource().distanceManager.runAllUpdates(level.getChunkSource().chunkMap); // probably not the most optimal to run updates here, but fast enough level.getChunkSource().distanceManager.runAllUpdates(level.getChunkSource().chunkMap); // probably not the most optimal to run updates here, but fast enough
ChunkHolder holder = level.getChunkSource().chunkMap.getUpdatingChunkIfPresent(pos.toLong()); ChunkHolder holder = level.getChunkSource().chunkMap.getUpdatingChunkIfPresent(pos.toLong());
if (holder == null) if (holder == null)
@@ -915,7 +923,11 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
chunkLevel = chunkWasGeneratedUpToFeatures ? ChunkLevel.byStatus(ChunkStatus.FEATURES) : 33; chunkLevel = chunkWasGeneratedUpToFeatures ? ChunkLevel.byStatus(ChunkStatus.FEATURES) : 33;
#endif #endif
#if MC_VER < MC_1_21_5
level.getChunkSource().distanceManager.removeTicket(DH_SERVER_GEN_TICKET, pos, chunkLevel, pos); level.getChunkSource().distanceManager.removeTicket(DH_SERVER_GEN_TICKET, pos, chunkLevel, pos);
#else
level.getChunkSource().removeTicketWithRadius(DH_SERVER_GEN_TICKET, pos, 0);
#endif
// mitigate OOM issues in vanilla chunk system: see https://github.com/pop4959/Chunky/pull/383 // mitigate OOM issues in vanilla chunk system: see https://github.com/pop4959/Chunky/pull/383
level.getChunkSource().chunkMap.tick(() -> false); level.getChunkSource().chunkMap.tick(() -> false);
@@ -29,7 +29,9 @@ import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import it.unimi.dsi.fastutil.shorts.ShortList; import it.unimi.dsi.fastutil.shorts.ShortList;
@@ -84,11 +86,13 @@ import net.minecraft.world.level.chunk.status.ChunkType;
#endif #endif
import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.Fluid;
import org.jetbrains.annotations.Nullable;
public class ChunkLoader public class ChunkLoader
{ {
private static boolean zeroChunkPosErrorLogged = false; private static final AtomicBoolean ZERO_CHUNK_POS_ERROR_LOGGED_REF = new AtomicBoolean(false);
#if MC_VER >= MC_1_19_2 #if MC_VER >= MC_1_19_2
private static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); private static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState());
@@ -120,19 +124,16 @@ public class ChunkLoader
CompoundTag tagLevel = chunkData; CompoundTag tagLevel = chunkData;
#endif #endif
ChunkPos actualPos = new ChunkPos(tagLevel.getInt("xPos"), tagLevel.getInt("zPos")); int chunkX = tagGetInt(tagLevel,"xPos");
int chunkZ = tagGetInt(tagLevel, "zPos");
ChunkPos actualPos = new ChunkPos(chunkX, chunkZ);
if (!Objects.equals(chunkPos, actualPos)) if (!Objects.equals(chunkPos, actualPos))
{ {
#if MC_VER >= MC_1_18_2 if (chunkX == 0 && chunkZ == 0)
if (actualPos.equals(ChunkPos.ZERO))
#else
if (actualPos.equals(ChunkPos.INVALID_CHUNK_POS))
#endif
{ {
if (!zeroChunkPosErrorLogged) if (!ZERO_CHUNK_POS_ERROR_LOGGED_REF.getAndSet(true))
{ {
zeroChunkPosErrorLogged = true;
// explicit chunkPos toString is necessary otherwise the JDK 17 compiler breaks // explicit chunkPos toString is necessary otherwise the JDK 17 compiler breaks
LOGGER.warn("Chunk file at ["+chunkPos.toString()+"] doesn't have a chunk pos. \n" + LOGGER.warn("Chunk file at ["+chunkPos.toString()+"] doesn't have a chunk pos. \n" +
"This might happen if the world was created using an external program. \n" + "This might happen if the world was created using an external program. \n" +
@@ -174,14 +175,27 @@ public class ChunkLoader
#endif #endif
long inhabitedTime = tagLevel.getLong("InhabitedTime"); long inhabitedTime = tagGetLong(tagLevel, "InhabitedTime");
//================== Read params for making the LevelChunk ================== //================== Read params for making the LevelChunk ==================
UpgradeData upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10)
? new UpgradeData(tagLevel.getCompound(TAG_UPGRADE_DATA)#if MC_VER >= MC_1_17_1 , level #endif )
: UpgradeData.EMPTY;
boolean isLightOn = tagLevel.getBoolean("isLightOn"); UpgradeData upgradeData;
#if MC_VER < MC_1_17_1
upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10)
? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA))
: UpgradeData.EMPTY;
#elif MC_VER < MC_1_21_5
upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10)
? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level)
: UpgradeData.EMPTY;
#else
upgradeData = tagLevel.contains(TAG_UPGRADE_DATA)
? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level)
: UpgradeData.EMPTY;
#endif
boolean isLightOn = tagGetBoolean(tagLevel, "isLightOn");
#if MC_VER < MC_1_18_2 #if MC_VER < MC_1_18_2
ChunkBiomeContainer chunkBiomeContainer = new ChunkBiomeContainer( ChunkBiomeContainer chunkBiomeContainer = new ChunkBiomeContainer(
level.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY)#if MC_VER >= MC_1_17_1 , level #endif , level.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY)#if MC_VER >= MC_1_17_1 , level #endif ,
@@ -203,11 +217,15 @@ public class ChunkLoader
string -> Registry.BLOCK.getOptional(ResourceLocation.tryParse(string)), chunkPos); string -> Registry.BLOCK.getOptional(ResourceLocation.tryParse(string)), chunkPos);
LevelChunkTicks<Fluid> fluidTicks = LevelChunkTicks.load(tagLevel.getList(FLUID_TICKS_TAG_18, 10), LevelChunkTicks<Fluid> fluidTicks = LevelChunkTicks.load(tagLevel.getList(FLUID_TICKS_TAG_18, 10),
string -> Registry.FLUID.getOptional(ResourceLocation.tryParse(string)), chunkPos); string -> Registry.FLUID.getOptional(ResourceLocation.tryParse(string)), chunkPos);
#else #elif MC_VER < MC_1_21_4
LevelChunkTicks<Block> blockTicks = LevelChunkTicks.load(tagLevel.getList(BLOCK_TICKS_TAG_18, 10), LevelChunkTicks<Block> blockTicks = LevelChunkTicks.load(tagLevel.getList(BLOCK_TICKS_TAG_18, 10),
(string -> BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(string))), chunkPos); (string -> BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(string))), chunkPos);
LevelChunkTicks<Fluid> fluidTicks = LevelChunkTicks.load(tagLevel.getList(FLUID_TICKS_TAG_18, 10), LevelChunkTicks<Fluid> fluidTicks = LevelChunkTicks.load(tagLevel.getList(FLUID_TICKS_TAG_18, 10),
string -> BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(string)), chunkPos); string -> BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(string)), chunkPos);
#else
// do we need the ticks for what we're doing?
LevelChunkTicks<Block> blockTicks = new LevelChunkTicks<>();
LevelChunkTicks<Fluid> fluidTicks = new LevelChunkTicks<>();
#endif #endif
#endif #endif
@@ -218,7 +236,6 @@ public class ChunkLoader
LevelChunk chunk = new LevelChunk((Level) level.getLevel(), chunkPos, chunkBiomeContainer, upgradeData, blockTicks, LevelChunk chunk = new LevelChunk((Level) level.getLevel(), chunkPos, chunkBiomeContainer, upgradeData, blockTicks,
fluidTicks, inhabitedTime, levelChunkSections, null); fluidTicks, inhabitedTime, levelChunkSections, null);
#else #else
LevelChunk chunk = new LevelChunk((Level) level, chunkPos, upgradeData, blockTicks, LevelChunk chunk = new LevelChunk((Level) level, chunkPos, upgradeData, blockTicks,
fluidTicks, inhabitedTime, levelChunkSections, null, blendingData); fluidTicks, inhabitedTime, levelChunkSections, null, blendingData);
#endif #endif
@@ -252,18 +269,23 @@ public class ChunkLoader
biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getOrThrow(Biomes.PLAINS)); biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getOrThrow(Biomes.PLAINS));
#endif #endif
#endif #endif
int sectionYIndex = #if MC_VER < MC_1_17_1 16; #else level.getSectionsCount(); #endif int sectionYIndex = #if MC_VER < MC_1_17_1 16; #else level.getSectionsCount(); #endif
LevelChunkSection[] chunkSections = new LevelChunkSection[sectionYIndex]; LevelChunkSection[] chunkSections = new LevelChunkSection[sectionYIndex];
boolean isLightOn = chunkData.getBoolean("isLightOn"); ListTag tagSections = tagGetListTag(chunkData, "Sections", 10);
boolean hasSkyLight = level.dimensionType().hasSkyLight(); if (tagSections == null || tagSections.isEmpty())
ListTag tagSections = chunkData.getList("Sections", 10); {
if (tagSections.isEmpty()) tagSections = chunkData.getList("sections", 10); tagSections = tagGetListTag(chunkData, "sections", 10);
}
if (tagSections != null)
{
for (int j = 0; j < tagSections.size(); ++j) for (int j = 0; j < tagSections.size(); ++j)
{ {
CompoundTag tagSection = tagSections.getCompound(j); CompoundTag tagSection = tagGetCompoundTag(tagSections, j);
int sectionYPos = tagSection.getByte("Y"); int sectionYPos = tagGetByte(tagSection, "Y");
#if MC_VER < MC_1_18_2 #if MC_VER < MC_1_18_2
if (tagSection.contains("Palette", 9) && tagSection.contains("BlockStates", 12)) if (tagSection.contains("Palette", 9) && tagSection.contains("BlockStates", 12))
@@ -287,15 +309,30 @@ public class ChunkLoader
PalettedContainer<Holder<Biome>> biomeContainer; PalettedContainer<Holder<Biome>> biomeContainer;
#endif #endif
blockStateContainer = tagSection.contains("block_states", 10) boolean containsBlockStates;
? BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagSection.getCompound("block_states")) #if MC_VER < MC_1_21_5
.promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string)) containsBlockStates = tagSection.contains("block_states", 10);
#if MC_VER < MC_1_20_6
.getOrThrow(false, (message) -> logWarningOnce(message))
#else #else
.getOrThrow((message) -> logErrorAndReturnException(message)) containsBlockStates = tagSection.contains("block_states");
#endif #endif
: new PalettedContainer<BlockState>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
if (containsBlockStates)
{
#if MC_VER < MC_1_20_6
blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states"))
.promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
.getOrThrow(false, (message) -> logParsingWarningOnce(message));
#else
blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states"))
.promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
.getOrThrow((message) -> logErrorAndReturnException(message));
#endif
}
else
{
blockStateContainer = new PalettedContainer<BlockState>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
}
#if MC_VER < MC_1_18_2 #if MC_VER < MC_1_18_2
biomeContainer = tagSection.contains("biomes", 10) biomeContainer = tagSection.contains("biomes", 10)
@@ -304,14 +341,22 @@ public class ChunkLoader
#else #else
if (tagSection.contains("biomes", 10)) boolean containsBiomes;
{ #if MC_VER < MC_1_21_5
biomeContainer = containsBiomes = tagSection.contains("biomes", 10);
biomeCodec.parse(NbtOps.INSTANCE, tagSection.getCompound("biomes"))
.promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string))
#if MC_VER < MC_1_20_6
.getOrThrow(false, (message) -> logWarningOnce(message));
#else #else
containsBiomes = tagSection.contains("biomes");
#endif
if (containsBiomes)
{
#if MC_VER < MC_1_20_6
biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes"))
.promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string))
.getOrThrow(false, (message) -> logParsingWarningOnce(message));
#else
biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes"))
.promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string))
.getOrThrow((message) -> logErrorAndReturnException(message)); .getOrThrow((message) -> logErrorAndReturnException(message));
#endif #endif
} }
@@ -337,6 +382,7 @@ public class ChunkLoader
#endif #endif
} }
}
return chunkSections; return chunkSections;
} }
private static private static
@@ -345,39 +391,54 @@ public class ChunkLoader
#else ChunkType #endif #else ChunkType #endif
readChunkType(CompoundTag tagLevel) readChunkType(CompoundTag tagLevel)
{ {
ChunkStatus chunkStatus = ChunkStatus.byName(tagLevel.getString("Status")); ChunkStatus chunkStatus = ChunkStatus.byName(tagGetString(tagLevel,"Status"));
if (chunkStatus != null) if (chunkStatus != null)
{ {
return chunkStatus.getChunkType(); return chunkStatus.getChunkType();
} }
return #if MC_VER <= MC_1_20_4
#if MC_VER <= MC_1_20_4 ChunkStatus.ChunkType.PROTOCHUNK; return ChunkStatus.ChunkType.PROTOCHUNK;
#else ChunkType.PROTOCHUNK; #endif #else
return ChunkType.PROTOCHUNK;
#endif
} }
private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData) private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData)
{ {
CompoundTag tagHeightmaps = chunkData.getCompound("Heightmaps"); CompoundTag tagHeightmaps = tagGetCompoundTag(chunkData, "Heightmaps");
for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter()) for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter())
{ {
String heightmap = type.getSerializationKey(); String heightmap = type.getSerializationKey();
#if MC_VER < MC_1_21_5
if (tagHeightmaps.contains(heightmap, 12)) if (tagHeightmaps.contains(heightmap, 12))
{
chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmap)); chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmap));
} }
#else
if (tagHeightmaps.contains(heightmap))
{
Optional<long[]> optionalHeightmap = tagHeightmaps.getLongArray(heightmap);
if (optionalHeightmap.isPresent())
{
chunk.setHeightmap(type, optionalHeightmap.get());
}
}
#endif
}
Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter()); Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter());
} }
private static void readPostPocessings(LevelChunk chunk, CompoundTag chunkData) private static void readPostPocessings(LevelChunk chunk, CompoundTag chunkData)
{ {
ListTag tagPostProcessings = chunkData.getList("PostProcessing", 9); ListTag tagPostProcessings = tagGetListTag(chunkData,"PostProcessing", 9);
for (int i = 0; i < tagPostProcessings.size(); ++i) for (int i = 0; i < tagPostProcessings.size(); ++i)
{ {
ListTag listTag3 = tagPostProcessings.getList(i); ListTag listTag3 = tagGetListTag(tagPostProcessings, i);
for (int j = 0; j < listTag3.size(); ++j) for (int j = 0; j < listTag3.size(); ++j)
{ {
#if MC_VER < MC_1_21_3 #if MC_VER < MC_1_21_3
chunk.addPackedPostProcess(listTag3.getShort(j), i); chunk.addPackedPostProcess(listTag3.getShort(j), i);
#else #else
chunk.addPackedPostProcess(ShortList.of(listTag3.getShort(j)), i); chunk.addPackedPostProcess(ShortList.of(tagGetShort(listTag3, j)), i);
#endif #endif
} }
} }
@@ -386,17 +447,39 @@ public class ChunkLoader
private static BlendingData readBlendingData(CompoundTag chunkData) private static BlendingData readBlendingData(CompoundTag chunkData)
{ {
BlendingData blendingData = null; BlendingData blendingData = null;
if (chunkData.contains("blending_data", 10))
boolean containsBlendingData;
#if MC_VER < MC_1_21_5
containsBlendingData = chunkData.contains("blending_data", 10);
#else
containsBlendingData = chunkData.contains("blending_data");
#endif
if (containsBlendingData)
{ {
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
Dynamic<CompoundTag> blendingDataTag = new Dynamic(NbtOps.INSTANCE, chunkData.getCompound("blending_data")); Dynamic<CompoundTag> blendingDataTag = new Dynamic(NbtOps.INSTANCE, chunkData.getCompound("blending_data"));
try
{
#if MC_VER < MC_1_21_3 #if MC_VER < MC_1_21_3
blendingData = BlendingData.CODEC.parse(blendingDataTag).resultOrPartial((message) -> logWarningOnce(message)).orElse(null); blendingData = BlendingData.CODEC.parse(blendingDataTag).resultOrPartial((message) -> logParsingWarningOnce(message)).orElse(null);
#else #else
blendingData = BlendingData.unpack(BlendingData.Packed.CODEC.parse(blendingDataTag).resultOrPartial((message) -> logWarningOnce(message)).orElse(null)); blendingData = BlendingData.unpack(BlendingData.Packed.CODEC.parse(blendingDataTag).resultOrPartial((message) -> logParsingWarningOnce(message)).orElse(null));
#endif #endif
} }
catch (Exception e)
{
String message = e.getMessage();
if (message == null || message.trim().isEmpty())
{
message = "Failed to parse blending data";
}
logParsingWarningOnce(message, e);
}
}
return blendingData; return blendingData;
} }
#endif #endif
@@ -472,9 +555,12 @@ public class ChunkLoader
// if null all lights = 0 // if null all lights = 0
byte[] blockLightNibbleArray = chunkSectionCompoundTag.getByteArray("BlockLight"); byte[] blockLightNibbleArray = tagGetByteArray(chunkSectionCompoundTag, "BlockLight");
byte[] skyLightNibbleArray = chunkSectionCompoundTag.getByteArray("SkyLight"); byte[] skyLightNibbleArray = tagGetByteArray(chunkSectionCompoundTag, "SkyLight");
if (blockLightNibbleArray != null
&& skyLightNibbleArray != null)
{
// if any sky light was found then all lights above will be max brightness // if any sky light was found then all lights above will be max brightness
if (skyLightNibbleArray.length != 0) if (skyLightNibbleArray.length != 0)
{ {
@@ -503,6 +589,7 @@ public class ChunkLoader
} }
} }
} }
}
return combinedStorage; return combinedStorage;
#endif #endif
@@ -547,9 +634,14 @@ public class ChunkLoader
}); });
} }
private static void logWarningOnce(String message) { logWarningOnce(message, null); } private static void logParsingWarningOnce(String message) { logParsingWarningOnce(message, null); }
private static void logWarningOnce(String message, Exception e) private static void logParsingWarningOnce(String message, Exception e)
{ {
if (message == null)
{
return;
}
LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) -> LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) ->
{ {
LOGGER.warn("Parsing error: ["+newMessage+"]. " + LOGGER.warn("Parsing error: ["+newMessage+"]. " +
@@ -576,6 +668,134 @@ public class ChunkLoader
//====================//
// tag helper methods //
//====================//
// TODO move into separate file (this file is getting long)
// these tag helpers are to simplify tag accessing between MC versions
/** defaults to "false" if the tag isn't present */
private static boolean tagGetBoolean(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getBoolean(key);
#else
return tag.getBoolean(key).orElse(false);
#endif
}
/** defaults to "0" if the tag isn't present */
private static byte tagGetByte(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getByte(key);
#else
return tag.getByte(key).orElse((byte)0);
#endif
}
/** defaults to "0" if the tag isn't present */
private static short tagGetShort(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getShort(index);
#else
return tag.getShort(index).orElse((short)0);
#endif
}
/** defaults to "0" if the tag isn't present */
private static int tagGetInt(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getInt(key);
#else
return tag.getInt(key).orElse(0);
#endif
}
/** defaults to "0" if the tag isn't present */
private static long tagGetLong(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getInt(key);
#else
return tag.getLong(key).orElse(0L);
#endif
}
/** defaults to null if the tag isn't present */
private static String tagGetString(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getString(key);
#else
return tag.getString(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
private static byte[] tagGetByteArray(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getByteArray(key);
#else
return tag.getByteArray(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
private static CompoundTag tagGetCompoundTag(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getCompound(key);
#else
return tag.getCompound(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
private static CompoundTag tagGetCompoundTag(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getCompound(index);
#else
return tag.getCompound(index).orElse(null);
#endif
}
/**
* defaults to null if the tag isn't present
* @param elementType unused after MC 1.21.5
*/
@Nullable
private static ListTag tagGetListTag(CompoundTag tag, String key, int elementType)
{
#if MC_VER < MC_1_21_5
return tag.getList(key, elementType);
#else
return tag.getList(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
private static ListTag tagGetListTag(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getList(index);
#else
return tag.getList(index).orElse(null);
#endif
}
//================// //================//
// helper classes // // helper classes //
@@ -229,8 +229,16 @@ public class DhLitWorldGenRegion extends WorldGenRegion
{ {
ChunkAccess chunkAccess = this.getChunk(blockPos); ChunkAccess chunkAccess = this.getChunk(blockPos);
if (chunkAccess instanceof LevelChunk) if (chunkAccess instanceof LevelChunk)
{
return true; return true;
}
#if MC_VER < MC_1_21_5
chunkAccess.setBlockState(blockPos, blockState, /*isBlockMoving*/false); chunkAccess.setBlockState(blockPos, blockState, /*isBlockMoving*/false);
#else
chunkAccess.setBlockState(blockPos, blockState, /*flags*/0);
#endif
// This is for post ticking for water on gen and stuff like that. Not enabled // This is for post ticking for water on gen and stuff like that. Not enabled
// for now. // for now.
// if (blockState.hasPostProcess(this, blockPos)) // if (blockState.hasPostProcess(this, blockPos))
+1 -1
View File
@@ -1,5 +1,5 @@
plugins { plugins {
id "fabric-loom" version "1.8-SNAPSHOT" id "fabric-loom" version "1.10-SNAPSHOT"
} }
loom { loom {
@@ -90,8 +90,8 @@ public class FabricMain extends AbstractModInitializer implements ClientModIniti
{ {
ModAccessorInjector.INSTANCE.bind(ISodiumAccessor.class, new SodiumAccessor()); ModAccessorInjector.INSTANCE.bind(ISodiumAccessor.class, new SodiumAccessor());
// If sodium is installed Indium is also necessary in order to use the Fabric rendering API // If sodium is installed Indium is also necessary for versions 0.5 and less in order to use the Fabric rendering API
if (!modChecker.isModLoaded("indium")) if (!modChecker.isModLoaded("indium") && SodiumAccessor.isSodiumV5OrLess)
{ {
String indiumMissingMessage = ModInfo.READABLE_NAME + " needs Indium to work with Sodium.\nPlease download Indium from https://modrinth.com/mod/indium"; String indiumMissingMessage = ModInfo.READABLE_NAME + " needs Indium to work with Sodium.\nPlease download Indium from https://modrinth.com/mod/indium";
LOGGER.fatal(indiumMissingMessage); LOGGER.fatal(indiumMissingMessage);
@@ -34,21 +34,34 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
#if MC_VER < MC_1_21_3 #if MC_VER < MC_1_21_3
import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.platform.NativeImage;
#else #elif MC_VER < MC_1_21_5
import com.mojang.blaze3d.pipeline.TextureTarget; import com.mojang.blaze3d.pipeline.TextureTarget;
#else
import com.mojang.blaze3d.opengl.GlTexture;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.GpuTexture;
#endif #endif
@Mixin(LightTexture.class) @Mixin(LightTexture.class)
public class MixinLightTexture public class MixinLightTexture
{ {
#if MC_VER < MC_1_21_3
@Shadow @Shadow
@Final @Final
#if MC_VER < MC_1_21_3
private NativeImage lightPixels; private NativeImage lightPixels;
#else #elif MC_VER < MC_1_21_5
@Shadow
@Final
private TextureTarget target; private TextureTarget target;
#else
@Shadow
@Final
private GpuTexture texture;
#endif #endif
@Inject(method = "updateLightTexture(F)V", at = @At("RETURN")) @Inject(method = "updateLightTexture(F)V", at = @At("RETURN"))
public void updateLightTexture(float partialTicks, CallbackInfo ci) public void updateLightTexture(float partialTicks, CallbackInfo ci)
{ {
@@ -63,8 +76,11 @@ public class MixinLightTexture
#if MC_VER < MC_1_21_3 #if MC_VER < MC_1_21_3
MinecraftRenderWrapper.INSTANCE.updateLightmap(this.lightPixels, clientLevel); MinecraftRenderWrapper.INSTANCE.updateLightmap(this.lightPixels, clientLevel);
#else #elif MC_VER < MC_1_21_5
MinecraftRenderWrapper.INSTANCE.setLightmapId(this.target.getColorTextureId(), clientLevel); MinecraftRenderWrapper.INSTANCE.setLightmapId(this.target.getColorTextureId(), clientLevel);
#else
GlTexture glTexture = (GlTexture) this.texture;
MinecraftRenderWrapper.INSTANCE.setLightmapId(glTexture.glId(), clientLevel);
#endif #endif
} }
@@ -1,12 +1,15 @@
package com.seibel.distanthorizons.fabric.mixins.client; package com.seibel.distanthorizons.fabric.mixins.client;
#if MC_VER < MC_1_21_5
import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.platform.GlStateManager;
#endif
import com.mojang.blaze3d.platform.TextureUtil; import com.mojang.blaze3d.platform.TextureUtil;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;
/** /**
* Sets Minecraft's LOD Bias (looks similar to mipmaps) * Sets Minecraft's LOD Bias (looks similar to mipmaps)
* *
@@ -15,6 +18,9 @@ import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(TextureUtil.class) @Mixin(TextureUtil.class)
public class MixinTextureUtil public class MixinTextureUtil
{ {
// TODO fix for MC 1.21.5+
#if MC_VER < MC_1_21_5
@Redirect(method = "Lcom/mojang/blaze3d/platform/TextureUtil;prepareImage(Lcom/mojang/blaze3d/platform/NativeImage$InternalGlFormat;IIII)V", @Redirect(method = "Lcom/mojang/blaze3d/platform/TextureUtil;prepareImage(Lcom/mojang/blaze3d/platform/NativeImage$InternalGlFormat;IIII)V",
at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/GlStateManager;_texParameter(IIF)V", #if MC_VER == MC_1_16_5 remap = true #else remap = false #endif)) at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/GlStateManager;_texParameter(IIF)V", #if MC_VER == MC_1_16_5 remap = true #else remap = false #endif))
private static void setLodBias(int target, int pname, float param) private static void setLodBias(int target, int pname, float param)
@@ -27,5 +33,6 @@ public class MixinTextureUtil
GlStateManager._texParameter(target, pname, biasValue); GlStateManager._texParameter(target, pname, biasValue);
} }
} }
#endif
} }
@@ -43,11 +43,22 @@ import net.minecraft.world.phys.AABB;
public class SodiumAccessor implements ISodiumAccessor public class SodiumAccessor implements ISodiumAccessor
{ {
/**
* True if sodium 0.5 or less is present. <br>
* This field is public because it's also used to check if we need Indium to be present. <br>
* We need Indium if Sodium 0.5 or less is present.
*/
public static final boolean isSodiumV5OrLess;
#if MC_VER >= MC_1_20_1 #if MC_VER >= MC_1_20_1
private static MethodHandle setFogOcclusionMethod; private static MethodHandle setFogOcclusionMethod;
private static Object sodiumPerformanceOptions; private static Object sodiumPerformanceOptions;
#endif #endif
static {
isSodiumV5OrLess = !classPresent("net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer");
}
//======================// //======================//
@@ -72,8 +83,7 @@ public class SodiumAccessor implements ISodiumAccessor
{ {
if (sodiumPerformanceOptions == null) if (sodiumPerformanceOptions == null)
{ {
boolean sodiumV6 = classPresent("net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer"); if (isSodiumV5OrLess)
if (!sodiumV6)
{ {
// sodium 0.5 // sodium 0.5
+3 -3
View File
@@ -5,7 +5,7 @@ org.gradle.caching=true
# Mod Info # Mod Info
mod_name=DistantHorizons mod_name=DistantHorizons
mod_version=2.3.1-b mod_version=2.3.3-b-dev
api_version=4.0.0 api_version=4.0.0
maven_group=com.seibel.distanthorizons maven_group=com.seibel.distanthorizons
mod_readable_name=Distant Horizons mod_readable_name=Distant Horizons
@@ -23,9 +23,9 @@ manifold_version=2024.1.37
nightconfig_version=3.6.6 nightconfig_version=3.6.6
lz4_version=1.8.0 lz4_version=1.8.0
xz_version=1.9 xz_version=1.9
# Before updating, read relocate_natives/README.md
sqlite_jdbc_version=3.47.2.0 sqlite_jdbc_version=3.47.2.0
# 8.2.1 is the newest version we can use since that's the version MC 1.16.5 uses # 8.2.1 is the newest version we can use since that's the version MC 1.16.5 uses
# (at least until we can fix the gradle script so core and main can use/shade different fastutil versions)
fastutil_version=8.2.1 fastutil_version=8.2.1
#svgSalamander_version=1.1.3 #svgSalamander_version=1.1.3
@@ -51,7 +51,7 @@ versionStr=
# This defines what MC version Intellij will use for the preprocessor # This defines what MC version Intellij will use for the preprocessor
# and what version is used automatically by build and run commands # and what version is used automatically by build and run commands
mcVer=1.21.4 mcVer=1.21.5
# Defines the maximum amount of memory Minecraft is allowed when run in a development environment # Defines the maximum amount of memory Minecraft is allowed when run in a development environment
#minecraftMemoryJavaArg="-Xmx4G" #minecraftMemoryJavaArg="-Xmx4G"
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
@@ -19,17 +19,12 @@
package com.seibel.distanthorizons.neoforge.mixins.client; package com.seibel.distanthorizons.neoforge.mixins.client;
import com.mojang.blaze3d.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.NativeImage;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftRenderWrapper; import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftRenderWrapper;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.LightTexture;
import org.lwjgl.opengl.GL32;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@@ -37,6 +32,16 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
#if MC_VER < MC_1_21_3
import com.mojang.blaze3d.platform.NativeImage;
#elif MC_VER < MC_1_21_5
import com.mojang.blaze3d.pipeline.TextureTarget;
#else
import com.mojang.blaze3d.opengl.GlTexture;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.GpuTexture;
#endif
@Mixin(LightTexture.class) @Mixin(LightTexture.class)
public class MixinLightTexture public class MixinLightTexture
{ {
@@ -44,23 +49,16 @@ public class MixinLightTexture
@Shadow @Shadow
@Final @Final
private NativeImage lightPixels; private NativeImage lightPixels;
#elif MC_VER < MC_1_21_5
@Shadow
@Inject(method = "updateLightTexture(F)V", at = @At("RETURN")) @Final
public void updateLightTexture(float partialTicks, CallbackInfo ci) private TextureTarget target;
{
IMinecraftClientWrapper mc = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
if (mc == null || mc.getWrappedClientLevel() == null)
{
return;
}
IClientLevelWrapper clientLevel = mc.getWrappedClientLevel();
MinecraftRenderWrapper.INSTANCE.updateLightmap(this.lightPixels, clientLevel);
}
#else #else
@Shadow
@Final
private GpuTexture texture;
#endif
@Shadow @Final private TextureTarget target;
@Inject(method = "updateLightTexture(F)V", at = @At("RETURN")) @Inject(method = "updateLightTexture(F)V", at = @At("RETURN"))
public void updateLightTexture(float partialTicks, CallbackInfo ci) public void updateLightTexture(float partialTicks, CallbackInfo ci)
{ {
@@ -70,9 +68,17 @@ public class MixinLightTexture
return; return;
} }
IClientLevelWrapper clientLevel = mc.getWrappedClientLevel();
MinecraftRenderWrapper.INSTANCE.setLightmapId(this.target.getColorTextureId(), clientLevel);
}
IClientLevelWrapper clientLevel = mc.getWrappedClientLevel();
#if MC_VER < MC_1_21_3
MinecraftRenderWrapper.INSTANCE.updateLightmap(this.lightPixels, clientLevel);
#elif MC_VER < MC_1_21_5
MinecraftRenderWrapper.INSTANCE.setLightmapId(this.target.getColorTextureId(), clientLevel);
#else
GlTexture glTexture = (GlTexture) this.texture;
MinecraftRenderWrapper.INSTANCE.setLightmapId(glTexture.glId(), clientLevel);
#endif #endif
} }
}
@@ -1,6 +1,8 @@
package com.seibel.distanthorizons.neoforge.mixins.client; package com.seibel.distanthorizons.neoforge.mixins.client;
#if MC_VER < MC_1_21_5
import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.platform.GlStateManager;
#endif
import com.mojang.blaze3d.platform.TextureUtil; import com.mojang.blaze3d.platform.TextureUtil;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@@ -15,6 +17,9 @@ import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(TextureUtil.class) @Mixin(TextureUtil.class)
public class MixinTextureUtil public class MixinTextureUtil
{ {
// TODO fix for MC 1.21.5+
#if MC_VER < MC_1_21_5
@Redirect(method = "prepareImage(Lcom/mojang/blaze3d/platform/NativeImage$InternalGlFormat;IIII)V", @Redirect(method = "prepareImage(Lcom/mojang/blaze3d/platform/NativeImage$InternalGlFormat;IIII)V",
at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/GlStateManager;_texParameter(IIF)V"), remap = false) at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/GlStateManager;_texParameter(IIF)V"), remap = false)
private static void setLodBias(int target, int pname, float param) private static void setLodBias(int target, int pname, float param)
@@ -27,5 +32,6 @@ public class MixinTextureUtil
GlStateManager._texParameter(target, pname, biasValue); GlStateManager._texParameter(target, pname, biasValue);
} }
} }
#endif
} }
+46
View File
@@ -0,0 +1,46 @@
This directory contains the native files of libraries that were modified for relocation. They will be copied from here during the normal build steps.
Before adding/updating a library, make sure you have Python 3.8+ installed and check the instructions below.
How to add a library's natives:
1. In `build.gradle`:
- Make sure the target package is the same length or shorter (untested) than the source package. Underscores in native methods will take 2 characters so account for that as well.
- Exclude the native files and add them as `relocateNative` (see example).
Example:
```groovy
relocate "org.sqlite", "dh_sqlite", {
exclude "org/sqlite/native/**"
}
transform(NativeTransformer) {
// NativeTransformer configuration
rootDir = project.rootDir
// Replace native strings, e.g. used in calls back to Java
relocateNative "org/sqlite", "dh_sqlite"
// Rename native methods used when calling from Java
relocateNative "org_sqlite", "dh_1sqlite"
}
```
How to update a library's natives:
1. Delete the library's folder in cache/.
2. It will repopulate during the next build.
Why does this step exist?
- Native files are not handled by the shadow plugin correctly.
- I preferred it as a more streamlined approach, although a bit hacky.
- Possible alternatives:
- Use edited libraries' source code: although more straightforward, it requires maintaining and updating the repositories for the libraries being added
- Interfacing with the necessary libraries directly: an absolute mess for technical reasons
What are libraries used?
- LIEF: for fixing binaries after replacing strings
- apple-codesign: for re-signing Mac binaries, since their signatures get invalidated after previous steps
+56
View File
@@ -0,0 +1,56 @@
# 1.21.4 version
java_version=21
minecraft_version=1.21.5
parchment_version=1.21:2024.07.28
compatible_minecraft_versions=["1.21.5"]
accessWidenerVersion=1_21_4
builds_for=fabric,neoforge
# forge is broken due to gradle/build script issues
# Netty
netty_version=4.1.97.Final
# Fabric loader
fabric_loader_version=0.16.10
fabric_api_version=0.119.5+1.21.5
modmenu_version=14.0.0-rc.2
starlight_version_fabric=
phosphor_version_fabric=
lithium_version=
sodium_version=mc1.21.5-0.6.11-fabric
iris_version=1.8.10+1.21.5-fabric
bclib_version=
immersive_portals_version=
canvas_version=
# some versions of 1.8.11 nightly builds may not work, but the ones after 2025-03-30 should
fabric_incompatibility_list={ "iris": "<=1.8.10" }
fabric_recommend_list={}
# Fabric mod run
# 0 = Don't enable and don't run
# 1 = Can be referenced in code but doesn't run
# 2 = Can be referenced in code and runs in client
enable_starlight=0
enable_phosphor=0
enable_sodium=1
enable_lithium=0
enable_iris=1
enable_bclib=0
enable_immersive_portals=0
enable_canvas=0
# (Neo)Forge loader
forge_version=
neoforge_version=21.5.0-beta
# (Neo)Forge mod versions
starlight_version_forge=
terraforged_version=
# (Neo)Forge mod run
# 0 = Don't enable and don't run
# 1 = Can be referenced in code but doesn't run
# 2 = Can be referenced in code and runs in client
enable_starlight_forge=0
enable_terraforged=0
enable_terrafirmacraft=0