Compare commits

..

22 Commits

Author SHA1 Message Date
s809 f69800dfe3 Move max generation distance check functionality to render distance config 2025-06-10 00:29:06 +05:00
James Seibel cc251e46b0 Add Api Before/After Text Create events 2025-06-09 07:50:25 -05:00
James Seibel 7aa0bfefec Fix hash collisions in FullDataPointIdMap 2025-06-06 07:43:44 -05:00
James Seibel 9bdad5e4f1 Fix 1.21.5 compiling 2025-06-06 07:36:00 -05:00
James Seibel baebb7323d Close #776 (fix Polytone client biome colors) 2025-06-05 07:53:23 -05:00
James Seibel 8a3175f345 Mark Polytone as incompatible 2025-06-04 07:49:15 -05:00
James Seibel dc58efb301 Handle nulls in ChunkLoader
Should fix WorldPainter worlds
2025-06-04 07:28:51 -05:00
James Seibel c9ac4b2ada Fix GLMC.glDeleteTextures() calls 2025-06-04 07:07:51 -05:00
s809 6e1ec476ed Check LOD timestamps in file handler threads 2025-06-03 23:41:51 +05:00
James Seibel d1aa5a524b Remove line ending from editorconfig
Done to fix some issues with some devs on linux
2025-05-17 11:47:06 -05:00
James Seibel 5c661a3a76 yaml indent 2 -> 4
for consistency
2025-05-17 11:25:37 -05:00
s809 d29e9085a1 Fix getWorldFolderName crash 2025-05-03 13:44:54 +05:00
Ran d392de3c0d Update Forgix
fixes a critical bug
2025-05-03 17:40:58 +10:00
Ran 7209193f8f Fix gradle versioning 2025-05-03 11:21:17 +10:00
s809 ed4f644a3f Bump protocol version 2025-05-03 00:08:12 +05:00
Ran 34038684a7 Fix gradle versioning for core application 2025-05-02 12:45:18 +10:00
s809 6fe6694c82 Merge branch 'feature/adaptive-data-rate' 2025-04-27 21:57:47 +05:00
s809 8e52f1aca5 Merge branch 'refactor/remove-python-dependency' 2025-04-27 21:48:53 +05:00
s809 5ce3dda2d5 Clear up the comment a bit 2025-04-27 21:48:28 +05:00
s809 f4f81f4d7f Account for forge byte when encoding protocol version instead of shifting the entire packet on pre-1.20.6 2025-04-27 21:40:50 +05:00
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
57 changed files with 457 additions and 335 deletions
+1 -2
View File
@@ -5,7 +5,6 @@ root = true
[*] [*]
charset = utf-8 charset = utf-8
end_of_line = crlf
indent_size = 4 indent_size = 4
indent_style = space indent_style = space
insert_final_newline = false insert_final_newline = false
@@ -688,7 +687,7 @@ ij_markdown_wrap_text_inside_blockquotes = true
ij_toml_keep_indents_on_empty_lines = false ij_toml_keep_indents_on_empty_lines = false
[{*.yaml,*.yml}] [{*.yaml,*.yml}]
indent_size = 2 indent_size = 4
ij_yaml_align_values_properties = do_not_align ij_yaml_align_values_properties = do_not_align
ij_yaml_autoinsert_sequence_marker = true ij_yaml_autoinsert_sequence_marker = true
ij_yaml_block_mapping_on_new_line = false ij_yaml_block_mapping_on_new_line = false
-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
@@ -44,7 +41,7 @@ build:
- ./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/;
- ./gradlew build -PmcVer="${MC_VER}" -PinfoGitCommit="${CI_COMMIT_SHA}" -PinfoGitBranch="${CI_COMMIT_BRANCH}" -PinfoBuildSource="GitLab CI (${CI_PIPELINE_ID})" --gradle-user-home cache/; - ./gradlew build -PmcVer="${MC_VER}" -PinfoGitCommit="${CI_COMMIT_SHA}" -PinfoGitBranch="${CI_COMMIT_BRANCH}" -PinfoBuildSource="GitLab CI (${CI_PIPELINE_ID})" --gradle-user-home cache/;
- ./gradlew mergeJars -PmcVer="${MC_VER}" -PinfoGitCommit="${CI_COMMIT_SHA}" -PinfoGitBranch="${CI_COMMIT_BRANCH}" -PinfoBuildSource="GitLab CI (${CI_PIPELINE_ID})" --gradle-user-home cache/; - ./gradlew mergeJars -PmcVer="${MC_VER}" -PinfoGitCommit="${CI_COMMIT_SHA}" -PinfoGitBranch="${CI_COMMIT_BRANCH}" -PinfoBuildSource="GitLab CI (${CI_PIPELINE_ID})" --gradle-user-home cache/;
- cp ./fabric/build/libs/* ./forge/build/libs/* ./neoforge/build/libs/* ./Merged/* . || true - cp ./fabric/build/libs/* ./forge/build/libs/* ./neoforge/build/libs/* ./build/merged/* . || true
artifacts: artifacts:
name: "NightlyBuild_${MC_VER}-${CI_COMMIT_SHORT_SHA}-${CI_COMMIT_TIMESTAMP}" name: "NightlyBuild_${MC_VER}-${CI_COMMIT_SHORT_SHA}-${CI_COMMIT_TIMESTAMP}"
paths: paths:
+9 -76
View File
@@ -13,7 +13,7 @@ plugins {
id "com.github.johnrengelman.shadow" version '8.1.1' apply false id "com.github.johnrengelman.shadow" version '8.1.1' apply false
// Plugin to create merged jars // Plugin to create merged jars
id "io.github.pacifistmc.forgix" version "1.2.9" id "io.github.pacifistmc.forgix" version "1.3.4"
// Manifold preprocessor // Manifold preprocessor
id "systems.manifold.manifold-gradle-plugin" version "0.0.2-alpha" id "systems.manifold.manifold-gradle-plugin" version "0.0.2-alpha"
@@ -67,63 +67,16 @@ project.gradle.ext.getProperties().each { prop ->
// Sets up manifold stuff // Sets up manifold stuff
writeBuildGradlePredefine(rootProject.mcVers, rootProject.mcIndex) writeBuildGradlePredefine(rootProject.mcVers, rootProject.mcIndex)
// Sets up the version string (the name we use for our jar) // Sets up the version string (the name we use for our jar)
rootProject.versionStr = rootProject.mod_version + "-" + rootProject.minecraft_version // + "-" + new Date().format("yyyy_MM_dd_HH_mm") rootProject.versionStr = rootProject.mod_version + "-" + rootProject.minecraft_version // + "-" + new Date().format("yyyy_MM_dd_HH_mm")
// Forgix settings (used for merging jars)
forgix {
String loaderHyphenSeparatedList = ((String)gradle.builds_for).replaceAll(",", "-");
group = "com.seibel.distanthorizons"
mergedJarName = "DistantHorizons-${loaderHyphenSeparatedList}-${rootProject.versionStr}.jar"
if (findProject(":forge"))
forge {
jarLocation = "build/libs/DistantHorizons-forge-${rootProject.versionStr}.jar"
}
if (findProject(":neoforge"))
custom {
projectName = "neoforge"
jarLocation = "build/libs/DistantHorizons-neoforge-${rootProject.versionStr}.jar"
}
if (findProject(":fabric"))
fabric {
jarLocation = "build/libs/DistantHorizons-fabric-${rootProject.versionStr}.jar"
}
if (findProject(":quilt"))
quilt {
jarLocation = "build/libs/DistantHorizons-quilt-${rootProject.versionStr}.jar"
}
removeDuplicate "com.seibel.distanthorizons"
}
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 +85,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 +341,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
relocate "org.sqlite", "dh_sqlite", {
exclude "org/sqlite/native/**"
}
relocate "jdbc:sqlite", "jdbc:dh_sqlite"
transform(NativeTransformer) { transform(NativeTransformer) {
rootDir = project.rootDir rootDir = project.rootDir
before {
relocate "org.sqlite", "dh_sqlite", {
exclude "org/sqlite/native/**"
}
relocate "jdbc:sqlite", "jdbc:dh_sqlite"
}
relocateNative "org/sqlite", "dh_sqlite" relocateNative "org/sqlite", "dh_sqlite"
relocateNative "org_sqlite", "dh_1sqlite" relocateNative "org_sqlite", "dh_1sqlite"
} }
@@ -582,8 +525,9 @@ allprojects { p ->
apply plugin: "java" apply plugin: "java"
apply plugin: "maven-publish" apply plugin: "maven-publish"
// Sets the name of the jar, the version will contain the name of the project if it isn't the root project
archivesBaseName = rootProject.mod_name archivesBaseName = rootProject.mod_name
version = project.name + "-" + rootProject.versionStr version = (project == rootProject ? "" : project.name + "-") + rootProject.versionStr
group = rootProject.maven_group group = rootProject.maven_group
// this is the text that appears at the top of the overview (home) page // this is the text that appears at the top of the overview (home) page
@@ -748,14 +692,3 @@ allprojects { p ->
withSourcesJar() withSourcesJar()
} }
} }
// Delete the merged folder when running clean
task cleanMergedJars() {
def mergedFolder = file("Merged")
if (mergedFolder.exists()) {
delete(mergedFolder)
}
}
// add cleanMergedJars to the end of the "clean" task
tasks["clean"].finalizedBy(cleanMergedJars)
+8 -4
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());
@@ -1,6 +1,7 @@
package com.seibel.distanthorizons.common; package com.seibel.distanthorizons.common;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
import com.seibel.distanthorizons.core.network.event.internal.ProtocolErrorInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.ProtocolErrorInternalEvent;
@@ -30,6 +31,16 @@ public abstract class AbstractPluginPacketSender implements IPluginPacketSender
public static final ResourceLocation WRAPPER_PACKET_RESOURCE = new ResourceLocation(ModInfo.RESOURCE_NAMESPACE, ModInfo.WRAPPER_PACKET_PATH); public static final ResourceLocation WRAPPER_PACKET_RESOURCE = new ResourceLocation(ModInfo.RESOURCE_NAMESPACE, ModInfo.WRAPPER_PACKET_PATH);
#endif #endif
// "Forge byte" is an unused packet ID. We have our own system which works with all mod loaders,
// so we're just accounting for it by reading the protocol version as a byte instead of a short in Forge, to keep cross-loader compatibility
private final boolean forgeByteInProtocolVersion;
public AbstractPluginPacketSender() { this(false); }
public AbstractPluginPacketSender(boolean forgeByteInProtocolVersion)
{
this.forgeByteInProtocolVersion = forgeByteInProtocolVersion;
}
@Override @Override
public final void sendToClient(IServerPlayerWrapper serverPlayer, AbstractNetworkMessage message) public final void sendToClient(IServerPlayerWrapper serverPlayer, AbstractNetworkMessage message)
@@ -41,7 +52,7 @@ public abstract class AbstractPluginPacketSender implements IPluginPacketSender
@Override @Override
public abstract void sendToServer(AbstractNetworkMessage message); public abstract void sendToServer(AbstractNetworkMessage message);
public static AbstractNetworkMessage decodeMessage(FriendlyByteBuf in) public AbstractNetworkMessage decodeMessage(FriendlyByteBuf in)
{ {
AbstractNetworkMessage message = null; AbstractNetworkMessage message = null;
@@ -49,7 +60,7 @@ public abstract class AbstractPluginPacketSender implements IPluginPacketSender
{ {
in.markReaderIndex(); in.markReaderIndex();
int protocolVersion = in.readShort(); int protocolVersion = this.forgeByteInProtocolVersion ? in.readByte() : in.readShort();
if (protocolVersion != ModInfo.PROTOCOL_VERSION) if (protocolVersion != ModInfo.PROTOCOL_VERSION)
{ {
return new IncompatibleMessageInternalEvent(protocolVersion); return new IncompatibleMessageInternalEvent(protocolVersion);
@@ -82,11 +93,19 @@ public abstract class AbstractPluginPacketSender implements IPluginPacketSender
} }
} }
public static void encodeMessage(FriendlyByteBuf out, AbstractNetworkMessage message) public void encodeMessage(FriendlyByteBuf out, AbstractNetworkMessage message)
{ {
// This is intentionally unhandled, because errors related to this are unlikely to appear in wild // This is intentionally unhandled, because errors related to this are unlikely to appear in wild
Objects.requireNonNull(message); Objects.requireNonNull(message);
out.writeShort(ModInfo.PROTOCOL_VERSION);
if (this.forgeByteInProtocolVersion)
{
out.writeByte(ModInfo.PROTOCOL_VERSION);
}
else
{
out.writeShort(ModInfo.PROTOCOL_VERSION);
}
try try
{ {
@@ -2,7 +2,9 @@ package com.seibel.distanthorizons.common;
#if MC_VER >= MC_1_20_6 #if MC_VER >= MC_1_20_6
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
@@ -12,6 +14,7 @@ import org.jetbrains.annotations.Nullable;
public record CommonPacketPayload(@Nullable AbstractNetworkMessage message) implements CustomPacketPayload public record CommonPacketPayload(@Nullable AbstractNetworkMessage message) implements CustomPacketPayload
{ {
public static final Type<CommonPacketPayload> TYPE = new Type<>(AbstractPluginPacketSender.WRAPPER_PACKET_RESOURCE); public static final Type<CommonPacketPayload> TYPE = new Type<>(AbstractPluginPacketSender.WRAPPER_PACKET_RESOURCE);
private static final AbstractPluginPacketSender PACKET_SENDER = (AbstractPluginPacketSender) SingletonInjector.INSTANCE.get(IPluginPacketSender.class);
@NotNull @NotNull
@Override @Override
@@ -23,11 +26,11 @@ public record CommonPacketPayload(@Nullable AbstractNetworkMessage message) impl
@NotNull @NotNull
@Override @Override
public CommonPacketPayload decode(@NotNull FriendlyByteBuf in) public CommonPacketPayload decode(@NotNull FriendlyByteBuf in)
{ return new CommonPacketPayload(AbstractPluginPacketSender.decodeMessage(in)); } { return new CommonPacketPayload(PACKET_SENDER.decodeMessage(in)); }
@Override @Override
public void encode(@NotNull FriendlyByteBuf out, CommonPacketPayload payload) public void encode(@NotNull FriendlyByteBuf out, CommonPacketPayload payload)
{ AbstractPluginPacketSender.encodeMessage(out, payload.message()); } { PACKET_SENDER.encodeMessage(out, payload.message()); }
} }
@@ -47,6 +47,7 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
#if MC_VER >= MC_1_18_2 #if MC_VER >= MC_1_18_2
import net.minecraft.world.level.biome.Biomes; import net.minecraft.world.level.biome.Biomes;
#endif #endif
@@ -130,14 +131,6 @@ public class BiomeWrapper implements IBiomeWrapper
//LOGGER.trace("Created BiomeWrapper ["+this.serialString+"] for ["+biome+"]"); //LOGGER.trace("Created BiomeWrapper ["+this.serialString+"] for ["+biome+"]");
} }
/** should only be used to create {@link BiomeWrapper#EMPTY_WRAPPER} */
private BiomeWrapper()
{
this.biome = null;
this.serialString = EMPTY_BIOME_STRING;
this.hashCode = Objects.hash(this.serialString);
}
//=========// //=========//
@@ -286,64 +279,16 @@ public class BiomeWrapper implements IBiomeWrapper
BiomeWrapper foundWrapper = EMPTY_WRAPPER; BiomeWrapper foundWrapper = EMPTY_WRAPPER;
try try
{ {
// parse the resource location
int separatorIndex = resourceLocationString.indexOf(":");
if (separatorIndex == -1)
{
throw new IOException("Unable to parse resource location string: [" + resourceLocationString + "].");
}
ResourceLocation resourceLocation;
try
{
#if MC_VER < MC_1_21_1
resourceLocation = new ResourceLocation(resourceLocationString.substring(0, separatorIndex), resourceLocationString.substring(separatorIndex + 1));
#else
resourceLocation = ResourceLocation.fromNamespaceAndPath(resourceLocationString.substring(0, separatorIndex), resourceLocationString.substring(separatorIndex + 1));
#endif
}
catch (Exception e)
{
throw new IOException("No Resource Location found for the string: [" + resourceLocationString + "] Error: [" + e.getMessage() + "].");
}
try try
{ {
Level level = (Level) levelWrapper.getWrappedMcObject(); Level level = (Level) levelWrapper.getWrappedMcObject();
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess(); net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
boolean success; BiomeDeserializeResult deserializeResult = deserializeBiome(resourceLocationString, registryAccess);
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
Biome biome = registryAccess.registryOrThrow(Registry.BIOME_REGISTRY).get(resourceLocation);
success = (biome != null);
#elif MC_VER == MC_1_18_2 || MC_VER == MC_1_19_2
Biome unwrappedBiome = registryAccess.registryOrThrow(Registry.BIOME_REGISTRY).get(resourceLocation);
success = (unwrappedBiome != null);
Holder<Biome> biome = new Holder.Direct<>(unwrappedBiome);
#elif MC_VER < MC_1_21_3
Biome unwrappedBiome = registryAccess.registryOrThrow(Registries.BIOME).get(resourceLocation);
success = (unwrappedBiome != null);
Holder<Biome> biome = new Holder.Direct<>(unwrappedBiome);
#else
Holder<Biome> biome;
Optional<Holder.Reference<Biome>> optionalBiomeHolder = registryAccess.lookupOrThrow(Registries.BIOME).get(resourceLocation);
if (optionalBiomeHolder.isPresent())
{
Biome unwrappedBiome = optionalBiomeHolder.get().value();
success = (unwrappedBiome != null);
biome = new Holder.Direct<>(unwrappedBiome);
}
else
{
success = false;
biome = null;
}
#endif
if (!success) if (!deserializeResult.success)
{ {
if (!brokenResourceLocationStrings.contains(resourceLocationString)) if (!brokenResourceLocationStrings.contains(resourceLocationString))
{ {
@@ -354,7 +299,7 @@ public class BiomeWrapper implements IBiomeWrapper
} }
foundWrapper = (BiomeWrapper) getBiomeWrapper(biome, levelWrapper); foundWrapper = (BiomeWrapper) getBiomeWrapper(deserializeResult.biome, levelWrapper);
return foundWrapper; return foundWrapper;
} }
catch (Exception e) catch (Exception e)
@@ -368,4 +313,82 @@ public class BiomeWrapper implements IBiomeWrapper
} }
} }
public static BiomeDeserializeResult deserializeBiome(String resourceLocationString, net.minecraft.core.RegistryAccess registryAccess) throws IOException
{
// parse the resource location
int separatorIndex = resourceLocationString.indexOf(":");
if (separatorIndex == -1)
{
throw new IOException("Unable to parse resource location string: [" + resourceLocationString + "].");
}
ResourceLocation resourceLocation;
try
{
#if MC_VER < MC_1_21_1
resourceLocation = new ResourceLocation(resourceLocationString.substring(0, separatorIndex), resourceLocationString.substring(separatorIndex + 1));
#else
resourceLocation = ResourceLocation.fromNamespaceAndPath(resourceLocationString.substring(0, separatorIndex), resourceLocationString.substring(separatorIndex + 1));
#endif
}
catch (Exception e)
{
throw new IOException("No Resource Location found for the string: [" + resourceLocationString + "] Error: [" + e.getMessage() + "].");
}
boolean success;
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
Biome biome = registryAccess.registryOrThrow(Registry.BIOME_REGISTRY).get(resourceLocation);
success = (biome != null);
#elif MC_VER == MC_1_18_2 || MC_VER == MC_1_19_2
Biome unwrappedBiome = registryAccess.registryOrThrow(Registry.BIOME_REGISTRY).get(resourceLocation);
success = (unwrappedBiome != null);
Holder<Biome> biome = new Holder.Direct<>(unwrappedBiome);
#elif MC_VER < MC_1_21_3
Biome unwrappedBiome = registryAccess.registryOrThrow(Registries.BIOME).get(resourceLocation);
success = (unwrappedBiome != null);
Holder<Biome> biome = new Holder.Direct<>(unwrappedBiome);
#else
Holder<Biome> biome;
Optional<Holder.Reference<Biome>> optionalBiomeHolder = registryAccess.lookupOrThrow(Registries.BIOME).get(resourceLocation);
if (optionalBiomeHolder.isPresent())
{
Biome unwrappedBiome = optionalBiomeHolder.get().value();
success = (unwrappedBiome != null);
biome = new Holder.Direct<>(unwrappedBiome);
}
else
{
success = false;
biome = null;
}
#endif
return new BiomeDeserializeResult(success, biome);
}
//================//
// helper classes //
//================//
public static class BiomeDeserializeResult
{
public final boolean success;
#if MC_VER < MC_1_18_2
public final Biome biome;
#else
public final Holder<Biome> biome;
#endif
public BiomeDeserializeResult(boolean success, #if MC_VER < MC_1_18_2 Biome #else Holder<Biome> #endif biome)
{
this.success = success;
this.biome = biome;
}
}
} }
@@ -91,7 +91,7 @@ public class ClientBlockStateColorCache
private static final RandomSource RANDOM = RandomSource.create(); private static final RandomSource RANDOM = RandomSource.create();
#endif #endif
private final IClientLevelWrapper levelWrapper; private final IClientLevelWrapper clientLevelWrapper;
private final BlockState blockState; private final BlockState blockState;
private final LevelReader level; private final LevelReader level;
@@ -174,7 +174,7 @@ public class ClientBlockStateColorCache
public ClientBlockStateColorCache(BlockState blockState, IClientLevelWrapper samplingLevel) public ClientBlockStateColorCache(BlockState blockState, IClientLevelWrapper samplingLevel)
{ {
this.blockState = blockState; this.blockState = blockState;
this.levelWrapper = samplingLevel; this.clientLevelWrapper = samplingLevel;
this.level = (LevelReader) samplingLevel.getWrappedMcObject(); this.level = (LevelReader) samplingLevel.getWrappedMcObject();
this.resolveColors(); this.resolveColors();
} }
@@ -471,7 +471,7 @@ public class ClientBlockStateColorCache
try try
{ {
tintColor = Minecraft.getInstance().getBlockColors() tintColor = Minecraft.getInstance().getBlockColors()
.getColor(this.blockState, new TintWithoutLevelOverrider(biome, this.levelWrapper), McObjectConverter.Convert(pos), this.tintIndex); .getColor(this.blockState, new TintWithoutLevelOverrider(biome, this.clientLevelWrapper), McObjectConverter.Convert(pos), this.tintIndex);
} }
catch (UnsupportedOperationException e) catch (UnsupportedOperationException e)
{ {
@@ -19,8 +19,10 @@
package com.seibel.distanthorizons.common.wrappers.block; package com.seibel.distanthorizons.common.wrappers.block;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
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.*;
@@ -29,6 +31,7 @@ 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;
@@ -36,22 +39,64 @@ import org.jetbrains.annotations.Nullable;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
#endif #endif
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class TintWithoutLevelOverrider implements BlockAndTintGetter public class TintWithoutLevelOverrider implements BlockAndTintGetter
{ {
/** private static final Logger LOGGER = DhLoggerBuilder.getLogger();
* This will only ever be null if there was an issue with {@link IClientLevelWrapper#getPlainsBiomeWrapper()}
* but {@link Nullable} is there just in case. #if MC_VER < MC_1_18_2
*/ public static final ConcurrentMap<String, Biome> BIOME_BY_RESOURCE_STRING = new ConcurrentHashMap<>();
@Nullable
#if MC_VER >= MC_1_18_2
public final Holder<Biome> biome;
#else #else
public final Biome biome; public static final ConcurrentMap<String, Holder<Biome>> BIOME_BY_RESOURCE_STRING = new ConcurrentHashMap<>();
#endif #endif
@NotNull
private final BiomeWrapper biomeWrapper;
//=============//
// constructor //
//=============//
public TintWithoutLevelOverrider(@NotNull BiomeWrapper biomeWrapper, IClientLevelWrapper clientLevelWrapper)
{ this.biomeWrapper = biomeWrapper; }
//=========//
// methods //
//=========//
@Override
public int getBlockTint(@NotNull BlockPos blockPos, @NotNull ColorResolver colorResolver)
{
String biomeString = this.biomeWrapper.getSerialString();
if (biomeString == null
|| biomeString.isEmpty()
|| biomeString.equals(BiomeWrapper.EMPTY_BIOME_STRING))
{
// default to "plains" for empty/invalid biomes
biomeString = "minecraft:plains";
}
return colorResolver.getColor(unwrap(getClientBiome(biomeString)), blockPos.getX(), blockPos.getZ());
}
private static Biome unwrap(#if MC_VER >= MC_1_18_2 Holder<Biome> #else Biome #endif biome)
{
#if MC_VER >= MC_1_18_2
return biome.value();
#else
return 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> * <p>Previously, this class might have immediately unwrapped the Holder like this:</p>
* <pre>{@code * <pre>{@code
* // Inside constructor (OLD WAY - PROBLEMATIC): * // Inside constructor (OLD WAY - PROBLEMATIC):
@@ -85,41 +130,52 @@ public class TintWithoutLevelOverrider implements BlockAndTintGetter
* whenever the biome information is needed, ensuring it always retrieves the most current {@code Biome} * whenever the biome information is needed, ensuring it always retrieves the most current {@code Biome}
* instance associated with the holder at that time.</p> * instance associated with the holder at that time.</p>
*/ */
public TintWithoutLevelOverrider(BiomeWrapper biomeWrapper, IClientLevelWrapper clientLevelWrapper) private static #if MC_VER < MC_1_18_2 Biome #else Holder<Biome> #endif getClientBiome(String biomeResourceString)
{ {
#if MC_VER >= MC_1_18_2 Holder<Biome> #else Biome #endif biome = biomeWrapper.biome; // cache the client biomes so we don't have to re-parse the resource location every time
if (biome == null) // We are looking at the empty biome wrapper return BIOME_BY_RESOURCE_STRING.compute(biomeResourceString,
{ (resourceString, existingBiome) ->
BiomeWrapper plainsBiomeWrapper = ((BiomeWrapper) clientLevelWrapper.getPlainsBiomeWrapper()); {
if (plainsBiomeWrapper != null) if (existingBiome != null)
{ {
biome = plainsBiomeWrapper.biome; return existingBiome;
} }
}
ClientLevel clientLevel = Minecraft.getInstance().level;
this.biome = biome; if (clientLevel == null)
} {
// shouldn't happen, but just in case
throw new IllegalStateException("Attempted to get client biome when no client level was loaded.");
}
@Override
public int getBlockTint(@NotNull BlockPos blockPos, @NotNull ColorResolver colorResolver) BiomeWrapper.BiomeDeserializeResult result;
{ try
if (this.biome == null) {
{ result = BiomeWrapper.deserializeBiome(resourceString, clientLevel.registryAccess());
// hopefully unneeded debug color }
return ColorUtil.CYAN; catch (Exception e)
} {
return colorResolver.getColor(unwrap(biome), blockPos.getX(), blockPos.getZ()); LOGGER.warn("Unable to deserialize client biome ["+resourceString+"], using fallback...");
}
try
private static Biome unwrap(#if MC_VER >= MC_1_18_2 Holder<Biome> #else Biome #endif biome) {
{ result = BiomeWrapper.deserializeBiome("minecraft:plains", clientLevel.registryAccess());
#if MC_VER >= MC_1_18_2 }
return biome.value(); catch (IOException ex)
#else {
return biome; // should never happen, if it does this log will explode, but just in case
#endif LOGGER.error("Unable to deserialize fallback client biome [minecraft:plains], returning NULL.");
return null;
}
}
if (result.success)
{
existingBiome = result.biome;
}
return existingBiome;
});
} }
@@ -42,6 +42,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
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;
@@ -115,12 +116,12 @@ 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? // Need specifically overworld since it's the only dimension that is stored in a server root folder
// this can have issues when the screenshot file is null/missing
#if MC_VER >= MC_1_17_1 #if MC_VER >= MC_1_21_3
return this.level.getServer().getWorldScreenshotFile().get().getParent().getFileName().toString(); return this.level.getServer().getLevel(Level.OVERWORLD).getChunkSource().getDataStorage().dataFolder.getParent().getFileName().toString();
#else // <= 1.16.5 #else // <= 1.21.3
return this.level.getServer().getWorldScreenshotFile().getParentFile().getName(); return this.level.getServer().getLevel(Level.OVERWORLD).getChunkSource().getDataStorage().dataFolder.getParentFile().getName();
#endif #endif
} }
@@ -179,20 +179,24 @@ public class ChunkLoader
//================== Read params for making the LevelChunk ================== //================== Read params for making the LevelChunk ==================
UpgradeData upgradeData; UpgradeData upgradeData = UpgradeData.EMPTY;
#if MC_VER < MC_1_17_1 // commented out 2025-06-04 as a test to see if the upgrade data
upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10) // is actually necessary for DH or if it can be ignored
? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA)) // (if it can't be ignored we'll need to handle null responses from tagGetCompoundTag())
: UpgradeData.EMPTY; //
#elif MC_VER < MC_1_21_5 //#if MC_VER < MC_1_17_1
upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10) //upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10)
? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level) // ? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA))
: UpgradeData.EMPTY; // : UpgradeData.EMPTY;
#else //#elif MC_VER < MC_1_21_5
upgradeData = tagLevel.contains(TAG_UPGRADE_DATA) //upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10)
? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level) // ? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level)
: UpgradeData.EMPTY; // : UpgradeData.EMPTY;
#endif //#else
//upgradeData = tagLevel.contains(TAG_UPGRADE_DATA)
// ? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level)
// : UpgradeData.EMPTY;
//#endif
boolean isLightOn = tagGetBoolean(tagLevel, "isLightOn"); boolean isLightOn = tagGetBoolean(tagLevel, "isLightOn");
@@ -242,7 +246,7 @@ public class ChunkLoader
// Set some states after object creation // Set some states after object creation
chunk.setLightCorrect(isLightOn); chunk.setLightCorrect(isLightOn);
readHeightmaps(chunk, chunkData); readHeightmaps(chunk, chunkData);
readPostPocessings(chunk, chunkData); //readPostPocessings(chunk, chunkData);
return chunk; return chunk;
} }
private static LevelChunkSection[] readSections(LevelAccessor level, ChunkPos chunkPos, CompoundTag chunkData) private static LevelChunkSection[] readSections(LevelAccessor level, ChunkPos chunkPos, CompoundTag chunkData)
@@ -285,48 +289,54 @@ public class ChunkLoader
for (int j = 0; j < tagSections.size(); ++j) for (int j = 0; j < tagSections.size(); ++j)
{ {
CompoundTag tagSection = tagGetCompoundTag(tagSections, j); CompoundTag tagSection = tagGetCompoundTag(tagSections, j);
int sectionYPos = tagGetByte(tagSection, "Y"); if (tagSection == null)
{
continue;
}
final 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))
{ {
LevelChunkSection levelChunkSection = new LevelChunkSection(sectionYPos << 4); LevelChunkSection levelChunkSection = new LevelChunkSection(sectionYPos << 4);
levelChunkSection.getStates().read(tagSection.getList("Palette", 10), levelChunkSection.getStates().read(tagSection.getList("Palette", 10),
tagSection.getLongArray("BlockStates")); tagSection.getLongArray("BlockStates"));
levelChunkSection.recalcBlockCounts(); levelChunkSection.recalcBlockCounts();
if (!levelChunkSection.isEmpty()) if (!levelChunkSection.isEmpty())
chunkSections[#if MC_VER < MC_1_17_1 sectionYPos #else level.getSectionIndexFromSectionY(sectionYPos) #endif ] chunkSections[#if MC_VER < MC_1_17_1 sectionYPos #else level.getSectionIndexFromSectionY(sectionYPos) #endif ]
= levelChunkSection; = levelChunkSection;
} }
#else #else
int sectionId = level.getSectionIndexFromSectionY(sectionYPos); int sectionId = level.getSectionIndexFromSectionY(sectionYPos);
if (sectionId >= 0 && sectionId < chunkSections.length) if (sectionId >= 0 && sectionId < chunkSections.length)
{ {
PalettedContainer<BlockState> blockStateContainer; PalettedContainer<BlockState> blockStateContainer;
#if MC_VER < MC_1_18_2 #if MC_VER < MC_1_18_2
PalettedContainer<Biome> biomeContainer; PalettedContainer<Biome> biomeContainer;
#else #else
PalettedContainer<Holder<Biome>> biomeContainer; PalettedContainer<Holder<Biome>> biomeContainer;
#endif #endif
boolean containsBlockStates; boolean containsBlockStates;
#if MC_VER < MC_1_21_5 #if MC_VER < MC_1_21_5
containsBlockStates = tagSection.contains("block_states", 10); containsBlockStates = tagSection.contains("block_states", 10);
#else #else
containsBlockStates = tagSection.contains("block_states"); containsBlockStates = tagSection.contains("block_states");
#endif #endif
if (containsBlockStates) if (containsBlockStates)
{ {
#if MC_VER < MC_1_20_6 #if MC_VER < MC_1_20_6
blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states")) blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states"))
.promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string)) .promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
.getOrThrow(false, (message) -> logParsingWarningOnce(message)); .getOrThrow(false, (message) -> logParsingWarningOnce(message));
#else #else
blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states")) blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states"))
.promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string)) .promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
.getOrThrow((message) -> logErrorAndReturnException(message)); .getOrThrow((message) -> logErrorAndReturnException(message));
#endif #endif
} }
else else
{ {
@@ -334,31 +344,31 @@ public class ChunkLoader
} }
#if MC_VER < MC_1_18_2 #if MC_VER < MC_1_18_2
biomeContainer = tagSection.contains("biomes", 10) biomeContainer = tagSection.contains("biomes", 10)
? biomeCodec.parse(NbtOps.INSTANCE, tagSection.getCompound("biomes")).promotePartial(string -> logErrors(chunkPos, sectionYPos, string)).getOrThrow(false, (message) -> logWarningOnce(message)) ? biomeCodec.parse(NbtOps.INSTANCE, tagSection.getCompound("biomes")).promotePartial(string -> logErrors(chunkPos, sectionYPos, string)).getOrThrow(false, (message) -> logWarningOnce(message))
: new PalettedContainer<Biome>(biomes, biomes.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); : new PalettedContainer<Biome>(biomes, biomes.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
#else #else
boolean containsBiomes; boolean containsBiomes;
#if MC_VER < MC_1_21_5 #if MC_VER < MC_1_21_5
containsBiomes = tagSection.contains("biomes", 10); containsBiomes = tagSection.contains("biomes", 10);
#else #else
containsBiomes = tagSection.contains("biomes"); containsBiomes = tagSection.contains("biomes");
#endif #endif
if (containsBiomes) if (containsBiomes)
{ {
#if MC_VER < MC_1_20_6 #if MC_VER < MC_1_20_6
biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes")) biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes"))
.promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string)) .promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string))
.getOrThrow(false, (message) -> logParsingWarningOnce(message)); .getOrThrow(false, (message) -> logParsingWarningOnce(message));
#else #else
biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes")) biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes"))
.promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string)) .promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string))
.getOrThrow((message) -> logErrorAndReturnException(message)); .getOrThrow((message) -> logErrorAndReturnException(message));
#endif #endif
} }
else else
{ {
@@ -371,15 +381,15 @@ public class ChunkLoader
PalettedContainer.Strategy.SECTION_BIOMES); PalettedContainer.Strategy.SECTION_BIOMES);
} }
#endif #endif
#if MC_VER < MC_1_20_1 #if MC_VER < MC_1_20_1
chunkSections[sectionId] = new LevelChunkSection(sectionYPos, blockStateContainer, biomeContainer); chunkSections[sectionId] = new LevelChunkSection(sectionYPos, blockStateContainer, biomeContainer);
#else #else
chunkSections[sectionId] = new LevelChunkSection(blockStateContainer, biomeContainer); chunkSections[sectionId] = new LevelChunkSection(blockStateContainer, biomeContainer);
#endif #endif
} }
#endif #endif
} }
} }
@@ -391,10 +401,14 @@ public class ChunkLoader
#else ChunkType #endif #else ChunkType #endif
readChunkType(CompoundTag tagLevel) readChunkType(CompoundTag tagLevel)
{ {
ChunkStatus chunkStatus = ChunkStatus.byName(tagGetString(tagLevel,"Status")); String statusString = tagGetString(tagLevel,"Status");
if (chunkStatus != null) if (statusString != null)
{ {
return chunkStatus.getChunkType(); ChunkStatus chunkStatus = ChunkStatus.byName(statusString);
if (chunkStatus != null)
{
return chunkStatus.getChunkType();
}
} }
#if MC_VER <= MC_1_20_4 #if MC_VER <= MC_1_20_4
@@ -406,43 +420,52 @@ public class ChunkLoader
private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData) private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData)
{ {
CompoundTag tagHeightmaps = tagGetCompoundTag(chunkData, "Heightmaps"); CompoundTag tagHeightmaps = tagGetCompoundTag(chunkData, "Heightmaps");
for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter()) if (tagHeightmaps != null)
{ {
String heightmap = type.getSerializationKey(); for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter())
#if MC_VER < MC_1_21_5
if (tagHeightmaps.contains(heightmap, 12))
{ {
chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmap)); String heightmap = type.getSerializationKey();
} #if MC_VER < MC_1_21_5
#else if (tagHeightmaps.contains(heightmap, 12))
if (tagHeightmaps.contains(heightmap))
{
Optional<long[]> optionalHeightmap = tagHeightmaps.getLongArray(heightmap);
if (optionalHeightmap.isPresent())
{ {
chunk.setHeightmap(type, optionalHeightmap.get()); chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmap));
} }
}
#endif
}
Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter());
}
private static void readPostPocessings(LevelChunk chunk, CompoundTag chunkData)
{
ListTag tagPostProcessings = tagGetListTag(chunkData,"PostProcessing", 9);
for (int i = 0; i < tagPostProcessings.size(); ++i)
{
ListTag listTag3 = tagGetListTag(tagPostProcessings, i);
for (int j = 0; j < listTag3.size(); ++j)
{
#if MC_VER < MC_1_21_3
chunk.addPackedPostProcess(listTag3.getShort(j), i);
#else #else
chunk.addPackedPostProcess(ShortList.of(tagGetShort(listTag3, j)), i); if (tagHeightmaps.contains(heightmap))
{
Optional<long[]> optionalHeightmap = tagHeightmaps.getLongArray(heightmap);
if (optionalHeightmap.isPresent())
{
chunk.setHeightmap(type, optionalHeightmap.get());
}
}
#endif #endif
} }
Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter());
} }
} }
// commented out as a test as of 2025-06-04 to see if this is actually necessary for DH
// DH probably doesn't need any chunk post-processing data
//private static void readPostPocessings(LevelChunk chunk, CompoundTag chunkData)
//{
// ListTag tagPostProcessings = tagGetListTag(chunkData,"PostProcessing", 9);
// if (tagPostProcessings != null)
// {
// for (int i = 0; i < tagPostProcessings.size(); ++i)
// {
// ListTag listTag3 = tagGetListTag(tagPostProcessings, i);
// for (int j = 0; j < listTag3.size(); ++j)
// {
// #if MC_VER < MC_1_21_3
// chunk.addPackedPostProcess(listTag3.getShort(j), i);
// #else
// chunk.addPackedPostProcess(ShortList.of(tagGetShort(listTag3, j)), i);
// #endif
// }
// }
// }
//}
#if MC_VER >= MC_1_18_2 #if MC_VER >= MC_1_18_2
private static BlendingData readBlendingData(CompoundTag chunkData) private static BlendingData readBlendingData(CompoundTag chunkData)
{ {
@@ -728,6 +751,7 @@ public class ChunkLoader
/** defaults to null if the tag isn't present */ /** defaults to null if the tag isn't present */
@Nullable
private static String tagGetString(CompoundTag tag, String key) private static String tagGetString(CompoundTag tag, String key)
{ {
#if MC_VER < MC_1_21_5 #if MC_VER < MC_1_21_5
@@ -738,6 +762,7 @@ public class ChunkLoader
} }
/** defaults to null if the tag isn't present */ /** defaults to null if the tag isn't present */
@Nullable
private static byte[] tagGetByteArray(CompoundTag tag, String key) private static byte[] tagGetByteArray(CompoundTag tag, String key)
{ {
#if MC_VER < MC_1_21_5 #if MC_VER < MC_1_21_5
@@ -33,6 +33,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.ISodiumAccessor; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.ISodiumAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.Mat4f;
@@ -82,6 +83,7 @@ public class FabricClientProxy implements AbstractModInitializer.IEventProxy
{ {
private final ClientApi clientApi = ClientApi.INSTANCE; private final ClientApi clientApi = ClientApi.INSTANCE;
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final AbstractPluginPacketSender PACKET_SENDER = (AbstractPluginPacketSender) SingletonInjector.INSTANCE.get(IPluginPacketSender.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
// TODO we shouldn't be filtering keys on the Forge/Fabric side, only in ClientApi // TODO we shouldn't be filtering keys on the Forge/Fabric side, only in ClientApi
@@ -316,9 +318,7 @@ public class FabricClientProxy implements AbstractModInitializer.IEventProxy
#else #else
ClientPlayNetworking.registerGlobalReceiver(AbstractPluginPacketSender.WRAPPER_PACKET_RESOURCE, (client, handler, buffer, packetSender) -> ClientPlayNetworking.registerGlobalReceiver(AbstractPluginPacketSender.WRAPPER_PACKET_RESOURCE, (client, handler, buffer, packetSender) ->
{ {
// Forge packet ID AbstractNetworkMessage message = PACKET_SENDER.decodeMessage(buffer);
buffer.readByte();
AbstractNetworkMessage message = AbstractPluginPacketSender.decodeMessage(buffer);
if (message != null) if (message != null)
{ {
ClientApi.INSTANCE.pluginMessageReceived(message); ClientApi.INSTANCE.pluginMessageReceived(message);
@@ -22,9 +22,7 @@ public class FabricPluginPacketSender extends AbstractPluginPacketSender
ClientPlayNetworking.send(new CommonPacketPayload(message)); ClientPlayNetworking.send(new CommonPacketPayload(message));
#else // < 1.20.6 #else // < 1.20.6
FriendlyByteBuf buffer = PacketByteBufs.create(); FriendlyByteBuf buffer = PacketByteBufs.create();
// Forge packet ID this.encodeMessage(buffer, message);
buffer.writeByte(0);
AbstractPluginPacketSender.encodeMessage(buffer, message);
ClientPlayNetworking.send(WRAPPER_PACKET_RESOURCE, buffer); ClientPlayNetworking.send(WRAPPER_PACKET_RESOURCE, buffer);
#endif #endif
} }
@@ -36,9 +34,7 @@ public class FabricPluginPacketSender extends AbstractPluginPacketSender
ServerPlayNetworking.send(serverPlayer, new CommonPacketPayload(message)); ServerPlayNetworking.send(serverPlayer, new CommonPacketPayload(message));
#else // < 1.20.6 #else // < 1.20.6
FriendlyByteBuf buffer = PacketByteBufs.create(); FriendlyByteBuf buffer = PacketByteBufs.create();
// Forge packet ID this.encodeMessage(buffer, message);
buffer.writeByte(0);
AbstractPluginPacketSender.encodeMessage(buffer, message);
ServerPlayNetworking.send(serverPlayer, WRAPPER_PACKET_RESOURCE, buffer); ServerPlayNetworking.send(serverPlayer, WRAPPER_PACKET_RESOURCE, buffer);
#endif #endif
} }
@@ -9,7 +9,10 @@ import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper; import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment;
import com.seibel.distanthorizons.core.api.internal.ServerApi; import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.common.AbstractPluginPacketSender;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.fabric.testing.TestWorldGenBindingEvent; import com.seibel.distanthorizons.fabric.testing.TestWorldGenBindingEvent;
@@ -32,7 +35,6 @@ import com.seibel.distanthorizons.common.CommonPacketPayload;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
#else #else
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.common.AbstractPluginPacketSender;
#endif #endif
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -48,6 +50,8 @@ import java.util.function.Supplier;
public class FabricServerProxy implements AbstractModInitializer.IEventProxy public class FabricServerProxy implements AbstractModInitializer.IEventProxy
{ {
private static final ServerApi SERVER_API = ServerApi.INSTANCE; private static final ServerApi SERVER_API = ServerApi.INSTANCE;
@SuppressWarnings("unused")
private static final AbstractPluginPacketSender PACKET_SENDER = (AbstractPluginPacketSender) SingletonInjector.INSTANCE.get(IPluginPacketSender.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private final boolean isDedicatedServer; private final boolean isDedicatedServer;
@@ -192,9 +196,7 @@ public class FabricServerProxy implements AbstractModInitializer.IEventProxy
#else #else
ServerPlayNetworking.registerGlobalReceiver(AbstractPluginPacketSender.WRAPPER_PACKET_RESOURCE, (server, serverPlayer, handler, buffer, packetSender) -> ServerPlayNetworking.registerGlobalReceiver(AbstractPluginPacketSender.WRAPPER_PACKET_RESOURCE, (server, serverPlayer, handler, buffer, packetSender) ->
{ {
// Forge packet ID AbstractNetworkMessage message = PACKET_SENDER.decodeMessage(buffer);
buffer.readByte();
AbstractNetworkMessage message = AbstractPluginPacketSender.decodeMessage(buffer);
if (message != null) if (message != null)
{ {
ServerApi.INSTANCE.pluginMessageReceived(ServerPlayerWrapper.getWrapper(serverPlayer), message); ServerApi.INSTANCE.pluginMessageReceived(ServerPlayerWrapper.getWrapper(serverPlayer), message);
@@ -33,6 +33,7 @@ import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
@@ -79,6 +80,7 @@ import java.util.concurrent.ThreadPoolExecutor;
public class ForgeClientProxy implements AbstractModInitializer.IEventProxy public class ForgeClientProxy implements AbstractModInitializer.IEventProxy
{ {
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final ForgePluginPacketSender PACKET_SENDER = (ForgePluginPacketSender) SingletonInjector.INSTANCE.get(IPluginPacketSender.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -96,7 +98,7 @@ public class ForgeClientProxy implements AbstractModInitializer.IEventProxy
MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.register(this);
// handles singleplayer, LAN, and connecting to a server // handles singleplayer, LAN, and connecting to a server
ForgePluginPacketSender.setPacketHandler((IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message) -> PACKET_SENDER.setPacketHandler((IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message) ->
{ {
ClientApi.INSTANCE.pluginMessageReceived(message); ClientApi.INSTANCE.pluginMessageReceived(message);
ServerApi.INSTANCE.pluginMessageReceived(player, message); ServerApi.INSTANCE.pluginMessageReceived(player, message);
@@ -45,16 +45,18 @@ public class ForgePluginPacketSender extends AbstractPluginPacketSender
); );
#endif #endif
public static void setPacketHandler(Consumer<AbstractNetworkMessage> consumer) public ForgePluginPacketSender() { super(true); }
public void setPacketHandler(Consumer<AbstractNetworkMessage> consumer)
{ {
setPacketHandler((player, message) -> consumer.accept(message)); this.setPacketHandler((player, message) -> consumer.accept(message));
} }
public static void setPacketHandler(BiConsumer<IServerPlayerWrapper, AbstractNetworkMessage> consumer) public void setPacketHandler(BiConsumer<IServerPlayerWrapper, AbstractNetworkMessage> consumer)
{ {
#if MC_VER >= MC_1_20_2 #if MC_VER >= MC_1_20_2
PLUGIN_CHANNEL.messageBuilder(MessageWrapper.class, 0) PLUGIN_CHANNEL.messageBuilder(MessageWrapper.class, 0)
.encoder((wrapper, out) -> AbstractPluginPacketSender.encodeMessage(out, wrapper.message)) .encoder((wrapper, out) -> this.encodeMessage(out, wrapper.message))
.decoder(in -> new MessageWrapper(AbstractPluginPacketSender.decodeMessage(in))) .decoder(in -> new MessageWrapper(this.decodeMessage(in)))
.consumerNetworkThread((wrapper, context) -> .consumerNetworkThread((wrapper, context) ->
{ {
if (wrapper.message != null) if (wrapper.message != null)
@@ -73,8 +75,8 @@ public class ForgePluginPacketSender extends AbstractPluginPacketSender
.add(); .add();
#else // < 1.20.2 #else // < 1.20.2
PLUGIN_CHANNEL.registerMessage(0, MessageWrapper.class, PLUGIN_CHANNEL.registerMessage(0, MessageWrapper.class,
(wrapper, out) -> AbstractPluginPacketSender.encodeMessage(out, wrapper.message), (wrapper, out) -> this.encodeMessage(out, wrapper.message),
in -> new MessageWrapper(AbstractPluginPacketSender.decodeMessage(in)), in -> new MessageWrapper(this.decodeMessage(in)),
(wrapper, context) -> (wrapper, context) ->
{ {
if (wrapper.message != null) if (wrapper.message != null)
@@ -7,7 +7,9 @@ import com.seibel.distanthorizons.common.wrappers.misc.ServerPlayerWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper; import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment;
import com.seibel.distanthorizons.core.api.internal.ServerApi; import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
@@ -51,6 +53,8 @@ import java.util.function.Supplier;
public class ForgeServerProxy implements AbstractModInitializer.IEventProxy public class ForgeServerProxy implements AbstractModInitializer.IEventProxy
{ {
private static final ForgePluginPacketSender PACKET_SENDER = (ForgePluginPacketSender) SingletonInjector.INSTANCE.get(IPluginPacketSender.class);
#if MC_VER < MC_1_19_2 #if MC_VER < MC_1_19_2
private static LevelAccessor GetEventLevel(WorldEvent e) { return e.getWorld(); } private static LevelAccessor GetEventLevel(WorldEvent e) { return e.getWorld(); }
#else #else
@@ -68,7 +72,7 @@ public class ForgeServerProxy implements AbstractModInitializer.IEventProxy
MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.register(this);
if (this.isDedicated) if (this.isDedicated)
{ {
ForgePluginPacketSender.setPacketHandler(ServerApi.INSTANCE::pluginMessageReceived); PACKET_SENDER.setPacketHandler(ServerApi.INSTANCE::pluginMessageReceived);
} }
} }
+1
View File
@@ -23,6 +23,7 @@ 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
fastutil_version=8.2.1 fastutil_version=8.2.1
+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 be mapped to `_1` 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
+1
View File
@@ -24,6 +24,7 @@ fabric_api_version=0.42.0+1.16
immersive_portals_version= immersive_portals_version=
canvas_version= canvas_version=
# iris - needs 1.7.4+ to support the DH API
fabric_incompatibility_list={ "iris": "*" } fabric_incompatibility_list={ "iris": "*" }
fabric_recommend_list={} fabric_recommend_list={}
+1
View File
@@ -24,6 +24,7 @@ fabric_api_version=0.46.1+1.17
immersive_portals_version= immersive_portals_version=
canvas_version= canvas_version=
# iris - needs 1.7.4+ to support the DH API
fabric_incompatibility_list={ "iris": "*" } fabric_incompatibility_list={ "iris": "*" }
fabric_recommend_list={} fabric_recommend_list={}
+1
View File
@@ -25,6 +25,7 @@ fabric_api_version=0.76.0+1.18.2
immersive_portals_version=v1.4.11-1.18 immersive_portals_version=v1.4.11-1.18
canvas_version=mc118:1.0.2616 canvas_version=mc118:1.0.2616
# iris - needs 1.7.4+ to support the DH API
fabric_incompatibility_list={ "iris": "*" } fabric_incompatibility_list={ "iris": "*" }
fabric_recommend_list={} fabric_recommend_list={}
+1
View File
@@ -24,6 +24,7 @@ fabric_api_version=0.76.1+1.19.2
immersive_portals_version= immersive_portals_version=
canvas_version=mc119-1.0.2480 canvas_version=mc119-1.0.2480
# iris - needs 1.7.4+ to support the DH API
fabric_incompatibility_list={ "iris": "*" } fabric_incompatibility_list={ "iris": "*" }
fabric_recommend_list={} fabric_recommend_list={}
+2 -1
View File
@@ -22,7 +22,8 @@ fabric_api_version=0.87.1+1.19.4
bclib_version=2.3.3 bclib_version=2.3.3
immersive_portals_version= immersive_portals_version=
canvas_version= canvas_version=
# iris - needs 1.7.4+ to support the DH API
fabric_incompatibility_list={ "iris": "*" } fabric_incompatibility_list={ "iris": "*" }
fabric_recommend_list={} fabric_recommend_list={}
+2 -1
View File
@@ -22,7 +22,8 @@ fabric_api_version=0.90.4+1.20.1
bclib_version=3.0.13 bclib_version=3.0.13
immersive_portals_version= immersive_portals_version=
canvas_version= canvas_version=
# iris - needs 1.7.4+ to support the DH API
fabric_incompatibility_list={ "iris": "<=1.7.4" } fabric_incompatibility_list={ "iris": "<=1.7.4" }
fabric_recommend_list={} fabric_recommend_list={}
+2 -1
View File
@@ -22,7 +22,8 @@ fabric_api_version=0.90.4+1.20.2
bclib_version=3.0.13 bclib_version=3.0.13
immersive_portals_version= immersive_portals_version=
canvas_version= canvas_version=
# iris - needs 1.7.4+ to support the DH API
fabric_incompatibility_list={ "iris": "<=1.7.4" } fabric_incompatibility_list={ "iris": "<=1.7.4" }
fabric_recommend_list={} fabric_recommend_list={}
+2 -1
View File
@@ -23,7 +23,8 @@ fabric_api_version=0.91.2+1.20.4
bclib_version= bclib_version=
immersive_portals_version= immersive_portals_version=
canvas_version= canvas_version=
# iris - needs 1.7.4+ to support the DH API
fabric_incompatibility_list={ "iris": "<=1.7.4" } fabric_incompatibility_list={ "iris": "<=1.7.4" }
fabric_recommend_list={} fabric_recommend_list={}
+2 -1
View File
@@ -23,7 +23,8 @@ fabric_api_version=0.97.8+1.20.6
bclib_version= bclib_version=
immersive_portals_version= immersive_portals_version=
canvas_version= canvas_version=
# iris - needs 1.7.4+ to support the DH API
fabric_incompatibility_list={ "iris": "<=1.7.4" } fabric_incompatibility_list={ "iris": "<=1.7.4" }
fabric_recommend_list={} fabric_recommend_list={}
+2 -1
View File
@@ -23,7 +23,8 @@ fabric_api_version=0.115.0+1.21.1
bclib_version= bclib_version=
immersive_portals_version= immersive_portals_version=
canvas_version= canvas_version=
# iris - needs 1.7.4+ to support the DH API
fabric_incompatibility_list={ "iris": "<=1.7.4" } fabric_incompatibility_list={ "iris": "<=1.7.4" }
fabric_recommend_list={} fabric_recommend_list={}
+1 -1
View File
@@ -23,7 +23,7 @@ fabric_api_version=0.110.5+1.21.4
bclib_version= bclib_version=
immersive_portals_version= immersive_portals_version=
canvas_version= canvas_version=
fabric_incompatibility_list={ } fabric_incompatibility_list={ }
fabric_recommend_list={} fabric_recommend_list={}
+1 -1
View File
@@ -23,7 +23,7 @@ fabric_api_version=0.119.5+1.21.5
immersive_portals_version= immersive_portals_version=
canvas_version= canvas_version=
# some versions of 1.8.11 nightly builds may not work, but the ones after 2025-03-30 should # Iris - 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_incompatibility_list={ "iris": "<=1.8.10" }
fabric_recommend_list={} fabric_recommend_list={}