Compare commits

..

8 Commits

Author SHA1 Message Date
Ran 0c8b3f3cda Merge remote-tracking branch 'origin/java_omega' into java_omega
# Conflicts:
#	build.gradle
#	coreSubProjects
#	fabric/build.gradle
#	forge/build.gradle
2024-07-26 00:21:34 +10:00
Ran a9a6a19307 Attempt to migrate most of the codebase to newer versions of Java 2024-07-26 00:17:15 +10:00
Cutiepie 208fc69eb7 Rebase 2024-07-25 23:00:43 +10:00
Cutiepie eb1a062934 Rebase 2024-07-25 22:59:16 +10:00
Cutiepie 55d5bca76d Downgrade to the minimum Java version required by the selected Minecraft version 2024-05-22 22:17:59 +10:00
Cutiepie f81026c707 Bump JVM Downgrader 2024-05-22 21:51:46 +10:00
Cutiepie e86af817e0 Java Ω & cleanup core's code 2024-05-22 21:06:36 +10:00
Cutiepie e0fd2b2f7a Java Ω & cleanup core's code 2024-05-22 21:03:38 +10:00
284 changed files with 6310 additions and 14173 deletions
+2 -1
View File
@@ -5,13 +5,14 @@ root = true
[*]
charset = utf-8
end_of_line = crlf
indent_size = 4
indent_style = space
insert_final_newline = false
max_line_length = 1000
tab_width = 4
trim_trailing_whitespace = false
ij_continuation_indent_size = 4
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = true
-4
View File
@@ -26,10 +26,6 @@ Merged/
# Folder created by the buildAll scripts
buildAllJars/
relocate_natives/.venv/
relocate_natives/__pycache__/
relocate_natives/apple-codesign/
# file from notepad++
*.bak
+26 -25
View File
@@ -18,45 +18,46 @@ variables:
.build_java:
#image: eclipse-temurin:17
cache:
key: "gradleCache_$CI_JOB_NAME_SLUG"
key: "gradleCache"
policy: pull-push
paths:
- .gradle
- cache/
allow_failure: true
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
build:
stage: build
parallel:
matrix:
- MC_VER: [
"1.21.11", "1.21.10", "1.21.9", "1.21.8", "1.21.6", "1.21.5", "1.21.4", "1.21.3", "1.21.1",
"1.20.6", "1.20.4", "1.20.2", "1.20.1",
"1.19.4", "1.19.2",
"1.18.2",
"1.17.1",
"1.16.5"
]
- 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"]
script:
# 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 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/;
- cp ./fabric/build/libs/* ./forge/build/libs/* ./neoforge/build/libs/* ./build/merged/* . || true
artifacts:
name: "NightlyBuild_${MC_VER}-${CI_COMMIT_SHORT_SHA}-${CI_COMMIT_TIMESTAMP}"
paths:
- ./*.jar
- Merged/*.jar
- quilt/build/libs/*.jar
- fabric/build/libs/*.jar
- forge/build/libs/*.jar
- neoforge/build/libs/*.jar
exclude:
- ./*-all.jar
- ./*-dev.jar
- ./*-sources.jar
# TODO: There is a lot of duplicate stuff here, try to maybe make it smaller
- fabric/build/libs/*-all.jar
- fabric/build/libs/*-dev.jar
- fabric/build/libs/*-sources.jar
- quilt/build/libs/*-all.jar
- quilt/build/libs/*-dev.jar
- quilt/build/libs/*-sources.jar
- forge/build/libs/*-all.jar
- forge/build/libs/*-dev.jar
- forge/build/libs/*-sources.jar
- neoforge/build/libs/*-all.jar
- neoforge/build/libs/*-dev.jar
- neoforge/build/libs/*-sources.jar
expire_in: 14 days
when: always
extends: .build_java
@@ -71,15 +72,15 @@ api:
# this also runs unit tests
- ./gradlew api:build --gradle-user-home cache/;
- ./gradlew api:addSourcesToCompiledJar --gradle-user-home cache/;
- cp ./coreSubProjects/api/build/libs/merged/* .
artifacts:
name: "NightlyBuild_Api-${CI_COMMIT_SHORT_SHA}-${CI_COMMIT_TIMESTAMP}"
name: "Api_NightlyBuild-${CI_COMMIT_SHORT_SHA}-${CI_COMMIT_TIMESTAMP}"
paths:
- ./*.jar
- coreSubProjects/api/build/libs/merged/*.jar
# can be uncommented if we don't want a jar with the source code
# - coreSubProjects/api/build/libs/*.jar
exclude:
- ./*-all.jar
- ./*-dev.jar
- ./*-sources.jar
- coreSubProjects/api/build/libs/merged/*-all.jar
- coreSubProjects/api/build/libs/merged/*-sources.jar
expire_in: 1 day
when: always
extends: .build_java
+3 -3
View File
@@ -6,13 +6,13 @@ Or click the checkbox once the issue has been created.
-->
1. [ ] Check the FAQ to see if your issue has already been reported and has a solution:
[Problems-and-solutions](https://gitlab.com/distant-horizons-team/distant-horizons/-/wikis/1-user-guide/1-frequently-asked-questions/2-problems-and-solutions/Problems-and-Solutions)
[Problems-and-solutions](https://gitlab.com/jeseibel/distant-horizons/-/wikis/2-frequently-asked-questions/2-problems-and-solutions/Problems-and-Solutions)
2. [ ] Make sure you are not using any mods on the incompatible list:
[Mod support FAQ](https://gitlab.com/distant-horizons-team/distant-horizons/-/wikis/1-user-guide/1-frequently-asked-questions/4-mod-support/Mod-Support)
[Mod support FAQ](https://gitlab.com/jeseibel/distant-horizons/-/wikis/2-frequently-asked-questions/4-mod-support/Mod-Support)
3. [ ] Check the existing issues to verify that your bug hasn't already been submitted:
[Issues](https://gitlab.com/distant-horizons-team/distant-horizons/-/issues)
[Issues](https://gitlab.com/jeseibel/distant-horizons/-/issues/)
4. [ ] Upload Minecraft's crash report and/or log. \
Minecraft crash reports are located in: `.minecraft/crash-reports` \
+2 -36
View File
@@ -1,37 +1,3 @@
## Check off each item in this list before submitting:
<!--
To mark a section as complete either put an "x" in between the square brackets, example: "[x]"
Or click the checkbox once the issue has been created.
-->
1. [ ] Check the FAQ to see if your issue has already been reported and has a solution:
[Problems-and-solutions](https://gitlab.com/distant-horizons-team/distant-horizons/-/wikis/1-user-guide/1-frequently-asked-questions/2-problems-and-solutions/Problems-and-Solutions)
2. [ ] Make sure you are not using any mods on the incompatible list:
[Mod support FAQ](https://gitlab.com/distant-horizons-team/distant-horizons/-/wikis/1-user-guide/1-frequently-asked-questions/4-mod-support/Mod-Support)
3. [ ] Check the existing issues to verify that your bug hasn't already been submitted:
[Issues](https://gitlab.com/distant-horizons-team/distant-horizons/-/issues)
4. [ ] Upload Minecraft's crash report and/or log. \
Minecraft crash reports are located in: `.minecraft/crash-reports` \
Minecraft logs are located in: `.minecraft/logs`
5. [ ] Upload your Distant Horizons Config. \
The config is found in: `.minecraft/configs/DistantHorizons.toml`
6. [ ] Fill out the information below:
* **minecraft version**:
* **Distant Horizons version**:
* **Mod loader**:
* **Installed mods (list the smallest number of mods that you can use to re-create the bug)**:
* **Describe the bug**:
* **Steps to reproduce the bug**:
Before creating an issue, please select the appropriate template from the dropdown above.
The template will walk you through submitting a bug, feature, or improvement request.
+1 -1
View File
@@ -1,4 +1,4 @@
- [ ] Check the existing [feature requests](https://gitlab.com/distant-horizons-team/distant-horizons/-/issues?sort=updated_desc&state=opened&label_name%5B%5D=Feature) to verify that your feature hasn't already been suggested.
- [ ] Check the existing [feature requests](https://gitlab.com/jeseibel/distant-horizons/-/issues/?sort=updated_desc&state=opened&label_name%5B%5D=Feature) to verify that your feature hasn't already been suggested.
1. **Describe the feature**:
@@ -1,3 +1,3 @@
1. Check the existing [improvement requests](https://gitlab.com/distant-horizons-team/distant-horizons/-/issues?sort=updated_desc&state=all&label_name%5B%5D=Improvement) to verify that your improvement hasn't already been suggested.
1. Check the existing [improvement requests](https://gitlab.com/jeseibel/distant-horizons/-/issues/?sort=updated_desc&state=all&label_name%5B%5D=Improvement) to verify that your improvement hasn't already been suggested.
2. **Describe the improvement**:
-7
View File
@@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Fabric Client &amp; Server" type="CompoundRunConfigurationType">
<toRun name="Fabric Client (:fabric)" type="Application" />
<toRun name="Fabric Server (:fabric)" type="Application" />
<method v="2" />
</configuration>
</component>
-7
View File
@@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Forge Client &amp; Server" type="CompoundRunConfigurationType">
<toRun name="Forge Client (gradle)" type="GradleRunConfiguration" />
<toRun name="Forge Server (gradle)" type="GradleRunConfiguration" />
<method v="2" />
</configuration>
</component>
-24
View File
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Forge Client (gradle)" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="forge:runClient" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
-24
View File
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Forge Server (gradle)" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="forge:runServer" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
-7
View File
@@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Neoforge Client &amp; Server" type="CompoundRunConfigurationType">
<toRun name="Neoforge Client (gradle)" type="GradleRunConfiguration" />
<toRun name="Neoforge Server (gradle)" type="GradleRunConfiguration" />
<method v="2" />
</configuration>
</component>
-24
View File
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Neoforge Client (gradle)" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="neoforge:runClient" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
-24
View File
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Neoforge Server (gradle)" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="neoforge:runServer" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
-24
View File
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="distant-horizons [build]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="build" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
-24
View File
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="distant-horizons [clean]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="clean" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="distant-horizons [core:build]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="core:build" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="distant-horizons [fabric:runClient]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="fabric:runClient" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="distant-horizons [forge:runClient]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="forge:runClient" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
-24
View File
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="distant-horizons [mergeJars]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="mergeJars" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="distant-horizons [neoforge:runClient]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="neoforge:runClient" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
-24
View File
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="distant-horizons [test]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="test" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
-1
View File
@@ -1 +0,0 @@
Distant Horizons logos © 2024 by Pankakes are licensed under CC BY-SA 4.0
+3 -3
View File
@@ -1,4 +1,4 @@
# <img src="https://gitlab.com/distant-horizons-team/distant-horizons-core/-/raw/main/_Misc%20Files/logo%20files/new/SVG/Distant-Horizons.svg" height="128px">
# <img src="https://gitlab.com/jeseibel/distant-horizons-core/-/raw/main/_Misc%20Files/logo%20files/LOD%20logo%20flat%20-%20with%20boarder.png" width="32"> Distant Horizons
_See farther without turning your game into a slide show._
<br>
@@ -131,14 +131,14 @@ Prerequisites:
From the File Explorer:
1. Download and extract the project zip
2. Download the core from https://gitlab.com/distant-horizons-team/distant-horizons-core and extract into a folder called `coreSubProjects`
2. Download the core from https://gitlab.com/jeseibel/distant-horizons-core and extract into a folder called `coreSubProjects`
3. Open a terminal emulator in the project folder (On Windows you can type `cmd` in the title bar)
4. Run the commands: `./gradlew assemble` (You may need to use a `.\` on Windows)
5. Merge the jars with `./gradlew mergeJars`
6. The compiled jar file will be in the folder `Merged`
From the command line:
1. `git clone --recurse-submodules https://gitlab.com/distant-horizons-team/distant-horizons.git`
1. `git clone --recurse-submodules https://gitlab.com/jeseibel/distant-horizons.git`
2. `cd distant-horizons`
3. `./gradlew assemble`
4. `./gradlew mergeJars`
+69 -184
View File
@@ -1,14 +1,3 @@
import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
import org.apache.tools.zip.ZipEntry
import javax.annotation.Nonnull
import org.apache.tools.zip.ZipOutputStream
import java.util.function.Function
import java.util.function.Predicate
plugins {
id "java"
@@ -16,13 +5,15 @@ plugins {
id "com.github.johnrengelman.shadow" version '8.1.1' apply false
// Plugin to create merged jars
id "io.github.pacifistmc.forgix" version "1.3.4"
id "io.github.pacifistmc.forgix" version "1.2.9"
// Manifold preprocessor
id "systems.manifold.manifold-gradle-plugin" version "0.0.2-alpha"
// Architectury is used here only as a replacement for forge's own loom
id "dev.architectury.loom" version "1.13-SNAPSHOT" apply false
id "dev.architectury.loom" version "1.6-SNAPSHOT" apply false
id 'xyz.wagyourtail.jvmdowngrader' version '0.9.1' apply true
}
@@ -46,10 +37,8 @@ def writeBuildGradlePredefine(List<String> mcVers, int mcIndex)
sb.append("MC_" + verStr + "=" + i.toString() + "\n");
if (mcIndex == i)
{
sb.append("MC_VER=" + i.toString() + "\n");
}
}
// Check if this is a development build
@@ -72,71 +61,38 @@ project.gradle.ext.getProperties().each { prop ->
// Sets up manifold stuff
writeBuildGradlePredefine(rootProject.mcVers, rootProject.mcIndex)
// 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")
// Forgix settings (used for merging jars)
forgix {
group = "com.seibel.distanthorizons"
mergedJarName = "DistantHorizons-${rootProject.versionStr}.jar"
class NativeTransformer implements Transformer {
private Predicate<String> fileMatcher
private Function<String, String> filePathMapper
private final HashMap<String, String> replacements = new HashMap()
private final HashMap<String, byte[]> rewrittenFiles = new HashMap()
private var nativeRelocator
public File rootDir
void matchFiles(Predicate<String> matcher) {
fileMatcher = matcher
if (findProject(":forge"))
forge {
jarLocation = "build/libs/DistantHorizons-forge-${rootProject.versionStr}.jar"
}
void mapPaths(Function<String, String> mapper) {
filePathMapper = mapper
if (findProject(":neoforge"))
custom {
projectName = "neoforge"
jarLocation = "build/libs/DistantHorizons-neoforge-${rootProject.versionStr}.jar"
}
void relocateNative(String target, String replacement) {
if (replacement.length() > target.length()) {
throw new GradleException("Length of value \"${replacement}\" exceeds the length of \"${target}\": ${replacement.length()} > ${target.length()}")
if (findProject(":fabric"))
fabric {
jarLocation = "build/libs/DistantHorizons-fabric-${rootProject.versionStr}.jar"
}
replacements.put(target, replacement)
if (findProject(":quilt"))
quilt {
jarLocation = "build/libs/DistantHorizons-quilt-${rootProject.versionStr}.jar"
}
@Override
boolean canTransformResource(@Nonnull FileTreeElement element) {
return fileMatcher.test(element.name)
}
@Override
void transform(@Nonnull TransformerContext context) {
byte[] content = context.is.readAllBytes()
if (nativeRelocator == null) {
nativeRelocator = new NativeRelocator(rootDir.toPath().resolve("relocate_natives"))
}
try {
String path = filePathMapper != null
? filePathMapper.apply(context.path)
: context.path
content = nativeRelocator.processBinary(path, content, replacements)
rewrittenFiles.put(path, content)
}
catch (Throwable e) {
throw new GradleException("Failed to relocate", e)
}
}
@Override
boolean hasTransformedResource() { return !rewrittenFiles.isEmpty() }
@Override
void modifyOutputStream(@Nonnull ZipOutputStream os, boolean preserveFileTimestamps) {
for (Map.Entry<String, byte[]> rewrittenFile : rewrittenFiles.entrySet()) {
os.putNextEntry(new ZipEntry(rewrittenFile.key))
os.write(rewrittenFile.value)
}
}
removeDuplicate "com.seibel.distanthorizons"
}
subprojects { p ->
@@ -148,16 +104,13 @@ subprojects { p ->
// Apply plugins
apply plugin: "java"
apply plugin: "com.github.johnrengelman.shadow"
apply plugin: "xyz.wagyourtail.jvmdowngrader"
if (isMinecraftSubProject)
apply plugin: "systems.manifold.manifold-gradle-plugin"
// Apply forge's loom
if ((findProject(":forge") && p == project(":forge")) ||
(findProject(":neoforge") && p == project(":neoforge"))
)
{
if ( (findProject(":forge") && p == project(":forge")) || (findProject(":neoforge") && p == project(":neoforge")) )
apply plugin: "dev.architectury.loom"
}
// Set the manifold version (may not be required tough)
@@ -205,7 +158,6 @@ subprojects { p ->
if (findProject(":neoforge"))
developmentNeoForge.extendsFrom coreProjects
// TODO remove unused fabricLike
if (findProject(":fabricLike") && p != project(":fabricLike")) {
// Shadow fabricLike
fabricLike
@@ -228,19 +180,10 @@ subprojects { p ->
}
// Log4j
if (p == project(":core"))
{
// the standalone core jar needs logging shaded otherwise it won't run
forgeShadowMe("org.apache.logging.log4j:log4j-api:${rootProject.log4j_version}")
forgeShadowMe("org.apache.logging.log4j:log4j-core:${rootProject.log4j_version}")
}
else
{
// When running in MC, MC already includes logging
// TODO: Change to shadowMe later to work in the standalone jar
// We cannot do this now as it would break Quilt
implementation("org.apache.logging.log4j:log4j-api:${rootProject.log4j_version}")
implementation("org.apache.logging.log4j:log4j-core:${rootProject.log4j_version}")
}
// JOML
if (project.hasProperty("embed_joml") && embed_joml == "true")
@@ -258,7 +201,6 @@ subprojects { p ->
// We cannot relocate this library since we call some MC classes that reference it
implementation("it.unimi.dsi:fastutil:${rootProject.fastutil_version}")
forgeShadowMe("com.github.luben:zstd-jni:${rootProject.zstd_version}")
// Compression
forgeShadowMe("org.lz4:lz4-java:${rootProject.lz4_version}") // LZ4
@@ -275,7 +217,8 @@ subprojects { p ->
// forgeShadowMe("com.formdev:svgSalamander:${rootProject.svgSalamander_version}")
// Netty
implementation("io.netty:netty-buffer:${rootProject.netty_version}")
// Breaks 1.16.5
//forgeShadowMe("io.netty:netty-all:${rootProject.netty_version}")
// Remember, for lwjgl dependencies that arent included in Minecraft, you need to also need to add it to the ShadowJar thing
forgeShadowMe("org.lwjgl:lwjgl-jawt:${rootProject.lwjgl_version}") {
@@ -348,44 +291,9 @@ subprojects { p ->
// Compression (LZ4)
relocate "net.jpountz", "${librariesLocation}.jpountz"
// Logging
relocate "org.slf4j", "${librariesLocation}.slf4j"
// Sqlite Database
// 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
relocate "org.sqlite", "dh_sqlite", {
exclude "org/sqlite/native/**"
}
relocate "jdbc:sqlite", "jdbc:dh_sqlite"
transform(NativeTransformer) {
rootDir = project.rootDir
matchFiles { it.startsWith("org/sqlite") }
mapPaths { it.replace("org/sqlite", "dh_sqlite") }
relocateNative "org/sqlite", "dh_sqlite"
relocateNative "org_sqlite", "dh_1sqlite"
}
// ZStd
// 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
relocate "com.github.luben", "dhcomgithubluben"
relocate "libzstd-jni", "libzstd-jni_dh"
relocate "zstd-jni", "zstd-jni_dh"
transform(NativeTransformer) {
rootDir = project.rootDir
matchFiles { it.contains("libzstd-jni") && !it.contains("aix/ppc64") }
mapPaths { it.replace("libzstd-jni", "libzstd-jni_dh") }
relocateNative "com/github/luben", "dhcomgithubluben"
relocateNative "com_github_luben", "dhcomgithubluben"
}
//At the moment, there is a bug in this library which doesnt allow it to be relocated
// relocate "org.sqlite", "${librariesLocation}.sqlite"
// JOML
if (project.hasProperty("embed_joml") && embed_joml == "true")
@@ -398,13 +306,25 @@ subprojects { p ->
// relocate "com.kitfox.svg", "${librariesLocation}.kitfox.svg"
// Netty
// Don't relocate, it causes problems with using MC's FriendlyByteBufs
// relocate "io.netty", "${librariesLocation}.netty"
relocate "io.netty", "${librariesLocation}.netty"
mergeServiceFiles()
}
// Using jar.finalizedBy(shadowJar) causes issues so we do this scuffed bypass
jar.dependsOn(shadowJar)
// For downgrading the Java version of our project to match the minimum Java version required by the selected Minecraft version.
task jarDowngrade(type: xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar) {
inputFile = tasks.shadowJar.archiveFile
downgradeTo = JavaVersion.toVersion(rootProject.java_version as Integer)
archiveClassifier = "downgraded-${rootProject.java_version}"
}
task apiDowngrade(type: xyz.wagyourtail.jvmdg.gradle.task.ShadeJar) {
inputFile = jarDowngrade.archiveFile
downgradeTo = JavaVersion.toVersion(rootProject.java_version as Integer)
archiveClassifier = "downgraded-${rootProject.java_version}-shaded-java-api"
}
// We're using a custom downgrade task so we disable the original downgrade tasks
downgradeJar.enabled = false
shadeDowngradedApi.enabled = false
// Put stuff from gradle.properties into the mod info
@@ -479,8 +399,6 @@ subprojects { p ->
fabric_incompatibility_list : fabric_incompatibility_list,
fabric_recommend_list : fabric_recommend_list,
neoforge_version_range : neoforge_version_range,
]
// replace any properties in the sub-projects with the values defined here
@@ -525,12 +443,9 @@ subprojects { p ->
jar {
from "LICENSE.txt"
manifest {
attributes(
'Implementation-Title': rootProject.mod_name,
attributes 'Implementation-Title': rootProject.mod_name,
'Implementation-Version': rootProject.mod_version,
'Multi-Release': true, // needed for logging in the standalone core jar
'Main-Class': 'com.seibel.distanthorizons.core.jar.JarMain', // When changing the main of the jar change this line
)
'Main-Class': 'com.seibel.distanthorizons.core.jar.JarMain' // When changing the main of the jar change this line
}
}
@@ -561,9 +476,8 @@ allprojects { p ->
apply plugin: "java"
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
version = (project == rootProject ? "" : project.name + "-") + rootProject.versionStr
version = project.name + "-" + rootProject.versionStr
group = rootProject.maven_group
// this is the text that appears at the top of the overview (home) page
@@ -585,9 +499,6 @@ allprojects { p ->
repositories {
// Mojang overrides (added to fix downloading the wrong LWJGL libs on M1 Mac's and potentially other arm64 based machines)
maven { url "https://libraries.minecraft.net/" }
// The central repo
mavenCentral()
@@ -595,7 +506,6 @@ allprojects { p ->
maven { url "https://repo.enonic.com/public/" }
// For parchment mappings
// versions can be found here: https://ldtteam.jfrog.io/ui/native/parchmentmc-public/org/parchmentmc/data/
maven { url "https://maven.parchmentmc.org" }
// For Architectury API
@@ -657,44 +567,6 @@ allprojects { p ->
// TODO: If neoforged is ever needed, should we use that, or call it a forge mod?
}
// Adds some dependencies that are in vanilla but not in core
if (p == project(":core")) {
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
// Set the OS lwjgl is using to the current os
project.ext.lwjglNatives = "natives-" + os.toFamilyName()
dependencies { // All of these dependencies are in Vanilla Minecraft, but we need to depend on it as we arent importing Minecraft in the core
// Imports most of lwjgl's libraries (well, only the ones that we need)
implementation platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}") // TODO: Use Minecraft's version for lwjgl_version (which changes in nearly every version) instead of a hard defined version for all versions
// REMEMBER: Dont shadow stuff here, these are just the libs that are included in Minecraft so that the core can use
implementation "org.lwjgl:lwjgl"
implementation "org.lwjgl:lwjgl-assimp"
implementation "org.lwjgl:lwjgl-glfw"
implementation "org.lwjgl:lwjgl-openal"
implementation "org.lwjgl:lwjgl-opengl"
implementation "org.lwjgl:lwjgl-stb"
implementation "org.lwjgl:lwjgl-tinyfd"
runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives"
implementation "org.joml:joml:${rootProject.joml_version}"
// Some other dependencies
implementation("org.jetbrains:annotations:16.0.2")
implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.google.common:google-collect:0.5")
implementation("com.google.guava:guava:31.1-jre")
}
}
task copyCommonLoaderResources(type: Copy) {
from project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener")
into(file(p.file("build/resources/main")))
@@ -716,15 +588,28 @@ allprojects { p ->
tasks.withType(JavaCompile) {
if (isMinecraftSubProject) {
options.release = rootProject.java_version as Integer
} else {
options.release = 8; // Core & Api should use Java 8 no matter what
//options.release = rootProject.java_version as Integer // But if you want to test some stuff, then this can be enabled
options.release = rootProject.java_version as Integer // Neoforge complains without this
options.compilerArgs += ["-Xplugin:Manifold"]
}
options.encoding = "UTF-8"
}
// Sets the project's actual Java version (it's recommended to use this over the `options.release` method above)
java {
sourceCompatibility = rootProject.java_version
targetCompatibility = rootProject.java_version
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)
+6 -12
View File
@@ -1,8 +1,7 @@
#!/bin/sh
echo "==================== Note: All build jars will be in the folder called 'buildAllJars' ===================="
mkdir -p buildAllJars
rm -rf buildAllJars/*
mkdir -p buildAllJars | true
# Loop trough everything in the version properties folder
for d in versionProperties/*; do
@@ -12,17 +11,12 @@ for d in versionProperties/*; do
# Clean out the folders, build it, and merge it
# (We could use "./" to run gradlew, but as it is a shell script im going to be running it with the "sh" command)
echo "==================== Cleaning workspace to build $version ===================="
sh gradlew clean -PmcVer=$version
if [ $? != 0 ]; then continue; fi
sh gradlew clean -PmcVer=$version --no-daemon || true
echo "====================Building $version ===================="
sh gradlew build -PmcVer=$version
if [ $? != 0 ]; then continue; fi
sh gradlew build -PmcVer=$version --no-daemon || true
echo "==================== Merging $version ===================="
sh gradlew mergeJars -PmcVer=$version
if [ $? != 0 ]; then continue; fi
sh gradlew mergeJars -PmcVer=$version --no-daemon || true
echo "==================== Moving jar ===================="
mv build/merged/*.jar buildAllJars/
mv Merged/*.jar buildAllJars/ || true
# The "| true" at the end of those are just to make sure the script continues even if a build fails
done
+4 -8
View File
@@ -5,7 +5,6 @@
echo ==================== Note: All build jars will be in the folder called 'buildAllJars' ====================
mkdir buildAllJars
del buildAllJars/*
@rem Loop trough everything in the version properties folder
for %%f in (versionProperties\*) do (
@@ -14,16 +13,13 @@ for %%f in (versionProperties\*) do (
@rem Clean out the folders, build it, and merge it
echo ==================== Cleaning workspace to build !version! ====================
call .\gradlew.bat clean
call .\gradlew.bat clean -PmcVer="!version!" --no-daemon
echo ==================== Building !version! ====================
call .\gradlew.bat build -PmcVer="!version!"
call .\gradlew.bat build -PmcVer="!version!" --no-daemon
echo ==================== Merging !version! ====================
call .\gradlew.bat mergeJars -PmcVer="!version!"
call .\gradlew.bat mergeJars -PmcVer="!version!" --no-daemon
echo ==================== Moving jar ====================
move build\merged\*.jar buildAllJars\
move Merged\*.jar buildAllJars\
)
endlocal
-214
View File
@@ -1,214 +0,0 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
class NativeRelocator
{
private final Path rootDirectory;
private final Path cacheRoot;
/**
* Initializes the NativeRelocator by preparing the environment if necessary.
* Executes the appropriate preparation script based on the OS.
*/
NativeRelocator(Path rootDirectory)
{
this.rootDirectory = rootDirectory;
this.cacheRoot = this.rootDirectory.resolve("cache");
}
private void prepare() throws Exception
{
if (this.rootDirectory.resolve(".venv").toFile().exists())
{
return;
}
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.directory(this.rootDirectory.toFile());
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win"))
{
processBuilder.command("powershell", "-ExecutionPolicy", "Bypass", "./prepare.ps1");
}
else if (os.contains("nix")
|| os.contains("nux")
|| os.contains("mac")
|| os.contains("freebsd"))
{
processBuilder.command("./prepare.sh");
}
else
{
throw new IllegalStateException("Unsupported operating system: " + os);
}
Process process = processBuilder.start();
CompletableFuture<Void> outputFuture = readOutputStreams(process);
int exitCode = process.waitFor();
outputFuture.get();
if (exitCode != 0)
{
throw new Exception("Prepare failed: " + exitCode);
}
}
/**
* Reads and prints the output and error streams of a process asynchronously.
*
* @param process The process whose streams should be read.
* @return A CompletableFuture that completes once all output has been processed.
*/
private static CompletableFuture<Void> readOutputStreams(Process process)
{
return CompletableFuture.runAsync(() -> {
try
{
while (process.isAlive() || process.getInputStream().available() > 0 || process.getErrorStream().available() > 0)
{
if (process.getInputStream().available() > 0)
{
byte[] data = new byte[process.getInputStream().available()];
//noinspection ResultOfMethodCallIgnored
process.getInputStream().read(data);
System.out.write(data);
}
if (process.getErrorStream().available() > 0)
{
byte[] data = new byte[process.getErrorStream().available()];
//noinspection ResultOfMethodCallIgnored
process.getErrorStream().read(data);
System.err.write(data);
}
//noinspection BusyWait
Thread.sleep(100);
}
}
catch (Throwable ignored)
{
}
});
}
/**
* Replaces occurrences of a target string in a byte array, ensuring null termination.
*
* @param byteArray The byte array where replacements should occur.
* @param target The string to replace.
* @param replacement The replacement string (must not be longer than the target).
* @throws IllegalArgumentException if the replacement is longer than the target.
*/
private void replaceInNullTerminatedStrings(byte[] byteArray, String target, String replacement)
{
if (target.length() < replacement.length())
{
throw new IllegalArgumentException("Replacement must be the same length or shorter than the target.");
}
byte[] targetBytes = target.getBytes(StandardCharsets.US_ASCII);
byte[] replacementBytes = replacement.getBytes(StandardCharsets.US_ASCII);
byte nullByte = 0;
for (int endPos = 0; endPos < byteArray.length - targetBytes.length - 1; endPos++)
{
int startPos = endPos;
int targetPos = 0;
while (targetPos < targetBytes.length && byteArray[endPos] == targetBytes[targetPos])
{
targetPos++;
endPos++;
}
if (targetPos == targetBytes.length)
{
System.arraycopy(replacementBytes, 0, byteArray, startPos, replacementBytes.length);
startPos = startPos + replacementBytes.length;
while (byteArray[endPos] != nullByte)
{
byteArray[startPos] = byteArray[endPos];
endPos++;
startPos++;
}
byteArray[startPos] = nullByte;
}
}
}
/**
* Runs an external script to fix a modified binary and returns the processed content.
*
* @param outputFilePath Path to store the processed binary.
* @param content The original binary content.
* @return The modified binary content.
* @throws Exception if the process execution fails.
*/
public byte[] fixModifiedBinary(Path outputFilePath, byte[] content) throws Exception
{
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.directory(this.rootDirectory.toFile());
processBuilder.command(
this.rootDirectory.resolve(".venv/Scripts").toFile().exists()
? this.rootDirectory.resolve(".venv/Scripts/python.exe").toString()
: this.rootDirectory.resolve(".venv/bin/python").toString(),
"./fix_modified_binary.py",
outputFilePath.toString()
);
Process process = processBuilder.start();
CompletableFuture<Void> outputFuture = readOutputStreams(process);
process.getOutputStream().write(content);
process.getOutputStream().close();
int exitCode = process.waitFor();
outputFuture.get();
if (exitCode != 0)
{
throw new Exception("Process failed: " + exitCode);
}
return Files.readAllBytes(outputFilePath);
}
/**
* Processes a binary file, applying string replacements and fixing modifications.
*
* @param outputPath The output file path relative to the cache directory.
* @param content The binary content to process.
* @param replacements A map of string replacements to apply.
* @return The modified binary content.
* @throws Exception if processing fails.
*/
public byte[] processBinary(String outputPath, byte[] content, Map<String, String> replacements) throws Exception
{
Path outputFilePath = this.cacheRoot.resolve(outputPath);
//noinspection ResultOfMethodCallIgnored
outputFilePath.getParent().toFile().mkdirs();
if (outputFilePath.toFile().exists())
{
return Files.readAllBytes(outputFilePath);
}
System.out.println("Relocating to " + outputPath + "...");
this.prepare();
for (Map.Entry<String, String> replacement : replacements.entrySet())
{
this.replaceInNullTerminatedStrings(content, replacement.getKey(), replacement.getValue());
}
return this.fixModifiedBinary(outputFilePath, content);
}
}
-2
View File
@@ -4,8 +4,6 @@ buildscript {
configurations.configureEach {
resolutionStrategy {
force 'org.spongepowered:vanillagradle:0.2.1-20240507.024226-82'
// newer versions can be found by going to the link:
// https://repo.spongepowered.org/#browse/browse:maven-public:org%2Fspongepowered%2Fvanillagradle%2F0.2.1-SNAPSHOT
}
}
}
@@ -3,20 +3,15 @@ package com.seibel.distanthorizons.common;
import com.mojang.brigadier.CommandDispatcher;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiAfterDhInitEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeDhInitEvent;
import com.seibel.distanthorizons.common.commands.CommandInitializer;
import com.seibel.distanthorizons.common.wrappers.DependencySetup;
import com.seibel.distanthorizons.common.wrappers.gui.DhDebugScreenEntry;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftServerWrapper;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftDedicatedServerWrapper;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.ConfigHandler;
import com.seibel.distanthorizons.core.config.ConfigBase;
import com.seibel.distanthorizons.core.config.eventHandlers.presets.ThreadPresetConfigEventHandler;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.jar.ModJarInfo;
import com.seibel.distanthorizons.core.jar.updater.SelfUpdater;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker;
@@ -25,8 +20,9 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.DedicatedServer;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.Logger;
import java.lang.invoke.MethodHandles;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -36,9 +32,9 @@ import java.util.function.Supplier;
*/
public abstract class AbstractModInitializer
{
protected static final DhLogger LOGGER = new DhLoggerBuilder().build();
protected static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
private CommandInitializer commandInitializer;
private CommandDispatcher<CommandSourceStack> commandDispatcher;
@@ -46,8 +42,7 @@ public abstract class AbstractModInitializer
// abstract methods //
//==================//
protected abstract void createInitialSharedBindings();
protected abstract void createInitialClientBindings();
protected abstract void createInitialBindings();
protected abstract IEventProxy createClientProxy();
protected abstract IEventProxy createServerProxy(boolean isDedicated);
protected abstract void initializeModCompat();
@@ -67,30 +62,23 @@ public abstract class AbstractModInitializer
public void onInitializeClient()
{
DependencySetup.createClientBindings();
this.createInitialClientBindings();
LOGGER.info("Initializing " + ModInfo.READABLE_NAME + " client, firing DhApiBeforeDhInitEvent...");
LOGGER.info("Initializing " + ModInfo.READABLE_NAME);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDhInitEvent.class, null);
this.startup();
this.logBuildInfo();
this.printModInfo(true);
this.createClientProxy().registerEvents();
this.createServerProxy(false).registerEvents();
this.initializeModCompat();
LOGGER.info(ModInfo.READABLE_NAME + " Initialized");
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterDhInitEvent.class, null);
// Client uses config for auto-updater, so it's initialized here instead of post-init stage
this.initConfig();
logModIncompatibilityWarnings(); // needs to be called after config loading
LOGGER.info(ModInfo.READABLE_NAME + " client Initialized.");
#if MC_VER < MC_1_21_9
// debug screen rendering handled via a mixin
#else
DhDebugScreenEntry.register();
#endif
this.subscribeClientStartedEvent(this::postInit);
}
@@ -99,11 +87,11 @@ public abstract class AbstractModInitializer
{
DependencySetup.createServerBindings();
LOGGER.info("Initializing " + ModInfo.READABLE_NAME + " server, firing DhApiBeforeDhInitEvent event...");
LOGGER.info("Initializing " + ModInfo.READABLE_NAME);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDhInitEvent.class, null);
this.startup();
this.logBuildInfo();
this.printModInfo(false);
// This prevents returning uninitialized Config values,
// resulting from a circular reference mid-initialization in a static class
@@ -112,23 +100,20 @@ public abstract class AbstractModInitializer
this.createServerProxy(true).registerEvents();
this.initializeModCompat();
LOGGER.info(ModInfo.READABLE_NAME + " Initialized");
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterDhInitEvent.class, null);
LOGGER.info(ModInfo.READABLE_NAME + " server Initialized, adding event subscribers...");
this.commandInitializer = new CommandInitializer();
this.subscribeRegisterCommandsEvent(dispatcher -> { this.commandInitializer.initCommands(dispatcher); });
this.subscribeRegisterCommandsEvent(dispatcher -> this.commandDispatcher = dispatcher);
this.subscribeServerStartingEvent(server ->
{
MinecraftServerWrapper.INSTANCE.dedicatedServer = (DedicatedServer)server;
MinecraftDedicatedServerWrapper.INSTANCE.dedicatedServer = (DedicatedServer)server;
this.initConfig();
this.postInit();
this.commandInitializer.onServerReady();
this.initCommands();
this.checkForUpdates();
LOGGER.info(ModInfo.READABLE_NAME + " server Initialized at " + server.getServerDirectory());
LOGGER.info("Dedicated server initialized at " + server.getServerDirectory());
});
}
@@ -142,16 +127,16 @@ public abstract class AbstractModInitializer
{
DependencySetup.createSharedBindings();
SharedApi.init();
this.createInitialSharedBindings();
this.createInitialBindings();
}
private void logBuildInfo()
private void printModInfo(boolean printGitInfo)
{
LOGGER.info(ModInfo.READABLE_NAME + ", Version: " + ModInfo.VERSION);
// if the build is stable the branch/commit/etc shouldn't be needed
if (ModInfo.IS_DEV_BUILD)
if (printGitInfo)
{
// Useful for dev builds
LOGGER.info("DH Branch: " + ModJarInfo.Git_Branch);
LOGGER.info("DH Commit: " + ModJarInfo.Git_Commit);
LOGGER.info("DH Jar Build Source: " + ModJarInfo.Build_Source);
@@ -166,141 +151,22 @@ public abstract class AbstractModInitializer
//noinspection unchecked
ModAccessorInjector.INSTANCE.bind((Class<? extends IModAccessor>) accessorClass, accessorConstructor.get());
}
else
{
LOGGER.debug("Skipping mod compatibility accessor for: ["+modId+"]");
}
}
private void initConfig()
{
ConfigHandler.tryRunFirstTimeSetup();
ConfigBase.INSTANCE = new ConfigBase(ModInfo.ID, ModInfo.NAME, Config.class, 2);
Config.completeDelayedSetup();
DhLogger.runDelayedConfigSetup();
}
private void checkForUpdates()
{
if (Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get())
{
if (Config.Client.Advanced.AutoUpdater.enableSilentUpdates.get())
{
LOGGER.info("Silent updates are not allowed for dedicated servers; force disabling.");
Config.Client.Advanced.AutoUpdater.enableSilentUpdates.set(false);
}
SelfUpdater.onStart();
}
}
private void postInit()
{
LOGGER.info("Running Delayed setup...");
LOGGER.info("Post-Initializing Mod");
this.runDelayedSetup();
if (ConfigHandler.INSTANCE == null)
{
throw new IllegalStateException("Config was not initialized. Make sure to call LodCommonMain.initConfig() before calling this method.");
LOGGER.info("Mod Post-Initialized");
}
LOGGER.info("Delayed setup complete, firing DhApiAfterDhInitEvent event...");
// should be fired after all delayed setup so singletons and config can be accessed
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterDhInitEvent.class, null);
}
//==================================//
// mod partial compatibility checks //
//==================================//
/**
* Some mods will work with a few tweaks
* or will partially work but have some known issues we can't solve.
* This method will log (and display to chat if enabled)
* these warnings and potential fixes.
*/
private static void logModIncompatibilityWarnings()
{
boolean showChatWarnings = Config.Common.Logging.Warning.showModCompatibilityWarningsOnStartup.get();
IModChecker modChecker = SingletonInjector.INSTANCE.get(IModChecker.class);
String startingString = "Partially Incompatible Distant Horizons mod detected: ";
// Alex's caves
if (modChecker.isModLoaded("alexscaves"))
{
// There've been a few reports about this mod breaking DH at a few different points in time
// the fixes for said breakage changes depending on the version so unfortunately
// all we can do is log a warning so the user can handle it.
if (showChatWarnings)
{
String message =
MinecraftTextFormat.ORANGE + "Distant Horizons: Alex's Cave detected." + MinecraftTextFormat.CLEAR_FORMATTING +
"You may have to change Alex's config for DH to render. ";
ClientApi.INSTANCE.showChatMessageNextFrame(message);
}
LOGGER.warn(startingString + "[Alex's Caves] may require some config changes in order to render Distant Horizons correctly.");
}
// William Wythers' Overhauled Overworld (WWOO)
if (modChecker.isModLoaded("wwoo"))
{
// WWOO has a bug with it's world gen that can't be fixed by DH or WWOO
// (at least that is what James learned after talking with WWOO)
// WWOO will cause grid lines to appear in the world when DH generates the chunks
// this might be due to how WWOO uses features for everything when generating
// and said features don't always get to the edge of said chunks.
String wwooWarning = "LODs generated by DH may have grid lines between sections. Disabling either WWOO or DH's distant generator will fix the problem.";
if (showChatWarnings)
{
String message =
MinecraftTextFormat.ORANGE + "Distant Horizons: WWOO detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
wwooWarning;
ClientApi.INSTANCE.showChatMessageNextFrame(message);
}
LOGGER.warn(startingString + "[WWOO] "+ wwooWarning);
}
// Chunky
boolean chunkyPresent = false;
try
{
Class.forName("org.popcraft.chunky.api.ChunkyAPI");
chunkyPresent = true;
}
catch (ClassNotFoundException ignore) { }
if (chunkyPresent)
{
// Chunky can generate chunks faster than DH can process them,
// causing holes in the LODs.
// Generally it's better and faster to use DH's world generator.
String chunkyWarning = "Chunky can cause DH LODs to have holes " +
"since Chunky can generate chunks faster than DH can process them. \n" +
"Using DH's distant generator instead of chunky or increasing DH's CPU thread count can resolve the issue.";
if (showChatWarnings)
{
String message =
MinecraftTextFormat.ORANGE + "Distant Horizons: Chunky detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
chunkyWarning;
ClientApi.INSTANCE.showChatMessageNextFrame(message);
}
LOGGER.warn(startingString + "[Chunky] "+ chunkyWarning);
}
}
private void initCommands() { /* currently unimplemented */ }
@@ -1,138 +0,0 @@
package com.seibel.distanthorizons.common;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
import com.seibel.distanthorizons.core.network.event.internal.ProtocolErrorInternalEvent;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import io.netty.buffer.ByteBufUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
#if MC_VER <= MC_1_21_10
import net.minecraft.resources.ResourceLocation;
#else
import net.minecraft.resources.Identifier;
#endif
import java.io.IOException;
import java.util.Objects;
public abstract class AbstractPluginPacketSender implements IPluginPacketSender
{
private static final DhLogger LOGGER = new DhLoggerBuilder()
.fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.build();
#if MC_VER <= MC_1_20_6
public static final ResourceLocation WRAPPER_PACKET_RESOURCE = new ResourceLocation(ModInfo.RESOURCE_NAMESPACE, ModInfo.WRAPPER_PACKET_PATH);
#elif MC_VER <= MC_1_21_10
public static final ResourceLocation WRAPPER_PACKET_RESOURCE = ResourceLocation.fromNamespaceAndPath(ModInfo.RESOURCE_NAMESPACE, ModInfo.WRAPPER_PACKET_PATH);
#else
public static final Identifier WRAPPER_PACKET_RESOURCE = Identifier.fromNamespaceAndPath(ModInfo.RESOURCE_NAMESPACE, ModInfo.WRAPPER_PACKET_PATH);
#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
public final void sendToClient(IServerPlayerWrapper serverPlayer, AbstractNetworkMessage message)
{
this.sendToClient((ServerPlayer) serverPlayer.getWrappedMcObject(), message);
}
public abstract void sendToClient(ServerPlayer serverPlayer, AbstractNetworkMessage message);
@Override
public abstract void sendToServer(AbstractNetworkMessage message);
public AbstractNetworkMessage decodeMessage(FriendlyByteBuf in)
{
AbstractNetworkMessage message = null;
try
{
in.markReaderIndex();
int protocolVersion = this.forgeByteInProtocolVersion ? in.readByte() : in.readShort();
if (protocolVersion != ModInfo.PROTOCOL_VERSION)
{
return new IncompatibleMessageInternalEvent(protocolVersion);
}
message = MessageRegistry.INSTANCE.createMessage(in.readUnsignedShort());
message.decode(in);
if (in.isReadable())
{
throw new IOException("Buffer has not been fully read");
}
return message;
}
catch (Exception e)
{
in.resetReaderIndex();
LOGGER.error("Failed to decode message", e);
LOGGER.error("Buffer: ["+in+"]");
LOGGER.error("Buffer contents: ["+ByteBufUtil.hexDump(in)+"]");
return new ProtocolErrorInternalEvent(e, message, true);
}
finally
{
// Prevent connection crashing if not entire buffer has been read
in.readerIndex(in.writerIndex());
}
}
public void encodeMessage(FriendlyByteBuf out, AbstractNetworkMessage message)
{
// This is intentionally unhandled, because errors related to this are unlikely to appear in wild
Objects.requireNonNull(message);
if (this.forgeByteInProtocolVersion)
{
out.writeByte(ModInfo.PROTOCOL_VERSION);
}
else
{
out.writeShort(ModInfo.PROTOCOL_VERSION);
}
try
{
out.markWriterIndex();
out.writeShort(MessageRegistry.INSTANCE.getMessageId(message));
message.encode(out);
}
catch (Exception e)
{
LOGGER.error("Failed to encode message", e);
LOGGER.error("Message: ["+message+"]");
message.getSession().tryHandleMessage(new ProtocolErrorInternalEvent(e, message, false));
// Encode close reason message instead
out.resetWriterIndex();
message = new CloseReasonMessage("Internal error on other side");
out.writeShort(MessageRegistry.INSTANCE.getMessageId(message));
message.encode(out);
}
}
}
@@ -1,39 +0,0 @@
package com.seibel.distanthorizons.common;
#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.wrapperInterfaces.misc.IPluginPacketSender;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public record CommonPacketPayload(@Nullable AbstractNetworkMessage message) implements CustomPacketPayload
{
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
@Override
public Type<? extends CustomPacketPayload> type() { return TYPE; }
public static class Codec implements StreamCodec<FriendlyByteBuf, CommonPacketPayload>
{
@NotNull
@Override
public CommonPacketPayload decode(@NotNull FriendlyByteBuf in)
{ return new CommonPacketPayload(PACKET_SENDER.decodeMessage(in)); }
@Override
public void encode(@NotNull FriendlyByteBuf out, CommonPacketPayload payload)
{ PACKET_SENDER.encodeMessage(out, payload.message()); }
}
}
#endif
@@ -1,102 +0,0 @@
package com.seibel.distanthorizons.common.commands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.seibel.distanthorizons.common.wrappers.misc.ServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import net.minecraft.commands.CommandSourceStack;
#if MC_VER >= MC_1_19_2
import net.minecraft.network.chat.Component;
import java.util.Objects;
#else // < 1.19.2
import net.minecraft.network.chat.TranslatableComponent;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
#endif
/**
* Abstract class providing common functionality for DH's commands.
*/
public abstract class AbstractCommand
{
public abstract LiteralArgumentBuilder<CommandSourceStack> buildCommand();
/**
* Sends a success response to the player with the given text.
*
* @param commandContext The command context to send the response to.
* @param text The text to display in the success message.
* @return 1, indicating that the command was successful.
*/
protected int sendSuccessResponse(CommandContext<CommandSourceStack> commandContext, String text, boolean notifyAdmins)
{
#if MC_VER >= MC_1_20_1
commandContext.getSource().sendSuccess(() -> Component.literal(text), notifyAdmins);
#elif MC_VER >= MC_1_19_2
commandContext.getSource().sendSuccess(Component.literal(text), notifyAdmins);
#else
commandContext.getSource().sendSuccess(new TranslatableComponent(text), notifyAdmins);
#endif
return 1;
}
/**
* Sends a failure response to the player with the given text.
*
* @param commandContext The command context to send the response to.
* @param text The text to display in the failure message.
* @return 1, indicating that the command was successful.
*/
protected int sendFailureResponse(CommandContext<CommandSourceStack> commandContext, String text)
{
#if MC_VER >= MC_1_20_1
commandContext.getSource().sendFailure(Component.literal(text));
#elif MC_VER >= MC_1_19_2
commandContext.getSource().sendFailure(Component.literal(text));
#else
commandContext.getSource().sendFailure(new TranslatableComponent(text));
#endif
return 1;
}
/**
* Gets the server player from a command context.
*
* @param commandContext The command context to get the server player from.
* @return The server player wrapper for the player who sent the command.
*/
protected IServerPlayerWrapper getSourcePlayer(CommandContext<CommandSourceStack> commandContext) #if MC_VER < MC_1_19_2 throws CommandSyntaxException #endif
{
#if MC_VER >= MC_1_19_2
return ServerPlayerWrapper.getWrapper(Objects.requireNonNull(commandContext.getSource().getPlayer()));
#else
return ServerPlayerWrapper.getWrapper(commandContext.getSource().getPlayerOrException());
#endif
}
/**
* Checks if the source of a command is a player.
*
* @param source The source of the command to check.
* @return True if the source is a player, false otherwise.
*/
protected boolean isPlayerSource(CommandSourceStack source)
{
#if MC_VER >= MC_1_19_2
return source.isPlayer();
#else
try
{
source.getPlayerOrException();
return true;
}
catch (CommandSyntaxException e)
{
return false;
}
#endif
}
}
@@ -1,84 +0,0 @@
package com.seibel.distanthorizons.common.commands;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import org.jetbrains.annotations.Nullable;
import static com.seibel.distanthorizons.core.network.messages.MessageRegistry.DEBUG_CODEC_CRASH_MESSAGE;
import static net.minecraft.commands.Commands.literal;
#if MC_VER <= MC_1_21_10
#else
import net.minecraft.server.permissions.PermissionCheck;
import net.minecraft.server.permissions.Permissions;
#endif
public class CommandInitializer
{
private boolean serverReady = false;
#if MC_VER <= MC_1_21_10
private static final int REQUIRED_PERMISSION_LEVEL = 4;
#else
private static final PermissionCheck COMMAND_PERMISSION_CHECK = new PermissionCheck.Require(Permissions.COMMANDS_OWNER);
#endif
/**
* A received command dispatcher, which is held until the server is ready to initialize the commands.
*/
@Nullable
private CommandDispatcher<CommandSourceStack> commandDispatcher;
/**
* Notify the command initializer that the game is ready to accept commands.
* If {@link CommandInitializer#initCommands(CommandDispatcher)} has been fired before it was ready, it will also initialize the commands.
*/
public void onServerReady()
{
this.serverReady = true;
if (this.commandDispatcher != null)
{
this.initCommands(this.commandDispatcher);
this.commandDispatcher = null;
}
}
/**
* Initializes all available commands.
* If the game is not ready yet, it stores the dispatcher to initialize the commands later.
*
* @param commandDispatcher The command dispatcher to register commands to.
*/
public void initCommands(CommandDispatcher<CommandSourceStack> commandDispatcher)
{
if (!this.serverReady)
{
this.commandDispatcher = commandDispatcher;
return;
}
LiteralArgumentBuilder<CommandSourceStack> builder = literal("dh")
.requires((source) ->
{
#if MC_VER <= MC_1_21_10
return source.hasPermission(REQUIRED_PERMISSION_LEVEL);
#else
return COMMAND_PERMISSION_CHECK.check(source.permissions());
#endif
});
builder.then(new ConfigCommand().buildCommand());
builder.then(new DebugCommand().buildCommand());
builder.then(new PregenCommand().buildCommand());
if (DEBUG_CODEC_CRASH_MESSAGE)
{
builder.then(new CrashCommand().buildCommand());
}
commandDispatcher.register(builder);
}
}
@@ -1,154 +0,0 @@
package com.seibel.distanthorizons.common.commands;
import com.mojang.brigadier.arguments.*;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.seibel.distanthorizons.core.config.ConfigHandler;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import net.minecraft.commands.CommandSourceStack;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.ToIntBiFunction;
import static com.mojang.brigadier.arguments.DoubleArgumentType.doubleArg;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
/**
* Command for managing config.
*/
public class ConfigCommand extends AbstractCommand
{
private static final List<CommandArgumentData<?>> commandArguments = Arrays.asList(
new CommandArgumentData<>(Integer.class, configEntry -> integer(configEntry.getMin(), configEntry.getMax()), IntegerArgumentType::getInteger),
new CommandArgumentData<>(Double.class, configEntry -> doubleArg(configEntry.getMin(), configEntry.getMax()), DoubleArgumentType::getDouble),
new CommandArgumentData<>(Boolean.class, BoolArgumentType::bool, BoolArgumentType::getBool),
new CommandArgumentData<>(String.class, StringArgumentType::string, StringArgumentType::getString)
);
/**
* Builds a command tree.
*/
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public LiteralArgumentBuilder<CommandSourceStack> buildCommand()
{
LiteralArgumentBuilder<CommandSourceStack> builder = literal("config");
HashSet<String> addedCommands = new HashSet<>();
for (AbstractConfigBase<?> type : ConfigHandler.INSTANCE.configBaseList)
{
// Skip non-config entries
if (!(type instanceof ConfigEntry))
{
continue;
}
//noinspection PatternVariableCanBeUsed
ConfigEntry configEntry = (ConfigEntry) type;
if (configEntry.getChatCommandName() == null)
{
continue;
}
if (!addedCommands.add(configEntry.getChatCommandName()))
{
throw new IllegalStateException("Duplicate command name: " + configEntry.getChatCommandName());
}
LiteralArgumentBuilder<CommandSourceStack> subcommand = literal(configEntry.getChatCommandName())
.executes(commandContext -> this.sendSuccessResponse(commandContext,
"\n" +
"Description of §l" + configEntry.getChatCommandName() + "§r:\n" +
"§o" + configEntry.getComment().trim() + "§r\n" +
"§7Config file name: §f" + configEntry.name + "§7, category: §f" + configEntry.category + "\n" +
"\n" +
"Current value of " + configEntry.getChatCommandName() + " is §n" + configEntry.get() + "§r",
false
));
ToIntBiFunction<CommandContext<CommandSourceStack>, Object> updateConfigValue = (commandContext, value) -> {
configEntry.set(value);
return this.sendSuccessResponse(commandContext, "Changed the value of [" + configEntry.getChatCommandName() + "] to [" + value + "]", true);
};
// Enum type needs a special case since enums aren't represented by existing argument type
// and need literals for each individual value
if (Enum.class.isAssignableFrom(configEntry.getType()))
{
for (Object choice : configEntry.getType().getEnumConstants())
{
subcommand.then(
literal(choice.toString())
.executes(c -> updateConfigValue.applyAsInt(c, choice))
);
}
}
else
{
boolean setterAdded = false;
for (CommandArgumentData<?> commandArgumentData : commandArguments)
{
if (!commandArgumentData.argumentClass.isAssignableFrom(configEntry.getType()))
{
continue;
}
subcommand.then(argument("value", commandArgumentData.getArgumentType(configEntry))
.executes(c -> updateConfigValue.applyAsInt(c, commandArgumentData.getValue(c, "value"))));
setterAdded = true;
break;
}
if (!setterAdded)
{
throw new RuntimeException("Config type of " + type.getName() + " is not supported: " + configEntry.getType().getSimpleName());
}
}
builder.then(subcommand);
}
return builder;
}
private static class CommandArgumentData<T>
{
public final Class<T> argumentClass;
public final Function<ConfigEntry<T>, ArgumentType<T>> argumentTypeFunction;
private final BiFunction<CommandContext<CommandSourceStack>, String, T> valueGetter;
public CommandArgumentData(Class<T> argumentClass, Supplier<ArgumentType<T>> argumentTypeSupplier, BiFunction<CommandContext<CommandSourceStack>, String, T> valueGetter)
{
this(argumentClass, configEntry -> argumentTypeSupplier.get(), valueGetter);
}
public CommandArgumentData(Class<T> argumentClass, Function<ConfigEntry<T>, ArgumentType<T>> argumentTypeFunction, BiFunction<CommandContext<CommandSourceStack>, String, T> valueGetter)
{
this.argumentClass = argumentClass;
this.argumentTypeFunction = argumentTypeFunction;
this.valueGetter = valueGetter;
}
public ArgumentType<T> getArgumentType(ConfigEntry<T> configEntry)
{
return this.argumentTypeFunction.apply(configEntry);
}
public T getValue(CommandContext<CommandSourceStack> commandContext, String argumentName)
{
return this.valueGetter.apply(commandContext, argumentName);
}
}
}
@@ -1,44 +0,0 @@
package com.seibel.distanthorizons.common.commands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
import com.seibel.distanthorizons.core.network.messages.base.CodecCrashMessage;
import net.minecraft.commands.CommandSourceStack;
import static net.minecraft.commands.Commands.literal;
public class CrashCommand extends AbstractCommand
{
@Override
public LiteralArgumentBuilder<CommandSourceStack> buildCommand()
{
return literal("crash")
.requires(this::isPlayerSource)
.then(literal("encode")
.executes(c -> {
assert SharedApi.tryGetDhServerWorld() != null;
ServerPlayerState serverPlayerState = SharedApi.tryGetDhServerWorld().getServerPlayerStateManager()
.getConnectedPlayer(this.getSourcePlayer(c));
if (serverPlayerState != null)
{
serverPlayerState.networkSession.sendMessage(new CodecCrashMessage(CodecCrashMessage.ECrashPhase.ENCODE));
}
return 1;
}))
.then(literal("decode")
.executes(c -> {
assert SharedApi.tryGetDhServerWorld() != null;
ServerPlayerState serverPlayerState = SharedApi.tryGetDhServerWorld().getServerPlayerStateManager()
.getConnectedPlayer(this.getSourcePlayer(c));
if (serverPlayerState != null)
{
serverPlayerState.networkSession.sendMessage(new CodecCrashMessage(CodecCrashMessage.ECrashPhase.DECODE));
}
return 1;
}));
}
}
@@ -1,25 +0,0 @@
package com.seibel.distanthorizons.common.commands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import net.minecraft.commands.CommandSourceStack;
import java.util.ArrayList;
import java.util.List;
import static net.minecraft.commands.Commands.literal;
public class DebugCommand extends AbstractCommand
{
@Override
public LiteralArgumentBuilder<CommandSourceStack> buildCommand()
{
return literal("debug")
.executes(c -> {
List<String> lines = new ArrayList<>();
F3Screen.addStringToDisplay(lines);
return this.sendSuccessResponse(c, String.join("\n", lines), false);
});
}
}
@@ -1,107 +0,0 @@
package com.seibel.distanthorizons.common.commands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.core.generation.PregenManager;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.DimensionArgument;
import net.minecraft.commands.arguments.coordinates.ColumnPosArgument;
import net.minecraft.server.level.ColumnPos;
import net.minecraft.server.level.ServerLevel;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
public class PregenCommand extends AbstractCommand
{
private final PregenManager pregenManager = new PregenManager();
@Override
public LiteralArgumentBuilder<CommandSourceStack> buildCommand()
{
LiteralArgumentBuilder<CommandSourceStack> statusCommand = literal("status")
.executes(this::pregenStatus);
LiteralArgumentBuilder<CommandSourceStack> startCommand = literal("start")
.then(argument("dimension", DimensionArgument.dimension())
.then(argument("origin", ColumnPosArgument.columnPos())
.then(argument("chunkRadius", integer(32))
.executes(this::pregenStart))));
LiteralArgumentBuilder<CommandSourceStack> stopCommand = literal("stop")
.executes(this::pregenStop);
return literal("pregen")
.then(statusCommand)
.then(startCommand)
.then(stopCommand);
}
private int pregenStatus(CommandContext<CommandSourceStack> c)
{
String statusString = this.pregenManager.getStatusString();
//noinspection ReplaceNullCheck
if (statusString != null)
{
return this.sendSuccessResponse(c, statusString, false);
}
else
{
return this.sendSuccessResponse(c, "Pregen is not running", false);
}
}
private int pregenStart(CommandContext<CommandSourceStack> c) throws CommandSyntaxException
{
this.sendSuccessResponse(c, "Starting pregen. Progress will be in the server console.", true);
ServerLevel level = DimensionArgument.getDimension(c, "dimension");
ColumnPos origin = ColumnPosArgument.getColumnPos(c, "origin");
int chunkRadius = getInteger(c, "chunkRadius");
CompletableFuture<Void> future = this.pregenManager.startPregen(
ServerLevelWrapper.getWrapper(level),
new DhBlockPos2D(#if MC_VER >= MC_1_19_2 origin.x(), origin.z() #else origin.x, origin.z #endif),
chunkRadius
);
future.whenComplete((result, throwable) -> {
if (throwable instanceof CancellationException)
{
this.sendSuccessResponse(c, "Pregen is cancelled", true);
return;
}
else if (throwable != null)
{
this.sendFailureResponse(c, "Pregen failed: " + throwable.getMessage() + "\n Check the logs for more details.");
return;
}
this.sendSuccessResponse(c, "Pregen is complete", true);
});
return 1;
}
private int pregenStop(CommandContext<CommandSourceStack> c)
{
CompletableFuture<Void> runningPregen = this.pregenManager.getRunningPregen();
if (runningPregen == null)
{
return this.sendFailureResponse(c, "Pregen is not running");
}
runningPregen.cancel(true);
return 1;
}
}
@@ -1,85 +0,0 @@
package com.seibel.distanthorizons.common.commonMixins;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
public class MixinChunkMapCommon
{
public static void onChunkSave(ServerLevel level, ChunkAccess chunk, CallbackInfoReturnable<Boolean> ci)
{
// is this position already being updated?
if (SharedApi.isChunkAtChunkPosAlreadyUpdating(chunk.getPos().x, chunk.getPos().z))
{
return;
}
// is this chunk being saved to disk?
boolean savingChunkToDisk = ci.getReturnValue();
// true means a chunk was saved to disk
if (!savingChunkToDisk)
{
return;
}
// TODO are the following validations necessary since we are checking above if
// the callback return value should state if the chunk was actually saved or not?
// Do we trust it to always be correct?
// corrupt/incomplete chunk validation //
// MC has a tendency to try saving incomplete or corrupted chunks (which show up as empty or black chunks)
// this logic should prevent that from happening
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
if (chunk.isUnsaved() || chunk.getUpgradeData() != null || !chunk.isLightCorrect())
{
return;
}
#else
if (chunk.isUnsaved() || chunk.isUpgrading() || !chunk.isLightCorrect())
{
return;
}
#endif
// biome validation //
// some chunks may be missing their biomes, which cause issues when attempting to save them
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
if (chunk.getBiomes() == null)
{
return;
}
#else
try
{
// this will throw an exception if the biomes aren't set up
chunk.getNoiseBiome(0,0,0);
}
catch (Exception e)
{
return;
}
#endif
// submit the update event
ServerApi.INSTANCE.serverChunkSaveEvent(
new ChunkWrapper(chunk, ServerLevelWrapper.getWrapper(level)),
ServerLevelWrapper.getWrapper(level)
);
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -22,8 +22,7 @@ package com.seibel.distanthorizons.common.wrappers;
import com.seibel.distanthorizons.common.wrappers.gui.ClassicConfigGUI;
import com.seibel.distanthorizons.common.wrappers.gui.LangWrapper;
import com.seibel.distanthorizons.common.wrappers.level.KeyedClientLevelManager;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftServerWrapper;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftDedicatedServerWrapper;
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
import com.seibel.distanthorizons.core.wrapperInterfaces.config.IConfigGui;
import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper;
@@ -33,7 +32,6 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
@@ -56,17 +54,21 @@ public class DependencySetup
SingletonInjector.INSTANCE.bind(IVersionConstants.class, VersionConstants.INSTANCE);
SingletonInjector.INSTANCE.bind(IWrapperFactory.class, WrapperFactory.INSTANCE);
SingletonInjector.INSTANCE.bind(IKeyedClientLevelManager.class, KeyedClientLevelManager.INSTANCE);
DependencySetupDoneCheck.isDone = true;
}
//@Environment(EnvType.SERVER)
public static void createServerBindings()
{ SingletonInjector.INSTANCE.bind(IMinecraftSharedWrapper.class, MinecraftServerWrapper.INSTANCE); }
{
SingletonInjector.INSTANCE.bind(IMinecraftSharedWrapper.class, MinecraftDedicatedServerWrapper.INSTANCE);
}
//@Environment(EnvType.CLIENT)
public static void createClientBindings()
{
SingletonInjector.INSTANCE.bind(IMinecraftClientWrapper.class, MinecraftClientWrapper.INSTANCE);
SingletonInjector.INSTANCE.bind(IMinecraftSharedWrapper.class, MinecraftClientWrapper.INSTANCE);
SingletonInjector.INSTANCE.bind(IMinecraftRenderWrapper.class, MinecraftRenderWrapper.INSTANCE);
SingletonInjector.INSTANCE.bind(IMinecraftGLWrapper.class, MinecraftGLWrapper.INSTANCE);
SingletonInjector.INSTANCE.bind(IConfigGui.class, ClassicConfigGUI.CONFIG_CORE_INTERFACE);
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -17,16 +17,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.fabric.wrappers.modAccessor;
package com.seibel.distanthorizons.common.wrappers;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IC2meAccessor;
import java.util.function.Supplier;
public class C2meAccessor implements IC2meAccessor
public class DependencySetupDoneCheck
{
@Override
public String getModName()
{
return "c2me";
}
// TODO move to DependencySetup
public static boolean isDone = false;
// TODO why is this here and what is its purpose?
public static Supplier<Boolean> getIsCurrentThreadDistantGeneratorThread = (() -> false);
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -20,9 +20,11 @@
package com.seibel.distanthorizons.common.wrappers;
import java.nio.FloatBuffer;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.math.Mat4f;
@@ -49,9 +51,7 @@ public class McObjectConverter
@Deprecated
public static Mat4f Convert(
#if MC_VER < MC_1_19_4 com.mojang.math.Matrix4f
#elif MC_VER < MC_1_21_6 org.joml.Matrix4f
#else org.joml.Matrix4fc
#endif
#else org.joml.Matrix4f #endif
mcMatrix)
{
FloatBuffer buffer = FloatBuffer.allocate(16);
@@ -65,9 +65,7 @@ public class McObjectConverter
/** Taken from Minecraft's com.mojang.math.Matrix4f class from 1.18.2 */
private static void storeMatrix(
#if MC_VER < MC_1_19_4 com.mojang.math.Matrix4f
#elif MC_VER < MC_1_21_6 org.joml.Matrix4f
#else org.joml.Matrix4fc
#endif
#else org.joml.Matrix4f #endif
matrix,
FloatBuffer buffer)
{
@@ -104,31 +102,15 @@ public class McObjectConverter
lodDirections = new EDhDirection[lodDirs.length];
for (EDhDirection lodDir : lodDirs)
{
Direction dir;
switch (lodDir.name().toUpperCase())
{
case "DOWN":
dir = Direction.DOWN;
break;
case "UP":
dir = Direction.UP;
break;
case "NORTH":
dir = Direction.NORTH;
break;
case "SOUTH":
dir = Direction.SOUTH;
break;
case "WEST":
dir = Direction.WEST;
break;
case "EAST":
dir = Direction.EAST;
break;
default:
dir = null;
break;
}
Direction dir = switch (lodDir.name().toUpperCase()) {
case "DOWN" -> Direction.DOWN;
case "UP" -> Direction.UP;
case "NORTH" -> Direction.NORTH;
case "SOUTH" -> Direction.SOUTH;
case "WEST" -> Direction.WEST;
case "EAST" -> Direction.EAST;
default -> null;
};
if (dir == null)
{
@@ -139,10 +121,41 @@ public class McObjectConverter
}
}
public static BlockPos Convert(DhBlockPos wrappedPos) { return new BlockPos(wrappedPos.getX(), wrappedPos.getY(), wrappedPos.getZ()); }
public static ChunkPos Convert(DhChunkPos wrappedPos) { return new ChunkPos(wrappedPos.getX(), wrappedPos.getZ()); }
public static BlockPos Convert(DhBlockPos wrappedPos)
{
return new BlockPos(wrappedPos.x, wrappedPos.y, wrappedPos.z);
}
public static ChunkPos Convert(DhChunkPos wrappedPos)
{
return new ChunkPos(wrappedPos.x, wrappedPos.z);
}
public static Direction Convert(EDhDirection lodDirection) { return directions[lodDirection.ordinal()]; }
public static EDhDirection Convert(Direction direction) { return lodDirections[direction.ordinal()]; }
public static Direction Convert(EDhDirection lodDirection)
{
return directions[lodDirection.ordinal()];
}
public static EDhDirection Convert(Direction direction)
{
return lodDirections[direction.ordinal()];
}
public static void DebugCheckAllPackers()
{
BiConsumer<Integer, Integer> func = (x, z) -> DhChunkPos._DebugCheckPacker(x, z, ChunkPos.asLong(x, z));
func.accept(0, 0);
func.accept(12345, 134);
func.accept(-12345, -134);
func.accept(-30000000 / 16, 30000000 / 16);
func.accept(30000000 / 16, -30000000 / 16);
func.accept(30000000 / 16, 30000000 / 16);
func.accept(-30000000 / 16, -30000000 / 16);
Consumer<BlockPos> func2 = (p) -> DhBlockPos._DebugCheckPacker(p.getX(), p.getY(), p.getZ(), p.asLong());
func2.accept(new BlockPos(0, 0, 0));
func2.accept(new BlockPos(12345, 134, 123));
func2.accept(new BlockPos(-12345, -134, -80));
func2.accept(new BlockPos(-30000000, 2047, 30000000));
func2.accept(new BlockPos(30000000, -2048, -30000000));
func2.accept(new BlockPos(30000000, 2047, 30000000));
func2.accept(new BlockPos(-30000000, -2048, -30000000));
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -20,6 +20,8 @@
package com.seibel.distanthorizons.common.wrappers;
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
/**
* @author James Seibel
@@ -36,59 +38,32 @@ public class VersionConstants implements IVersionConstants
}
@Override
public int getMinimumWorldHeight()
{
return 0;
}
@Override
public int getWorldGenerationCountPerThread()
{
return 1;
}
@Override
public boolean isVanillaRenderedChunkSquare()
{
return false;
}
@Override
public String getMinecraftVersion()
{
// these values are hard-coded to prevent an issue with Forge (specifically 1.18.2) where
// it can't load client classes when running as a dedicated server,
// which was how we were dynamically accessing the MC version string
#if MC_VER == MC_1_16_5
return "1.16.5";
#elif MC_VER == MC_1_17_1
return "1.17.1";
#elif MC_VER == MC_1_18_2
return "1.18.2";
#elif MC_VER == MC_1_19_2
return "1.19.2";
#elif MC_VER == MC_1_19_4
return "1.19.4";
#elif MC_VER == MC_1_20_1
return "1.20.1";
#elif MC_VER == MC_1_20_2
return "1.20.2";
#elif MC_VER == MC_1_20_4
return "1.20.4";
#elif MC_VER == MC_1_20_6
return "1.20.6";
#elif MC_VER == MC_1_21_1
return "1.21.1";
#elif MC_VER == MC_1_21_3
return "1.21.3";
#elif MC_VER == MC_1_21_4
return "1.21.4";
#elif MC_VER == MC_1_21_5
return "1.21.5";
#elif MC_VER == MC_1_21_6
return "1.21.6";
#elif MC_VER == MC_1_21_8
return "1.21.8";
#elif MC_VER == MC_1_21_9
return "1.21.9";
#elif MC_VER == MC_1_21_10
return "1.21.10";
#elif MC_VER == MC_1_21_11
return "1.21.11";
#if MC_VER < MC_1_19_2
return Minecraft.getInstance().getGame().getVersion().getId();
#else
ERROR MC version constant missing
return SharedConstants.getCurrentVersion().getId();
#endif
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -38,7 +38,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.IBatchGeneratorEnvironmentWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvironmentWrapper;
import net.minecraft.client.multiplayer.ClientLevel;
#if MC_VER > MC_1_17_1
import net.minecraft.core.Holder;
@@ -67,7 +67,7 @@ public class WrapperFactory implements IWrapperFactory
//==============//
@Override
public IBatchGeneratorEnvironmentWrapper createBatchGenerator(IDhLevel targetLevel)
public AbstractBatchGenerationEnvironmentWrapper createBatchGenerator(IDhLevel targetLevel)
{
if (targetLevel instanceof IDhServerLevel)
{
@@ -155,41 +155,60 @@ public class WrapperFactory implements IWrapperFactory
}
}
//#if MC_VER <= MC_1_XX_X
#if MC_VER <= MC_1_21
else if (objectArray.length == 2)
{
// correct number of parameters from the API
// chunk
if (!(objectArray[0] instanceof ChunkAccess))
if (!(objectArray[0] instanceof ChunkAccess chunk))
{
throw new ClassCastException(createChunkWrapperErrorMessage(objectArray));
}
ChunkAccess chunk = (ChunkAccess) objectArray[0];
// level / light source
if (!(objectArray[1] instanceof Level))
if (!(objectArray[1] instanceof Level level))
{
throw new ClassCastException(createChunkWrapperErrorMessage(objectArray));
}
// the level is needed for the DH level wrapper...
Level level = (Level) objectArray[1];
// ...the LevelReader is needed for chunk lighting
LevelReader lightSource = level;
// level wrapper
ILevelWrapper levelWrapper = level.isClientSide()
? ClientLevelWrapper.getWrapper((ClientLevel)level)
: ServerLevelWrapper.getWrapper((ServerLevel)level);
ILevelWrapper levelWrapper;
if (level instanceof ServerLevel)
{
levelWrapper = ServerLevelWrapper.getWrapper((ServerLevel)level);
}
else if (level instanceof ClientLevel)
{
levelWrapper = ClientLevelWrapper.getWrapper((ClientLevel)level);
}
else
{
throw new ClassCastException(createChunkWrapperErrorMessage(objectArray));
}
return new ChunkWrapper(chunk, levelWrapper);
return new ChunkWrapper(chunk, lightSource, levelWrapper);
}
// incorrect number of parameters from the API
else
{
throw new ClassCastException(createChunkWrapperErrorMessage(objectArray));
}
//#endif
#else
// Intentional compiler error to bring attention to the missing wrapper function.
// If you need to work on an unimplemented version but don't have the ability to implement this yet
// you can comment it out, but please don't commit it. Someone will have to implement it.
// After implementing the new version please read this method's javadocs for instructions
// on what other locations also need to be updated, the DhAPI specifically needs to
// be updated to state which objects this method accepts.
not implemented for this version of Minecraft!
#endif
}
/**
* Note: when this is updated for different MC versions,
@@ -199,13 +218,16 @@ public class WrapperFactory implements IWrapperFactory
{
String[] expectedClassNames;
//#if MC_VER <= MC_1_XX_X
#if MC_VER <= MC_1_21
expectedClassNames = new String[]
{
ChunkAccess.class.getName(),
"[ServerLevel] or [ClientLevel]" // Classes are not referenced by names to avoid exception when one of them is missing
ServerLevel.class.getName() + "] or [" + ClientLevel.class.getName()
};
//#endif
#else
// See preprocessor comment in createChunkWrapper() for full documentation
not implemented for this version of Minecraft!
#endif
return createWrapperErrorMessage("Chunk wrapper", expectedClassNames, objectArray);
}
@@ -221,11 +243,10 @@ public class WrapperFactory implements IWrapperFactory
public IDhApiBiomeWrapper getBiomeWrapper(Object[] objectArray, IDhApiLevelWrapper levelWrapper)
{
// confirm the API level wrapper is also a Core wrapper
if (!(levelWrapper instanceof ILevelWrapper))
if (!(levelWrapper instanceof ILevelWrapper coreLevelWrapper))
{
throw new ClassCastException("Invalid ["+IDhApiLevelWrapper.class.getSimpleName()+"] value given. Level wrapper object must be one given by the DH API (it can't be a custom implementation), specifically of type ["+ILevelWrapper.class.getName()+"].");
}
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
@@ -244,7 +265,7 @@ public class WrapperFactory implements IWrapperFactory
Biome biome = (Biome) objectArray[0];
return BiomeWrapper.getBiomeWrapper(biome, coreLevelWrapper);
#else
#elif MC_VER <= MC_1_21
if (!(objectArray[0] instanceof Holder) || !(((Holder<?>) objectArray[0]).value() instanceof Biome))
{
throw new ClassCastException(createBiomeWrapperErrorMessage(objectArray));
@@ -252,6 +273,9 @@ public class WrapperFactory implements IWrapperFactory
Holder<Biome> biomeHolder = (Holder<Biome>) objectArray[0];
return BiomeWrapper.getBiomeWrapper(biomeHolder, coreLevelWrapper);
#else
// See preprocessor comment in createChunkWrapper() for full documentation (not a typo, check createChunkWrapper()'s else statement for full documentation)
not implemented for this version of Minecraft!
#endif
}
/**
@@ -264,8 +288,11 @@ public class WrapperFactory implements IWrapperFactory
#if MC_VER < MC_1_18_2
expectedClassNames = new String[] { Biome.class.getName() };
#else
#elif MC_VER <= MC_1_21
expectedClassNames = new String[] { Holder.class.getName()+"<"+Biome.class.getName()+">" };
#else
// See preprocessor comment in createChunkWrapper() for full documentation
not implemented for this version of Minecraft!
#endif
return createWrapperErrorMessage("Biome wrapper", expectedClassNames, objectArray);
@@ -274,27 +301,28 @@ public class WrapperFactory implements IWrapperFactory
public IDhApiBlockStateWrapper getBlockStateWrapper(Object[] objectArray, IDhApiLevelWrapper levelWrapper)
{
// confirm the API level wrapper is also a Core wrapper
if (!(levelWrapper instanceof ILevelWrapper))
if (!(levelWrapper instanceof ILevelWrapper coreLevelWrapper))
{
throw new ClassCastException("Invalid ["+IDhApiLevelWrapper.class.getSimpleName()+"] value given. Level wrapper object must be one given by the DH API (it can't be a custom implementation), specifically of type ["+ILevelWrapper.class.getName()+"].");
}
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
//#if MC_VER <= MC_1_XX_X
#if MC_VER <= MC_1_21
if (objectArray.length != 1)
{
throw new ClassCastException(createBlockStateWrapperErrorMessage(objectArray));
}
if (!(objectArray[0] instanceof BlockState))
if (!(objectArray[0] instanceof BlockState blockState))
{
throw new ClassCastException(createBlockStateWrapperErrorMessage(objectArray));
}
BlockState blockState = (BlockState) objectArray[0];
return BlockStateWrapper.fromBlockState(blockState, coreLevelWrapper);
//#endif
#else
// See preprocessor comment in createChunkWrapper() for full documentation (not a typo, check createChunkWrapper()'s else statement for full documentation)
not implemented for this version of Minecraft!
#endif
}
/**
* Note: when this is updated for different MC versions,
@@ -306,8 +334,11 @@ public class WrapperFactory implements IWrapperFactory
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
expectedClassNames = new String[] { Biome.class.getName() };
#else
#elif MC_VER <= MC_1_21
expectedClassNames = new String[] { Holder.class.getName()+"<"+Biome.class.getName()+">" };
#else
// See preprocessor comment in createChunkWrapper() for full documentation
not implemented for this version of Minecraft!
#endif
return createWrapperErrorMessage("BlockState wrapper", expectedClassNames, objectArray);
@@ -1,337 +0,0 @@
package com.seibel.distanthorizons.common.wrappers.block;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.BlockBiomeWrapperPair;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.ColorResolver;
import net.minecraft.world.level.biome.Biome;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
#if MC_VER >= MC_1_18_2
import net.minecraft.core.Holder;
#endif
public abstract class AbstractDhTintGetter implements BlockAndTintGetter
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
#if MC_VER < MC_1_18_2
private static final ConcurrentHashMap<String, Biome> BIOME_BY_RESOURCE_STRING = new ConcurrentHashMap<>();
#else
private static final ConcurrentHashMap<String, Holder<Biome>> BIOME_BY_RESOURCE_STRING = new ConcurrentHashMap<>();
#endif
private static final ConcurrentHashMap<BlockBiomeWrapperPair, Integer> COLOR_BY_BLOCK_BIOME_PAIR = new ConcurrentHashMap<>();
/** returned if the color cache is incomplete */
public static final int INVALID_COLOR = Integer.MIN_VALUE;
protected BiomeWrapper biomeWrapper;
protected BlockStateWrapper blockStateWrapper;
protected FullDataSourceV2 fullDataSource;
protected int smoothingRadiusInBlocks;
protected IClientLevelWrapper clientLevelWrapper;
//=============//
// constructor //
//=============//
public AbstractDhTintGetter() { }
/**
* Mutates this getter so we can access the necessary
* variables for tint getting.
*/
public void update(BiomeWrapper biomeWrapper, BlockStateWrapper blockStateWrapper, FullDataSourceV2 fullDataSource, IClientLevelWrapper clientLevelWrapper)
{
this.biomeWrapper = biomeWrapper;
this.blockStateWrapper = blockStateWrapper;
this.fullDataSource = fullDataSource;
this.clientLevelWrapper = clientLevelWrapper;
this.smoothingRadiusInBlocks = Config.Client.Advanced.Graphics.Quality.lodBiomeBlending.get();
}
//================//
// shared methods //
//================//
/** Called by MC's tint getter */
@Override
public int getBlockTint(@NotNull BlockPos blockPos, @NotNull ColorResolver colorResolver)
{
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable(blockPos.getX(), blockPos.getY(), blockPos.getZ());
return this.tryGetBlockTint(mutableBlockPos, colorResolver);
}
/**
* Can be called by DH directly, skipping some of MC's logic
* to speed up tint getting slightly.
*
* @return {@link AbstractDhTintGetter#INVALID_COLOR} if any of the biomes needed for this position
* were not cached. In that case calling {@link AbstractDhTintGetter#getBlockTint(BlockPos, ColorResolver)}
* will need to be called by MC's ColorResolver so we can
* populate the color cache.
*/
public int tryGetBlockTint(DhBlockPosMutable mutableBlockPos)
{ return this.tryGetBlockTint(mutableBlockPos, null); }
private int tryGetBlockTint(DhBlockPosMutable mutableBlockPos, @Nullable ColorResolver colorResolver)
{
// determine how wide this data source is so we can determine
// if blending should be used
byte dataSourceDetailLevel = DhSectionPos.getDetailLevel(this.fullDataSource.getPos());
// convert from section detail level to absolute detail level
dataSourceDetailLevel = (byte)(dataSourceDetailLevel - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
int dataSourceLodWidthInBlocks = DhSectionPos.getDetailLevelWidthInBlocks(dataSourceDetailLevel);
// don't do any smoothing if smoothing is disabled or if the LOD
// is to large for block-based smoothing to show up
if (this.smoothingRadiusInBlocks == 0
|| dataSourceLodWidthInBlocks > this.smoothingRadiusInBlocks)
{
return this.tryGetClientBiomeColor(colorResolver, this.biomeWrapper);
}
// use a rolling average to calculate the color
int dataPointCount = 0;
int rollingRed = 0;
int rollingGreen = 0;
int rollingBlue = 0;
int xMin = mutableBlockPos.getX() - this.smoothingRadiusInBlocks;
int xMax = mutableBlockPos.getX() + this.smoothingRadiusInBlocks + 1; // +1 to account for the center block
int zMin = mutableBlockPos.getZ() - this.smoothingRadiusInBlocks;
int zMax = mutableBlockPos.getZ() + this.smoothingRadiusInBlocks + 1;
int levelMinY = this.clientLevelWrapper.getMinHeight();
for (int x = xMin; x < xMax; x++)
{
for (int z = zMin; z < zMax; z++)
{
mutableBlockPos.setX(x);
mutableBlockPos.setZ(z);
// this can return the same position/datapoint for larger LODs duplicating work,
// however for small smoothing ranges that isn't a big deal and for large LODs
// we ignore smoothing anyway
long dataPoint = this.fullDataSource.getDataPointAtBlockPos(mutableBlockPos.getX(), mutableBlockPos.getY(), mutableBlockPos.getZ(), levelMinY);
if (dataPoint == FullDataPointUtil.EMPTY_DATA_POINT)
{
continue;
}
// get the color for this nearby position
int id = FullDataPointUtil.getId(dataPoint);
BiomeWrapper biomeWrapper = (BiomeWrapper) this.fullDataSource.mapping.getBiomeWrapper(id);
int color = this.tryGetClientBiomeColor(colorResolver, biomeWrapper);
if (color == INVALID_COLOR)
{
return INVALID_COLOR;
}
// rolling average
rollingRed += ColorUtil.getRed(color);
rollingGreen += ColorUtil.getGreen(color);
rollingBlue += ColorUtil.getBlue(color);
dataPointCount++;
}
}
// if no data was present (rarely possible)
// just use the default center's color
if (dataPointCount == 0)
{
return this.tryGetClientBiomeColor(colorResolver, this.biomeWrapper);
}
int colorInt = ColorUtil.argbToInt(
255, // blending often ignores alpha, having it always 255 prevents multiplication issues later
rollingRed / dataPointCount,
rollingGreen / dataPointCount,
rollingBlue / dataPointCount);
return colorInt;
}
/**
* If given a ColorResolver this will always succeed. <Br>
* If not it will attempt to use the cached color.
*/
private int tryGetClientBiomeColor(@Nullable ColorResolver colorResolver, BiomeWrapper biomeWrapper)
{
BlockBiomeWrapperPair pair = BlockBiomeWrapperPair.get(this.blockStateWrapper, biomeWrapper);
// use the cached color if possible
Integer cachedColor = COLOR_BY_BLOCK_BIOME_PAIR.get(pair); // explicit Integer return here reduces unnecessary allocations
if (cachedColor != null)
{
return cachedColor;
}
if (colorResolver == null)
{
// no color resolver is present,
// the cache needs to be populated before
// we can use the fast path
return INVALID_COLOR;
}
int color = colorResolver.getColor(unwrapClientBiome(biomeWrapper), 0, 0);
COLOR_BY_BLOCK_BIOME_PAIR.put(pair, color);
return color;
}
protected static Biome unwrapClientBiome(BiomeWrapper biomeWrapper)
{
String biomeString = biomeWrapper.getSerialString();
if (biomeString == null
|| biomeString.isEmpty()
|| biomeString.equals(BiomeWrapper.EMPTY_BIOME_STRING))
{
// default to "plains" for empty/invalid biomes
biomeString = "minecraft:plains";
}
return unwrapBiome(getClientBiome(biomeString));
}
protected static Biome unwrapBiome(#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
}
/**
* <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>
*/
private static #if MC_VER < MC_1_18_2 Biome #else Holder<Biome> #endif getClientBiome(String biomeResourceString)
{
#if MC_VER < MC_1_18_2
Biome biome;
#else
Holder<Biome> biome;
#endif
// calling get instead of compute is slightly faster for already
// computed values
biome = BIOME_BY_RESOURCE_STRING.get(biomeResourceString);
if (biome != null)
{
return biome;
}
// cache the client biomes so we don't have to re-parse the resource location every time
return BIOME_BY_RESOURCE_STRING.compute(biomeResourceString,
(resourceString, existingBiome) ->
{
if (existingBiome != null)
{
return existingBiome;
}
ClientLevel clientLevel = Minecraft.getInstance().level;
if (clientLevel == null)
{
// shouldn't happen, but just in case
throw new IllegalStateException("Attempted to get client biome when no client level was loaded.");
}
BiomeWrapper.BiomeDeserializeResult result;
try
{
result = BiomeWrapper.deserializeBiome(resourceString, clientLevel.registryAccess());
}
catch (Exception e)
{
LOGGER.warn("Unable to deserialize client biome ["+resourceString+"], using fallback...");
try
{
result = BiomeWrapper.deserializeBiome(BiomeWrapper.PLAINS_RESOURCE_LOCATION_STRING, clientLevel.registryAccess());
}
catch (IOException ex)
{
// should never happen, if it does this log will explode, but just in case
LOGGER.error("Unable to deserialize fallback client biome ["+BiomeWrapper.PLAINS_RESOURCE_LOCATION_STRING+"], returning NULL.");
return null;
}
}
if (result.success)
{
existingBiome = result.biome;
}
return existingBiome;
});
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -22,14 +22,13 @@ package com.seibel.distanthorizons.common.wrappers.block;
import java.io.IOException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import net.minecraft.world.level.Level;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
@@ -45,14 +44,8 @@ import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
#endif
#if MC_VER <= MC_1_21_10
import net.minecraft.resources.ResourceLocation;
#else
import net.minecraft.resources.Identifier;
#endif
import net.minecraft.world.level.biome.Biome;
#if MC_VER >= MC_1_18_2
import net.minecraft.world.level.biome.Biomes;
#endif
@@ -62,7 +55,7 @@ import net.minecraft.world.level.biome.Biomes;
public class BiomeWrapper implements IBiomeWrapper
{
// must be defined before AIR, otherwise a null pointer will be thrown
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final Logger LOGGER = LogManager.getLogger();
#if MC_VER < MC_1_18_2
@@ -108,7 +101,7 @@ public class BiomeWrapper implements IBiomeWrapper
// constructors //
//==============//
public static BiomeWrapper getBiomeWrapper(#if MC_VER < MC_1_18_2 Biome #else Holder<Biome> #endif biome, ILevelWrapper levelWrapper)
static public IBiomeWrapper getBiomeWrapper(#if MC_VER < MC_1_18_2 Biome #else Holder<Biome> #endif biome, ILevelWrapper levelWrapper)
{
if (biome == null)
{
@@ -116,10 +109,9 @@ public class BiomeWrapper implements IBiomeWrapper
}
BiomeWrapper biomeWrapper = WRAPPER_BY_BIOME.get(biome);
if (biomeWrapper != null)
if (WRAPPER_BY_BIOME.containsKey(biome))
{
return biomeWrapper;
return WRAPPER_BY_BIOME.get(biome);
}
else
{
@@ -137,6 +129,14 @@ public class BiomeWrapper implements IBiomeWrapper
//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);
}
//=========//
@@ -221,20 +221,13 @@ public class BiomeWrapper implements IBiomeWrapper
Level level = (Level)levelWrapper.getWrappedMcObject();
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
#if MC_VER < MC_1_21_11
ResourceLocation resourceLocation;
#else
Identifier resourceLocation;
#endif
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
resourceLocation = registryAccess.registryOrThrow(Registry.BIOME_REGISTRY).getKey(this.biome);
#elif MC_VER == MC_1_18_2 || MC_VER == MC_1_19_2
resourceLocation = registryAccess.registryOrThrow(Registry.BIOME_REGISTRY).getKey(this.biome.value());
#elif MC_VER < MC_1_21_3
resourceLocation = registryAccess.registryOrThrow(Registries.BIOME).getKey(this.biome.value());
#else
resourceLocation = registryAccess.lookupOrThrow(Registries.BIOME).getKey(this.biome.value());
resourceLocation = registryAccess.registryOrThrow(Registries.BIOME).getKey(this.biome.value());
#endif
if (resourceLocation == null)
@@ -273,7 +266,7 @@ public class BiomeWrapper implements IBiomeWrapper
}
return EMPTY_WRAPPER;
}
else if (resourceLocationString.trim().isEmpty() || resourceLocationString.equals(""))
else if (resourceLocationString.trim().isEmpty() || resourceLocationString.isEmpty())
{
LOGGER.warn("Null biome string deserialized.");
return EMPTY_WRAPPER;
@@ -290,16 +283,50 @@ public class BiomeWrapper implements IBiomeWrapper
BiomeWrapper foundWrapper = EMPTY_WRAPPER;
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
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
{
Level level = (Level) levelWrapper.getWrappedMcObject();
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
BiomeDeserializeResult deserializeResult = deserializeBiome(resourceLocationString, registryAccess);
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);
#else
Biome unwrappedBiome = registryAccess.registryOrThrow(Registries.BIOME).get(resourceLocation);
success = (unwrappedBiome != null);
Holder<Biome> biome = new Holder.Direct<>(unwrappedBiome);
#endif
if (!deserializeResult.success)
if (!success)
{
if (!brokenResourceLocationStrings.contains(resourceLocationString))
{
@@ -310,7 +337,7 @@ public class BiomeWrapper implements IBiomeWrapper
}
foundWrapper = getBiomeWrapper(deserializeResult.biome, levelWrapper);
foundWrapper = (BiomeWrapper) getBiomeWrapper(biome, levelWrapper);
return foundWrapper;
}
catch (Exception e)
@@ -324,88 +351,4 @@ 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 + "].");
}
#if MC_VER < MC_1_21_11
ResourceLocation resourceLocation;
#else
Identifier resourceLocation;
#endif
try
{
#if MC_VER <= MC_1_20_6
resourceLocation = new ResourceLocation(resourceLocationString.substring(0, separatorIndex), resourceLocationString.substring(separatorIndex + 1));
#elif MC_VER <= MC_1_21_10
resourceLocation = ResourceLocation.fromNamespaceAndPath(resourceLocationString.substring(0, separatorIndex), resourceLocationString.substring(separatorIndex + 1));
#else
resourceLocation = Identifier.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;
}
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -28,47 +28,37 @@ import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.block.BeaconBeamBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockState;
import com.seibel.distanthorizons.core.logging.DhLogger;
import net.minecraft.world.level.block.state.properties.Property;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
import net.minecraft.core.Registry;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.EmptyBlockGetter;
#elif MC_VER == MC_1_18_2 || MC_VER == MC_1_19_2
import net.minecraft.tags.TagKey;
import net.minecraft.client.Minecraft;
import net.minecraft.world.level.Level;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.world.level.EmptyBlockGetter;
#else
import net.minecraft.tags.TagKey;
import net.minecraft.client.Minecraft;
import net.minecraft.world.level.Level;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.EmptyBlockGetter;
import net.minecraft.core.Holder;
#endif
#if MC_VER <= MC_1_21_10
import net.minecraft.resources.ResourceLocation;
#else
import net.minecraft.resources.Identifier;
#endif
public class BlockStateWrapper implements IBlockStateWrapper
@@ -80,7 +70,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
// must be defined before AIR, otherwise a null pointer will be thrown
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static final ConcurrentHashMap<BlockState, BlockStateWrapper> WRAPPER_BY_BLOCK_STATE = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<String, BlockStateWrapper> WRAPPER_BY_RESOURCE_LOCATION = new ConcurrentHashMap<>();
@@ -89,46 +79,32 @@ public class BlockStateWrapper implements IBlockStateWrapper
public static final BlockStateWrapper AIR = new BlockStateWrapper(null, null);
public static final String DIRT_RESOURCE_LOCATION_STRING = "minecraft:dirt";
public static final String WATER_RESOURCE_LOCATION_STRING = "minecraft:water";
/** Used to handle older MC versions that don't have an simple way of getting the block's tags */
public static final List<String> OLD_BEACON_BASE_BLOCK_NAME_LIST = Arrays.asList(
"iron_block",
"gold_block",
"diamond_block",
"emerald_block",
"netherite_block"
);
public static HashSet<IBlockStateWrapper> rendererIgnoredBlocks = null;
public static HashSet<IBlockStateWrapper> rendererIgnoredCaveBlocks = null;
/** keep track of broken blocks so we don't log every time */
#if MC_VER <= MC_1_21_10
private static final HashSet<ResourceLocation> BROKEN_RESOURCE_LOCATIONS = new HashSet<>();
#else
private static final HashSet<Identifier> BROKEN_RESOURCE_LOCATIONS = new HashSet<>();
#endif
private static final HashSet<ResourceLocation> BrokenResourceLocations = new HashSet<>();
// properties //
@Nullable
public final BlockState blockState;
/** technically final, but since it requires a method call to generate it can't be marked as such */
private String serialString;
private final int hashCode;
/** Should be between {@link LodUtil#BLOCK_FULLY_OPAQUE} and {@link LodUtil#BLOCK_FULLY_OPAQUE} */
private final int opacity;
/**
* Cached opacity value, -1 if not populated. <br>
* 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 */
private byte blockMaterialId = 0;
private final boolean isBeaconBlock;
private final boolean isBeaconBaseBlock;
private final boolean allowsBeaconBeamPassage;
/** null if this block can't tint beacons */
private final Color beaconTintColor;
private final boolean isGlassBlock;
private final Color mapColor;
@@ -157,45 +133,18 @@ public class BlockStateWrapper implements IBlockStateWrapper
}
}
/**
* Can be faster than {@link BlockStateWrapper#fromBlockState(BlockState, ILevelWrapper)}
* in cases where the same block state is expected to be referenced multiple times.
*/
public static BlockStateWrapper fromBlockState(BlockState blockState, ILevelWrapper levelWrapper, IBlockStateWrapper guess)
{
BlockState guessBlockState = (guess == null || guess.isAir()) ? null : (BlockState) guess.getWrappedMcObject();
BlockState inputBlockState = (blockState == null || blockState.isAir()) ? null : blockState;
if (guess instanceof BlockStateWrapper
&& guessBlockState == inputBlockState)
{
return (BlockStateWrapper) guess;
}
else
{
return fromBlockState(blockState, levelWrapper);
}
}
private BlockStateWrapper(BlockState blockState, ILevelWrapper levelWrapper)
{
this.blockState = blockState;
this.serialString = this.serialize(levelWrapper);
this.hashCode = Objects.hash(this.serialString);
this.blockMaterialId = this.calculateEDhApiBlockMaterialId().index;
this.opacity = this.calculateOpacity();
String lowercaseSerial = this.serialString.toLowerCase();
// beacon base blocks
#if MC_VER <= MC_1_18_2
// Older MC versions are harder to get block tags, so just use a static list to determine beacon blocks
boolean isBeaconBaseBlock = false;
for (int i = 0; i < OLD_BEACON_BASE_BLOCK_NAME_LIST.size(); i++)
for (int i = 0; i < LodUtil.BEACON_BASE_BLOCK_NAME_LIST.size(); i++)
{
String baseBlockName = OLD_BEACON_BASE_BLOCK_NAME_LIST.get(i);
String baseBlockName = LodUtil.BEACON_BASE_BLOCK_NAME_LIST.get(i);
if (lowercaseSerial.contains(baseBlockName))
{
isBeaconBaseBlock = true;
@@ -203,83 +152,8 @@ public class BlockStateWrapper implements IBlockStateWrapper
}
}
this.isBeaconBaseBlock = isBeaconBaseBlock;
#else
if (blockState != null)
{
// check if this block has any tags
Stream<TagKey<Block>> tags = blockState.getTags();
this.isBeaconBaseBlock = tags.anyMatch((TagKey<Block> tag) -> tag.location().getPath().toLowerCase().contains("beacon_base_blocks"));
}
else
{
this.isBeaconBaseBlock = false;
}
#endif
// beacon block
this.isBeaconBlock = lowercaseSerial.contains("minecraft:beacon");
// beacon tint color
Color beaconTintColor = null;
if (this.blockState != null
// beacon blocks also show up here, but since they block the beacon beam we don't want their color
&& !this.isBeaconBlock)
{
Block block = this.blockState.getBlock();
if (block instanceof BeaconBeamBlock)
{
int colorInt;
#if MC_VER <= MC_1_19_4
colorInt = ((BeaconBeamBlock) block).getColor().getMaterialColor().col;
#else
colorInt = ((BeaconBeamBlock) block).getColor().getMapColor().col;
#endif
beaconTintColor = ColorUtil.toColorObjRGB(colorInt);
}
}
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 (lowercaseSerial.contains("minecraft:tinted_glass"))
{
// tinted glass is a special case where it isn't fully opaque,
// but should block beacons
allowsBeaconBeamPassage = false;
}
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;
this.isGlassBlock = lowercaseSerial.contains("glass");
int mcColor = 0;
if (this.blockState != null)
@@ -319,7 +193,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
HashSet<String> baseIgnoredBlock = new HashSet<>();
baseIgnoredBlock.add(AIR_STRING);
rendererIgnoredBlocks = getAllBlockWrappers(Config.Client.Advanced.Graphics.Culling.ignoredRenderBlockCsv, baseIgnoredBlock, levelWrapper);
rendererIgnoredBlocks = getBlockWrappers(Config.Client.Advanced.LodBuilding.ignoredRenderBlockCsv, baseIgnoredBlock, levelWrapper);
return rendererIgnoredBlocks;
}
/**
@@ -336,7 +210,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
HashSet<String> baseIgnoredBlock = new HashSet<>();
baseIgnoredBlock.add(AIR_STRING);
rendererIgnoredCaveBlocks = getAllBlockWrappers(Config.Client.Advanced.Graphics.Culling.ignoredRenderCaveBlockCsv, baseIgnoredBlock, levelWrapper);
rendererIgnoredCaveBlocks = getBlockWrappers(Config.Client.Advanced.LodBuilding.ignoredRenderCaveBlockCsv, baseIgnoredBlock, levelWrapper);
return rendererIgnoredCaveBlocks;
}
@@ -347,7 +221,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
// lod builder helpers //
private static HashSet<IBlockStateWrapper> getAllBlockWrappers(ConfigEntry<String> config, HashSet<String> baseResourceLocations, ILevelWrapper levelWrapper)
private static HashSet<IBlockStateWrapper> getBlockWrappers(ConfigEntry<String> config, HashSet<String> baseResourceLocations, ILevelWrapper levelWrapper)
{
// get the base blocks
HashSet<String> blockStringList = new HashSet<>();
@@ -360,12 +234,12 @@ public class BlockStateWrapper implements IBlockStateWrapper
String ignoreBlockCsv = config.get();
if (ignoreBlockCsv != null)
{
blockStringList.addAll(Arrays.asList(ignoreBlockCsv.split(",")));
blockStringList.addAll(List.of(ignoreBlockCsv.split(",")));
}
return getAllBlockWrappers(blockStringList, levelWrapper);
return getBlockWrappers(blockStringList, levelWrapper);
}
private static HashSet<IBlockStateWrapper> getAllBlockWrappers(HashSet<String> blockResourceLocationSet, ILevelWrapper levelWrapper)
private static HashSet<IBlockStateWrapper> getBlockWrappers(HashSet<String> blockResourceLocationSet, ILevelWrapper levelWrapper)
{
// deserialize each of the given resource locations
HashSet<IBlockStateWrapper> blockStateWrappers = new HashSet<>();
@@ -424,13 +298,13 @@ public class BlockStateWrapper implements IBlockStateWrapper
//=================//
@Override
public int getOpacity() { return this.opacity; }
private int calculateOpacity()
public int getOpacity()
{
// get block properties (defaults to the values used by air)
boolean canOcclude = this.getCanOcclude();
boolean propagatesSkyLightDown = this.getPropagatesSkyLightDown();
// use the cached opacity value if possible
if (this.opacity != -1)
{
return this.opacity;
}
// this method isn't perfect, but works well enough for our use case
@@ -439,20 +313,15 @@ public class BlockStateWrapper implements IBlockStateWrapper
{
opacity = LodUtil.BLOCK_FULLY_TRANSPARENT;
}
else if (this.isLiquid() && !canOcclude)
else if (this.isLiquid() && !this.blockState.canOcclude())
{
// probably not a waterlogged block (which should block light entirely)
// +1 to indicate that the block is translucent (in between transparent and opaque)
opacity = LodUtil.BLOCK_FULLY_TRANSPARENT + 1;
}
else if (propagatesSkyLightDown && !canOcclude)
else if (this.blockState.propagatesSkylightDown(EmptyBlockGetter.INSTANCE, BlockPos.ZERO))
{
// probably glass or some other fully transparent block
// !canOcclude is required to ignore stairs and slabs since
// propagateSkyLightDown is true for them, but they're solid and don't actually let light through
opacity = LodUtil.BLOCK_FULLY_TRANSPARENT;
}
else
@@ -462,37 +331,9 @@ public class BlockStateWrapper implements IBlockStateWrapper
}
return opacity;
this.opacity = 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
public int getLightEmission() { return (this.blockState != null) ? this.blockState.getLightEmission() : 0; }
@@ -532,11 +373,6 @@ public class BlockStateWrapper implements IBlockStateWrapper
@Override
public boolean isSolid()
{
if (this.isAir())
{
return false;
}
#if MC_VER < MC_1_20_1
return this.blockState.getMaterial().isSolid();
#else
@@ -564,14 +400,10 @@ public class BlockStateWrapper implements IBlockStateWrapper
@Override
public boolean isBeaconBaseBlock() { return this.isBeaconBaseBlock; }
@Override
public boolean isBeaconTintBlock() { return this.beaconTintColor != null; }
@Override
public boolean allowsBeaconBeamPassage() { return this.allowsBeaconBeamPassage; }
public boolean isGlassBlock() { return this.isGlassBlock; }
@Override
public Color getMapColor() { return this.mapColor; }
@Override
public Color getBeaconTintColor() { return this.beaconTintColor; }
@Override
public byte getMaterialId() { return this.blockMaterialId; }
@@ -600,20 +432,13 @@ public class BlockStateWrapper implements IBlockStateWrapper
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
#endif
#if MC_VER < MC_1_21_11
ResourceLocation resourceLocation;
#else
Identifier resourceLocation;
#endif
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
resourceLocation = Registry.BLOCK.getKey(this.blockState.getBlock());
#elif MC_VER == MC_1_18_2 || MC_VER == MC_1_19_2
resourceLocation = registryAccess.registryOrThrow(Registry.BLOCK_REGISTRY).getKey(this.blockState.getBlock());
#elif MC_VER < MC_1_21_3
resourceLocation = registryAccess.registryOrThrow(Registries.BLOCK).getKey(this.blockState.getBlock());
#else
resourceLocation = registryAccess.lookupOrThrow(Registries.BLOCK).getKey(this.blockState.getBlock());
resourceLocation = registryAccess.registryOrThrow(Registries.BLOCK).getKey(this.blockState.getBlock());
#endif
@@ -637,8 +462,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
// we need the final string for the concurrent hash map later
final String finalResourceStateString = resourceStateString;
if (finalResourceStateString.equals(AIR_STRING)
|| finalResourceStateString.equals("")) // the empty string shouldn't normally happen, but just in case
if (finalResourceStateString.equals(AIR_STRING) || finalResourceStateString.isEmpty()) // the empty string shouldn't normally happen, but just in case
{
return AIR;
}
@@ -672,20 +496,13 @@ public class BlockStateWrapper implements IBlockStateWrapper
throw new IOException("Unable to parse Resource Location out of string: [" + resourceStateString + "].");
}
#if MC_VER < MC_1_21_11
ResourceLocation resourceLocation;
#else
Identifier resourceLocation;
#endif
try
{
#if MC_VER < MC_1_21_1
#if MC_VER < MC_1_21
resourceLocation = new ResourceLocation(resourceStateString.substring(0, separatorIndex), resourceStateString.substring(separatorIndex + 1));
#elif MC_VER <= MC_1_21_10
resourceLocation = ResourceLocation.fromNamespaceAndPath(resourceStateString.substring(0, separatorIndex), resourceStateString.substring(separatorIndex + 1));
#else
resourceLocation = Identifier.fromNamespaceAndPath(resourceStateString.substring(0, separatorIndex), resourceStateString.substring(separatorIndex + 1));
resourceLocation = ResourceLocation.fromNamespaceAndPath(resourceStateString.substring(0, separatorIndex), resourceStateString.substring(separatorIndex + 1));
#endif
}
catch (Exception e)
@@ -700,8 +517,9 @@ public class BlockStateWrapper implements IBlockStateWrapper
{
#if MC_VER > MC_1_17_1
LodUtil.assertTrue(levelWrapper != null && levelWrapper.getWrappedMcObject() != null);
Level level = (Level)levelWrapper.getWrappedMcObject();
// use the given level if possible, otherwise try using the currently loaded one
Level level = (levelWrapper != null ? (Level) levelWrapper.getWrappedMcObject() : null);
level = (level == null ? Minecraft.getInstance().level : level);
#endif
Block block;
@@ -710,22 +528,18 @@ public class BlockStateWrapper implements IBlockStateWrapper
#elif MC_VER == MC_1_18_2 || MC_VER == MC_1_19_2
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
block = registryAccess.registryOrThrow(Registry.BLOCK_REGISTRY).get(resourceLocation);
#elif MC_VER < MC_1_21_3
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
block = registryAccess.registryOrThrow(Registries.BLOCK).get(resourceLocation);
#else
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
Optional<Holder.Reference<Block>> optionalBlockHolder = registryAccess.lookupOrThrow(Registries.BLOCK).get(resourceLocation);
block = optionalBlockHolder.isPresent() ? optionalBlockHolder.get().value() : null;
block = registryAccess.registryOrThrow(Registries.BLOCK).get(resourceLocation);
#endif
if (block == null)
{
// shouldn't normally happen, but here to make the compiler happy
if (!BROKEN_RESOURCE_LOCATIONS.contains(resourceLocation))
if (!BrokenResourceLocations.contains(resourceLocation))
{
BROKEN_RESOURCE_LOCATIONS.add(resourceLocation);
BrokenResourceLocations.add(resourceLocation);
LOGGER.warn("Unable to find BlockState with the resourceLocation [" + resourceLocation + "] and properties: [" + blockStatePropertiesString + "]. Air will be used instead, some data may be lost.");
}
@@ -755,9 +569,9 @@ public class BlockStateWrapper implements IBlockStateWrapper
if (blockStatePropertiesString != null)
{
// we should have found a blockstate, but didn't
if (!BROKEN_RESOURCE_LOCATIONS.contains(resourceLocation))
if (!BrokenResourceLocations.contains(resourceLocation))
{
BROKEN_RESOURCE_LOCATIONS.add(resourceLocation);
BrokenResourceLocations.add(resourceLocation);
LOGGER.warn("Unable to find BlockState for Block [" + resourceLocation + "] with properties: [" + blockStatePropertiesString + "]. Using the default block state.");
}
}
@@ -789,7 +603,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
// alphabetically sort the list so they are always in the same order
List<net.minecraft.world.level.block.state.properties.Property<?>> sortedBlockPropteryList = new ArrayList<>(blockPropertyCollection);
sortedBlockPropteryList.sort((a, b) -> a.getName().compareTo(b.getName()));
sortedBlockPropteryList.sort(Comparator.comparing(Property::getName));
StringBuilder stringBuilder = new StringBuilder();
@@ -1,601 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.*;
#if MC_VER >= MC_1_19_2
import net.minecraft.util.RandomSource;
#else
import java.util.Random;
#endif
import net.minecraft.world.level.block.state.BlockState;
import com.seibel.distanthorizons.core.logging.DhLogger;
import net.minecraft.world.level.block.state.properties.SlabType;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
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
* the given {@link BlockState} should have based
* on the given {@link IClientLevelWrapper}.
*
* @see ColorUtil
*/
public class ClientBlockStateColorCache
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final Minecraft MC = Minecraft.getInstance();
private static final HashSet<BlockState> BLOCK_STATES_THAT_NEED_LEVEL = new HashSet<>();
private static final HashSet<BlockState> BROKEN_BLOCK_STATES = new HashSet<>();
/**
* Methods using MC's "RandomSource" object aren't thread safe <br>
* so we need to put locks around that logic. <br>
* specifically:
* <code>
* getBlockModel(this.blockState).getQuads(this.blockState, direction, RANDOM)
* </code>
*/
private static final ReentrantLock RESOLVE_LOCK = new ReentrantLock();
/** This is the order each direction on a block is processed when attempting to get the texture/color */
private static final @Nullable Direction[] COLOR_RESOLUTION_DIRECTION_ORDER =
{
Direction.UP,
null, // null represents "unculled" faces, IE the top of farmland
Direction.NORTH,
Direction.EAST,
Direction.WEST,
Direction.SOUTH,
Direction.DOWN
};
private static final int FLOWER_COLOR_SCALE = 5;
#if MC_VER < MC_1_19_2
private static final Random RANDOM = new Random(0);
#else
/** Note: this object isn't thread safe and must be put in a lock */
private static final RandomSource RANDOM = RandomSource.create();
#endif
private final IClientLevelWrapper clientLevelWrapper;
private final BlockState blockState;
private final BlockStateWrapper blockStateWrapper;
private boolean isColorResolved = false;
private int baseColor = 0;
private boolean needPostTinting = false;
private int tintIndex = 0;
//===========//
// constants //
//===========//
private static final int MIN_SRGB_BITS = 0x39000000; // 2^(-13)
private static final int MAX_SRGB_BITS = 0x3f7fffff; // 1.0 - f32::EPSILON
private static final float MIN_SRGB_BOUND = Float.intBitsToFloat(MIN_SRGB_BITS);
private static final float MAX_SRGB_BOUND = Float.intBitsToFloat(MAX_SRGB_BITS);
private static final int[] linearToSrgbTable = new int[]
{
0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d,
0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a,
0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033,
0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067,
0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5,
0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2,
0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143,
0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af,
0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240,
0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300,
0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401,
0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559,
0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723,
};
private static final float[] srgbToLinearTable = new float[]
{
0.0f, 0.000303527f, 0.000607054f, 0.00091058103f, 0.001214108f, 0.001517635f, 0.0018211621f, 0.002124689f,
0.002428216f, 0.002731743f, 0.00303527f, 0.0033465356f, 0.003676507f, 0.004024717f, 0.004391442f,
0.0047769533f, 0.005181517f, 0.0056053917f, 0.0060488326f, 0.006512091f, 0.00699541f, 0.0074990317f,
0.008023192f, 0.008568125f, 0.009134057f, 0.009721218f, 0.010329823f, 0.010960094f, 0.011612245f,
0.012286487f, 0.012983031f, 0.013702081f, 0.014443844f, 0.015208514f, 0.015996292f, 0.016807375f,
0.017641952f, 0.018500218f, 0.019382361f, 0.020288562f, 0.02121901f, 0.022173883f, 0.023153365f,
0.02415763f, 0.025186857f, 0.026241222f, 0.027320892f, 0.028426038f, 0.029556843f, 0.03071345f, 0.03189604f,
0.033104774f, 0.03433981f, 0.035601325f, 0.036889452f, 0.038204376f, 0.039546248f, 0.04091521f, 0.042311423f,
0.043735042f, 0.045186214f, 0.046665095f, 0.048171833f, 0.049706575f, 0.051269468f, 0.052860655f, 0.05448028f,
0.056128494f, 0.057805434f, 0.05951124f, 0.06124607f, 0.06301003f, 0.06480328f, 0.06662595f, 0.06847818f,
0.07036011f, 0.07227186f, 0.07421358f, 0.07618539f, 0.07818743f, 0.08021983f, 0.082282715f, 0.084376216f,
0.086500466f, 0.088655606f, 0.09084173f, 0.09305898f, 0.095307484f, 0.09758736f, 0.09989874f, 0.10224175f,
0.10461649f, 0.10702311f, 0.10946172f, 0.111932434f, 0.11443538f, 0.116970696f, 0.11953845f, 0.12213881f,
0.12477186f, 0.12743773f, 0.13013652f, 0.13286836f, 0.13563336f, 0.13843165f, 0.14126332f, 0.1441285f,
0.1470273f, 0.14995982f, 0.15292618f, 0.1559265f, 0.15896086f, 0.16202943f, 0.16513224f, 0.16826946f,
0.17144115f, 0.17464745f, 0.17788847f, 0.1811643f, 0.18447503f, 0.1878208f, 0.19120172f, 0.19461787f,
0.19806935f, 0.2015563f, 0.20507877f, 0.2086369f, 0.21223079f, 0.21586053f, 0.21952623f, 0.22322798f,
0.22696589f, 0.23074007f, 0.23455065f, 0.23839766f, 0.2422812f, 0.2462014f, 0.25015837f, 0.25415218f,
0.2581829f, 0.26225072f, 0.26635566f, 0.27049786f, 0.27467737f, 0.27889434f, 0.2831488f, 0.2874409f,
0.2917707f, 0.29613832f, 0.30054384f, 0.30498737f, 0.30946895f, 0.31398875f, 0.31854683f, 0.32314324f,
0.32777813f, 0.33245158f, 0.33716366f, 0.34191445f, 0.3467041f, 0.3515327f, 0.35640025f, 0.36130688f,
0.3662527f, 0.37123778f, 0.37626222f, 0.3813261f, 0.38642952f, 0.39157256f, 0.3967553f, 0.40197787f,
0.4072403f, 0.4125427f, 0.41788515f, 0.42326775f, 0.42869055f, 0.4341537f, 0.43965724f, 0.44520125f,
0.45078585f, 0.45641106f, 0.46207705f, 0.46778384f, 0.47353154f, 0.47932023f, 0.48514998f, 0.4910209f,
0.49693304f, 0.5028866f, 0.50888145f, 0.5149178f, 0.5209957f, 0.52711535f, 0.5332766f, 0.5394797f,
0.5457247f, 0.5520116f, 0.5583406f, 0.5647117f, 0.57112503f, 0.57758063f, 0.5840786f, 0.590619f, 0.597202f,
0.60382754f, 0.61049575f, 0.61720675f, 0.62396055f, 0.63075733f, 0.637597f, 0.6444799f, 0.6514058f,
0.65837497f, 0.66538745f, 0.67244333f, 0.6795426f, 0.68668544f, 0.69387203f, 0.70110214f, 0.70837605f,
0.7156938f, 0.72305536f, 0.730461f, 0.7379107f, 0.7454045f, 0.75294244f, 0.76052475f, 0.7681514f, 0.77582246f,
0.78353804f, 0.79129815f, 0.79910296f, 0.8069525f, 0.8148468f, 0.822786f, 0.8307701f, 0.83879924f, 0.84687346f,
0.8549928f, 0.8631574f, 0.87136734f, 0.8796226f, 0.8879232f, 0.89626956f, 0.90466136f, 0.913099f, 0.92158204f,
0.93011117f, 0.9386859f, 0.9473069f, 0.9559735f, 0.9646866f, 0.9734455f, 0.98225087f, 0.9911022f, 1.0f
};
private static final ThreadLocal<TintWithoutLevelOverrider> TintWithoutLevelOverrideGetter = ThreadLocal.withInitial(() -> new TintWithoutLevelOverrider());
private static final ThreadLocal<TintGetterOverride> TintOverrideGetter = ThreadLocal.withInitial(() -> new TintGetterOverride());
//=============//
// constructor //
//=============//
public ClientBlockStateColorCache(BlockState blockState, IClientLevelWrapper clientLevelWrapper)
{
this.blockState = blockState;
this.blockStateWrapper = BlockStateWrapper.fromBlockState(blockState, clientLevelWrapper);
this.clientLevelWrapper = clientLevelWrapper;
this.resolveColors();
}
//===================//
// color calculation //
//===================//
private void resolveColors()
{
if (this.isColorResolved)
{
return;
}
try
{
// getQuads() isn't thread safe so we need to put this logic in a lock
RESOLVE_LOCK.lock();
if (this.blockState.getFluidState().isEmpty())
{
// look for the first non-empty direction
List<BakedQuad> quads = null;
for (Direction direction : COLOR_RESOLUTION_DIRECTION_ORDER)
{
quads = this.getQuadsForDirection(direction);
if (quads != null && !quads.isEmpty()
&& !(
this.blockState.getBlock() instanceof RotatedPillarBlock
&& direction == Direction.UP
)
)
{
break;
}
}
if (quads == null || quads.isEmpty())
{
quads = this.getUnculledQuads();
}
if (quads != null
&& !quads.isEmpty()
&& quads.get(0) != null)
{
BakedQuad firstQuad = quads.get(0);
this.needPostTinting = firstQuad.isTinted();
#if MC_VER <= MC_1_21_4
this.tintIndex = firstQuad.getTintIndex();
#else
this.tintIndex = firstQuad.tintIndex();
#endif
#if MC_VER < MC_1_17_1
this.baseColor = calculateColorFromTexture(
firstQuad.sprite,
EColorMode.getColorMode(this.blockState.getBlock()));
#elif MC_VER < MC_1_21_5
this.baseColor = calculateColorFromTexture(
firstQuad.getSprite(),
EColorMode.getColorMode(this.blockState.getBlock()));
#else
this.baseColor = calculateColorFromTexture(
firstQuad.sprite(),
EColorMode.getColorMode(this.blockState.getBlock()));
#endif
}
else
{
// Backup method.
this.needPostTinting = false;
this.tintIndex = 0;
this.baseColor = this.getParticleIconColor();
}
}
else
{
// Liquid Block
this.needPostTinting = true;
this.tintIndex = 0;
this.baseColor = this.getParticleIconColor();
}
this.isColorResolved = true;
}
finally
{
RESOLVE_LOCK.unlock();
}
}
@Nullable
private List<BakedQuad> getUnculledQuads() { return this.getQuadsForDirection(null); }
@Nullable
private List<BakedQuad> getQuadsForDirection(@Nullable Direction direction)
{
BlockState effectiveBlockState = this.blockState;
// if this block is a slab, use it's double variant so we can get the top face,
// otherwise the color will use the side, which isn't as accurate
if (this.blockState.getBlock() instanceof SlabBlock)
{
effectiveBlockState = this.blockState.setValue( SlabBlock.TYPE, SlabType.DOUBLE );
}
List<BakedQuad> quads;
#if MC_VER < MC_1_21_5
quads = MC.getModelManager().getBlockModelShaper().
getBlockModel(effectiveBlockState).getQuads(effectiveBlockState, direction, RANDOM);
#else
List<BlockModelPart> blockModelPartList = MC.getModelManager().getBlockModelShaper().
getBlockModel(effectiveBlockState).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?
private static int calculateColorFromTexture(TextureAtlasSprite texture, EColorMode colorMode)
{
int count = 0;
int alpha = 0;
double red = 0;
double green = 0;
double blue = 0;
int tempColor;
// don't render Chiseled blocks.
// Since EColorMode is set per block, you only need to check this once.
if (colorMode != EColorMode.Chisel)
{
// textures normally use u and v instead of x and y
for (int v = 0; v < getTextureHeight(texture); v++)
{
for (int u = 0; u < getTextureWidth(texture); u++)
{
//note: Minecraft color format is: 0xAA BB GG RR
//________ DH mod color format is: 0xAA RR GG BB
//OpenGL RGBA format native order: 0xRR GG BB AA
//_ OpenGL RGBA format Java Order: 0xAA BB GG RR
tempColor = TextureAtlasSpriteWrapper.getPixelRGBA(texture, 0, u, v);
int r = (tempColor & 0x000000FF);
int g = (tempColor & 0x0000FF00) >>> 8;
int b = (tempColor & 0x00FF0000) >>> 16;
int a = (tempColor & 0xFF000000) >>> 24;
int scale = 1;
if (colorMode == EColorMode.Leaves)
{
//switch (//FIXME add config option)
// case BLACK:
// a = 255; //simulate black background of fast leaves
// break;
// case IGNORE:
if (a == 0) {
continue; //same long grass
}
else
{
a = 255; //just in case there are semi transparent pixels
}
// break;
// case TRANSPARENT:
// break; //do nothing, let it count towards transparency
}
else if (a == 0 && colorMode != EColorMode.Glass)
{
continue;
}
else if (colorMode == EColorMode.Flower && (g + 25 < b || g + 25 < r))
{
scale = FLOWER_COLOR_SCALE;
}
count += scale;
//apparently alpha is linear
alpha += a * scale;
//gamma correction is complicated
red += srgbToLinearTable[r] * a * scale;
green += srgbToLinearTable[g] * a * scale;
blue += srgbToLinearTable[b] * a * scale;
}
}
}
if (count == 0)
{
// this block is entirely transparent
tempColor = ColorUtil.argbToInt(0, 255, 255, 255);
}
else
{
// determine the average color
tempColor = ColorUtil.argbToInt(
alpha / count,
linearToSrgb((float) (red / (double) alpha)),
linearToSrgb((float) (green / (double) alpha)),
linearToSrgb((float) (blue / (double) alpha)));
}
//check if not missing texture
if (tempColor == ColorUtil.argbToInt(255, 182, 0, 182))
{
//make it not render at all
tempColor = ColorUtil.argbToInt(0, 255, 255, 255);
}
return tempColor;
}
private static int getTextureWidth(TextureAtlasSprite texture)
{
#if MC_VER < MC_1_19_4
return texture.getWidth();
#else
return texture.contents().width();
#endif
}
private static int getTextureHeight(TextureAtlasSprite texture)
{
#if MC_VER < MC_1_19_4
return texture.getHeight();
#else
return texture.contents().height();
#endif
}
/**
* This method was suggested by IMS from the Iris/Sodium team.
* That's where the numbers and code are based.
*/
private static int linearToSrgb(float color)
{
if (!(color > MIN_SRGB_BOUND))
{
color = MIN_SRGB_BOUND;
}
if (color > MAX_SRGB_BOUND)
{
color = MAX_SRGB_BOUND;
}
int inputBits = Float.floatToRawIntBits(color);
int entry = linearToSrgbTable[((inputBits - MIN_SRGB_BITS) >> 20)];
int bias = (entry >>> 16) << 9;
int scale = entry & 0xffff;
int t = (inputBits >>> 12) & 0xff;
return (bias + (scale * t)) >>> 16;
}
private int getParticleIconColor()
{
return calculateColorFromTexture(
Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(this.blockState),
EColorMode.getColorMode(this.blockState.getBlock()));
}
//===============//
// public getter //
//===============//
public int getColor(BiomeWrapper biomeWrapper, FullDataSourceV2 fullDataSource, DhBlockPos blockPos)
{
// only get the tint if the block needs to be tinted
if (!this.needPostTinting)
{
return this.baseColor;
}
// don't try tinting blocks that don't support our method of tint getting
if (BROKEN_BLOCK_STATES.contains(this.blockState))
{
return this.baseColor;
}
// attempt to get the tint
int tintColor = -1;
try
{
// try to use the fast tint getter logic first
if (!BLOCK_STATES_THAT_NEED_LEVEL.contains(this.blockState))
{
try
{
TintWithoutLevelOverrider tintOverride = TintWithoutLevelOverrideGetter.get();
tintOverride.update(biomeWrapper, this.blockStateWrapper, fullDataSource, this.clientLevelWrapper);
// try using DH's cached tint values first if possible
tintColor = tintOverride.tryGetBlockTint(new DhBlockPosMutable(blockPos));
if (tintColor == AbstractDhTintGetter.INVALID_COLOR)
{
// one or more tint values weren't calculated,
// we need MC's color resolver
tintColor = Minecraft.getInstance()
.getBlockColors()
.getColor(this.blockState,
tintOverride,
McObjectConverter.Convert(blockPos),
this.tintIndex);
}
}
catch (UnsupportedOperationException e)
{
// this exception generally occurs if the tint requires other blocks besides itself
LOGGER.debug("Unable to use ["+ TintWithoutLevelOverrider.class.getSimpleName()+"] to get the block tint for block: [" + this.blockState + "] and biome: [" + biomeWrapper + "] at pos: " + blockPos + ". Error: [" + e.getMessage() + "]. Attempting to use backup method...", e);
BLOCK_STATES_THAT_NEED_LEVEL.add(this.blockState);
}
}
// use the level logic only if requested
if (BLOCK_STATES_THAT_NEED_LEVEL.contains(this.blockState))
{
// the level shouldn't be used all the time due to it breaking some blocks tinting
// specifically oceans don't render correctly
TintGetterOverride tintOverride = TintOverrideGetter.get();
tintOverride.update(biomeWrapper, this.blockStateWrapper, fullDataSource, this.clientLevelWrapper);
tintColor = tintOverride.tryGetBlockTint(new DhBlockPosMutable(blockPos));
if (tintColor == AbstractDhTintGetter.INVALID_COLOR)
{
tintColor = Minecraft.getInstance()
.getBlockColors()
.getColor(this.blockState,
tintOverride,
McObjectConverter.Convert(blockPos),
this.tintIndex);
}
}
}
catch (Exception e)
{
// only display the error once per block/biome type to reduce log spam
if (!BROKEN_BLOCK_STATES.contains(this.blockState))
{
LOGGER.warn("Failed to get block color for block: [" + this.blockState + "] and biome: [" + biomeWrapper + "] at pos: " + blockPos + ". Error: ["+e.getMessage() + "]. Note: future errors for this block/biome will be ignored.", e);
BROKEN_BLOCK_STATES.add(this.blockState);
}
}
if (tintColor != -1)
{
return ColorUtil.multiplyARGBwithRGB(this.baseColor, tintColor);
}
else
{
// unable to get the tinted color, use the base color instead
return this.baseColor;
}
}
//================//
// helper classes //
//================//
private enum EColorMode
{
Default,
Flower,
Leaves,
Chisel,
Glass;
static EColorMode getColorMode(Block block)
{
if (block instanceof LeavesBlock)
{
return Leaves;
}
if (block instanceof FlowerBlock)
{
return Flower;
}
if (block.toString().contains("glass"))
{
return Glass;
}
if (block.toString().equals("Block{chiselsandbits:chiseled}"))
{
return Chisel;
}
return Default;
}
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -19,14 +19,8 @@
package com.seibel.distanthorizons.common.wrappers.block;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
#if MC_VER < MC_1_17_1
#elif MC_VER < MC_1_21_3
#else
import com.seibel.distanthorizons.core.util.ColorUtil;
import net.minecraft.client.renderer.texture.SpriteContents;
#endif
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
/**
* For wrapping/utilizing around TextureAtlasSprite
@@ -35,6 +29,15 @@ import net.minecraft.client.renderer.texture.SpriteContents;
*/
public class TextureAtlasSpriteWrapper
{
/**
* This code is from Minecraft Forge
* Which is licensed under the terms of GNU Lesser General Public License
* as published by the Free Software Foundation version 2.1
* of the License.
*
* The code has been modified to use TextureAtlasSprite
*/
public static int getPixelRGBA(TextureAtlasSprite sprite, int frameIndex, int x, int y)
{
#if MC_VER < MC_1_17_1
@@ -48,29 +51,13 @@ public class TextureAtlasSpriteWrapper
y += sprite.animatedTexture.getFrameY(frameIndex) * sprite.height;
}
return sprite.mainImage[0].getPixelRGBA(x, y);
#elif MC_VER < MC_1_21_3
#else
if (sprite.contents().animatedTexture != null)
{
x += sprite.contents().animatedTexture.getFrameX(frameIndex) * sprite.contents().width();
y += sprite.contents().animatedTexture.getFrameY(frameIndex) * sprite.contents().width();
}
return sprite.contents().originalImage.getPixelRGBA(x, y);
#else
SpriteContents content = sprite.contents(); // don't close, otherwise MC will be corrupted and you won't be able to re-access the texture
if (content.animatedTexture != null)
{
x += content.animatedTexture.getFrameX(frameIndex) * content.width();
y += content.animatedTexture.getFrameY(frameIndex) * content.width();
}
int abgr = content.originalImage.getPixel(x, y);
// re-pack the color so we can access it normally
int a = (abgr & 0xFF000000) >>> 24;
int b = (abgr & 0x00FF0000) >>> 16;
int g = (abgr & 0x0000FF00) >>> 8;
int r = (abgr & 0x000000FF);
return ColorUtil.argbToInt(a, r, g, b);
#endif
}
@@ -0,0 +1,231 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class TintGetterOverrideFast implements BlockAndTintGetter
{
LevelReader parent;
public TintGetterOverrideFast(LevelReader parent)
{
this.parent = parent;
}
private Biome _getBiome(BlockPos pos)
{
#if MC_VER >= MC_1_18_2
return parent.getBiome(pos).value();
#else
return parent.getBiome(pos);
#endif
}
@Override
public int getBlockTint(BlockPos blockPos, ColorResolver colorResolver) { return colorResolver.getColor(this._getBiome(blockPos), blockPos.getX(), blockPos.getZ()); }
@Override
public float getShade(Direction direction, boolean bl) { return this.parent.getShade(direction, bl); }
@Override
public LevelLightEngine getLightEngine()
{
return parent.getLightEngine();
}
@Override
public int getBrightness(LightLayer lightLayer, BlockPos blockPos)
{
return parent.getBrightness(lightLayer, blockPos);
}
@Override
public int getRawBrightness(BlockPos blockPos, int i)
{
return parent.getRawBrightness(blockPos, i);
}
@Override
public boolean canSeeSky(BlockPos blockPos)
{
return parent.canSeeSky(blockPos);
}
@Override
@Nullable
public BlockEntity getBlockEntity(BlockPos blockPos)
{
return parent.getBlockEntity(blockPos);
}
@Override
public BlockState getBlockState(BlockPos blockPos)
{
return parent.getBlockState(blockPos);
}
@Override
public FluidState getFluidState(BlockPos blockPos)
{
return parent.getFluidState(blockPos);
}
@Override
public int getLightEmission(BlockPos blockPos)
{
return parent.getLightEmission(blockPos);
}
@Override
public int getMaxLightLevel()
{
return parent.getMaxLightLevel();
}
@Override
public Stream<BlockState> getBlockStates(AABB aABB)
{
return parent.getBlockStates(aABB);
}
@Override
public BlockHitResult clip(ClipContext clipContext)
{
return parent.clip(clipContext);
}
@Override
@Nullable
public BlockHitResult clipWithInteractionOverride(Vec3 vec3, Vec3 vec32, BlockPos blockPos, VoxelShape voxelShape, BlockState blockState)
{
return parent.clipWithInteractionOverride(vec3, vec32, blockPos, voxelShape, blockState);
}
@Override
public double getBlockFloorHeight(VoxelShape voxelShape, Supplier<VoxelShape> supplier)
{
return parent.getBlockFloorHeight(voxelShape, supplier);
}
@Override
public double getBlockFloorHeight(BlockPos blockPos)
{
return parent.getBlockFloorHeight(blockPos);
}
@Override
public int getMaxBuildHeight()
{
return parent.getMaxBuildHeight();
}
#if MC_VER >= MC_1_17_1
@Override
public <T extends BlockEntity> Optional<T> getBlockEntity(BlockPos blockPos, BlockEntityType<T> blockEntityType)
{
return parent.getBlockEntity(blockPos, blockEntityType);
}
@Override
public BlockHitResult isBlockInLine(ClipBlockStateContext clipBlockStateContext)
{
return parent.isBlockInLine(clipBlockStateContext);
}
@Override
public int getHeight()
{
return parent.getHeight();
}
@Override
public int getMinBuildHeight()
{
return parent.getMinBuildHeight();
}
@Override
public int getSectionsCount()
{
return parent.getSectionsCount();
}
@Override
public int getMinSection()
{
return parent.getMinSection();
}
@Override
public int getMaxSection()
{
return parent.getMaxSection();
}
@Override
public boolean isOutsideBuildHeight(BlockPos blockPos)
{
return parent.isOutsideBuildHeight(blockPos);
}
@Override
public boolean isOutsideBuildHeight(int i)
{
return parent.isOutsideBuildHeight(i);
}
@Override
public int getSectionIndex(int i)
{
return parent.getSectionIndex(i);
}
@Override
public int getSectionIndexFromSectionY(int i)
{
return parent.getSectionIndexFromSectionY(i);
}
@Override
public int getSectionYFromSectionIndex(int i)
{
return parent.getSectionYFromSectionIndex(i);
}
#endif
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -19,11 +19,11 @@
package com.seibel.distanthorizons.common.wrappers.block;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Cursor3D;
import net.minecraft.core.Direction;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
@@ -39,29 +39,51 @@ import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class TintGetterOverride extends AbstractDhTintGetter
public class TintGetterOverrideSmooth implements BlockAndTintGetter
{
private LevelReader parent;
LevelReader parent;
public int smoothingRange;
//=============//
// constructor //
//=============//
public TintGetterOverride() { }
public void update(BiomeWrapper biomeWrapper, BlockStateWrapper blockStateWrapper, FullDataSourceV2 fullDataSource, IClientLevelWrapper clientLevelWrapper)
public TintGetterOverrideSmooth(LevelReader parent, int smoothingRange)
{
super.update(biomeWrapper, blockStateWrapper, fullDataSource, clientLevelWrapper);
this.parent = (LevelReader)this.clientLevelWrapper.getWrappedMcObject();
this.parent = parent;
this.smoothingRange = smoothingRange;
}
private Biome _getBiome(BlockPos pos)
{
#if MC_VER >= MC_1_18_2
return parent.getBiome(pos).value();
#else
return parent.getBiome(pos);
#endif
}
public int calculateBlockTint(BlockPos blockPos, ColorResolver colorResolver)
{
int i = smoothingRange;
if (i == 0)
return colorResolver.getColor(_getBiome(blockPos), blockPos.getX(), blockPos.getZ());
int j = (i * 2 + 1) * (i * 2 + 1);
int k = 0;
int l = 0;
int m = 0;
Cursor3D cursor3D = new Cursor3D(blockPos.getX() - i, blockPos.getY(), blockPos.getZ() - i, blockPos.getX() + i, blockPos.getY(), blockPos.getZ() + i);
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
while (cursor3D.advance())
{
mutableBlockPos.set(cursor3D.nextX(), cursor3D.nextY(), cursor3D.nextZ());
int n = colorResolver.getColor(this._getBiome(mutableBlockPos), mutableBlockPos.getX(), mutableBlockPos.getZ());
//=========//
// methods //
//=========//
k += (n & 0xFF0000) >> 16;
l += (n & 0xFF00) >> 8;
m += n & 0xFF;
}
return (k / j & 0xFF) << 16 | (l / j & 0xFF) << 8 | m / j & 0xFF;
}
@Override
public int getBlockTint(BlockPos blockPos, ColorResolver colorResolver) { return this.calculateBlockTint(blockPos, colorResolver); }
@Override
public float getShade(Direction direction, boolean bl) { return this.parent.getShade(direction, bl); }
@@ -91,11 +113,8 @@ public class TintGetterOverride extends AbstractDhTintGetter
@Override
public int getLightEmission(BlockPos blockPos) { return this.parent.getLightEmission(blockPos); }
#if MC_VER < MC_1_21_3
@Override
public int getMaxLightLevel() { return this.parent.getMaxLightLevel(); }
#else
#endif
@Override
public Stream<BlockState> getBlockStates(AABB aABB) { return this.parent.getBlockStates(aABB); }
@@ -116,13 +135,8 @@ public class TintGetterOverride extends AbstractDhTintGetter
@Override
public double getBlockFloorHeight(BlockPos blockPos) { return this.parent.getBlockFloorHeight(blockPos); }
#if MC_VER < MC_1_21_3
@Override
public int getMaxBuildHeight() { return this.parent.getMaxBuildHeight(); }
#else
@Override
public int getMaxY() { return this.parent.getMaxY(); }
#endif
#if MC_VER >= MC_1_17_1
@Override
@@ -134,32 +148,17 @@ public class TintGetterOverride extends AbstractDhTintGetter
@Override
public int getHeight() { return this.parent.getHeight(); }
#if MC_VER < MC_1_21_3
@Override
public int getMinBuildHeight() { return this.parent.getMinBuildHeight(); }
#else
@Override
public int getMinY() { return this.parent.getMinY(); }
#endif
@Override
public int getSectionsCount() { return this.parent.getSectionsCount(); }
#if MC_VER < MC_1_21_3
@Override
public int getMinSection() { return this.parent.getMinSection(); }
#else
@Override
public int getMinSectionY() { return super.getMinSectionY(); }
#endif
#if MC_VER < MC_1_21_3
@Override
public int getMaxSection() { return this.parent.getMaxSection(); }
#else
@Override
public int getMaxSectionY() { return this.parent.getMaxSectionY(); }
#endif
@Override
public boolean isOutsideBuildHeight(BlockPos blockPos) { return this.parent.isOutsideBuildHeight(blockPos); }
@@ -176,7 +175,4 @@ public class TintGetterOverride extends AbstractDhTintGetter
@Override
public int getSectionYFromSectionIndex(int i) { return this.parent.getSectionYFromSectionIndex(i); }
#endif
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -19,72 +19,130 @@
package com.seibel.distanthorizons.common.wrappers.block;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.*;
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.state.BlockState;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.FluidState;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class TintWithoutLevelOverrider extends AbstractDhTintGetter
#if MC_VER >= MC_1_18_2
import net.minecraft.core.Holder;
#endif
public class TintWithoutLevelOverrider implements BlockAndTintGetter
{
//=============//
// constructor //
//=============//
public TintWithoutLevelOverrider()
{ }
/**
* This will only ever be null if there was an issue with {@link IClientLevelWrapper#getPlainsBiomeWrapper()}
* but {@link Nullable} is there just in case.
*/
@Nullable
private final Biome biome;
//=========//
// methods //
//=========//
public TintWithoutLevelOverrider(BiomeWrapper biomeWrapper, IClientLevelWrapper clientLevelWrapper)
{
// try to get the wrapped biome
Biome unwrappedBiome = null;
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());
if (plainsBiomeWrapper != null)
{
unwrappedBiome = unwrap(plainsBiomeWrapper.biome);
}
}
this.biome = unwrappedBiome;
}
@Override
public float getShade(Direction direction, boolean shade)
{ throw new UnsupportedOperationException("ERROR: getShade() called on TintWithoutLevelOverrider. Object is for tinting only."); }
public int getBlockTint(@NotNull BlockPos blockPos, @NotNull ColorResolver colorResolver)
{
if (this.biome != null)
{
return colorResolver.getColor(this.biome, blockPos.getX(), blockPos.getZ());
}
else
{
// hopefully unneeded debug color
return ColorUtil.CYAN;
}
}
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
}
//================//
// unused methods //
//================//
@Override
public LevelLightEngine getLightEngine()
{ throw new UnsupportedOperationException("ERROR: getLightEngine() called on TintWithoutLevelOverrider. Object is for tinting only."); }
public float getShade(@NotNull Direction direction, boolean shade)
{
throw new UnsupportedOperationException("ERROR: getShade() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Override
public @NotNull LevelLightEngine getLightEngine()
{
throw new UnsupportedOperationException("ERROR: getLightEngine() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos)
{ throw new UnsupportedOperationException("ERROR: getBlockEntity() called on TintWithoutLevelOverrider. Object is for tinting only."); }
public BlockEntity getBlockEntity(@NotNull BlockPos pos)
{
throw new UnsupportedOperationException("ERROR: getBlockEntity() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Override
public BlockState getBlockState(BlockPos pos)
{ throw new UnsupportedOperationException("ERROR: getBlockState() called on TintWithoutLevelOverrider. Object is for tinting only."); }
public @NotNull BlockState getBlockState(@NotNull BlockPos pos)
{
throw new UnsupportedOperationException("ERROR: getBlockState() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Override
public FluidState getFluidState(BlockPos pos)
{ throw new UnsupportedOperationException("ERROR: getFluidState() called on TintWithoutLevelOverrider. Object is for tinting only."); }
public @NotNull FluidState getFluidState(@NotNull BlockPos pos)
{
throw new UnsupportedOperationException("ERROR: getFluidState() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
//==============//
// post MC 1.17 //
//==============//
#if MC_VER >= MC_1_17_1
@Override
public int getHeight()
{ throw new UnsupportedOperationException("ERROR: getHeight() called on TintWithoutLevelOverrider. Object is for tinting only."); }
#if MC_VER < MC_1_21_3
{
throw new UnsupportedOperationException("ERROR: getHeight() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Override
public int getMinBuildHeight()
{ throw new UnsupportedOperationException("ERROR: getMinBuildHeight() called on TintWithoutLevelSmoothOverrider. Object is for tinting only."); }
#else
@Override
public int getMinY()
{ throw new UnsupportedOperationException("ERROR: getMinY() called on TintWithoutLevelOverrider. Object is for tinting only."); }
#endif
{
throw new UnsupportedOperationException("ERROR: getMinBuildHeight() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
#endif
}
@@ -0,0 +1,132 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.ColorResolver;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.FluidState;
import org.jetbrains.annotations.Nullable;
#if MC_VER >= MC_1_18_2
import net.minecraft.core.Holder;
#endif
public class TintWithoutLevelSmoothOverrider implements BlockAndTintGetter
{
final BiomeWrapper biome;
public int smoothingRange;
public TintWithoutLevelSmoothOverrider(BiomeWrapper biome, int smoothingRange)
{
this.biome = biome;
this.smoothingRange = smoothingRange;
}
@Override
public int getBlockTint(BlockPos blockPos, ColorResolver colorResolver)
{
return colorResolver.getColor(_unwrap(biome.biome), blockPos.getX(), blockPos.getZ());
}
private 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
}
//
// public int calculateBlockTint(BlockPos blockPos, ColorResolver colorResolver)
// {
// int i = smoothingRange;
// if (i == 0)
// return colorResolver.getColor(_getBiome(blockPos), blockPos.getX(), blockPos.getZ());
// int j = (i * 2 + 1) * (i * 2 + 1);
// int k = 0;
// int l = 0;
// int m = 0;
// Cursor3D cursor3D = new Cursor3D(blockPos.getX() - i, blockPos.getY(), blockPos.getZ() - i, blockPos.getX() + i, blockPos.getY(), blockPos.getZ() + i);
// BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
// while (cursor3D.advance())
// {
// mutableBlockPos.set(cursor3D.nextX(), cursor3D.nextY(), cursor3D.nextZ());
// int n;
// if (LodCommonMain.forgeMethodCaller != null) {
// n = LodCommonMain.forgeMethodCaller.colorResolverGetColor(colorResolver, _getBiome(mutableBlockPos),
// mutableBlockPos.getX(), mutableBlockPos.getZ());
// } else {
// n = colorResolver.getColor(_getBiome(mutableBlockPos), mutableBlockPos.getX(), mutableBlockPos.getZ());
// }
//
// k += (n & 0xFF0000) >> 16;
// l += (n & 0xFF00) >> 8;
// m += n & 0xFF;
// }
// return (k / j & 0xFF) << 16 | (l / j & 0xFF) << 8 | m / j & 0xFF;
// }
@Override
public float getShade(Direction direction, boolean shade)
{
throw new UnsupportedOperationException("ERROR: getShade() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Override
public LevelLightEngine getLightEngine()
{
throw new UnsupportedOperationException("ERROR: getLightEngine() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos)
{
throw new UnsupportedOperationException("ERROR: getBlockEntity() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Override
public BlockState getBlockState(BlockPos pos)
{
throw new UnsupportedOperationException("ERROR: getBlockState() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Override
public FluidState getFluidState(BlockPos pos)
{
throw new UnsupportedOperationException("ERROR: getFluidState() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
#if MC_VER >= MC_1_17_1
@Override
public int getHeight()
{
throw new UnsupportedOperationException("ERROR: getHeight() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Override
public int getMinBuildHeight()
{
throw new UnsupportedOperationException("ERROR: getMinBuildHeight() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
#endif
}
@@ -0,0 +1,48 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block.cache;
import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import net.minecraft.world.level.block.state.BlockState;
import java.util.concurrent.ConcurrentHashMap;
public class ClientBlockDetailMap
{
private final ConcurrentHashMap<BlockState, ClientBlockStateCache> blockCache = new ConcurrentHashMap<>();
//private final ConcurrentHashMap<#if MC_VER < MC_1_18_2 Biome #else Holder<Biome> #endif, Biome> biomeMap = new ConcurrentHashMap<>();
private final ClientLevelWrapper level;
public ClientBlockDetailMap(ClientLevelWrapper level) { this.level = level; }
public ClientBlockStateCache getBlockStateData(BlockState state, DhBlockPos pos)
{ //TODO: Allow a per pos unique setting
return blockCache.computeIfAbsent(state, (s) -> new ClientBlockStateCache(s, level, pos));
}
public void clear() { blockCache.clear(); }
public int getColor(BlockState state, BiomeWrapper biome, DhBlockPos pos)
{
return getBlockStateData(state, pos).getAndResolveFaceColor(biome, pos);
}
}
@@ -0,0 +1,412 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block.cache;
import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
import com.seibel.distanthorizons.common.wrappers.block.TextureAtlasSpriteWrapper;
import com.seibel.distanthorizons.common.wrappers.block.TintWithoutLevelOverrider;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.common.wrappers.block.*;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FlowerBlock;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.RotatedPillarBlock;
#if MC_VER >= MC_1_19_2
import net.minecraft.util.RandomSource;
#else
import java.util.Random;
#endif
import net.minecraft.world.level.block.state.BlockState;
import org.apache.logging.log4j.Logger;
import java.util.HashSet;
import java.util.List;
/**
* @version 2022-9-16
*/
public class ClientBlockStateCache
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final HashSet<BlockState> BLOCK_STATES_THAT_NEED_LEVEL = new HashSet<>();
private static final HashSet<BlockState> BROKEN_BLOCK_STATES = new HashSet<>();
#if MC_VER < MC_1_19_2
public static final Random random = new Random(0);
#else
public static final RandomSource random = RandomSource.create();
#endif
public final IClientLevelWrapper levelWrapper;
public final BlockState blockState;
public final LevelReader level;
public final BlockPos pos;
public ClientBlockStateCache(BlockState blockState, IClientLevelWrapper samplingLevel, DhBlockPos samplingPos)
{
this.blockState = blockState;
this.levelWrapper = samplingLevel;
this.level = (LevelReader) samplingLevel.getWrappedMcObject();
this.pos = McObjectConverter.Convert(samplingPos);
this.resolveColors();
//LOGGER.info("ClientBlocKCache created for {}", blockState);
}
boolean isColorResolved = false;
int baseColor = 0; //TODO: Impl per-face color
boolean needShade = true;
boolean needPostTinting = false;
int tintIndex = 0;
public static final int FLOWER_COLOR_SCALE = 5;
enum ColorMode
{
Default,
Flower,
Leaves,
Chisel,
Glass;
static ColorMode getColorMode(Block b)
{
if (b instanceof LeavesBlock) return Leaves;
if (b instanceof FlowerBlock) return Flower;
if (b.toString().contains("glass")) return Glass;
if (b.toString().equals("Block{chiselsandbits:chiseled}")) return Chisel;
return Default;
}
}
//Way to efficiently do this was suggested by IMS from sodium. This is where those numbers and support code was lifted from.
private static final int MIN_BITS = 0x39000000; // 2^(-13)
private static final int MAX_BITS = 0x3f7fffff; // 1.0 - f32::EPSILON
private static final float MIN_BOUND = Float.intBitsToFloat(MIN_BITS);
private static final float MAX_BOUND = Float.intBitsToFloat(MAX_BITS);
private static final int[] linearToSrgbTable = new int[] {
0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d,
0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a,
0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033,
0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067,
0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5,
0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2,
0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143,
0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af,
0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240,
0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300,
0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401,
0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559,
0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723,
};
private static final float[] srgbToLinearTable = new float[] {
0.0f, 0.000303527f, 0.000607054f, 0.00091058103f, 0.001214108f, 0.001517635f, 0.0018211621f, 0.002124689f,
0.002428216f, 0.002731743f, 0.00303527f, 0.0033465356f, 0.003676507f, 0.004024717f, 0.004391442f,
0.0047769533f, 0.005181517f, 0.0056053917f, 0.0060488326f, 0.006512091f, 0.00699541f, 0.0074990317f,
0.008023192f, 0.008568125f, 0.009134057f, 0.009721218f, 0.010329823f, 0.010960094f, 0.011612245f,
0.012286487f, 0.012983031f, 0.013702081f, 0.014443844f, 0.015208514f, 0.015996292f, 0.016807375f,
0.017641952f, 0.018500218f, 0.019382361f, 0.020288562f, 0.02121901f, 0.022173883f, 0.023153365f,
0.02415763f, 0.025186857f, 0.026241222f, 0.027320892f, 0.028426038f, 0.029556843f, 0.03071345f, 0.03189604f,
0.033104774f, 0.03433981f, 0.035601325f, 0.036889452f, 0.038204376f, 0.039546248f, 0.04091521f, 0.042311423f,
0.043735042f, 0.045186214f, 0.046665095f, 0.048171833f, 0.049706575f, 0.051269468f, 0.052860655f, 0.05448028f,
0.056128494f, 0.057805434f, 0.05951124f, 0.06124607f, 0.06301003f, 0.06480328f, 0.06662595f, 0.06847818f,
0.07036011f, 0.07227186f, 0.07421358f, 0.07618539f, 0.07818743f, 0.08021983f, 0.082282715f, 0.084376216f,
0.086500466f, 0.088655606f, 0.09084173f, 0.09305898f, 0.095307484f, 0.09758736f, 0.09989874f, 0.10224175f,
0.10461649f, 0.10702311f, 0.10946172f, 0.111932434f, 0.11443538f, 0.116970696f, 0.11953845f, 0.12213881f,
0.12477186f, 0.12743773f, 0.13013652f, 0.13286836f, 0.13563336f, 0.13843165f, 0.14126332f, 0.1441285f,
0.1470273f, 0.14995982f, 0.15292618f, 0.1559265f, 0.15896086f, 0.16202943f, 0.16513224f, 0.16826946f,
0.17144115f, 0.17464745f, 0.17788847f, 0.1811643f, 0.18447503f, 0.1878208f, 0.19120172f, 0.19461787f,
0.19806935f, 0.2015563f, 0.20507877f, 0.2086369f, 0.21223079f, 0.21586053f, 0.21952623f, 0.22322798f,
0.22696589f, 0.23074007f, 0.23455065f, 0.23839766f, 0.2422812f, 0.2462014f, 0.25015837f, 0.25415218f,
0.2581829f, 0.26225072f, 0.26635566f, 0.27049786f, 0.27467737f, 0.27889434f, 0.2831488f, 0.2874409f,
0.2917707f, 0.29613832f, 0.30054384f, 0.30498737f, 0.30946895f, 0.31398875f, 0.31854683f, 0.32314324f,
0.32777813f, 0.33245158f, 0.33716366f, 0.34191445f, 0.3467041f, 0.3515327f, 0.35640025f, 0.36130688f,
0.3662527f, 0.37123778f, 0.37626222f, 0.3813261f, 0.38642952f, 0.39157256f, 0.3967553f, 0.40197787f,
0.4072403f, 0.4125427f, 0.41788515f, 0.42326775f, 0.42869055f, 0.4341537f, 0.43965724f, 0.44520125f,
0.45078585f, 0.45641106f, 0.46207705f, 0.46778384f, 0.47353154f, 0.47932023f, 0.48514998f, 0.4910209f,
0.49693304f, 0.5028866f, 0.50888145f, 0.5149178f, 0.5209957f, 0.52711535f, 0.5332766f, 0.5394797f,
0.5457247f, 0.5520116f, 0.5583406f, 0.5647117f, 0.57112503f, 0.57758063f, 0.5840786f, 0.590619f, 0.597202f,
0.60382754f, 0.61049575f, 0.61720675f, 0.62396055f, 0.63075733f, 0.637597f, 0.6444799f, 0.6514058f,
0.65837497f, 0.66538745f, 0.67244333f, 0.6795426f, 0.68668544f, 0.69387203f, 0.70110214f, 0.70837605f,
0.7156938f, 0.72305536f, 0.730461f, 0.7379107f, 0.7454045f, 0.75294244f, 0.76052475f, 0.7681514f, 0.77582246f,
0.78353804f, 0.79129815f, 0.79910296f, 0.8069525f, 0.8148468f, 0.822786f, 0.8307701f, 0.83879924f, 0.84687346f,
0.8549928f, 0.8631574f, 0.87136734f, 0.8796226f, 0.8879232f, 0.89626956f, 0.90466136f, 0.913099f, 0.92158204f,
0.93011117f, 0.9386859f, 0.9473069f, 0.9559735f, 0.9646866f, 0.9734455f, 0.98225087f, 0.9911022f, 1.0f
};
private static int linearToSrgb(float c) {
if (!(c > MIN_BOUND)) {
c = MIN_BOUND;
}
if (c > MAX_BOUND) {
c = MAX_BOUND;
}
int inputBits = Float.floatToRawIntBits(c);
int entry = linearToSrgbTable[((inputBits - MIN_BITS) >> 20)];
int bias = (entry >>> 16) << 9;
int scale = entry & 0xffff;
int t = (inputBits >>> 12) & 0xff;
return (bias + (scale * t)) >>> 16;
}
//////////////
private static int getWidth(TextureAtlasSprite texture)
{
#if MC_VER < MC_1_19_4
return texture.getWidth();
#else
return texture.contents().width();
#endif
}
private static int getHeight(TextureAtlasSprite texture)
{
#if MC_VER < MC_1_19_4
return texture.getHeight();
#else
return texture.contents().height();
#endif
}
//TODO: Perhaps make this not just use the first frame?
private static int calculateColorFromTexture(TextureAtlasSprite texture, ColorMode colorMode)
{
int count = 0;
int alpha = 0;
double red = 0;
double green = 0;
double blue = 0;
int tempColor;
//make Chiseled block not render. Since ColorMode is set per block, you only need to check it once
if (colorMode != ColorMode.Chisel)
{
// textures normally use u and v instead of x and y
for (int v = 0; v < getHeight(texture); v++)
{
for (int u = 0; u < getWidth(texture); u++)
{
//note: Minecraft color format is: 0xAA BB GG RR
//________ DH mod color format is: 0xAA RR GG BB
//OpenGL RGBA format native order: 0xRR GG BB AA
//_ OpenGL RGBA format Java Order: 0xAA BB GG RR
tempColor = TextureAtlasSpriteWrapper.getPixelRGBA(texture, 0, u, v);
int r = (tempColor & 0x000000FF);
int g = (tempColor & 0x0000FF00) >>> 8;
int b = (tempColor & 0x00FF0000) >>> 16;
int a = (tempColor & 0xFF000000) >>> 24;
int scale = 1;
if (colorMode == ColorMode.Leaves)
{
//switch (//FIXME add config option)
// case BLACK:
// a = 255; //simulate black background of fast leaves
// break;
// case IGNORE:
if (a == 0) {
continue; //same long grass
}
else
{
a = 255; //just in case there are semi transparent pixels
}
// break;
// case TRANSPARENT:
// break; //do nothing, let it count towards transparency
}
else if (a == 0 && colorMode != ColorMode.Glass)
{
continue;
}
else if (colorMode == ColorMode.Flower && (g + 25 < b || g + 25 < r))
{
scale = FLOWER_COLOR_SCALE;
}
count += scale;
//apparently alpha is linear
alpha += a * scale;
//gamma correction is complicated
red += srgbToLinearTable[r] * a * scale;
green += srgbToLinearTable[g] * a * scale;
blue += srgbToLinearTable[b] * a * scale;
}
}
}
if (count == 0)
// this block is entirely transparent
tempColor = ColorUtil.rgbToInt(0, 255, 255, 255);
else
{
// determine the average color
tempColor = ColorUtil.rgbToInt(
alpha / count,
linearToSrgb((float) (red / (double) alpha)),
linearToSrgb((float) (green / (double) alpha)),
linearToSrgb((float) (blue / (double) alpha)));
}
//check if not missing texture
if (tempColor == ColorUtil.rgbToInt(255, 182, 0, 182))
{
//make it not render at all
tempColor = ColorUtil.rgbToInt(0, 255, 255, 255);
}
return tempColor;
}
private static final Direction[] DIRECTION_ORDER = {Direction.UP, Direction.NORTH, Direction.EAST, Direction.WEST, Direction.SOUTH, Direction.DOWN};
private void resolveColors()
{
if (isColorResolved) return;
if (blockState.getFluidState().isEmpty())
{
List<BakedQuad> quads = null;
for (Direction direction : DIRECTION_ORDER)
{
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(blockState).getQuads(blockState, direction, random); // TODO getQuads sometimes throws a "makeThreadingException", is there anything we can do about that? Note: this isn't a critical issue, it just prints an ugly error and the render data will need to be regenered.
if (quads != null && !quads.isEmpty() &&
!(blockState.getBlock() instanceof RotatedPillarBlock && direction == Direction.UP))
break;
} ;
if (quads == null || quads.isEmpty())
{
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(blockState).getQuads(blockState, null, random);
}
if (quads != null && !quads.isEmpty())
{
needPostTinting = quads.getFirst().isTinted();
needShade = quads.getFirst().isShade();
tintIndex = quads.getFirst().getTintIndex();
baseColor = calculateColorFromTexture(
#if MC_VER < MC_1_17_1 quads.get(0).sprite,
#else quads.getFirst().getSprite(), #endif
ColorMode.getColorMode(blockState.getBlock()));
}
else
{ // Backup method.
needPostTinting = false;
needShade = false;
tintIndex = 0;
baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(blockState),
ColorMode.getColorMode(blockState.getBlock()));
}
}
else
{ // Liquid Block
needPostTinting = true;
needShade = false;
tintIndex = 0;
baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(blockState),
ColorMode.getColorMode(blockState.getBlock()));
}
isColorResolved = true;
}
public int getAndResolveFaceColor(BiomeWrapper biome, DhBlockPos pos)
{
// FIXME: impl per-face colors
// only get the tint if the block needs to be tinted
if (!this.needPostTinting)
{
return this.baseColor;
}
// don't try tinting blocks that don't support our method of tint getting
if (BROKEN_BLOCK_STATES.contains(this.blockState))
{
return this.baseColor;
}
// attempt to get the tint
int tintColor = -1;
try
{
// try to use the fast tint getter logic first
if (!BLOCK_STATES_THAT_NEED_LEVEL.contains(this.blockState))
{
try
{
tintColor = Minecraft.getInstance().getBlockColors()
.getColor(this.blockState, new TintWithoutLevelOverrider(biome, this.levelWrapper), McObjectConverter.Convert(pos), this.tintIndex);
}
catch (UnsupportedOperationException e)
{
// this exception generally occurs if the tint requires other blocks besides itself
LOGGER.debug("Unable to use ["+TintWithoutLevelOverrider.class.getSimpleName()+"] to get the block tint for block: [" + this.blockState + "] and biome: [" + biome + "] at pos: " + pos + ". Error: [" + e.getMessage() + "]. Attempting to use backup method...", e);
BLOCK_STATES_THAT_NEED_LEVEL.add(this.blockState);
}
}
// use the level logic only if requested
if (BLOCK_STATES_THAT_NEED_LEVEL.contains(this.blockState))
{
// this logic can't be used all the time due to it breaking some blocks tinting
// specifically oceans don't render correctly
tintColor = Minecraft.getInstance().getBlockColors()
.getColor(this.blockState, new TintGetterOverrideFast(this.level), McObjectConverter.Convert(pos), this.tintIndex);
}
}
catch (Exception e)
{
// only display the error once per block/biome type to reduce log spam
if (!BROKEN_BLOCK_STATES.contains(this.blockState))
{
LOGGER.warn("Failed to get block color for block: [" + this.blockState + "] and biome: [" + biome + "] at pos: " + pos + ". Error: ["+e.getMessage() + "]. Note: future errors for this block/biome will be ignored.", e);
BROKEN_BLOCK_STATES.add(this.blockState);
}
}
if (tintColor != -1)
{
return ColorUtil.multiplyARGBwithRGB(this.baseColor, tintColor);
}
else
{
// unable to get the tinted color, use the base color instead
return this.baseColor;
}
}
}
@@ -0,0 +1,43 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block.cache;
import java.util.concurrent.ConcurrentHashMap;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import net.minecraft.world.level.block.state.BlockState;
public class ServerBlockDetailMap
{
private final ConcurrentHashMap<BlockState, ServerBlockStateCache> blockCache = new ConcurrentHashMap<>();
//private final ConcurrentHashMap<#if MC_VER < MC_1_18_2 Biome #else Holder<Biome> #endif, Biome> biomeMap = new ConcurrentHashMap<>();
private final ServerLevelWrapper level;
public ServerBlockDetailMap(ServerLevelWrapper level) { this.level = level; }
public ServerBlockStateCache getBlockStateData(BlockState state, DhBlockPos pos)
{ //TODO: Allow a per pos unique setting
return blockCache.computeIfAbsent(state, (s) -> new ServerBlockStateCache(s, level, new DhBlockPos(0, 0, 0)));
}
public void clear() { blockCache.clear(); }
}
@@ -0,0 +1,104 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block.cache;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
/**
* @version 2022-9-16
*/
public class ServerBlockStateCache
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final BlockState state;
public final LevelReader level;
public final BlockPos pos;
public ServerBlockStateCache(BlockState blockState, ILevelWrapper samplingLevel, DhBlockPos samplingPos)
{
state = blockState;
level = (LevelReader) samplingLevel.getWrappedMcObject();
pos = McObjectConverter.Convert(samplingPos);
resolveShapes();
//LOGGER.info("ServerBlockState created for {}", blockState);
}
boolean noCollision = false;
boolean[] occludeFaces = null;
boolean[] fullFaces = null;
boolean isShapeResolved = false;
public void resolveShapes()
{
if (isShapeResolved) return;
if (state.getFluidState().isEmpty())
{
noCollision = state.getCollisionShape(level, pos).isEmpty();
occludeFaces = new boolean[6];
if (state.canOcclude())
{
for (Direction dir : Direction.values())
{
// Note: isEmpty() isn't quite correct... best would be a isFull() or something...
occludeFaces[McObjectConverter.Convert(dir).ordinal()]
= !state.getFaceOcclusionShape(level, pos, dir).isEmpty();
}
}
VoxelShape voxelShape = state.getShape(level, pos);
fullFaces = new boolean[6];
if (!voxelShape.isEmpty())
{
for (Direction dir : Direction.values())
{
VoxelShape faceShape = voxelShape.getFaceShape(dir);
AABB aabb = faceShape.bounds();
boolean xFull = aabb.minX <= 0.01 && aabb.maxX >= 0.99;
boolean yFull = aabb.minY <= 0.01 && aabb.maxY >= 0.99;
boolean zFull = aabb.minZ <= 0.01 && aabb.maxZ >= 0.99;
fullFaces[McObjectConverter.Convert(dir).ordinal()] =
(xFull || dir.getAxis().equals(Direction.Axis.X))
&& (yFull || dir.getAxis().equals(Direction.Axis.Y))
&& (zFull || dir.getAxis().equals(Direction.Axis.Z));
}
}
}
else
{ // Liquid Block. Treat as full block
occludeFaces = new boolean[6];
Arrays.fill(occludeFaces, true);
fullFaces = new boolean[6];
Arrays.fill(fullFaces, true);
}
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -16,31 +16,34 @@
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.chunk;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper;
import com.seibel.distanthorizons.common.wrappers.misc.MutableBlockPosWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.DhLitWorldGenRegion;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IMutableBlockPosWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.levelgen.Heightmap;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.Logger;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
#if MC_VER >= MC_1_17_1
import net.minecraft.core.QuartPos;
@@ -64,6 +67,8 @@ import net.minecraft.world.level.chunk.LevelChunkSection;
#if MC_VER >= MC_1_20_1
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.core.SectionPos;
#endif
#if MC_VER <= MC_1_20_4
@@ -75,34 +80,45 @@ import net.minecraft.world.level.chunk.status.ChunkStatus;
public class ChunkWrapper implements IChunkWrapper
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/** can be used for interactions with the underlying chunk where creating new BlockPos objects could cause issues for the garbage collector. */
private static final ThreadLocal<BlockPos.MutableBlockPos> MUTABLE_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new BlockPos.MutableBlockPos());
private static final ThreadLocal<MutableBlockPosWrapper> MUTABLE_BLOCK_POS_WRAPPER_REF = ThreadLocal.withInitial(() -> new MutableBlockPosWrapper());
private static boolean heightmapThreadWarningLogged = false;
private static final ThreadLocal<BlockPos.MutableBlockPos> MUTABLE_BLOCK_POS_REF = ThreadLocal.withInitial(BlockPos.MutableBlockPos::new);
private final ChunkAccess chunk;
private final DhChunkPos chunkPos;
private final LevelReader lightSource;
private final ILevelWrapper wrappedLevel;
private boolean isDhBlockLightCorrect = false;
private boolean isDhSkyLightCorrect = false;
private boolean isDhLightCorrect = false;
/** only used when connected to a dedicated server */
private boolean isMcClientLightingCorrect = false;
private ChunkLightStorage blockLightStorage;
private ChunkLightStorage skyLightStorage;
private ArrayList<DhBlockPos> blockLightPosList = null;
private boolean useDhLighting;
private int minNonEmptyHeight = Integer.MIN_VALUE;
private int maxNonEmptyHeight = Integer.MAX_VALUE;
/** will be null if we are using MC heightmaps */
private int[][] solidHeightMap = null;
/** will be null if we are using MC heightmaps */
private int[][] lightBlockingHeightMap = null;
private int blockBiomeHashCode = 0;
/**
* Due to vanilla `isClientLightReady()` not being designed for use by a non-render thread, it may return 'true'
* before the light engine has ticked, (right after all light changes is marked by the engine to be processed).
* To fix this, on client-only mode, we mixin-redirect the `isClientLightReady()` so that after the call, it will
* trigger a synchronous update of this flag here on all chunks that are wrapped. <br><br>
*
* Note: Using a static weak hash map to store the chunks that need to be updated, as instance of chunk wrapper
* can be duplicated, with same chunk instance. And the data stored here are all temporary, and thus will not be
* visible when a chunk is re-wrapped later. <br>
* (Also, thread safety done via a reader writer lock)
*/
private static final ConcurrentLinkedQueue<ChunkWrapper> chunksNeedingClientLightUpdating = new ConcurrentLinkedQueue<>();
@@ -110,20 +126,22 @@ public class ChunkWrapper implements IChunkWrapper
// constructor //
//=============//
/**
* Note: this constructor should be very
* fast since it will be called frequently on the MC
* server thread and a slow method will cause server lag.
*/
public ChunkWrapper(ChunkAccess chunk, ILevelWrapper wrappedLevel)
public ChunkWrapper(ChunkAccess chunk, LevelReader lightSource, ILevelWrapper wrappedLevel)
{
this.chunk = chunk;
this.lightSource = lightSource;
this.wrappedLevel = wrappedLevel;
this.chunkPos = new DhChunkPos(chunk.getPos().x, chunk.getPos().z);
}
@Override
public ChunkWrapper copy() { return new ChunkWrapper(this.chunk, this.wrappedLevel); }
// TODO is this the best way to differentiate between when we are generating chunks and when MC gave us a chunk?
boolean isDhGeneratedChunk = (this.lightSource.getClass() == DhLitWorldGenRegion.class);
// MC loaded chunks should get their lighting from MC, DH generated chunks should get their lighting from DH
this.useDhLighting = isDhGeneratedChunk;
// FIXME +1 is to handle the fact that LodDataBuilder adds +1 to all block lighting calculations, also done in the relative position validator
chunksNeedingClientLightUpdating.add(this);
}
@@ -143,29 +161,19 @@ public class ChunkWrapper implements IChunkWrapper
}
@Override
public int getInclusiveMinBuildHeight() { return getInclusiveMinBuildHeight(this.chunk); }
public static int getInclusiveMinBuildHeight(ChunkAccess chunk)
public int getMinBuildHeight() { return getMinBuildHeight(this.chunk); }
public static int getMinBuildHeight(ChunkAccess chunk)
{
#if MC_VER < MC_1_17_1
return 0;
#elif MC_VER < MC_1_21_3
return chunk.getMinBuildHeight();
#else
return chunk.getMinY();
return chunk.getMinBuildHeight();
#endif
}
@Override
public int getExclusiveMaxBuildHeight() { return getExclusiveMaxBuildHeight(this.chunk); }
public static int getExclusiveMaxBuildHeight(ChunkAccess chunk)
{
#if MC_VER < MC_1_21_3
return chunk.getMaxBuildHeight();
#else
// +1 since Minecraft made the max value inclusive
return chunk.getMaxY() + 1;
#endif
}
public int getMaxBuildHeight() { return getMaxBuildHeight(this.chunk); }
public static int getMaxBuildHeight(ChunkAccess chunk) { return chunk.getMaxBuildHeight(); }
@Override
public int getMinNonEmptyHeight()
@@ -177,7 +185,7 @@ public class ChunkWrapper implements IChunkWrapper
// default if every section is empty or missing
this.minNonEmptyHeight = this.getInclusiveMinBuildHeight();
this.minNonEmptyHeight = this.getMinBuildHeight();
// determine the lowest empty section (bottom up)
LevelChunkSection[] sections = this.chunk.getSections();
@@ -209,7 +217,7 @@ public class ChunkWrapper implements IChunkWrapper
// default if every section is empty or missing
this.maxNonEmptyHeight = this.getExclusiveMaxBuildHeight();
this.maxNonEmptyHeight = this.getMaxBuildHeight();
// determine the highest empty section (top down)
LevelChunkSection[] sections = this.chunk.getSections();
@@ -242,99 +250,14 @@ public class ChunkWrapper implements IChunkWrapper
return section.hasOnlyAir();
#endif
}
private int getChunkSectionMinHeight(int index) { return (index * 16) + this.getInclusiveMinBuildHeight(); }
private int getChunkSectionMinHeight(int index) { return (index * 16) + this.getMinBuildHeight(); }
@Override
public void createDhHeightMaps()
{
if (heightmapThreadWarningLogged
&& !DhApi.isDhThread())
{
LOGGER.warn("ChunkWrapper Height maps created on non-DH thread ["+Thread.currentThread().getName()+"]. This may cause stuttering.");
}
this.solidHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH];
this.lightBlockingHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH];
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
int minInclusiveBuildHeight = this.getMinNonEmptyHeight();
// if no blocks are found the height map will be at the bottom of the world
int solidHeight = minInclusiveBuildHeight;
int lightBlockingHeight = minInclusiveBuildHeight;
int y = this.getMaxNonEmptyHeight(); //this.getExclusiveMaxBuildHeight();
IBlockStateWrapper block = this.getBlockState(x, y, z);
while (// go down until we reach the minimum build height
y > minInclusiveBuildHeight
// keep going until we find both height map values
&&
(
solidHeight == minInclusiveBuildHeight
|| lightBlockingHeight == minInclusiveBuildHeight
)
)
{
// is this block solid?
if (solidHeight == minInclusiveBuildHeight
&& block.isSolid())
{
solidHeight = y;
}
// is this block light blocking?
if (lightBlockingHeight == minInclusiveBuildHeight
&& block.getOpacity() != LodUtil.BLOCK_FULLY_TRANSPARENT)
{
lightBlockingHeight = y;
}
// get the next block down
y--;
block = this.getBlockState(x, y, z);
}
this.solidHeightMap[x][z] = solidHeight;
this.lightBlockingHeightMap[x][z] = lightBlockingHeight;
}
}
}
public int getSolidHeightMapValue(int xRel, int zRel) { return this.chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE).getFirstAvailable(xRel, zRel); }
@Override
public int getSolidHeightMapValue(int xRel, int zRel)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(xRel, zRel);
// will be null if we want to use MC heightmaps
if (this.solidHeightMap == null)
{
return this.chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE).getFirstAvailable(xRel, zRel);
}
else
{
return this.solidHeightMap[xRel][zRel];
}
}
@Override
public int getLightBlockingHeightMapValue(int xRel, int zRel)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(xRel, zRel);
if (this.lightBlockingHeightMap == null)
{
return this.chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING).getFirstAvailable(xRel, zRel);
}
else
{
return this.lightBlockingHeightMap[xRel][zRel];
}
}
public int getLightBlockingHeightMapValue(int xRel, int zRel) { return this.chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING).getFirstAvailable(xRel, zRel); }
@Override
@@ -371,134 +294,18 @@ public class ChunkWrapper implements IChunkWrapper
blockPos.setY(relY);
blockPos.setZ(relZ);
// TODO copy into pooled array, this isn't thread safe and can cause MC to throw errors if the chunk is loaded
return BlockStateWrapper.fromBlockState(this.chunk.getBlockState(blockPos), this.wrappedLevel);
}
@Override
public IBlockStateWrapper getBlockState(int relX, int relY, int relZ, IMutableBlockPosWrapper mcBlockPos, IBlockStateWrapper guess)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, relY, relZ);
BlockPos.MutableBlockPos pos = (BlockPos.MutableBlockPos)mcBlockPos.getWrappedMcObject();
pos.setX(relX);
pos.setY(relY);
pos.setZ(relZ);
return BlockStateWrapper.fromBlockState(this.chunk.getBlockState(pos), this.wrappedLevel, guess);
}
/**
// Commented out experimental LevelChunkSection cloning logic to fix extremely rare concurrency modification issue
// James has only ever seen a report relating to LevelSection concurrent modification once,
// the issue can cause DH lighting/LOD building to fail due to the chunk being modified on the server.
// James has only heard of this issue once, so it isn't a high priority issue.
// And from James' quick look at a few different MC versions it appears the LevelChunkSection object changes quite drastically between MC versions,
// meaning any cloning logic would have to either be a new wrapper or very MC version dependent, either way a lot of additional work.
// Due to the large time cost and extremely rare nature of the issue, this logic is commented out unless this issue pops up again in the future.
// instance variable to hold the cloned sections
private final LevelChunkSection[] levelChunkSections;
// new constructor logic to clone the sections
public constructor(...)
{
// other constructor logic //
LevelChunkSection[] sections = this.chunk.getSections();
this.levelChunkSections = new LevelChunkSection[sections.length];
for (int i = 0; i < sections.length; i++)
{
LevelChunkSection section = sections[i];
if (section != null)
{
// TODO implement section cloning for older MC versions, only 1.21.4 MC (and maybe other semi recent versions) have a clean way to handle this
// TODO we probably want a wrapper object instead
#if MC_VER < MC_1_21_4
this.levelChunkSections[i] = section;
#else
this.levelChunkSections[i] = section.copy();
#endif
}
}
}
// replacement getters
@Override
public IBlockStateWrapper getBlockState(int relX, int relY, int relZ)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, relY, relZ);
return this.getBlockStateInternal(relX, relY, relZ, null);
}
@Override
public IBlockStateWrapper getBlockState(int relX, int relY, int relZ, IMutableBlockPosWrapper mcBlockPos, IBlockStateWrapper guess)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, relY, relZ);
return this.getBlockStateInternal(relX, relY, relZ, guess);
}
// internal getter logic
private IBlockStateWrapper getBlockStateInternal(int relX, int y, int relZ, @Nullable IBlockStateWrapper guess)
{
try
{
// attempt to get the section for this position
int i = (y - this.getInclusiveMinBuildHeight()) / 16;
if (i >= 0 && i < this.levelChunkSections.length)
{
LevelChunkSection section = this.levelChunkSections[i];
if (!section.hasOnlyAir())
{
if (guess != null)
{
return BlockStateWrapper.fromBlockState(section.getBlockState(relX & 15, y & 15, relZ & 15), this.wrappedLevel, guess);
}
else
{
return BlockStateWrapper.fromBlockState(section.getBlockState(relX & 15, y & 15, relZ & 15), this.wrappedLevel);
}
}
}
return BlockStateWrapper.AIR;
}
catch (Exception e)
{
return BlockStateWrapper.AIR;
}
}
*/
@Override
public IMutableBlockPosWrapper getMutableBlockPosWrapper() { return MUTABLE_BLOCK_POS_WRAPPER_REF.get(); }
@Override
public DhChunkPos getChunkPos() { return this.chunkPos; }
public ChunkAccess getChunk() { return this.chunk; }
public void trySetStatus(ChunkStatus status) { trySetStatus(this.getChunk(), status); }
/** does nothing if the chunk object doesn't support setting it's status */
public static void trySetStatus(ChunkAccess chunk, ChunkStatus status)
{
if (chunk instanceof ProtoChunk)
{
#if MC_VER < MC_1_21_1
((ProtoChunk) chunk).setStatus(status);
#else
((ProtoChunk) chunk).setPersistedStatus(status);
#endif
}
}
public ChunkStatus getStatus() { return getStatus(this.getChunk()); }
public static ChunkStatus getStatus(ChunkAccess chunk)
{
#if MC_VER < MC_1_21_1
#if MC_VER < MC_1_21
return chunk.getStatus();
#else
return chunk.getPersistedStatus();
@@ -521,14 +328,43 @@ public class ChunkWrapper implements IChunkWrapper
//==========//
@Override
public void setIsDhSkyLightCorrect(boolean isDhLightCorrect) { this.isDhSkyLightCorrect = isDhLightCorrect; }
@Override
public void setIsDhBlockLightCorrect(boolean isDhLightCorrect) { this.isDhBlockLightCorrect = isDhLightCorrect; }
public void setIsDhLightCorrect(boolean isDhLightCorrect) { this.isDhLightCorrect = isDhLightCorrect; }
@Override
public boolean isDhBlockLightingCorrect() { return this.isDhBlockLightCorrect; }
public void setUseDhLighting(boolean useDhLighting) { this.useDhLighting = useDhLighting; }
@Override
public boolean isDhSkyLightCorrect() { return this.isDhSkyLightCorrect; }
public boolean isLightCorrect()
{
if (this.useDhLighting)
{
return this.isDhLightCorrect;
}
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
return false; // MC's lighting engine doesn't work consistently enough to trust for 1.16 or 1.17
#else
if (this.chunk instanceof LevelChunk levelChunk)
{
if (levelChunk.getLevel() instanceof ClientLevel)
{
// connected to a server
return this.isMcClientLightingCorrect;
}
else
{
// in a single player world
return this.chunk.isLightCorrect() && levelChunk.loaded;
}
}
else
{
// called when in a single player world and the chunk is a proto chunk (in world gen, and not active)
return this.chunk.isLightCorrect();
}
#endif
}
@Override
@@ -553,8 +389,6 @@ public class ChunkWrapper implements IChunkWrapper
return this.blockLightStorage;
}
public void setBlockLightStorage(ChunkLightStorage lightStorage) { this.blockLightStorage = lightStorage; }
@Override
public void clearDhBlockLighting() { this.getBlockLightStorage().clear(); }
@Override
@@ -569,8 +403,6 @@ public class ChunkWrapper implements IChunkWrapper
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
this.getSkyLightStorage().set(relX, y, relZ, lightValue);
}
@Override
public void clearDhSkyLighting() { this.getSkyLightStorage().clear(); }
private ChunkLightStorage getSkyLightStorage()
{
@@ -583,12 +415,50 @@ public class ChunkWrapper implements IChunkWrapper
public void setSkyLightStorage(ChunkLightStorage lightStorage) { this.skyLightStorage = lightStorage; }
@Override
public int getBlockLight(int relX, int y, int relZ)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
// use the full lighting engine when the chunks are within render distance or the config requests it
if (this.useDhLighting)
{
// DH lighting method
return this.getBlockLightStorage().get(relX, y, relZ);
}
else
{
// note: this returns 0 if the chunk is unload
// MC lighting method
return this.lightSource.getBrightness(LightLayer.BLOCK, new BlockPos(relX + this.getMinBlockX(), y, relZ + this.getMinBlockZ()));
}
}
@Override
public int getSkyLight(int relX, int y, int relZ)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
// use the full lighting engine when the chunks are within render distance or the config requests it
if (this.useDhLighting)
{
// DH lighting method
return this.getSkyLightStorage().get(relX, y, relZ);
}
else
{
// MC lighting method
return this.lightSource.getBrightness(LightLayer.SKY, new BlockPos(relX + this.getMinBlockX(), y, relZ + this.getMinBlockZ()));
}
}
/**
* FIXME synchronized is necessary for a rare issue where this method is called from two separate threads at the same time
* before the list has finished populating.
*/
@Override
public synchronized ArrayList<DhBlockPos> getWorldBlockLightPosList()
public synchronized ArrayList<DhBlockPos> getBlockLightPosList()
{
// only populate the list once
if (this.blockLightPosList == null)
@@ -603,21 +473,104 @@ public class ChunkWrapper implements IChunkWrapper
});
#else
this.chunk.findBlockLightSources((blockPos, blockState) ->
{
DhBlockPos pos = new DhBlockPos(blockPos.getX(), blockPos.getY(), blockPos.getZ());
// this can be uncommented if MC decides to return relative block positions in the future instead of world positions
//pos.mutateToChunkRelativePos(pos);
//pos.mutateOffset(this.chunkPos.getMinBlockX(), 0, this.chunkPos.getMinBlockZ(), pos);
this.blockLightPosList.add(pos);
});
this.blockLightPosList.add(new DhBlockPos(blockPos.getX(), blockPos.getY(), blockPos.getZ())));
#endif
}
return this.blockLightPosList;
}
public static void syncedUpdateClientLightStatus()
{
#if MC_VER < MC_1_18_2
// TODO: Check what to do in 1.18.1 and older
// since we don't currently handle this list,
// clear it to prevent memory leaks
chunksNeedingClientLightUpdating.clear();
#else
// update the chunks client lighting
ChunkWrapper chunkWrapper = chunksNeedingClientLightUpdating.poll();
while (chunkWrapper != null)
{
chunkWrapper.updateIsClientLightingCorrect();
chunkWrapper = chunksNeedingClientLightUpdating.poll();
}
#endif
}
/** Should be called after client light updates are triggered. */
private void updateIsClientLightingCorrect()
{
if (this.chunk instanceof LevelChunk levelChunk && ((LevelChunk) this.chunk).getLevel() instanceof ClientLevel)
{
ClientChunkCache clientChunkCache = ((ClientLevel) levelChunk.getLevel()).getChunkSource();
this.isMcClientLightingCorrect = clientChunkCache.getChunkForLighting(this.chunk.getPos().x, this.chunk.getPos().z) != null &&
#if MC_VER <= MC_1_17_1
levelChunk.isLightCorrect();
#elif MC_VER < MC_1_20_1
levelChunk.isClientLightReady();
#else
checkLightSectionsOnChunk(levelChunk, levelChunk.getLevel().getLightEngine());
#endif
}
}
#if MC_VER >= MC_1_20_1
private static boolean checkLightSectionsOnChunk(LevelChunk chunk, LevelLightEngine engine)
{
LevelChunkSection[] sections = chunk.getSections();
int minY = chunk.getMinSection();
int maxY = chunk.getMaxSection();
for (int y = minY; y < maxY; ++y)
{
LevelChunkSection section = sections[chunk.getSectionIndexFromSectionY(y)];
if (section.hasOnlyAir()) continue;
if (!engine.lightOnInSection(SectionPos.of(chunk.getPos(), y)))
{
return false;
}
}
return true;
}
#endif
//===============//
// other methods //
//===============//
@Override
public boolean doNearbyChunksExist()
{
if (this.lightSource instanceof DhLitWorldGenRegion)
{
return true;
}
for (int dx = -1; dx <= 1; dx++)
{
for (int dz = -1; dz <= 1; dz++)
{
if (dx == 0 && dz == 0)
{
continue;
}
else if (this.lightSource.getChunk(dx + this.chunk.getPos().x, dz + this.chunk.getPos().z, ChunkStatus.BIOMES, false) == null)
{
return false;
}
}
}
return true;
}
@Override
public boolean isStillValid() { return this.wrappedLevel.tryGetChunk(this.chunkPos) == this; }
//================//
@@ -1,98 +0,0 @@
package com.seibel.distanthorizons.common.wrappers.gui;
#if MC_VER < MC_1_21_9
// not supported for older MC versions
#else
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.client.gui.components.debug.DebugScreenDisplayer;
import net.minecraft.client.gui.components.debug.DebugScreenEntries;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.LevelChunk;
#if MC_VER <= MC_1_21_10
import net.minecraft.resources.ResourceLocation;
#endif
#endif
#if MC_VER < MC_1_21_9
// not supported for older MC versions
public class DhDebugScreenEntry
{}
#else
public class DhDebugScreenEntry implements net.minecraft.client.gui.components.debug.DebugScreenEntry
{
public static void register()
{
// This method is private, so its access will need to be widened
DebugScreenEntries.register(
// The id, this will be displayed on the options screen
#if MC_VER <= MC_1_21_10
ResourceLocation.fromNamespaceAndPath(ModInfo.RESOURCE_NAMESPACE, "distant_horizons"),
#else
"distant_horizons",
#endif
// The screen entry
new DhDebugScreenEntry()
);
}
@Override
public void display(@NotNull DebugScreenDisplayer displayer, @Nullable Level level, @Nullable LevelChunk clientChunk, @Nullable LevelChunk serverChunk)
{
List<String> messageList = new ArrayList<>();
F3Screen.addStringToDisplay(messageList);
for (String message : messageList)
{
displayer.addLine(message);
}
//// The following will display like so if it is the only entry on the screen:
//// First left! First Right!
////
//// Hello world! Random text!
//// Lorem ipsum.
//// I am another group!
//// I am one group This will appear after with no line breaks!
//// All in a row
//// Provided in a list.
////
//
//displayer.addLine("Hello world!");
//displayer.addLine("Lorem ipsum.");
//displayer.addLine("Random text!");
//
//// These will be displayed first
//displayer.addPriorityLine("First left!");
//displayer.addPriorityLine("First right!");
//
//// These will be grouped separately based on the key
//displayer.addToGroup(GROUP_ONE, List.of(
// "I am one group",
// "All in a row",
// "Provided in a list."
//));
//
//displayer.addToGroup(GROUP_TWO, "I am another group!");
//displayer.addToGroup(GROUP_TWO, "This will appear after with no line breaks!");
}
@Override
public boolean isAllowed(boolean reducedDebugInfo)
{
// Always show regardless of accessibility option
return true;
}
}
#endif
@@ -52,27 +52,6 @@ public class DhScreen extends Screen
{
renderTooltip(guiStack, comp, x, y);
}
#elif MC_VER < MC_1_21_6
protected void DhDrawCenteredString(GuiGraphics guiStack, Font font, Component text, int x, int y, int color)
{
guiStack.drawCenteredString(font, text, x, y, color);
}
protected void DhDrawString(GuiGraphics guiStack, Font font, Component text, int x, int y, int color)
{
guiStack.drawString(font, text, x, y, color);
}
//protected void DhRenderTooltip(GuiGraphics guiStack, Font font, List<? extends net.minecraft.util.FormattedCharSequence> text, int x, int y)
//{
// guiStack.renderTooltip(font, text, x, y);
//}
protected void DhRenderComponentTooltip(GuiGraphics guiStack, Font font, List<Component> comp, int x, int y)
{
guiStack.renderComponentTooltip(font, comp, x, y);
}
protected void DhRenderTooltip(GuiGraphics guiStack, Font font, Component text, int x, int y)
{
guiStack.renderTooltip(font, text, x, y);
}
#else
protected void DhDrawCenteredString(GuiGraphics guiStack, Font font, Component text, int x, int y, int color)
{
@@ -82,20 +61,17 @@ public class DhScreen extends Screen
{
guiStack.drawString(font, text, x, y, color);
}
//protected void DhRenderTooltip(GuiGraphics guiStack, Font font, List<? extends net.minecraft.util.FormattedCharSequence> text, int x, int y)
//{
// //guiStack.renderTooltip(font, text, x, y);
//}
protected void DhRenderTooltip(GuiGraphics guiStack, Font font, List<? extends net.minecraft.util.FormattedCharSequence> text, int x, int y)
{
guiStack.renderTooltip(font, text, x, y);
}
protected void DhRenderComponentTooltip(GuiGraphics guiStack, Font font, List<Component> comp, int x, int y)
{
guiStack.setComponentTooltipForNextFrame(font, comp, x, y);
guiStack.renderComponentTooltip(font, comp, x, y);
}
protected void DhRenderTooltip(GuiGraphics guiStack, Font font, Component text, int x, int y)
{
guiStack.setTooltipForNextFrame(font, text, x, y);
guiStack.renderTooltip(font, text, x, y);
}
#endif
}
@@ -1,55 +1,42 @@
package com.seibel.distanthorizons.common.wrappers.gui;
import com.seibel.distanthorizons.core.config.ConfigHandler;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.config.ConfigBase;
import com.seibel.distanthorizons.core.config.gui.ConfigScreen;
import com.seibel.distanthorizons.core.config.gui.JavaScreenHandlerScreen;
import com.seibel.distanthorizons.core.config.gui.OpenGLConfigScreen;
import net.minecraft.client.gui.screens.Screen;
import com.seibel.distanthorizons.core.logging.DhLogger;
public class GetConfigScreen
{
protected static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static type useScreen = type.Classic;
public static EType useScreen = EType.Classic;
public enum EType
public enum type
{
Classic,
JavaSwing;
@Deprecated
OpenGL, // This was just an attempt, it didn't work out, and we are going to change to javafx soon (as soon as that works)
JavaFX;
}
public static Screen getScreen(Screen parent)
{
// TODO it'd be nice to have this run automatically on startup
// but this will only work once MC has added our lang file,
// which won't be for sure added until we request a GUI
if (ModInfo.IS_DEV_BUILD)
{
String missingLangEntries = ConfigHandler.INSTANCE.generateLang(true, true);
// Generate the language
// This shouldn't be here, but I need a way to test it after Minecraft inits its assets
//System.out.println(ConfigBase.INSTANCE.generateLang(false, true));
// trim to remove any newlines/spaces
// that may be present when no lang entries need changing
// then we can check length != 0 if any items are missing and need adding
String trimmedMissingEntries = missingLangEntries.trim();
if (!trimmedMissingEntries.isEmpty())
return switch (useScreen)
{
LOGGER.warn("One or more language entries is missing:");
LOGGER.warn(missingLangEntries);
}
}
switch (useScreen)
case Classic -> ClassicConfigGUI.getScreen(ConfigBase.INSTANCE, parent, "client");
case OpenGL ->
{
case Classic:
return ClassicConfigGUI.getScreen(parent, "client");
case JavaSwing:
//return MinecraftScreen.getScreen(parent, new JavaScreenHandlerScreen(new ConfigScreen()), ModInfo.ID + ".title");
return MinecraftScreen.getScreen(parent, new JavaScreenHandlerScreen(new JavaScreenHandlerScreen.ExampleScreen()), ModInfo.ID + ".title");
default:
throw new IllegalArgumentException("No config screen implementation defined for ["+useScreen+"].");
MinecraftScreen.getScreen(parent, new OpenGLConfigScreen(), ModInfo.ID + ".title");
yield null;
}
// case JavaFX -> MinecraftScreen.getScreen(parent, new JavaScreenHandlerScreen(new JavaScreenHandlerScreen.ExampleScreen()), ModInfo.ID + ".title");
case JavaFX -> MinecraftScreen.getScreen(parent, new JavaScreenHandlerScreen(new ConfigScreen()), ModInfo.ID + ".title");
default -> throw new IllegalArgumentException("No config screen implementation defined for [" + useScreen + "].");
};
}
}
@@ -24,26 +24,26 @@ public class MinecraftScreen
private static class ConfigScreenRenderer extends DhScreen
{
private final Screen parent;
private ConfigListWidget configListWidget;
private ConfigListWidget list;
private AbstractScreen screen;
#if MC_VER < MC_1_19_2
public static net.minecraft.network.chat.TranslatableComponent translate(String str, Object... args)
{ return new net.minecraft.network.chat.TranslatableComponent(str, args); }
{
return new net.minecraft.network.chat.TranslatableComponent(str, args);
}
#else
public static net.minecraft.network.chat.MutableComponent translate(String str, Object... args)
{ return net.minecraft.network.chat.Component.translatable(str, args); }
{
return net.minecraft.network.chat.Component.translatable(str, args);
}
#endif
protected ConfigScreenRenderer(Screen parent, AbstractScreen screen, String translationName)
{
super(translate(translationName));
#if MC_VER < MC_1_21_9
screen.minecraftWindow = Minecraft.getInstance().getWindow().getWindow();
#else
screen.minecraftWindow = Minecraft.getInstance().getWindow().handle();
#endif
this.parent = parent;
this.screen = screen;
}
@@ -53,22 +53,20 @@ public class MinecraftScreen
{
super.init(); // Init Minecraft's screen
Window mcWindow = this.minecraft.getWindow();
this.screen.width = mcWindow.getWidth();
this.screen.height = mcWindow.getHeight();
this.screen.scaledWidth = this.width;
this.screen.scaledHeight = this.height;
this.screen.init(); // Init our own config screen
screen.width = mcWindow.getWidth();
screen.height = mcWindow.getHeight();
screen.scaledWidth = this.width;
screen.scaledHeight = this.height;
screen.init(); // Init our own config screen
this.configListWidget = new ConfigListWidget(this.minecraft, this.width, this.height, 0, 0, 25); // Select the area to tint
this.list = new ConfigListWidget(this.minecraft, this.width, this.height, 0, 0, 25); // Select the area to tint
#if MC_VER < MC_1_20_6 // no background is rendered in MC 1.20.6+
if (this.minecraft != null && this.minecraft.level != null) // Check if in game
{
this.configListWidget.setRenderBackground(false); // Disable from rendering
}
this.list.setRenderBackground(false); // Disable from rendering
#endif
this.addWidget(this.configListWidget); // Add the tint to the things to be rendered
this.addWidget(this.list); // Add the tint to the things to be rendered
}
@Override
@@ -80,71 +78,58 @@ public class MinecraftScreen
{
#if MC_VER < MC_1_20_2
this.renderBackground(matrices); // Render background
#elif MC_VER < MC_1_21_6
this.renderBackground(matrices, mouseX, mouseY, delta); // Render background
#else
// background blur is already being rendered, rendering again causes the game to crash
this.renderBackground(matrices, mouseX, mouseY, delta); // Render background
#endif
this.list.render(matrices, mouseX, mouseY, delta); // Renders the items in the render list (currently only used to tint background darker)
this.configListWidget.render(matrices, mouseX, mouseY, delta); // Renders the items in the render list (currently only used to tint background darker)
this.screen.mouseX = mouseX;
this.screen.mouseY = mouseY;
this.screen.render(delta); // Render everything on the main screen
screen.mouseX = mouseX;
screen.mouseY = mouseY;
screen.render(delta); // Render everything on the main screen
super.render(matrices, mouseX, mouseY, delta); // Render the vanilla stuff (currently only used for the background and tint)
}
#if MC_VER <= MC_1_21_10
@Override
public void resize(Minecraft mc, int width, int height)
#else
@Override
public void resize(int width, int height)
#endif
{
// Resize Minecraft's screen
#if MC_VER <= MC_1_21_10
super.resize(mc, width, height);
#else
super.resize(width, height);
#endif
super.resize(mc, width, height); // Resize Minecraft's screen
Window mcWindow = this.minecraft.getWindow();
this.screen.width = mcWindow.getWidth();
this.screen.height = mcWindow.getHeight();
this.screen.scaledWidth = this.width;
this.screen.scaledHeight = this.height;
this.screen.onResize(); // Resize our screen
screen.width = mcWindow.getWidth();
screen.height = mcWindow.getHeight();
screen.scaledWidth = this.width;
screen.scaledHeight = this.height;
screen.onResize(); // Resize our screen
}
@Override
public void tick()
{
super.tick(); // Tick Minecraft's screen
this.screen.tick(); // Tick our screen
if (this.screen.close) // If we decide to close the screen, then actually close the screen
{
this.onClose();
}
screen.tick(); // Tick our screen
if (screen.close) // If we decide to close the screen, then actually close the screen
onClose();
}
@Override
public void onClose()
{
this.screen.onClose(); // Close our screen
Objects.requireNonNull(this.minecraft).setScreen(this.parent); // Goto the parent screen
screen.onClose(); // Close our screen
Objects.requireNonNull(minecraft).setScreen(this.parent); // Goto the parent screen
}
@Override
public void onFilesDrop(@NotNull List<Path> files)
{ this.screen.onFilesDrop(files); }
{
screen.onFilesDrop(files);
}
// For checking if it should close when you press the escape key
@Override
public boolean shouldCloseOnEsc()
{ return this.screen.shouldCloseOnEsc; }
{
return screen.shouldCloseOnEsc;
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -19,49 +19,31 @@
package com.seibel.distanthorizons.common.wrappers.gui;
import net.minecraft.network.chat.Component;
#if MC_VER >= MC_1_17_1
import net.minecraft.client.gui.components.Button;
#endif
#if MC_VER < MC_1_17_1
import net.minecraft.client.gui.components.ImageButton;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
#elif MC_VER < MC_1_20_1
import net.minecraft.client.gui.components.ImageButton;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.renderer.GameRenderer;
#elif MC_VER < MC_1_20_2
import net.minecraft.client.gui.components.ImageButton;
import net.minecraft.client.gui.GuiGraphics;
#elif MC_VER < MC_1_21_6
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
#elif MC_VER <= MC_1_21_10
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderPipelines;
#else
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderPipelines;
#endif
#if MC_VER <= MC_1_21_10
import net.minecraft.resources.ResourceLocation;
#else
import net.minecraft.resources.Identifier;
#endif
/**
* Creates a button with a texture on it (and a background) that works with all mc versions
*
* @author coolGi
* @version 2023-10-03
*/
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.gui.components.AbstractButton;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.ImageButton;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
#if MC_VER >= MC_1_17_1
import net.minecraft.client.renderer.GameRenderer;
#endif
#if MC_VER < MC_1_20_1
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
#else
import net.minecraft.client.gui.GuiGraphics;
#endif
#if MC_VER < MC_1_20_2
public class TexturedButtonWidget extends ImageButton
#else
@@ -75,36 +57,20 @@ public class TexturedButtonWidget extends Button
private final int v;
private final int hoveredVOffset;
#if MC_VER <= MC_1_21_10
private final ResourceLocation textureResourceLocation;
#else
private final Identifier textureResourceLocation;
#endif
private final ResourceLocation texture;
private final int textureWidth;
private final int textureHeight;
#endif
public TexturedButtonWidget(
int x, int y, int width, int height, int u, int v, int hoveredVOffset,
#if MC_VER <= MC_1_21_10 ResourceLocation textureResourceLocation,
#else Identifier textureResourceLocation,
#endif
int textureWidth, int textureHeight, OnPress pressAction, Component text)
{
this(x, y, width, height, u, v, hoveredVOffset, textureResourceLocation, textureWidth, textureHeight, pressAction, text, true);
public TexturedButtonWidget(int x, int y, int width, int height, int u, int v, int hoveredVOffset, ResourceLocation texture, int textureWidth, int textureHeight, OnPress pressAction, Component text) {
this(x, y, width, height, u, v, hoveredVOffset, texture, textureWidth, textureHeight, pressAction, text, true);
}
public TexturedButtonWidget(
int x, int y, int width, int height, int u, int v, int hoveredVOffset,
#if MC_VER <= MC_1_21_10 ResourceLocation textureResourceLocation,
#else Identifier textureResourceLocation,
#endif
int textureWidth, int textureHeight, OnPress pressAction, Component text,
boolean renderBackground)
public TexturedButtonWidget(int x, int y, int width, int height, int u, int v, int hoveredVOffset, ResourceLocation texture, int textureWidth, int textureHeight, OnPress pressAction, Component text, boolean renderBackground)
{
#if MC_VER < MC_1_20_2
super(x, y, width, height, u, v, hoveredVOffset, textureResourceLocation, textureWidth, textureHeight, pressAction, text);
super(x, y, width, height, u, v, hoveredVOffset, texture, textureWidth, textureHeight, pressAction, text);
#else
// We don't pass on the text option as otherwise it will render (we normally pass it for narration)
// TODO: Find a fix for it
@@ -114,7 +80,7 @@ public class TexturedButtonWidget extends Button
this.v = v;
this.hoveredVOffset = hoveredVOffset;
this.textureResourceLocation = textureResourceLocation;
this.texture = texture;
this.textureWidth = textureWidth;
this.textureHeight = textureHeight;
@@ -126,8 +92,7 @@ public class TexturedButtonWidget extends Button
#if MC_VER < MC_1_20_2
#if MC_VER < MC_1_19_4
@Override
public void renderButton(PoseStack matrices, int mouseX, int mouseY, float delta)
{
public void renderButton(PoseStack matrices, int mouseX, int mouseY, float delta) {
if (this.renderBackground) // Renders the background of the button
{
#if MC_VER < MC_1_17_1
@@ -154,7 +119,6 @@ public class TexturedButtonWidget extends Button
super.renderButton(matrices, mouseX, mouseY, delta);
}
#else
#if MC_VER < MC_1_20_1
@Override
@@ -190,68 +154,24 @@ public class TexturedButtonWidget extends Button
super.renderWidget(matrices, mouseX, mouseY, delta);
}
#endif
#else
#if MC_VER < MC_1_21_11
@Override
public void renderWidget(GuiGraphics matrices, int mouseX, int mouseY, float delta)
#else
@Override
protected void renderContents(GuiGraphics matrices, int mouseX, int mouseY, float delta)
#endif
{
if (this.renderBackground)
{
#if MC_VER < MC_1_21_3
//RenderSystem.enableBlend();
//RenderSystem.enableDepthTest();
matrices.blitSprite(SPRITES.get(this.active, this.isHoveredOrFocused()), this.getX(), this.getY(), this.getWidth(), this.getHeight());
#elif MC_VER < MC_1_21_6
matrices.blitSprite(
RenderType::guiTextured,
SPRITES.get(this.active, this.isHoveredOrFocused()),
this.getX(), this.getY(),
this.getWidth(), this.getHeight());
#else
matrices.blitSprite(
RenderPipelines.GUI_TEXTURED,
SPRITES.get(this.active, this.isHoveredOrFocused()),
this.getX(), this.getY(),
this.getWidth(), this.getHeight());
#endif
}
// Renders the sprite
int i = 0;
if (!this.active)
{
i = 2;
}
else if (this.isHovered)
{
i = 1;
}
if (!this.active) i = 2;
else if (this.isHovered) i = 1;
#if MC_VER < MC_1_21_3
matrices.blit(this.textureResourceLocation, this.getX(), this.getY(), this.u, this.v + (this.hoveredVOffset * i), this.width, this.height, this.textureWidth, this.textureHeight);
#elif MC_VER < MC_1_21_6
matrices.blit(
RenderType::guiTextured,
this.textureResourceLocation,
this.getX(), this.getY(),
this.u, this.v + (this.hoveredVOffset * i),
this.width, this.height,
this.textureWidth, this.textureHeight);
#else
matrices.blit(
RenderPipelines.GUI_TEXTURED,
this.textureResourceLocation,
this.getX(), this.getY(),
this.u, this.v + (this.hoveredVOffset * i),
this.width, this.height,
this.textureWidth, this.textureHeight);
#endif
matrices.blit(this.texture, this.getX(), this.getY(), this.u, this.v + (this.hoveredVOffset * i), this.width, this.height, this.textureWidth, this.textureHeight);
}
#endif
}
@@ -1,33 +0,0 @@
package com.seibel.distanthorizons.common.wrappers.gui.config;
import com.seibel.distanthorizons.core.config.gui.IConfigGuiInfo;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.Nullable;
import java.util.AbstractMap;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* holds information needed by the config GUI for rendering.
*
* @see AbstractConfigBase
*/
public class ConfigGuiInfo implements IConfigGuiInfo
{
/**
* Used to display validation errors.
* Null if no error is present.
*/
@Nullable
public Component errorMessage;
public BiFunction<EditBox, Button, Predicate<String>> tooltipFunction;
/** determines which options the button will show */
public AbstractMap.SimpleEntry<Button.OnPress, Function<Object, Component>> buttonOptionMap;
}
@@ -1,8 +1,8 @@
package com.seibel.distanthorizons.common.wrappers.gui.updater;
import com.mojang.blaze3d.vertex.PoseStack;
import com.seibel.distanthorizons.common.wrappers.gui.DhScreen;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.jar.installer.MarkdownFormatter;
@@ -14,7 +14,6 @@ import net.minecraft.client.gui.components.ContainerObjectSelectionList;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import com.seibel.distanthorizons.core.logging.DhLogger;
#if MC_VER >= MC_1_17_1
import net.minecraft.client.gui.narration.NarratableEntry;
@@ -22,7 +21,6 @@ import net.minecraft.client.gui.narration.NarratableEntry;
#if MC_VER < MC_1_20_1
import net.minecraft.client.gui.GuiComponent;
import com.mojang.blaze3d.vertex.PoseStack;
#else
import net.minecraft.client.gui.GuiGraphics;
#endif
@@ -41,9 +39,6 @@ import java.util.*;
// TODO: Make this
public class ChangelogScreen extends DhScreen
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private Screen parent;
private String versionID;
private List<String> changelog;
@@ -56,33 +51,24 @@ public class ChangelogScreen extends DhScreen
this(parent, null);
if (!ModrinthGetter.initted) // Make sure the modrinth stuff is initted
{
ModrinthGetter.init();
}
if (!ModrinthGetter.initted) // If its not initted, then this isnt usable
{
return;
}
if (!ModrinthGetter.mcVersions.contains(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion()))
{
return;
}
String versionID = ModrinthGetter.getLatestIDForVersion(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion());
if (versionID == null)
{
return;
}
try
{
this.setupChangelog(versionID);
this.usable = true;
setupChangelog(versionID);
usable = true;
}
catch (Exception e)
{
LOGGER.error("failed to setup changelog, error: ["+e.getMessage()+"].", e);
e.printStackTrace();
}
}
@@ -94,13 +80,11 @@ public class ChangelogScreen extends DhScreen
if (versionID == null)
{
return;
}
try
{
this.setupChangelog(versionID);
this.usable = true;
setupChangelog(versionID);
usable = true;
}
catch (Exception e)
{
@@ -145,23 +129,19 @@ public class ChangelogScreen extends DhScreen
protected void init()
{
super.init();
if (!this.usable)
{
if (!usable)
return;
}
this.addBtn( // Close
MakeBtn(Translatable(ModInfo.ID + ".general.back"), 5, this.height - 25, 100, 20, (btn) -> {
this.onClose();
})
MakeBtn(Translatable(ModInfo.ID + ".general.back"), 5, this.height - 25, 100, 20, (btn) -> this.onClose())
);
this.changelogArea = new TextArea(this.minecraft, this.width * 2, this.height, 32, 32, 10);
for (int i = 0; i < this.changelog.size(); i++)
for (int i = 0; i < changelog.size(); i++)
{
this.changelogArea.addButton(TextOrLiteral(this.changelog.get(i)));
this.changelogArea.addButton(TextOrLiteral(changelog.get(i)));
// drawString(matrices, this.font, changelog.get(i), this.width / 2 - 175, this.height / 2 - 100 + i*10, 0xFFFFFF);
}
@@ -176,47 +156,32 @@ public class ChangelogScreen extends DhScreen
{
#if MC_VER < MC_1_20_2
this.renderBackground(matrices); // Render background
#elif MC_VER < MC_1_21_6
#else
this.renderBackground(matrices, mouseX, mouseY, delta); // Render background
#else
// background blur is already being rendered, rendering again causes the game to crash
#endif
if (!this.usable)
{
if (!usable)
return;
}
int maxScroll;
#if MC_VER <= MC_1_21_3
maxScroll = this.changelogArea.getMaxScroll();
#else
maxScroll = this.changelogArea.maxScrollAmount();
#endif
// Set the scroll position to the mouse height relative to the screen
// This is a bit of a hack as we cannot scroll on this area
double scrollAmount = ((double) mouseY) / ((double) this.height) * 1.1 * maxScroll;
double scrollAmount = ((double) mouseY) / ((double) this.height) * 1.1 * this.changelogArea.getMaxScroll();
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
this.changelogArea.setScrollAmount(scrollAmount);
#elif MC_VER <= MC_1_21_3
this.changelogArea.scrollAmount = scrollAmount;
#else
this.changelogArea.setScrollAmount(scrollAmount);
this.changelogArea.scrollAmount = scrollAmount;
#endif
// render order matters, otherwise on 1.20.6+ the blurred background will render on top of the text
super.render(matrices, mouseX, mouseY, delta); // Render the buttons
this.changelogArea.render(matrices, mouseX, mouseY, delta); // Render the changelog
this.DhDrawCenteredString(matrices, this.font, this.title, this.width / 2, 15, 0xFFFFFF); // Render title
DhDrawCenteredString(matrices, font, title, width / 2, 15, 0xFFFFFF); // Render title
}
@Override
public void onClose()
{
Objects.requireNonNull(this.minecraft).setScreen(this.parent); // Goto the parent screen
Objects.requireNonNull(minecraft).setScreen(this.parent); // Goto the parent screen
}
public static class TextArea extends ContainerObjectSelectionList<ButtonEntry>
@@ -231,7 +196,7 @@ public class ChangelogScreen extends DhScreen
super(minecraftClient, canvasWidth, canvasHeight - (topMargin + botMargin), topMargin, itemSpacing);
#endif
this.centerListVertically = false;
this.textRenderer = minecraftClient.font;
textRenderer = minecraftClient.font;
}
public void addButton(Component text)
@@ -253,35 +218,42 @@ public class ChangelogScreen extends DhScreen
private final Component text;
private final List<AbstractWidget> children = new ArrayList<>();
private ButtonEntry(Component text) { this.text = text; }
private ButtonEntry(Component text)
{
this.text = text;
}
public static ButtonEntry create(Component text)
{ return new ButtonEntry(text); }
{
return new ButtonEntry(text);
}
#if MC_VER < MC_1_20_1
@Override
public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta)
{ GuiComponent.drawString(matrices, textRenderer, text, 12, y + 5, 0xFFFFFF); }
#elif MC_VER < MC_1_21_9
@Override
public void render(GuiGraphics matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta)
{ matrices.drawString(textRenderer, this.text, 12, y + 5, 0xFFFFFF); }
{
GuiComponent.drawString(matrices, textRenderer, text, 12, y + 5, 0xFFFFFF);
}
#else
@Override
public void renderContent(GuiGraphics matrices, int y, int x, boolean hovered, float tickDelta)
{ matrices.drawString(textRenderer, this.text, 12, y + 5, 0xFFFFFF); }
public void render(GuiGraphics matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta)
{
matrices.drawString(textRenderer, text, 12, y + 5, 0xFFFFFF);
}
#endif
@Override
public List<? extends GuiEventListener> children() { return this.children; }
public List<? extends GuiEventListener> children()
{
return children;
}
#if MC_VER >= MC_1_17_1
@Override
public List<? extends NarratableEntry> narratables() { return this.children; }
public List<? extends NarratableEntry> narratables()
{
return children;
}
#endif
}
}
@@ -4,26 +4,17 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiUpdateBranch;
import com.seibel.distanthorizons.common.wrappers.gui.DhScreen;
import com.seibel.distanthorizons.common.wrappers.gui.TexturedButtonWidget;
import com.seibel.distanthorizons.core.jar.ModJarInfo;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.jar.installer.ModrinthGetter;
import com.seibel.distanthorizons.core.jar.updater.SelfUpdater;
import com.seibel.distanthorizons.core.logging.DhLogger;
import net.minecraft.client.gui.screens.Screen;
#if MC_VER >= MC_1_20_1
import net.minecraft.client.gui.GuiGraphics;
#else
import com.mojang.blaze3d.vertex.PoseStack;
#endif
#if MC_VER <= MC_1_21_10
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.resources.ResourceLocation;
#else
import net.minecraft.resources.Identifier;
#endif
import static com.seibel.distanthorizons.common.wrappers.gui.GuiHelper.*;
@@ -38,9 +29,6 @@ import java.util.*;
// and also maybe add this suggestion https://discord.com/channels/881614130614767666/1035863487110467625/1035949054485594192
public class UpdateModScreen extends DhScreen
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private Screen parent;
private String newVersionID;
@@ -48,7 +36,7 @@ public class UpdateModScreen extends DhScreen
private String nextVer;
public UpdateModScreen(Screen parent, String newVersionID) throws IllegalArgumentException
public UpdateModScreen(Screen parent, String newVersionID)
{
super(Translatable(ModInfo.ID + ".updater.title"));
this.parent = parent;
@@ -66,13 +54,6 @@ public class UpdateModScreen extends DhScreen
this.currentVer = ModJarInfo.Git_Commit.substring(0,7);
this.nextVer = this.newVersionID.substring(0,7);
}
// done to prevent trying to update to "null"
// (this can happen if no versions are available to check/download from modrinth/gitlab)
if (this.nextVer == null)
{
throw new IllegalArgumentException("No new version found with the ID ["+newVersionID+"].");
}
}
@Override
@@ -88,21 +69,19 @@ public class UpdateModScreen extends DhScreen
// Logo image
this.addBtn(new TexturedButtonWidget(
// Where the button is on the screen
this.width / 2 - 95, this.height / 2 - 110,
this.width / 2 - 65, this.height / 2 - 110,
// Width and height of the button
195, 65,
130, 65,
// Offset
0, 0,
// Some textuary stuff
0,
#if MC_VER <= MC_1_20_6
#if MC_VER < MC_1_21
new ResourceLocation(ModInfo.ID, "logo.png"),
#elif MC_VER <= MC_1_21_10
ResourceLocation.fromNamespaceAndPath(ModInfo.ID, "logo.png"),
#else
Identifier.fromNamespaceAndPath(ModInfo.ID, "logo.png"),
ResourceLocation.fromNamespaceAndPath(ModInfo.ID, "logo.png"),
#endif
195, 65,
130, 65,
// Create the button and tell it where to go
// For now it goes to the client option by default
(buttonWidget) -> System.out.println("Nice, you found an easter egg :)"), // TODO: Add a proper easter egg to pressing the logo (maybe with confetti)
@@ -114,7 +93,7 @@ public class UpdateModScreen extends DhScreen
}
catch (Exception e)
{
LOGGER.error("Failed to setup update mod screen, error: ["+e.getMessage()+"].", e);
e.printStackTrace();
}
if (!ModInfo.IS_DEV_BUILD)
@@ -128,16 +107,14 @@ public class UpdateModScreen extends DhScreen
0, 0,
// Some textuary stuff
0,
#if MC_VER < MC_1_21_1
#if MC_VER < MC_1_21
new ResourceLocation(ModInfo.ID, "textures/gui/changelog.png"),
#elif MC_VER <= MC_1_21_10
ResourceLocation.fromNamespaceAndPath(ModInfo.ID, "textures/gui/changelog.png"),
#else
Identifier.fromNamespaceAndPath(ModInfo.ID, "textures/gui/changelog.png"),
ResourceLocation.fromNamespaceAndPath(ModInfo.ID, "textures/gui/changelog.png"),
#endif
20, 20,
// Create the button and tell it where to go
(buttonWidget) -> Objects.requireNonNull(this.minecraft).setScreen(new ChangelogScreen(this, this.newVersionID)),
(buttonWidget) -> Objects.requireNonNull(minecraft).setScreen(new ChangelogScreen(this, this.newVersionID)), // TODO: Add a proper easter egg to pressing the logo (maybe with confetti)
// Add a title to the button
Translatable(ModInfo.ID + ".updater.title")
));
@@ -158,9 +135,7 @@ public class UpdateModScreen extends DhScreen
})
);
this.addBtn( // Later (not now)
MakeBtn(Translatable(ModInfo.ID + ".updater.later"), this.width / 2 + 2, this.height / 2 + 70, 100, 20, (btn) -> {
this.onClose();
})
MakeBtn(Translatable(ModInfo.ID + ".updater.later"), this.width / 2 + 2, this.height / 2 + 70, 100, 20, (btn) -> this.onClose())
);
this.addBtn( // Never
MakeBtn(Translatable(ModInfo.ID + ".updater.never"), this.width / 2 - 102, this.height / 2 + 70, 100, 20, (btn) -> {
@@ -180,10 +155,8 @@ public class UpdateModScreen extends DhScreen
{
#if MC_VER < MC_1_20_2
this.renderBackground(matrices); // Render background
#elif MC_VER < MC_1_21_6
this.renderBackground(matrices, mouseX, mouseY, delta); // Render background
#else
// background blur is already being rendered, rendering again causes the game to crash
this.renderBackground(matrices, mouseX, mouseY, delta); // Render background
#endif
// TODO: add the tooltips for the buttons
@@ -191,30 +164,16 @@ public class UpdateModScreen extends DhScreen
// TODO: Add tooltips
// Render the text's
this.DhDrawCenteredString(matrices, this.font,
Translatable(ModInfo.ID + ".updater.text1"),
this.width / 2, this.height / 2 - 35,
#if MC_VER < MC_1_21_6
0xFFFFFF // RGB
#else
0xFFFFFFFF // ARGB
#endif
);
this.DhDrawCenteredString(matrices, this.font,
Translatable(ModInfo.ID + ".updater.text2", this.currentVer, this.nextVer),
this.width / 2, this.height / 2 - 20,
#if MC_VER < MC_1_21_6
0x52FD52 // RGB
#else
0xFF52FD52 // ARGB
#endif
);
DhDrawCenteredString(matrices, this.font, Translatable(ModInfo.ID + ".updater.text1"), this.width / 2, this.height / 2 - 35, 0xFFFFFF);
DhDrawCenteredString(matrices, this.font,
Translatable(ModInfo.ID + ".updater.text2", currentVer, nextVer),
this.width / 2, this.height / 2 - 20, 0x52FD52);
}
@Override
public void onClose()
{
Objects.requireNonNull(this.minecraft).setScreen(this.parent); // Go to the parent screen
Objects.requireNonNull(minecraft).setScreen(this.parent); // Goto the parent screen
}
}
@@ -1,22 +1,21 @@
package com.seibel.distanthorizons.common.wrappers.level;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import net.minecraft.client.multiplayer.ClientLevel;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public class KeyedClientLevelManager implements IKeyedClientLevelManager
{
public static final KeyedClientLevelManager INSTANCE = new KeyedClientLevelManager();
/** This is set and managed by the ClientApi for servers with support for DH. */
@Nullable
private IServerKeyedClientLevel serverKeyedLevel = null;
/** Allows to keep level manager enabled between loading different keyed levels */
private boolean enabled = false;
private IServerKeyedClientLevel overrideWrapper = null;
private boolean useOverrideWrapper = false;
//=============//
@@ -32,24 +31,24 @@ public class KeyedClientLevelManager implements IKeyedClientLevelManager
//======================//
@Override
@Nullable
public IServerKeyedClientLevel getServerKeyedLevel() { return this.serverKeyedLevel; }
public void setServerKeyedLevel(IServerKeyedClientLevel clientLevel) { this.overrideWrapper = clientLevel; }
@Override
public IServerKeyedClientLevel getOverrideWrapper() { return this.overrideWrapper; }
@Override
public IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String serverKey, String levelKey)
public IServerKeyedClientLevel getServerKeyedLevel(ILevelWrapper level, String serverLevelKey)
{
IServerKeyedClientLevel keyedLevel = new ServerKeyedClientLevelWrapper((ClientLevel) clientLevel.getWrappedMcObject(), serverKey, levelKey);
this.serverKeyedLevel = keyedLevel;
this.enabled = true;
return keyedLevel;
Objects.requireNonNull(level);
Objects.requireNonNull(serverLevelKey);
return new ServerKeyedClientLevel((ClientLevel) level.getWrappedMcObject(), serverLevelKey);
}
@Override
public void clearKeyedLevel() { this.serverKeyedLevel = null; }
public void setUseOverrideWrapper(boolean useOverrideWrapper) { this.useOverrideWrapper = useOverrideWrapper; }
@Override
public boolean isEnabled() { return this.enabled; }
@Override
public void disable() { this.enabled = false; }
public boolean getUseOverrideWrapper() { return this.useOverrideWrapper; }
}
@@ -0,0 +1,21 @@
package com.seibel.distanthorizons.common.wrappers.level;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import net.minecraft.client.multiplayer.ClientLevel;
public class ServerKeyedClientLevel extends ClientLevelWrapper implements IServerKeyedClientLevel
{
/** A unique identifier (generally the level's name) for differentiating multiverse levels */
private final String serverLevelKey;
public ServerKeyedClientLevel(ClientLevel level, String serverLevelKey)
{
super(level);
this.serverLevelKey = serverLevelKey;
}
@Override
public String getServerLevelKey() { return this.serverLevelKey; }
}
@@ -1,44 +0,0 @@
package com.seibel.distanthorizons.common.wrappers.level;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import net.minecraft.client.multiplayer.ClientLevel;
public class ServerKeyedClientLevelWrapper extends ClientLevelWrapper implements IServerKeyedClientLevel
{
/** Returns the folder name the server wants the client to use. */
private final String serverKey;
/** A unique identifier (generally the level's name) for differentiating multiverse levels */
private final String serverLevelKey;
//=============//
// constructor //
//=============//
public ServerKeyedClientLevelWrapper(ClientLevel level, String serverKey, String serverLevelKey)
{
super(level);
this.serverKey = serverKey;
this.serverLevelKey = serverLevelKey;
}
@Override
public String getServerKey() { return this.serverKey; }
//======================//
// level identification //
//======================//
@Override
public String getServerLevelKey() { return this.serverLevelKey; }
@Override
public String getDhIdentifier() { return this.getServerLevelKey(); }
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -20,41 +20,42 @@
package com.seibel.distanthorizons.common.wrappers.minecraft;
import java.io.File;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.UUID;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.platform.NativeImage;
import com.seibel.distanthorizons.api.enums.config.EDhApiLodShading;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.logging.DhLogger;
import net.minecraft.CrashReport;
import net.minecraft.client.CloudStatus;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.core.BlockPos;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.ChunkPos;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.Direction;
#if MC_VER < MC_1_19_2
import net.minecraft.network.chat.TextComponent;
#endif
#if MC_VER < MC_1_21_3
#else
import net.minecraft.util.profiling.Profiler;
#endif
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
/**
* A singleton that wraps the Minecraft object.
@@ -63,34 +64,87 @@ import net.minecraft.util.profiling.Profiler;
*/
public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecraftSharedWrapper
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
private static final Minecraft MINECRAFT = Minecraft.getInstance();
public static final MinecraftClientWrapper INSTANCE = new MinecraftClientWrapper();
/**
* The lightmap for the current:
* Time, dimension, brightness setting, etc.
*/
private NativeImage lightMap = null;
private ProfilerWrapper profilerWrapper;
private MinecraftClientWrapper()
{
//======================//
// multiplayer handling //
//======================//
}
//================//
// helper methods //
//================//
/**
* This should be called at the beginning of every frame to
* clear any Minecraft data that becomes out of date after a frame. <br> <br>
* <p>
* LightMaps and other time sensitive objects fall in this category. <br> <br>
* <p>
* This doesn't affect OpenGL objects in any way.
*/
@Override
public void clearFrameObjectCache() { this.lightMap = null; }
//=================//
// method wrappers //
//=================//
@Override
public float getShade(EDhDirection lodDirection)
{
EDhApiLodShading lodShading = Config.Client.Advanced.Graphics.AdvancedGraphics.lodShading.get();
switch (lodShading)
{
default:
case AUTO:
if (MINECRAFT.level != null)
{
Direction mcDir = McObjectConverter.Convert(lodDirection);
return MINECRAFT.level.getShade(mcDir, true);
}
else
{
return 0.0f;
}
case ENABLED:
return switch (lodDirection) {
case DOWN -> 0.5F;
default -> 1.0F;
case NORTH, SOUTH -> 0.8F;
case WEST, EAST -> 0.6F;
};
case DISABLED:
return 1.0F;
}
}
@Override
public boolean hasSinglePlayerServer() { return MINECRAFT.hasSingleplayerServer(); }
@Override
public boolean clientConnectedToDedicatedServer()
{
return MINECRAFT.getCurrentServer() != null
&& !this.hasSinglePlayerServer();
}
public boolean clientConnectedToDedicatedServer() { return MINECRAFT.getCurrentServer() != null && !this.hasSinglePlayerServer(); }
@Override
public boolean connectedToReplay()
{
return MINECRAFT.getCurrentServer() == null
&& !this.hasSinglePlayerServer() ;
}
public boolean connectedToReplay() { return !MINECRAFT.hasSingleplayerServer() && MINECRAFT.getCurrentServer() == null; }
@Override
public String getCurrentServerName()
@@ -105,6 +159,7 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
return (server != null) ? server.name : "NULL";
}
}
@Override
public String getCurrentServerIp()
{
@@ -118,6 +173,7 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
return (server != null) ? server.ip : "NA";
}
}
@Override
public String getCurrentServerVersion()
{
@@ -125,211 +181,115 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
return (server != null) ? server.version.getString() : "UNKOWN";
}
//=================//
// player handling //
//=================//
//=============//
// Simple gets //
//=============//
public LocalPlayer getPlayer() { return MINECRAFT.player; }
@Override
public boolean playerExists() { return MINECRAFT.player != null; }
@Override
public UUID getPlayerUUID() { return this.getPlayer().getUUID(); }
@Override
public DhBlockPos getPlayerBlockPos()
{
LocalPlayer player = this.getPlayer();
if (player == null)
{
return new DhBlockPos(0, 0, 0);
}
BlockPos playerPos = player.blockPosition();
BlockPos playerPos = this.getPlayer().blockPosition();
return new DhBlockPos(playerPos.getX(), playerPos.getY(), playerPos.getZ());
}
@Override
public DhChunkPos getPlayerChunkPos()
{
LocalPlayer player = this.getPlayer();
if (player == null)
{
return new DhChunkPos(0, 0);
}
#if MC_VER < MC_1_17_1
ChunkPos playerPos = new ChunkPos(player.blockPosition());
ChunkPos playerPos = new ChunkPos(this.getPlayer().blockPosition());
#else
ChunkPos playerPos = player.chunkPosition();
ChunkPos playerPos = this.getPlayer().chunkPosition();
#endif
return new DhChunkPos(playerPos.x, playerPos.z);
}
//================//
// level handling //
//================//
@Nullable
@Override
public IClientLevelWrapper getWrappedClientLevel() { return this.getWrappedClientLevel(false); }
@Override
@Nullable
public IClientLevelWrapper getWrappedClientLevel(boolean bypassLevelKeyManager)
public IClientLevelWrapper getWrappedClientLevel()
{
ClientLevel level = MINECRAFT.level;
if (level == null)
if (MINECRAFT.level == null)
{
return null;
}
return ClientLevelWrapper.getWrapper(level, bypassLevelKeyManager);
}
//===========//
// messaging //
//===========//
@Override
public void sendChatMessage(String string)
{
LocalPlayer player = this.getPlayer();
if (player == null)
{
return;
}
#if MC_VER < MC_1_19_2
player.sendMessage(new TextComponent(string), getPlayer().getUUID());
#elif MC_VER < MC_1_21_9
player.displayClientMessage(net.minecraft.network.chat.Component.translatable(string), /*isOverlay*/false);
#else
GLProxy.queueRunningOnRenderThread(() ->
{
player.displayClientMessage(net.minecraft.network.chat.Component.translatable(string), /*isOverlay*/false);
});
#endif
}
@Override
public void sendOverlayMessage(String string)
{
LocalPlayer player = this.getPlayer();
if (player == null)
{
return;
}
#if MC_VER < MC_1_19_2
player.displayClientMessage(new TextComponent(string), /*isOverlay*/true);
#else
player.displayClientMessage(net.minecraft.network.chat.Component.translatable(string), /*isOverlay*/true);
#endif
}
//==========================//
// vanilla option overrides //
//==========================//
public void disableVanillaClouds()
{
#if MC_VER <= MC_1_18_2
MINECRAFT.options.renderClouds = CloudStatus.OFF;
#else
MINECRAFT.options.cloudStatus().set(CloudStatus.OFF);
#endif
}
public void disableVanillaChunkFadeIn()
{
#if MC_VER <= MC_1_21_10
// chunk fade in was added MC 1.21.11
#else
MINECRAFT.options.chunkSectionFadeInTime().set(0.0);
#endif
}
//======//
// misc //
//======//
/**
* no override and not included in {@link IMinecraftClientWrapper}
* since this would only be used in common/client, not core.
*/
public
#if MC_VER < MC_1_21_9 long
#else Window
#endif
getGlfwWindowId()
{
#if MC_VER < MC_1_21_9
long glfwWindowId = MINECRAFT.getWindow().getWindow();
return glfwWindowId;
#else
return MINECRAFT.getWindow();
#endif
return ClientLevelWrapper.getWrapperIgnoringOverride(MINECRAFT.level);
}
@Override
public IProfilerWrapper getProfiler()
{
ProfilerFiller profiler;
#if MC_VER < MC_1_21_3
profiler = MINECRAFT.getProfiler();
#else
profiler = Profiler.get();
#endif
if (this.profilerWrapper == null)
{
this.profilerWrapper = new ProfilerWrapper(profiler);
this.profilerWrapper = new ProfilerWrapper(MINECRAFT.getProfiler());
}
else if (profiler != this.profilerWrapper.profiler)
else if (MINECRAFT.getProfiler() != this.profilerWrapper.profiler)
{
this.profilerWrapper.profiler = profiler;
this.profilerWrapper.profiler = MINECRAFT.getProfiler();
}
return this.profilerWrapper;
}
/** Returns all worlds available to the server */
@Override
public void crashMinecraft(String errorMessage, Throwable exception)
public ArrayList<ILevelWrapper> getAllServerWorlds()
{
LOGGER.fatal(ModInfo.READABLE_NAME + " had the following error: [" + errorMessage + "]. Crashing Minecraft...", exception);
CrashReport report = new CrashReport(errorMessage, exception);
#if MC_VER < MC_1_20_4
Minecraft.crash(report);
#else
MINECRAFT.delayCrash(report);
#endif
ArrayList<ILevelWrapper> worlds = new ArrayList<>();
Iterable<ServerLevel> serverWorlds = MINECRAFT.getSingleplayerServer().getAllLevels();
for (ServerLevel world : serverWorlds)
{
worlds.add(ServerLevelWrapper.getWrapper(world));
}
return worlds;
}
//=============//
// mod support //
//=============//
@Override
public void sendChatMessage(String string)
{
LocalPlayer player = this.getPlayer();
if (player == null) return;
#if MC_VER < MC_1_19_2
player.sendMessage(new TextComponent(string), getPlayer().getUUID());
#else
player.sendSystemMessage(net.minecraft.network.chat.Component.translatable(string));
#endif
}
/**
* Crashes Minecraft, displaying the given errorMessage <br> <br>
* In the following format: <br>
*
* The game crashed whilst <strong>errorMessage</strong> <br>
* Error: <strong>ExceptionClass: exceptionErrorMessage</strong> <br>
* Exit Code: -1 <br>
*/
@Override
public void crashMinecraft(String errorMessage, Throwable exception)
{
LOGGER.error(ModInfo.READABLE_NAME + " had the following error: [" + errorMessage + "]. Crashing Minecraft...", exception);
CrashReport report = new CrashReport(errorMessage, exception);
#if MC_VER < MC_1_20_4
Minecraft.crash(report);
#else
Minecraft.getInstance().delayCrash(report);
#endif
}
@Override
public Object getOptionsObject() { return MINECRAFT.options; }
//========//
// shared //
//========//
@Override
public boolean isDedicatedServer() { return false; }
@@ -337,19 +297,6 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
public File getInstallationDirectory() { return MINECRAFT.gameDirectory; }
@Override
public int getPlayerCount()
{
// can be null if the server hasn't finished booting up yet
if (MINECRAFT.getSingleplayerServer() == null)
{
return 1;
}
else
{
return MINECRAFT.getSingleplayerServer().getPlayerCount();
}
}
public void executeOnRenderThread(Runnable runnable) { MINECRAFT.execute(runnable); }
}
@@ -0,0 +1,31 @@
package com.seibel.distanthorizons.common.wrappers.minecraft;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import net.minecraft.server.dedicated.DedicatedServer;
import java.io.File;
//@Environment(EnvType.SERVER)
public class MinecraftDedicatedServerWrapper implements IMinecraftSharedWrapper
{
public static final MinecraftDedicatedServerWrapper INSTANCE = new MinecraftDedicatedServerWrapper();
private MinecraftDedicatedServerWrapper() { }
public DedicatedServer dedicatedServer = null;
@Override
public boolean isDedicatedServer() { return true; }
@Override
public File getInstallationDirectory()
{
if (this.dedicatedServer == null)
{
throw new IllegalStateException("Trying to get Installation Direction before Dedicated server complete initialization!");
}
#if MC_VER < MC_1_21
return this.dedicatedServer.getServerDirectory();
#else
return this.dedicatedServer.getServerDirectory().toFile();
#endif
}
}
@@ -1,252 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.minecraft;
#if MC_VER < MC_1_21_5
import com.mojang.blaze3d.platform.GlStateManager;
#else
import com.mojang.blaze3d.opengl.GlStateManager;
#endif
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.lwjgl.opengl.GL32;
/**
* <b>Why does DH often call GL methods twice? </b><br>
* Once using the base {@link GL32} function and a second time using
* Minecraft's {@link GlStateManager}?<br><br>
*
* <b>Answer: </b><br>
* Compatibility and robustness<br>
* In general all MC rendering should go through MC's {@link GlStateManager},
* however that isn't always the case.
* So, to prevent issues if a mod (or MC itself) calls a direct GL function
* instead of the {@link GlStateManager} wrapper, we need to be sure about what the actual
* set value is (whether setting or getting) and that MC knows what DH has done.
* This way whether a mod (or MC) is using the {@link GlStateManager} or direct GL calls,
* they should always have the correct value for anything DH has modified.
* <br><br>
* This may slow down some low end GPUs that are driver limited,
* however James would rather have slow correct rendering vs fast broken rendering.
*/
public class MinecraftGLWrapper implements IMinecraftGLWrapper
{
public static final MinecraftGLWrapper INSTANCE = new MinecraftGLWrapper();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/*
private static final StencilState STENCIL;
*/
// scissor //
/** @see GL32#GL_SCISSOR_TEST */
@Override
public void enableScissorTest()
{
GL32.glEnable(GL32.GL_SCISSOR_TEST);
GlStateManager._enableScissorTest();
}
/** @see GL32#GL_SCISSOR_TEST */
@Override
public void disableScissorTest()
{
GL32.glDisable(GL32.GL_SCISSOR_TEST);
GlStateManager._disableScissorTest();
}
// stencil //
//
// /** @see GL32#GL_SCISSOR_TEST */
// public void enableScissorTest() { GlStateManager._stencilFunc(); }
// /** @see GL32#GL_SCISSOR_TEST */
// public void disableScissorTest() { GlStateManager._disableScissorTest(); }
// depth //
/** @see GL32#GL_DEPTH_TEST */
@Override
public void enableDepthTest()
{
GL32.glEnable(GL32.GL_DEPTH_TEST);
GlStateManager._enableDepthTest();
}
/** @see GL32#GL_DEPTH_TEST */
@Override
public void disableDepthTest()
{
GL32.glDisable(GL32.GL_DEPTH_TEST);
GlStateManager._disableDepthTest();
}
/** @see GL32#glDepthFunc(int) */
@Override
public void glDepthFunc(int func)
{
GL32.glDepthFunc(func);
GlStateManager._depthFunc(func);
}
/** @see GL32#glDepthMask(boolean) */
@Override
public void enableDepthMask()
{
GL32.glDepthMask(true);
GlStateManager._depthMask(true);
}
/** @see GL32#glDepthMask(boolean) */
@Override
public void disableDepthMask()
{
GL32.glDepthMask(false);
GlStateManager._depthMask(false);
}
// blending //
/** @see GL32#GL_BLEND */
@Override
public void enableBlend()
{
GL32.glEnable(GL32.GL_BLEND);
GlStateManager._enableBlend();
}
/** @see GL32#GL_BLEND */
@Override
public void disableBlend()
{
GL32.glDisable(GL32.GL_BLEND);
GlStateManager._disableBlend();
}
/** @see GL32#glBlendFunc */
@Override
public void glBlendFunc(int sfactor, int dfactor)
{
GL32.glBlendFunc(sfactor, dfactor);
#if MC_VER < MC_1_21_5
GlStateManager._blendFunc(sfactor, dfactor);
#endif
}
/** @see GL32#glBlendFuncSeparate */
@Override
public void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha)
{
GL32.glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha);
GlStateManager._blendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha);
}
// frame buffers //
/** @see GL32#glBindFramebuffer */
@Override
public void glBindFramebuffer(int target, int framebuffer)
{
GL32.glBindFramebuffer(target, framebuffer);
GlStateManager._glBindFramebuffer(target, framebuffer);
}
// buffers //
/** @see GL32#glGenBuffers() */
@Override
public int glGenBuffers()
{ return GlStateManager._glGenBuffers(); }
/** @see GL32#glDeleteBuffers(int) */
@Override
public void glDeleteBuffers(int buffer)
{
GL32.glDeleteBuffers(buffer);
// MC's implementation has a bug where it will throw:
// GL_INVALID_OPERATION in glBufferData(immutable)
// when attempting to delete Storage Buffers
// So we need to manually delete the buffers ourselves
//GlStateManager._glDeleteBuffers(buffer);
}
// culling //
/** @see GL32#GL_CULL_FACE */
@Override
public void enableFaceCulling()
{
GL32.glEnable(GL32.GL_CULL_FACE);
GlStateManager._enableCull();
}
/** @see GL32#GL_CULL_FACE */
@Override
public void disableFaceCulling()
{
GL32.glDisable(GL32.GL_CULL_FACE);
GlStateManager._disableCull();
}
// textures //
/** @see GL32#glGenTextures() */
@Override
public int glGenTextures() { return GlStateManager._genTexture(); }
/** @see GL32#glDeleteTextures(int) */
@Override
public void glDeleteTextures(int texture) { GlStateManager._deleteTexture(texture); }
/** @see GL32#glActiveTexture(int) */
@Override
public void glActiveTexture(int textureId)
{
GL32.glActiveTexture(textureId);
GlStateManager._activeTexture(textureId);
}
@Override
public int getActiveTexture() { return GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D); }
/**
* Always binds to {@link GL32#GL_TEXTURE_2D}
* @see GL32#glBindTexture(int, int)
*/
@Override
public void glBindTexture(int texture)
{
GL32.glBindTexture(GL32.GL_TEXTURE_2D, texture);
GlStateManager._bindTexture(texture);
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -21,54 +21,53 @@ package com.seibel.distanthorizons.common.wrappers.minecraft;
import java.awt.Color;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.platform.NativeImage;
import com.seibel.distanthorizons.api.enums.config.EDhApiLodShading;
import com.mojang.blaze3d.systems.RenderSystem;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.common.wrappers.WrapperFactory;
import com.seibel.distanthorizons.common.wrappers.misc.LightMapWrapper;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
#if MC_VER < MC_1_17_1
#elif MC_VER < MC_1_21_6
import net.minecraft.client.renderer.FogRenderer;
import com.mojang.blaze3d.systems.RenderSystem;
#else
import net.minecraft.client.renderer.fog.FogRenderer;
#endif
#if MC_VER < MC_1_19_4
import org.joml.Matrix4f;
import org.joml.Vector3f;
#else
import org.joml.Matrix4f;
import org.joml.Vector3f;
#endif
#if MC_VER >= MC_1_20_2
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
#endif
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.ISodiumAccessor;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.FogRenderer;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.phys.Vec3;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector4f;
#if MC_VER < MC_1_17_1
import net.minecraft.tags.FluidTags;
import net.minecraft.world.entity.Entity;
@@ -77,27 +76,28 @@ import org.lwjgl.opengl.GL15;
#else
import net.minecraft.world.level.material.FogType;
#endif
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.apache.logging.log4j.Logger;
#if MC_VER >= MC_1_21_5
import com.mojang.blaze3d.opengl.GlTexture;
#else
#endif
#if MC_VER <= MC_1_21_10
#else
import net.minecraft.world.attribute.EnvironmentAttributes;
#endif
/**
* A singleton that contains everything
* related to rendering in Minecraft.
*
* @author James Seibel
* @version 12-12-2021
*/
//@Environment(EnvType.CLIENT)
public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
{
public static final MinecraftRenderWrapper INSTANCE = new MinecraftRenderWrapper();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
private static final Minecraft MC = Minecraft.getInstance();
private static final IWrapperFactory FACTORY = WrapperFactory.INSTANCE;
private static final IOptifineAccessor OPTIFINE_ACCESSOR = ModAccessorInjector.INSTANCE.get(IOptifineAccessor.class);
/**
* In the case of immersive portals multiple levels may be active at once, causing conflicting lightmaps. <br>
@@ -111,48 +111,26 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
*/
public int finalLevelFrameBufferId = -1;
public boolean colorTextureCastFailLogged = false;
public boolean depthTextureCastFailLogged = false;
#if MC_VER < MC_1_21_6
#else
private static FogRenderer mcFogRenderer = null;
#endif
//=========//
// methods //
//=========//
@Override
public Vec3f getLookAtVector()
{
#if MC_VER <= MC_1_21_10
Camera camera = MC.gameRenderer.getMainCamera();
return new Vec3f(camera.getLookVector().x(), camera.getLookVector().y(), camera.getLookVector().z());
#else
Camera camera = MC.gameRenderer.getMainCamera();
return new Vec3f(camera.forwardVector().x(), camera.forwardVector().y(), camera.forwardVector().z());
#endif
}
/**
* Unless you really need to know if the player is blind,
* use {@link MinecraftRenderWrapper#isFogStateSpecial()} or {@link IMinecraftRenderWrapper#isFogStateSpecial()} instead
*/
@Override
public DhBlockPos getCameraBlockPosition()
{
Camera camera = MC.gameRenderer.getMainCamera();
BlockPos blockPos = camera.getBlockPosition();
return new DhBlockPos(blockPos.getX(), blockPos.getY(), blockPos.getZ());
}
@Override
/** Unless you really need to know if the player is blind, use {@link MinecraftRenderWrapper#isFogStateSpecial()}/{@link IMinecraftRenderWrapper#isFogStateSpecial()} instead */
public boolean playerHasBlindingEffect()
{
if (MC.player == null)
{
return false;
}
else if (MC.player.getActiveEffectsMap() == null)
{
return false;
}
else
{
return MC.player.getActiveEffectsMap().get(MobEffects.BLINDNESS) != null
#if MC_VER >= MC_1_19_2
@@ -160,101 +138,71 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
#endif
;
}
}
@Override
public Vec3d getCameraExactPosition()
{
Camera camera = MC.gameRenderer.getMainCamera();
#if MC_VER <= MC_1_21_10
Vec3 projectedView = camera.getPosition();
#else
Vec3 projectedView = camera.position();
#endif
return new Vec3d(projectedView.x, projectedView.y, projectedView.z);
}
@Override
public Mat4f getWorldViewMatrix()
{
Camera camera = MC.gameRenderer.getMainCamera();
Vector3f cameraVec3 = new Vector3f(
(float)camera.getPosition().x,
(float)camera.getPosition().y,
(float)camera.getPosition().z);
cameraVec3 = cameraVec3.negate();
Matrix4f matWorldView = new Matrix4f()
.rotateX((float)Math.toRadians(camera.getXRot()))
.rotateY((float)Math.toRadians(camera.getYRot() + 180f))
.translate(cameraVec3);
return new Mat4f(matWorldView);
}
@Override
public Mat4f getDefaultProjectionMatrix(float partialTicks)
{
#if MC_VER < MC_1_17_1
return McObjectConverter.Convert(Minecraft.getInstance().gameRenderer.getProjectionMatrix(Minecraft.getInstance().gameRenderer.getMainCamera(), partialTicks, true));
#else
return McObjectConverter.Convert(MC.gameRenderer.getProjectionMatrix(MC.gameRenderer.getFov(MC.gameRenderer.getMainCamera(), partialTicks, true)));
#endif
}
@Override
public double getGamma()
{
#if MC_VER < MC_1_19_2
return MC.options.gamma;
#else
return MC.options.gamma().get();
#endif
}
@Override
public Color getFogColor(float partialTicks)
{
#if MC_VER < MC_1_17_1
float[] colorValues = new float[4];
GL15.glGetFloatv(GL15.GL_FOG_COLOR, colorValues);
return new Color(
Math.max(0f, Math.min(colorValues[0], 1f)), // r
Math.max(0f, Math.min(colorValues[1], 1f)), // g
Math.max(0f, Math.min(colorValues[2], 1f)), // b
Math.max(0f, Math.min(colorValues[3], 1f)) // a
);
#elif MC_VER < MC_1_21_3
#else
FogRenderer.setupColor(MC.gameRenderer.getMainCamera(), partialTicks, MC.level, 1, MC.gameRenderer.getDarkenWorldAmount(partialTicks));
float[] colorValues = RenderSystem.getShaderFogColor();
return new Color(
Math.max(0f, Math.min(colorValues[0], 1f)), // r
Math.max(0f, Math.min(colorValues[1], 1f)), // g
Math.max(0f, Math.min(colorValues[2], 1f)), // b
Math.max(0f, Math.min(colorValues[3], 1f)) // a
);
#elif MC_VER < MC_1_21_6
Vector4f colorValues = FogRenderer.computeFogColor(MC.gameRenderer.getMainCamera(), partialTicks, MC.level, 1, MC.gameRenderer.getDarkenWorldAmount(partialTicks));
return new Color(
Math.max(0f, Math.min(colorValues.x, 1f)), // r
Math.max(0f, Math.min(colorValues.y, 1f)), // g
Math.max(0f, Math.min(colorValues.z, 1f)), // b
Math.max(0f, Math.min(colorValues.w, 1f)) // a
);
#elif MC_VER <= MC_1_21_10
if (mcFogRenderer == null)
{
mcFogRenderer = new FogRenderer();
}
if (MC.level == null)
{
// shouldn't happen, but just in case
return Color.white;
}
boolean isFoggy =
MC.level.effects().isFoggyAt(
MC.gameRenderer.getMainCamera().getBlockPosition().getX(),
MC.gameRenderer.getMainCamera().getBlockPosition().getZ())
|| MC.gui.getBossOverlay().shouldCreateWorldFog();
Vector4f colorValues = mcFogRenderer.setupFog(MC.gameRenderer.getMainCamera(), MC.options.getEffectiveRenderDistance(), isFoggy, MC.deltaTracker, MC.gameRenderer.getDarkenWorldAmount(MC.deltaTracker.getGameTimeDeltaPartialTick(true)), MC.level);
return new Color(
Math.max(0f, Math.min(colorValues.x, 1f)), // r
Math.max(0f, Math.min(colorValues.y, 1f)), // g
Math.max(0f, Math.min(colorValues.z, 1f)), // b
Math.max(0f, Math.min(colorValues.w, 1f)) // a
);
#else
if (mcFogRenderer == null)
{
mcFogRenderer = new FogRenderer();
}
if (MC.level == null)
{
// shouldn't happen, but just in case
return Color.white;
}
Vector4f colorValues = mcFogRenderer.setupFog(
MC.gameRenderer.getMainCamera(),
MC.options.getEffectiveRenderDistance(),
MC.deltaTracker,
MC.gameRenderer.getDarkenWorldAmount(MC.deltaTracker.getGameTimeDeltaPartialTick(true)),
MC.level);
return new Color(
Math.max(0f, Math.min(colorValues.x, 1f)), // r
Math.max(0f, Math.min(colorValues.y, 1f)), // g
Math.max(0f, Math.min(colorValues.z, 1f)), // b
Math.max(0f, Math.min(colorValues.w, 1f)) // a
);
#endif
return new Color(
Math.max(0f, Math.min(colorValues[0], 1f)),
Math.max(0f, Math.min(colorValues[1], 1f)),
Math.max(0f, Math.min(colorValues[2], 1f)),
Math.max(0f, Math.min(colorValues[3], 1f))
);
}
// getSpecialFogColor() is the same as getFogColor()
@Override
public Color getSkyColor()
@@ -262,29 +210,18 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
if (MC.level.dimensionType().hasSkyLight())
{
float frameTime;
#if MC_VER < MC_1_21_1
#if MC_VER < MC_1_21
frameTime = MC.getFrameTime();
#elif MC_VER < MC_1_21_3
frameTime = MC.getTimer().getRealtimeDeltaTicks();
#elif MC_VER <= MC_1_21_10
frameTime = MC.deltaTracker.getGameTimeDeltaTicks();
#else
frameTime = 0f; // unused
frameTime = MC.getTimer().getRealtimeDeltaTicks();
#endif
#if MC_VER < MC_1_17_1
Vec3 colorValues = MC.level.getSkyColor(MC.gameRenderer.getMainCamera().getBlockPosition(), frameTime);
return new Color((float) colorValues.x, (float) colorValues.y, (float) colorValues.z);
#elif MC_VER < MC_1_21_3
Vec3 colorValues = MC.level.getSkyColor(MC.gameRenderer.getMainCamera().getPosition(), frameTime);
return new Color((float) colorValues.x, (float) colorValues.y, (float) colorValues.z);
#elif MC_VER <= MC_1_21_10
int argbColorInt = MC.level.getSkyColor(MC.gameRenderer.getMainCamera().getPosition(), frameTime);
return ColorUtil.toColorObjARGB(argbColorInt);
#else
int argbColor = MC.level.environmentAttributes().getValue(EnvironmentAttributes.SKY_COLOR, BlockPos.ZERO);
return new Color(ColorUtil.getRed(argbColor), ColorUtil.getGreen(argbColor), ColorUtil.getBlue(argbColor), 255 /* ignore alpha since DH clouds don't render correctly with transparency */);
Vec3 colorValues = MC.level.getSkyColor(MC.gameRenderer.getMainCamera().getPosition(), frameTime);
#endif
return new Color((float) colorValues.x, (float) colorValues.y, (float) colorValues.z);
}
else
{
@@ -293,7 +230,10 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
}
@Override
public double getFov(float partialTicks) { return MC.gameRenderer.getFov(MC.gameRenderer.getMainCamera(), partialTicks, true); }
public double getFov(float partialTicks)
{
return MC.gameRenderer.getFov(MC.gameRenderer.getMainCamera(), partialTicks, true);
}
/** Measured in chunks */
@Override
@@ -307,30 +247,45 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
#endif
}
protected RenderTarget getRenderTarget() { return MC.getMainRenderTarget(); }
@Override
public boolean mcRendersToFrameBuffer()
public int getScreenWidth()
{
#if MC_VER < MC_1_21_5
return true;
#else
return false;
#endif
// alternate ways of getting the window's resolution,
// using one of these methods may fix the optifine render resolution bug
// TODO: test these once we can run with Optifine again
// int[] heightArray = new int[1];
// int[] widthArray = new int[1];
//
// long window = GLProxy.getInstance().minecraftGlContext;
// GLFW.glfwGetWindowSize(window, widthArray, heightArray); // option 1
// GLFW.glfwGetFramebufferSize(window, widthArray, heightArray); // option 2
int width = MC.getWindow().getWidth();
if (OPTIFINE_ACCESSOR != null)
{
// TODO remove comment after testing:
// this should fix the issue where different optifine render resolutions screw up the LOD rendering
width *= OPTIFINE_ACCESSOR.getRenderResolutionMultiplier();
}
return width;
}
@Override
public int getScreenHeight()
{
int height = MC.getWindow().getHeight();
if (OPTIFINE_ACCESSOR != null)
{
height *= OPTIFINE_ACCESSOR.getRenderResolutionMultiplier();
}
return height;
}
@Override
public boolean runningLegacyOpenGL()
{
#if MC_VER <= MC_1_16_5
return true;
#else
return false;
#endif
}
private RenderTarget getRenderTarget() { return MC.getMainRenderTarget(); }
@Override
public int getTargetFramebuffer()
public int getTargetFrameBuffer()
{
// used so we can access the framebuffer shaders end up rendering to
if (AbstractOptifineAccessor.optifinePresent())
@@ -338,100 +293,100 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
return this.finalLevelFrameBufferId;
}
#if MC_VER < MC_1_21_5
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
public void clearTargetFrameBuffer() { this.finalLevelFrameBufferId = -1; }
@Override
public int getDepthTextureId()
public int getDepthTextureId() { return this.getRenderTarget().getDepthTextureId(); }
@Override
public int getTargetFrameBufferViewportWidth()
{
return getRenderTarget().viewWidth;
}
@Override
public int getTargetFrameBufferViewportHeight()
{
return getRenderTarget().viewHeight;
}
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame. <br><br>
* <p>
*/
public boolean usingBackupGetVanillaRenderedChunks = false;
@Override
public HashSet<DhChunkPos> getVanillaRenderedChunks()
{
ISodiumAccessor sodium = ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class);
if (sodium != null)
{
return sodium.getNormalRenderedChunks();
}
IOptifineAccessor optifine = ModAccessorInjector.INSTANCE.get(IOptifineAccessor.class);
if (optifine != null)
{
HashSet<DhChunkPos> pos = optifine.getNormalRenderedChunks();
if (pos == null)
pos = getMaximumRenderedChunks();
return pos;
}
if (!usingBackupGetVanillaRenderedChunks)
{
#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 -1;
}
#if MC_VER < MC_1_20_2
LevelRenderer levelRenderer = MC.levelRenderer;
Collection<LevelRenderer.RenderChunkInfo> chunks =
#if MC_VER < MC_1_18_2 levelRenderer.renderChunks;
#else levelRenderer.renderChunkStorage.get().renderChunks; #endif
return glTexture.glId();
return (chunks.stream().map((chunk) -> {
AABB chunkBoundingBox =
#if MC_VER < MC_1_18_2 chunk.chunk.bb;
#else chunk.chunk.getBoundingBox(); #endif
return new DhChunkPos(Math.floorDiv((int) chunkBoundingBox.minX, 16),
Math.floorDiv((int) chunkBoundingBox.minZ, 16));
}).collect(Collectors.toCollection(HashSet::new)));
#else
LevelRenderer levelRenderer = MC.levelRenderer;
Collection<SectionRenderDispatcher.RenderSection> chunks = levelRenderer.visibleSections;
}
catch (Exception 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 -1;
}
return (chunks.stream().map((chunk) -> {
AABB chunkBoundingBox = chunk.getBoundingBox();
return new DhChunkPos(Math.floorDiv((int) chunkBoundingBox.minX, 16),
Math.floorDiv((int) chunkBoundingBox.minZ, 16));
}).collect(Collectors.toCollection(HashSet::new)));
#endif
}
@Override
public int getColorTextureId()
catch (LinkageError e)
{
#if MC_VER < MC_1_21_5
return this.getRenderTarget().getColorTextureId();
#else
try
{
GlTexture glTexture = (GlTexture) this.getRenderTarget().getColorTexture();
if (glTexture == null)
MinecraftClientWrapper.INSTANCE.sendChatMessage(
"\u00A7e\u00A7l\u00A7uWARNING: Distant Horizons: getVanillaRenderedChunks method failed."
+ " Using Backup Method.");
MinecraftClientWrapper.INSTANCE.sendChatMessage(
"\u00A7eOverdraw prevention will be worse than normal.");
}
catch (Exception e2)
{
// shouldn't happen, but just in case
return -1;
}
return glTexture.glId();
LOGGER.error("getVanillaRenderedChunks Error: ", e);
usingBackupGetVanillaRenderedChunks = true;
}
catch (Exception 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 -1;
}
#endif
return getMaximumRenderedChunks();
}
@Override
public int getTargetFramebufferViewportWidth()
{
#if MC_VER < MC_1_21_9
return this.getRenderTarget().viewWidth;
#else
return this.getRenderTarget().width;
#endif
}
@Override
public int getTargetFramebufferViewportHeight()
{
#if MC_VER < MC_1_21_9
return this.getRenderTarget().viewHeight;
#else
return this.getRenderTarget().height;
#endif
}
@Override
public ILightMapWrapper getLightmapWrapper(@NotNull ILevelWrapper level) { return this.lightmapByDimensionType.get(level.getDimensionType()); }
public ILightMapWrapper getLightmapWrapper(ILevelWrapper level) { return this.lightmapByDimensionType.get(level.getDimensionType()); }
@Override
public boolean isFogStateSpecial()
@@ -450,10 +405,6 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
#endif
}
/**
* It's better to use {@link MinecraftRenderWrapper#setLightmapId(int, IClientLevelWrapper)} if possible,
* however old MC versions don't support it.
*/
public void updateLightmap(NativeImage lightPixels, IClientLevelWrapper level)
{
// Using ClientLevelWrapper as the key would be better, but we don't have a consistent way to create the same
@@ -464,56 +415,5 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
LightMapWrapper wrapper = this.lightmapByDimensionType.computeIfAbsent(dimensionType, (dimType) -> new LightMapWrapper());
wrapper.uploadLightmap(lightPixels);
}
public void setLightmapId(int tetxureId, IClientLevelWrapper level)
{
// Using ClientLevelWrapper as the key would be better, but we don't have a consistent way to create the same
// object for the same MC level and/or the same hash,
// so this will have to do for now
IDimensionTypeWrapper dimensionType = level.getDimensionType();
LightMapWrapper wrapper = this.lightmapByDimensionType.computeIfAbsent(dimensionType, (dimType) -> new LightMapWrapper());
wrapper.setLightmapId(tetxureId);
}
@Override
public float getShade(EDhDirection lodDirection)
{
EDhApiLodShading lodShading = Config.Client.Advanced.Graphics.Quality.lodShading.get();
switch (lodShading)
{
default:
case AUTO:
if (MC.level != null)
{
Direction mcDir = McObjectConverter.Convert(lodDirection);
return MC.level.getShade(mcDir, true);
}
else
{
return 0.0f;
}
case ENABLED:
switch (lodDirection)
{
case DOWN:
return 0.5F;
default:
case UP:
return 1.0F;
case NORTH:
case SOUTH:
return 0.8F;
case WEST:
case EAST:
return 0.6F;
}
case DISABLED:
return 1.0F;
}
}
}
@@ -1,62 +0,0 @@
package com.seibel.distanthorizons.common.wrappers.minecraft;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import net.minecraft.server.dedicated.DedicatedServer;
import org.jetbrains.annotations.Nullable;
import java.io.File;
public class MinecraftServerWrapper implements IMinecraftSharedWrapper
{
public static final MinecraftServerWrapper INSTANCE = new MinecraftServerWrapper();
/** set during server startup */
@Nullable
public DedicatedServer dedicatedServer = null;
//=============//
// constructor //
//=============//
private MinecraftServerWrapper() { }
//=========//
// methods //
//=========//
@Override
public boolean isDedicatedServer() { return true; }
@Override
public File getInstallationDirectory()
{
if (this.dedicatedServer == null)
{
throw new IllegalStateException("Trying to get Installation Direction before dedicated server completed initialization!");
}
#if MC_VER < MC_1_21_1
return this.dedicatedServer.getServerDirectory();
#else
return this.dedicatedServer.getServerDirectory().toFile();
#endif
}
@Override
public int getPlayerCount()
{
if (this.dedicatedServer == null)
{
throw new IllegalStateException("Trying to get player count before dedicated server completed initialization!");
}
return this.dedicatedServer.getPlayerCount();
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -31,19 +31,31 @@ public class ProfilerWrapper implements IProfilerWrapper
{
public ProfilerFiller profiler;
public ProfilerWrapper(ProfilerFiller newProfiler) { this.profiler = newProfiler; }
public ProfilerWrapper(ProfilerFiller newProfiler)
{
profiler = newProfiler;
}
/** starts a new section inside the currently running section */
@Override
public void push(String newSection) { this.profiler.push(newSection); }
public void push(String newSection)
{
profiler.push(newSection);
}
/** ends the currently running section and starts a new one */
@Override
public void popPush(String newSection) { this.profiler.popPush(newSection); }
public void popPush(String newSection)
{
profiler.popPush(newSection);
}
/** ends the currently running section */
@Override
public void pop() { this.profiler.pop(); }
public void pop()
{
profiler.pop();
}
}
@@ -1,15 +0,0 @@
package com.seibel.distanthorizons.common.wrappers.misc;
import net.minecraft.server.level.ServerLevel;
import org.jetbrains.annotations.Nullable;
public interface IMixinServerPlayer
{
@Nullable
ServerLevel distantHorizons$getDimensionChangeDestination();
#if MC_VER == MC_1_16_5
void distantHorizons$setDimensionChangeDestination(ServerLevel dimensionChangeDestination);
#endif
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -20,24 +20,16 @@
package com.seibel.distanthorizons.common.wrappers.misc;
import com.mojang.blaze3d.platform.NativeImage;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.lwjgl.opengl.GL32;
import java.nio.ByteBuffer;
public class LightMapWrapper implements ILightMapWrapper
{
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private int textureId = 0;
//==============//
// constructors //
//==============//
@@ -46,67 +38,41 @@ public class LightMapWrapper implements ILightMapWrapper
//==================//
// lightmap syncing //
//==================//
//=========//
// methods //
//=========//
public void uploadLightmap(NativeImage image)
{
#if MC_VER < MC_1_21_5
int currentTexture = GLMC.getActiveTexture();
int currentBind = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D);
if (this.textureId == 0)
{
this.createLightmap(image);
}
else
{
GLMC.glBindTexture(this.textureId);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, this.textureId);
}
image.upload(0, 0, 0, false);
// getActiveTexture() may return textures that aren't valid and attempting to bind them will
// throw a GL error in MC 1.21.1
if (GL32.glIsTexture(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
GL32.glBindTexture(GL32.GL_TEXTURE_2D, currentBind);
}
private void createLightmap(NativeImage image)
{
#if MC_VER < MC_1_21_5
this.textureId = GLMC.glGenTextures();
GLMC.glBindTexture(this.textureId);
this.textureId = GL32.glGenTextures();
GL32.glBindTexture(GL32.GL_TEXTURE_2D, this.textureId);
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, image.format().glFormat(), image.getWidth(), image.getHeight(),
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)
{
// just use the MC texture ID
this.textureId = minecraftLightmapTetxureId;
}
//==============//
// lightmap use //
//==============//
@Override
public void bind()
{
GLMC.glActiveTexture(GL32.GL_TEXTURE0);
GLMC.glBindTexture(this.textureId);
GL32.glActiveTexture(GL32.GL_TEXTURE0);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, this.textureId);
}
@Override
public void unbind() { GLMC.glBindTexture(0); }
public void unbind() { GL32.glBindTexture(GL32.GL_TEXTURE_2D, 0); }
}
@@ -1,30 +0,0 @@
package com.seibel.distanthorizons.common.wrappers.misc;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IMutableBlockPosWrapper;
import net.minecraft.core.BlockPos;
public class MutableBlockPosWrapper implements IMutableBlockPosWrapper
{
public final BlockPos.MutableBlockPos pos;
//=============//
// constructor //
//=============//
public MutableBlockPosWrapper()
{
this.pos = new BlockPos.MutableBlockPos();
}
//===========//
// overrides //
//===========//
@Override
public Object getWrappedMcObject() { return this.pos; }
}
@@ -1,104 +1,54 @@
package com.seibel.distanthorizons.common.wrappers.misc;
import com.google.common.base.Objects;
import com.google.common.collect.MapMaker;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.world.phys.Vec3;
import java.net.SocketAddress;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
/**
* This wrapper transparently ensures that the underlying {@link ServerPlayer} is always valid,
* unless the player has disconnected.
*/
public class ServerPlayerWrapper implements IServerPlayerWrapper
{
private static final ConcurrentMap<ServerGamePacketListenerImpl, ServerPlayerWrapper> serverPlayerWrapperMap = new MapMaker().weakKeys().weakValues().makeMap();
private static final ConcurrentMap<ServerPlayer, ServerPlayerWrapper>
serverPlayerWrapperMap = new MapMaker().weakKeys().makeMap();
private final ServerGamePacketListenerImpl connection;
//=============//
// constructor //
//=============//
private final ServerPlayer serverPlayer;
public static ServerPlayerWrapper getWrapper(ServerPlayer serverPlayer)
{ return serverPlayerWrapperMap.computeIfAbsent(serverPlayer.connection, ignored -> new ServerPlayerWrapper(serverPlayer.connection)); }
{
return serverPlayerWrapperMap.computeIfAbsent(serverPlayer, ServerPlayerWrapper::new);
}
private ServerPlayerWrapper(ServerGamePacketListenerImpl connection) { this.connection = connection; }
private ServerPlayerWrapper(ServerPlayer serverPlayer)
{
this.serverPlayer = serverPlayer;
}
public UUID getUUID()
{
return serverPlayer.getUUID();
}
//=========//
// getters //
//=========//
private ServerPlayer getServerPlayer() { return this.connection.player; }
@Override
public String getName() { return this.getServerPlayer().getName().getString(); }
@Override
public IServerLevelWrapper getLevel()
{
ServerLevel level = ((IMixinServerPlayer) this.getServerPlayer()).distantHorizons$getDimensionChangeDestination();
if (level == null)
{
#if MC_VER < MC_1_20_1
level = this.getServerPlayer().getLevel();
#elif MC_VER < MC_1_21_6
level = this.getServerPlayer().serverLevel();
return ServerLevelWrapper.getWrapper(this.serverPlayer.getLevel());
#else
level = this.getServerPlayer().level();
return ServerLevelWrapper.getWrapper(this.serverPlayer.serverLevel());
#endif
}
return ServerLevelWrapper.getWrapper(level);
}
@Override
public Vec3d getPosition()
public Object getWrappedMcObject()
{
Vec3 position = this.getServerPlayer().position();
return new Vec3d(position.x, position.y, position.z);
return serverPlayer;
}
//================//
// base overrides //
//================//
@Override
public Object getWrappedMcObject() { return this.getServerPlayer(); }
@Override
public String toString() { return "Wrapped{" + this.getServerPlayer() + "}"; }
@Override
public boolean equals(Object obj)
public String toString()
{
if (this == obj)
{
return true;
return "Wrapped{" + serverPlayer.toString() + "}";
}
if (!(obj instanceof ServerPlayerWrapper))
{
return false;
}
ServerPlayerWrapper that = (ServerPlayerWrapper) obj;
return Objects.equal(this.connection, that.connection);
}
@Override
public int hashCode() { return Objects.hashCode(this.connection); }
}
@@ -2,16 +2,16 @@ package com.seibel.distanthorizons.common.wrappers.world;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper;
import com.seibel.distanthorizons.common.wrappers.block.ClientBlockStateColorCache;
import com.seibel.distanthorizons.common.wrappers.block.cache.ClientBlockDetailMap;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftClientWrapper;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.*;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -21,23 +21,15 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWra
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import com.seibel.distanthorizons.core.logging.DhLogger;
import net.minecraft.world.level.chunk.ChunkSource;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
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.function.Function;
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
@@ -45,42 +37,21 @@ import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
#if MC_VER < MC_1_21_3
import net.minecraft.world.phys.Vec3;
#else
import com.seibel.distanthorizons.core.util.ColorUtil;
#endif
#if MC_VER <= MC_1_21_10
#else
import net.minecraft.world.attribute.EnvironmentAttributes;
#endif
public class ClientLevelWrapper implements IClientLevelWrapper
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/**
* 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 Logger LOGGER = DhLoggerBuilder.getLogger(ClientLevelWrapper.class.getSimpleName());
private static final ConcurrentHashMap<ClientLevel, ClientLevelWrapper> LEVEL_WRAPPER_BY_CLIENT_LEVEL = new ConcurrentHashMap<>(); // TODO can leak
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
private static final Minecraft MINECRAFT = Minecraft.getInstance();
private final ClientLevel level;
private final ConcurrentHashMap<BlockState, ClientBlockStateColorCache> blockColorCacheByBlockState = new ConcurrentHashMap<>();
/** cached method reference to reduce GC overhead */
private final Function<BlockState, ClientBlockStateColorCache> createCachedBlockColorCacheFunc = (blockState) -> new ClientBlockStateColorCache(blockState, this);
private boolean cloudColorFailLogged = false;
private final ClientBlockDetailMap blockMap = new ClientBlockDetailMap(this);
private BlockStateWrapper dirtBlockWrapper;
private IDhLevel dhLevel;
private BiomeWrapper plainsBiomeWrapper;
@Deprecated // TODO circular references are bad
private IDhLevel parentDhLevel;
@@ -92,39 +63,12 @@ public class ClientLevelWrapper implements IClientLevelWrapper
//==================//
// instance methods //
//==================//
/**
* can be used when speed is important and the same level is likely to be passed in,
* IE rendering.
*/
@Nullable
public static IClientLevelWrapper getWrapperIfDifferent(@Nullable IClientLevelWrapper levelWrapper, @NotNull ClientLevel level) // TODO handle null level
{
if (KEYED_CLIENT_LEVEL_MANAGER.isEnabled() && KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel() != levelWrapper)
{
return getWrapper(level);
}
ClientLevelWrapper clientLevelWrapper = (ClientLevelWrapper)levelWrapper;
if (clientLevelWrapper == null
|| clientLevelWrapper.level != level)
{
return getWrapper(level);
}
return clientLevelWrapper;
}
//===============//
// wrapper logic //
//===============//
@Nullable
public static IClientLevelWrapper getWrapper(@NotNull ClientLevel level) { return getWrapper(level, false); }
@Nullable
public static IClientLevelWrapper getWrapper(@Nullable ClientLevel level, boolean bypassLevelKeyManager)
{
if (!bypassLevelKeyManager)
public static IClientLevelWrapper getWrapper(@Nullable ClientLevel level)
{
if (level == null)
{
@@ -132,39 +76,14 @@ public class ClientLevelWrapper implements IClientLevelWrapper
}
// used if the client is connected to a server that defines the currently loaded level
IServerKeyedClientLevel overrideLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
if (overrideLevel != null)
if (KEYED_CLIENT_LEVEL_MANAGER.getUseOverrideWrapper())
{
return overrideLevel;
}
return KEYED_CLIENT_LEVEL_MANAGER.getOverrideWrapper();
}
WeakReference<ClientLevelWrapper> levelRef = LEVEL_WRAPPER_REF_BY_CLIENT_LEVEL.get(level);
if (levelRef != null)
{
ClientLevelWrapper levelWrapper = levelRef.get();
if (levelWrapper != null)
{
return levelWrapper;
}
}
return LEVEL_WRAPPER_REF_BY_CLIENT_LEVEL.compute(level, (newLevel, newLevelRef) ->
{
if (newLevelRef != null)
{
ClientLevelWrapper oldLevelWrapper = newLevelRef.get();
if (oldLevelWrapper != null)
{
return newLevelRef;
}
}
return new WeakReference<>(new ClientLevelWrapper(newLevel));
}).get();
return getWrapperIgnoringOverride(level);
}
public static IClientLevelWrapper getWrapperIgnoringOverride(@NotNull ClientLevel level) { return LEVEL_WRAPPER_BY_CLIENT_LEVEL.computeIfAbsent(level, ClientLevelWrapper::new); }
@Nullable
@Override
@@ -172,17 +91,13 @@ public class ClientLevelWrapper implements IClientLevelWrapper
{
try
{
// this method only makes sense if we are running a single-player server
if (MINECRAFT.getSingleplayerServer() == null)
{
return null;
}
Iterable<ServerLevel> serverLevels = MINECRAFT.getSingleplayerServer().getAllLevels();
// attempt to find the server level with the same dimension type
// Note: this assumes only one level per dimension type, multiverse servers may not behave correctly
// TODO this assumes only one level per dimension type, the SubDimensionLevelMatcher will need to be added for supporting multiple levels per dimension
ServerLevelWrapper foundLevelWrapper = null;
// TODO: Surely there is a more efficient way to write this code
for (ServerLevel serverLevel : serverLevels)
{
if (serverLevel.dimension() == this.level.dimension())
@@ -196,7 +111,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
}
catch (Exception e)
{
LOGGER.error("Failed to get server side wrapper for client level: " + this.level);
LOGGER.error("Failed to get server side wrapper for client level: " + level);
return null;
}
}
@@ -208,20 +123,11 @@ public class ClientLevelWrapper implements IClientLevelWrapper
//====================//
@Override
public int getBlockColor(DhBlockPos blockPos, IBiomeWrapper biome, FullDataSourceV2 fullDataSource, IBlockStateWrapper blockWrapper)
public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper blockState)
{
ClientBlockStateColorCache blockColorCache = this.blockColorCacheByBlockState.get(((BlockStateWrapper) blockWrapper).blockState);
if (blockColorCache == null)
{
blockColorCache = this.blockColorCacheByBlockState.computeIfAbsent(
((BlockStateWrapper) blockWrapper).blockState,
this.createCachedBlockColorCacheFunc);
return this.blockMap.getColor(((BlockStateWrapper) blockState).blockState, (BiomeWrapper) biome, pos);
}
return blockColorCache.getColor((BiomeWrapper) biome, fullDataSource, blockPos);
}
@Override
public int getDirtBlockColor()
{
@@ -239,37 +145,31 @@ public class ClientLevelWrapper implements IClientLevelWrapper
}
}
return this.getBlockColor(DhBlockPos.ZERO, BiomeWrapper.EMPTY_WRAPPER, null, this.dirtBlockWrapper);
return this.blockMap.getColor(this.dirtBlockWrapper.blockState, BiomeWrapper.EMPTY_WRAPPER, DhBlockPos.ZERO);
}
@Override
public void clearBlockColorCache() { this.blockColorCacheByBlockState.clear(); }
@Override
public IDimensionTypeWrapper getDimensionType()
public IBiomeWrapper getPlainsBiomeWrapper()
{
#if MC_VER <= MC_1_21_10
return DimensionTypeWrapper.getDimensionTypeWrapper(this.level.dimensionType());
#else
return DimensionTypeWrapper.getDimensionTypeWrapper(this.level.dimensionType(), this.getDimensionName());
#endif
}
@Override
public String getDimensionName()
if (this.plainsBiomeWrapper == null)
{
#if MC_VER <= MC_1_21_10
return this.level.dimension().location().toString();
#else
return this.level.dimension().identifier().toString();
#endif
try
{
this.plainsBiomeWrapper = (BiomeWrapper) BiomeWrapper.deserialize(BiomeWrapper.PLAINS_RESOURCE_LOCATION_STRING, this);
}
catch (IOException e)
{
// shouldn't happen, but just in case
LOGGER.warn("Unable to get planes biome with resource location ["+BiomeWrapper.PLAINS_RESOURCE_LOCATION_STRING+"] with level ["+this+"].", e);
return null;
}
}
return this.plainsBiomeWrapper;
}
@Override
public long getHashedSeed() { return this.level.getBiomeManager().biomeZoomSeed; }
@Override
public String getDhIdentifier() { return this.getHashedSeedEncoded() + "@" + this.getDimensionName(); }
public IDimensionTypeWrapper getDimensionType() { return DimensionTypeWrapper.getDimensionTypeWrapper(this.level.dimensionType()); }
@Override
public EDhApiLevelType getLevelType() { return EDhApiLevelType.CLIENT_LEVEL; }
@@ -290,35 +190,54 @@ public class ClientLevelWrapper implements IClientLevelWrapper
{
#if MC_VER < MC_1_17_1
return 0;
#elif MC_VER < MC_1_21_3
return this.level.getMinBuildHeight();
#else
return this.level.getMinY();
return this.level.getMinBuildHeight();
#endif
}
@Override
public IChunkWrapper tryGetChunk(DhChunkPos pos)
{
if (!this.level.hasChunk(pos.x, pos.z))
{
return null;
}
ChunkAccess chunk = this.level.getChunk(pos.x, pos.z, ChunkStatus.EMPTY, false);
if (chunk == null)
{
return null;
}
return new ChunkWrapper(chunk, this.level, this);
}
@Override
public boolean hasChunkLoaded(int chunkX, int chunkZ)
{
ChunkSource source = this.level.getChunkSource();
return source.hasChunk(chunkX, chunkZ);
}
@Override
public IBlockStateWrapper getBlockState(DhBlockPos pos)
{
return BlockStateWrapper.fromBlockState(this.level.getBlockState(McObjectConverter.Convert(pos)), this);
}
@Override
public IBiomeWrapper getBiome(DhBlockPos pos) { return BiomeWrapper.getBiomeWrapper(this.level.getBiome(McObjectConverter.Convert(pos)), this); }
@Override
public ClientLevel getWrappedMcObject() { return this.level; }
@Override
public void onUnload()
{
LEVEL_WRAPPER_REF_BY_CLIENT_LEVEL.remove(this.level);
this.dhLevel = null;
LEVEL_WRAPPER_BY_CLIENT_LEVEL.remove(this.level);
this.parentDhLevel = null;
}
@Override
public File getDhSaveFolder()
{
if (this.dhLevel == null)
{
return null;
}
return this.dhLevel.getSaveStructure().getSaveFolder(this);
}
//===================//
@@ -326,88 +245,17 @@ public class ClientLevelWrapper implements IClientLevelWrapper
//===================//
@Override
public void setDhLevel(IDhLevel dhLevel) { this.dhLevel = dhLevel; }
@Override
public IDhLevel getDhLevel() { return this.dhLevel; }
public void setParentLevel(IDhLevel parentLevel) { this.parentDhLevel = parentLevel; }
@Override
public IDhApiCustomRenderRegister getRenderRegister()
{
if (this.dhLevel == null)
if (this.parentDhLevel == null)
{
return null;
}
return this.dhLevel.getGenericRenderer();
}
@Override
public Color getCloudColor(float tickDelta)
{
#if MC_VER < MC_1_21_3
Vec3 colorVec3 = null;
try
{
colorVec3 = this.level.getCloudColor(tickDelta);
return new Color((float)colorVec3.x, (float)colorVec3.y, (float)colorVec3.z);
}
catch (Exception e)
{
// extra logging is due to some mods returning weird values, this way we can track down the issue better
if (!this.cloudColorFailLogged)
{
this.cloudColorFailLogged = true;
String colorString = "NULL";
if (colorVec3 != null)
{
colorString = "r["+(float)colorVec3.x+"] g["+(float)colorVec3.y+"] b["+(float)colorVec3.z+"]";
}
LOGGER.warn("Failed to get cloud color for ["+this.getDhIdentifier()+"]. vec3 ["+colorString+"], error: ["+e.getMessage()+"].", e);
}
// default to white if there's an issue
return Color.WHITE;
}
#elif MC_VER <= MC_1_21_10
int argbColor = 0;
try
{
argbColor = this.level.getCloudColor(tickDelta);
return ColorUtil.toColorObjARGB(argbColor);
}
catch (Exception e)
{
// extra logging is due to some mods returning weird values, this way we can track down the issue better
if (!this.cloudColorFailLogged)
{
this.cloudColorFailLogged = true;
LOGGER.warn("Failed to get cloud color for ["+this.getDhIdentifier()+"]. Int ["+argbColor+"], col ["+ColorUtil.toString(argbColor)+"], error: ["+e.getMessage()+"].", e);
}
// default to white if there's an issue
return Color.WHITE;
}
#else
int argbColor = 0;
try
{
argbColor = this.level.environmentAttributes().getValue(EnvironmentAttributes.CLOUD_COLOR, BlockPos.ZERO);
return new Color(ColorUtil.getRed(argbColor), ColorUtil.getGreen(argbColor), ColorUtil.getBlue(argbColor), 255 /* ignore alpha since DH clouds don't render correctly with transparency */);
}
catch (Exception e)
{
// extra logging is due to some mods returning weird values, this way we can track down the issue better
if (!this.cloudColorFailLogged)
{
this.cloudColorFailLogged = true;
LOGGER.warn("Failed to get cloud color for ["+this.getDhIdentifier()+"]. Int ["+argbColor+"], col ["+ColorUtil.toString(argbColor)+"], error: ["+e.getMessage()+"].", e);
}
// default to white if there's an issue
return Color.WHITE;
}
#endif
return this.parentDhLevel.getGenericRenderer();
}
@@ -424,7 +272,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
return "Wrapped{null}";
}
return "Wrapped{" + this.level.toString() + "@" + this.getDhIdentifier() + "}";
return "Wrapped{" + this.level.toString() + "@" + this.getDimensionType().getDimensionName() + "}";
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -26,108 +26,67 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWra
import net.minecraft.world.level.dimension.DimensionType;
/**
* @author James Seibel
* @version 2022-9-16
*/
public class DimensionTypeWrapper implements IDimensionTypeWrapper
{
private static final ConcurrentMap<String, DimensionTypeWrapper> DIMENSION_WRAPPER_BY_NAME = new ConcurrentHashMap<>();
private static final ConcurrentMap<DimensionType, DimensionTypeWrapper> dimensionTypeWrapperMap = new ConcurrentHashMap<>();
private final DimensionType dimensionType;
private final String name;
//=============//
// Constructor //
//=============//
#if MC_VER <= MC_1_21_10
public DimensionTypeWrapper(DimensionType dimensionType)
#else
public DimensionTypeWrapper(DimensionType dimensionType, String name)
#endif
{
this.dimensionType = dimensionType;
#if MC_VER <= MC_1_21_10
this.name = determineName(dimensionType);
#else
this.name = name;
#endif
}
#if MC_VER <= MC_1_21_10
public static DimensionTypeWrapper getDimensionTypeWrapper(DimensionType dimensionType)
#else
public static DimensionTypeWrapper getDimensionTypeWrapper(DimensionType dimensionType, String name)
#endif
{
#if MC_VER <= MC_1_21_10
String dimName = determineName(dimensionType);
#else
String dimName = name;
#endif
// check if the dimension has already been wrapped
if (DIMENSION_WRAPPER_BY_NAME.containsKey(dimName)
&& DIMENSION_WRAPPER_BY_NAME.get(dimName) != null)
{
return DIMENSION_WRAPPER_BY_NAME.get(dimName);
}
//first we check if the biome has already been wrapped
if (dimensionTypeWrapperMap.containsKey(dimensionType) && dimensionTypeWrapperMap.get(dimensionType) != null)
return dimensionTypeWrapperMap.get(dimensionType);
// create the missing wrapper
#if MC_VER <= MC_1_21_10
//if it hasn't been created yet, we create it and save it in the map
DimensionTypeWrapper dimensionTypeWrapper = new DimensionTypeWrapper(dimensionType);
#else
DimensionTypeWrapper dimensionTypeWrapper = new DimensionTypeWrapper(dimensionType, dimName);
#endif
dimensionTypeWrapperMap.put(dimensionType, dimensionTypeWrapper);
DIMENSION_WRAPPER_BY_NAME.put(dimName, dimensionTypeWrapper);
//we return the newly created wrapper
return dimensionTypeWrapper;
}
private static String determineName(DimensionType dimensionType)
public static void clearMap()
{
#if MC_VER <= MC_1_16_5
// effectsLocation() is marked as client only, so using the backing field directly
return dimensionType.effectsLocation.getPath();
#elif MC_VER <= MC_1_21_10
return dimensionType.effectsLocation().getPath();
#else
throw new UnsupportedOperationException("As of MC 1.21.11 the dimension type no longer stores it's name and must be determined from the level.");
#endif
dimensionTypeWrapperMap.clear();
}
public static void clearMap() { DIMENSION_WRAPPER_BY_NAME.clear(); }
//=================//
// wrapper methods //
//=================//
@Override
public String getName() { return this.name; }
public String getDimensionName()
{
return dimensionType.effectsLocation().getPath();
}
@Override
public boolean hasCeiling() { return this.dimensionType.hasCeiling(); }
public boolean hasCeiling()
{
return dimensionType.hasCeiling();
}
@Override
public boolean hasSkyLight() { return this.dimensionType.hasSkyLight(); }
public boolean hasSkyLight()
{
return dimensionType.hasSkyLight();
}
@Override
public Object getWrappedMcObject() { return this.dimensionType; }
@Override
public boolean isTheEnd() { return this.getName().equalsIgnoreCase("the_end"); }
@Override
public double getCoordinateScale() { return this.dimensionType.coordinateScale(); }
public Object getWrappedMcObject()
{
return this.dimensionType;
}
//================//
// base overrides //
//================//
@Override
public boolean equals(Object obj)
{
@@ -138,10 +97,9 @@ public class DimensionTypeWrapper implements IDimensionTypeWrapper
else
{
DimensionTypeWrapper other = (DimensionTypeWrapper) obj;
return other.getName().equals(this.getName());
return other.getDimensionName().equals(this.getDimensionName());
}
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -20,26 +20,27 @@
package com.seibel.distanthorizons.common.wrappers.world;
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 com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper;
import com.seibel.distanthorizons.common.wrappers.block.cache.ServerBlockDetailMap;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftClientWrapper;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
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.ChunkSource;
@@ -49,26 +50,20 @@ import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
/**
* @version 2022-9-16
*/
public class ServerLevelWrapper implements IServerLevelWrapper
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/**
* 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 static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final ConcurrentHashMap<ServerLevel, ServerLevelWrapper> LEVEL_WRAPPER_BY_SERVER_LEVEL = new ConcurrentHashMap<>();
private final ServerLevel level;
private IDhLevel dhLevel;
/**
* this name is cached to prevent issues during shutdown where
* the server variables needed may no longer be available.
*/
private final String KeyedLevelDimensionName;
@Deprecated // TODO circular references are bad
private IDhLevel parentDhLevel;
@@ -76,189 +71,117 @@ public class ServerLevelWrapper implements IServerLevelWrapper
// constructors //
//==============//
public static ServerLevelWrapper getWrapper(ServerLevel level)
{
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 static ServerLevelWrapper getWrapper(ServerLevel level) { return LEVEL_WRAPPER_BY_SERVER_LEVEL.computeIfAbsent(level, ServerLevelWrapper::new); }
public ServerLevelWrapper(ServerLevel level)
{
this.level = level;
this.KeyedLevelDimensionName = this.createKeyedLevelDimensionName();
}
//==================//
// instance methods //
//==================//
//=========//
// methods //
//=========//
@Override
public File getMcSaveFolder()
public File getSaveFolder()
{
#if MC_VER < MC_1_21_3
return this.level.getChunkSource().getDataStorage().dataFolder;
#else
return this.level.getChunkSource().getDataStorage().dataFolder.toFile();
#endif
return level.getChunkSource().getDataStorage().dataFolder;
}
@Override
public String getKeyedLevelDimensionName() { return this.KeyedLevelDimensionName; }
private String createKeyedLevelDimensionName()
{
String dimensionName = this.getDhIdentifier();
if (Config.Server.sendLevelKeys.get())
{
String levelKeyPrefix = Config.Server.levelKeyPrefix.get();
if (SharedApi.getEnvironment() == EWorldEnvironment.CLIENT_SERVER)
{
String cleanWorldFolderName = this.getWorldFolderName()
.replaceAll("[^" + LevelInitMessage.ALLOWED_CHARS_REGEX + " ]", "")
.replaceAll(" ", "_");
levelKeyPrefix += (!levelKeyPrefix.isEmpty() ? "_" : "") + cleanWorldFolderName
+ "_" + this.getHashedSeedEncoded();
}
if (levelKeyPrefix.isEmpty())
{
levelKeyPrefix = this.getHashedSeedEncoded();
}
String mainPart = "@" + dimensionName;
return levelKeyPrefix.substring(0, Math.min(
LevelInitMessage.MAX_LENGTH - mainPart.length(),
levelKeyPrefix.length()
)) + mainPart;
}
return dimensionName;
}
private String getWorldFolderName()
{
try
{
// We use the overworld since it's the only dimension that is stored in the server root folder
#if MC_VER >= MC_1_21_3
return this.level.getServer().getLevel(Level.OVERWORLD).getChunkSource().getDataStorage().dataFolder.getParent().getFileName().toString();
#else // <= 1.21.3
return this.level.getServer().getLevel(Level.OVERWORLD).getChunkSource().getDataStorage().dataFolder.getParentFile().getName();
#endif
}
catch (Exception e)
{
LOGGER.warn("Unable to get world folder name. LODs may not load or save correctly. Error: ["+e.getMessage()+"].", e);
return "unknown_world";
}
}
@Override
public DimensionTypeWrapper getDimensionType()
{
#if MC_VER <= MC_1_21_10
return DimensionTypeWrapper.getDimensionTypeWrapper(this.level.dimensionType());
#else
return DimensionTypeWrapper.getDimensionTypeWrapper(this.level.dimensionType(), this.getDimensionName());
#endif
return DimensionTypeWrapper.getDimensionTypeWrapper(level.dimensionType());
}
@Override
public String getDimensionName()
{
#if MC_VER <= MC_1_21_10
return this.level.dimension().location().toString();
#else
return this.level.dimension().identifier().toString();
#endif
}
@Override
public long getHashedSeed() { return this.level.getBiomeManager().biomeZoomSeed; }
@Override
public String getDhIdentifier() { return this.getDimensionName(); }
@Override
public EDhApiLevelType getLevelType() { return EDhApiLevelType.SERVER_LEVEL; }
public ServerLevel getLevel() { return this.level; }
public ServerLevel getLevel()
{
return level;
}
@Override
public boolean hasCeiling() { return this.level.dimensionType().hasCeiling(); }
public boolean hasCeiling()
{
return level.dimensionType().hasCeiling();
}
@Override
public boolean hasSkyLight() { return this.level.dimensionType().hasSkyLight(); }
public boolean hasSkyLight()
{
return level.dimensionType().hasSkyLight();
}
@Override
public int getMaxHeight() { return this.level.getHeight(); }
public int getMaxHeight()
{
return level.getHeight();
}
@Override
public int getMinHeight()
{
#if MC_VER < MC_1_17_1
return 0;
#elif MC_VER < MC_1_21_3
return this.level.getMinBuildHeight();
#else
return this.level.getMinY();
return level.getMinBuildHeight();
#endif
}
@Override
public IChunkWrapper tryGetChunk(DhChunkPos pos)
{
if (!level.hasChunk(pos.x, pos.z)) return null;
ChunkAccess chunk = level.getChunk(pos.x, pos.z, ChunkStatus.FULL, false);
if (chunk == null) return null;
return new ChunkWrapper(chunk, level, this);
}
@Override
public boolean hasChunkLoaded(int chunkX, int chunkZ)
{
// world.hasChunk(chunkX, chunkZ); THIS DOES NOT WORK FOR CLIENT LEVEL CAUSE MOJANG ALWAYS RETURN TRUE FOR THAT!
ChunkSource source = level.getChunkSource();
return source.hasChunk(chunkX, chunkZ);
}
@Override
public IBlockStateWrapper getBlockState(DhBlockPos pos)
{
return BlockStateWrapper.fromBlockState(level.getBlockState(McObjectConverter.Convert(pos)), this);
}
@Override
public IBiomeWrapper getBiome(DhBlockPos pos)
{
return BiomeWrapper.getBiomeWrapper(level.getBiome(McObjectConverter.Convert(pos)), this);
}
@Override
public ServerLevel getWrappedMcObject() { return this.level; }
@Override
public void onUnload() { LEVEL_WRAPPER_REF_BY_SERVER_LEVEL.remove(this.level); }
public void onUnload() { LEVEL_WRAPPER_BY_SERVER_LEVEL.remove(this.level); }
@Override
public void setDhLevel(IDhLevel dhLevel) { this.dhLevel = dhLevel; }
@Override
@Nullable
public IDhLevel getDhLevel() { return this.dhLevel; }
public void setParentLevel(IDhLevel parentLevel) { this.parentDhLevel = parentLevel; }
@Override
public IDhApiCustomRenderRegister getRenderRegister()
{
if (this.dhLevel == null)
if (this.parentDhLevel == null)
{
return null;
}
return this.dhLevel.getGenericRenderer();
return this.parentDhLevel.getGenericRenderer();
}
@Override
public File getDhSaveFolder()
{
if (this.dhLevel == null)
{
return null;
}
return this.dhLevel.getSaveStructure().getSaveFolder(this);
}
//================//
@@ -266,6 +189,6 @@ public class ServerLevelWrapper implements IServerLevelWrapper
//================//
@Override
public String toString() { return "Wrapped{" + this.level.toString() + "@" + this.getDhIdentifier() + "}"; }
public String toString() { return "Wrapped{" + this.level.toString() + "@" + this.getDimensionType().getDimensionName() + "}"; }
}
@@ -1,92 +0,0 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration;
import net.minecraft.world.level.ChunkPos;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class ChunkPosGenStream
{
public static Iterator<ChunkPos> getIterator(int genMinX, int genMinZ, int width, int extraRadius)
{ return getStream(genMinX, genMinZ, width, extraRadius).iterator(); }
/** @param extraRadius in both the positive and negative directions */
public static Stream<ChunkPos> getStream(int genMinX, int genMinZ, int width, int extraRadius)
{ return StreamSupport.stream(new InclusiveChunkPosIterator(genMinX, genMinZ, width, extraRadius), false); }
private static class InclusiveChunkPosIterator extends Spliterators.AbstractSpliterator<ChunkPos>
{
private final int minX;
private final int minZ;
private final int maxX;
private final int maxZ;
/** current X pos */
int x;
/** current Z pos */
private int z;
//=============//
// constructor //
//=============//
protected InclusiveChunkPosIterator(int genMinX, int genMinZ, int width, int extraRadius)
{
super(getCount(width, extraRadius), Spliterator.SIZED);
this.minX = genMinX - extraRadius;
this.minZ = genMinZ - extraRadius;
this.maxX = genMinX + (width - 1) + extraRadius;
this.maxZ = genMinZ + (width - 1) + extraRadius;
// X starts at 1 minus the minX so we can immediately re-add 1 in the tryAdvance() loop
this.x = this.minX - 1;
this.z = this.minZ;
}
private static int getCount(int width, int extraRadius)
{
int widthPlusExtra = width + (extraRadius * 2);
return widthPlusExtra * widthPlusExtra;
}
//=================//
// iterator method //
//=================//
@Override
public boolean tryAdvance(Consumer<? super ChunkPos> consumer)
{
if (this.x == this.maxX && this.z == this.maxZ)
{
// the last returned position was the final valid position
return false;
}
if (this.x == this.maxX)
{
// we reached the max X position, loop back around in the next Z row
this.x = this.minX;
this.z++;
}
else
{
this.x++;
}
consumer.accept(new ChunkPos(this.x, this.z));
return true;
}
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -19,153 +19,135 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.ThreadWorldGenParams;
import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.objects.EventTimer;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.Logger;
public final class GenerationEvent
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();;
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
private static int generationFutureDebugIDs = 0;
private static final AtomicInteger DEBUG_ID_REF = new AtomicInteger(0);
/** can be used for troubleshooting */
public final int id;
public final ThreadWorldGenParams threadedParam;
public final ThreadedParameters threadedParam;
public final DhChunkPos minPos;
public final int widthInChunks;
public final int size;
public final EDhApiWorldGenerationStep targetGenerationStep;
public final EDhApiDistantGeneratorMode generatorMode;
public final CompletableFuture<Void> future;
public EventTimer timer = null;
public long inQueueTime;
public long timeoutTime = -1;
public CompletableFuture<Void> future = null;
public final Consumer<IChunkWrapper> resultConsumer;
//=============//
// constructor //
//=============//
private GenerationEvent(
DhChunkPos minPos, int widthInChunks, BatchGenerationEnvironment generationGroup,
EDhApiDistantGeneratorMode generatorMode, EDhApiWorldGenerationStep targetGenerationStep, Consumer<IChunkWrapper> resultConsumer)
public GenerationEvent(
DhChunkPos minPos, int size, BatchGenerationEnvironment generationGroup,
EDhApiWorldGenerationStep targetGenerationStep, Consumer<IChunkWrapper> resultConsumer)
{
this.id = DEBUG_ID_REF.getAndIncrement();
this.inQueueTime = System.nanoTime();
this.id = generationFutureDebugIDs++;
this.minPos = minPos;
this.widthInChunks = widthInChunks;
this.size = size;
this.targetGenerationStep = targetGenerationStep;
this.generatorMode = generatorMode;
this.threadedParam = ThreadWorldGenParams.getOrMake(generationGroup.globalParams);
this.future = new CompletableFuture<>();
this.threadedParam = ThreadedParameters.getOrMake(generationGroup.params);
this.resultConsumer = resultConsumer;
}
//=======//
// start //
//=======//
public static GenerationEvent start(
DhChunkPos minPos, int widthInChunks, BatchGenerationEnvironment genEnvironment,
EDhApiDistantGeneratorMode generatorMode, EDhApiWorldGenerationStep target, Consumer<IChunkWrapper> resultConsumer,
public static GenerationEvent startEvent(
DhChunkPos minPos, int size, BatchGenerationEnvironment genEnvironment,
EDhApiWorldGenerationStep target, Consumer<IChunkWrapper> resultConsumer,
ExecutorService worldGeneratorThreadPool)
{
GenerationEvent genEvent = new GenerationEvent(minPos, widthInChunks, genEnvironment, generatorMode, target, resultConsumer);
try
if (size % 2 == 0)
{
worldGeneratorThreadPool.execute(() ->
{
try
{
BatchGenerationEnvironment.isDhWorldGenThreadRef.set(true);
if (genEvent.generatorMode == EDhApiDistantGeneratorMode.INTERNAL_SERVER)
{
genEnvironment.internalServerGenerator.generateChunksViaInternalServer(genEvent);
genEvent.future.complete(null);
size += 1; // size must be odd for vanilla world gen regions to work
}
else
GenerationEvent generationEvent = new GenerationEvent(minPos, size, genEnvironment, target, resultConsumer);
generationEvent.future = CompletableFuture.runAsync(() ->
{
long runStartTime = System.nanoTime();
generationEvent.timeoutTime = runStartTime;
generationEvent.inQueueTime = runStartTime - generationEvent.inQueueTime;
generationEvent.timer = new EventTimer("setup");
BatchGenerationEnvironment.isDistantGeneratorThread.set(true);
try
{
genEnvironment.generateEvent(genEvent);
//LOGGER.info("generating [{}]", event.minPos);
genEnvironment.generateLodFromList(generationEvent);
}
catch (Throwable throwable)
catch (InterruptedException ignored)
{
handleWorldGenThrowable(genEvent, throwable);
}
finally
{
genEvent.future.complete(null);
BatchGenerationEnvironment.isDistantGeneratorThread.remove();
}
}
}
catch (Throwable initialThrowable)
{
handleWorldGenThrowable(genEvent, initialThrowable);
}
finally
{
BatchGenerationEnvironment.isDhWorldGenThreadRef.remove();
}
});
}
catch (RejectedExecutionException e)
{
genEvent.future.completeExceptionally(e);
}, worldGeneratorThreadPool);
return generationEvent;
}
return genEvent;
}
/** There's probably a better way to handle this, but it'll work for now */
private static void handleWorldGenThrowable(GenerationEvent generationEvent, Throwable initialThrowable)
public boolean isComplete() { return this.future.isDone(); }
public boolean hasTimeout(int duration, TimeUnit unit)
{
Throwable throwable = initialThrowable;
while (throwable instanceof CompletionException)
if (this.timeoutTime == -1)
{
throwable = throwable.getCause();
return false;
}
boolean isShutdownException = ExceptionUtil.isShutdownException(throwable);
if (isShutdownException)
long currentTime = System.nanoTime();
long delta = currentTime - this.timeoutTime;
return (delta > TimeUnit.NANOSECONDS.convert(duration, unit));
}
public boolean terminate()
{
// these exceptions can be ignored, generally they just mean
// the thread is busy so it'll need to try again later.
// FIXME this should cause the world gen task to be re-queued so we can try again later
// however, currently it can cause large gaps in the world gen instead.
// These gaps will generate correctly if the level is reloaded and the world gen is re-queued,
// however this is makes it look like the generator isn't working or skipped something.
LOGGER.info("======================DUMPING ALL THREADS FOR WORLD GEN=======================");
ThreadPoolUtil.WORLD_GEN_THREAD_FACTORY.dumpAllThreadStacks();
this.future.cancel(true);
return this.future.isCancelled();
}
else
public boolean tooClose(int minX, int minZ, int width)
{
generationEvent.future.completeExceptionally(throwable);
}
int aMinX = this.minPos.x;
int aMinZ = this.minPos.z;
int aSize = this.size;
// Account for required empty chunks in the border
aSize += 1;
width += 1;
// Do a AABB to AABB intersection test
return (aMinX + aSize >= minX &&
aMinX <= minX + width &&
aMinZ + aSize >= minZ &&
aMinZ <= minZ + width);
}
//================//
// base overrides //
//================//
public void refreshTimeout()
{
this.timeoutTime = System.nanoTime();
UncheckedInterruptedException.throwIfInterrupted();
}
@Override
public String toString() { return this.id + ":" + this.widthInChunks + "@" + this.minPos + "(" + this.targetGenerationStep + ")"; }
public String toString() { return this.id + ":" + this.size + "@" + this.minPos + "(" + this.targetGenerationStep + ")"; }
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.worldGeneration.params;
package com.seibel.distanthorizons.common.wrappers.worldGeneration;
import com.mojang.datafixers.DataFixer;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
@@ -27,112 +27,79 @@ import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ThreadedLevelLightEngine;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.chunk.ChunkGenerator;
#if MC_VER >= MC_1_18_2
import net.minecraft.world.level.chunk.storage.ChunkScanAccess;
#endif
import net.minecraft.world.level.levelgen.WorldGenSettings;
#if MC_VER < MC_1_19_2
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
#elif MC_VER < MC_1_19_2
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
#else
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import net.minecraft.world.level.levelgen.RandomState;
#if MC_VER >= MC_1_19_4
import net.minecraft.world.level.levelgen.WorldOptions;
import net.minecraft.core.registries.Registries;
#endif
#endif
import net.minecraft.world.level.storage.WorldData;
#if MC_VER < MC_1_19_4
#elif MC_VER < MC_1_21_3
import net.minecraft.core.registries.Registries;
#else
import net.minecraft.core.registries.Registries;
#endif
#if MC_VER < MC_1_19_4
import net.minecraft.world.level.levelgen.WorldGenSettings;
#else
import net.minecraft.world.level.levelgen.WorldOptions;
#endif
/**
* Handles parameters that are relevant for the entire MC world.
*
* @see ThreadWorldGenParams
*/
public final class GlobalWorldGenParams
public final class GlobalParameters
{
public final ChunkGenerator generator;
public final IDhServerLevel dhServerLevel;
public final ServerLevel mcServerLevel;
public final Registry<Biome> biomes;
public final RegistryAccess registry;
public final long worldSeed;
public final DataFixer dataFixer;
#if MC_VER < MC_1_19_2
public final StructureManager structures;
#else
public final StructureTemplateManager structures;
public final RandomState randomState;
#endif
#if MC_VER < MC_1_19_4
public final WorldGenSettings worldGenSettings;
#else
public final WorldOptions worldOptions;
#endif
public final IDhServerLevel lodLevel;
public final ServerLevel level;
public final Registry<Biome> biomes;
public final RegistryAccess registry;
public final long worldSeed;
public final DataFixer fixerUpper;
#if MC_VER >= MC_1_18_2
public final BiomeManager biomeManager;
public final ChunkScanAccess chunkScanner;
public final ChunkScanAccess chunkScanner; // FIXME: Figure out if this is actually needed
#endif
//=============//
// constructor //
//=============//
public GlobalWorldGenParams(IDhServerLevel dhServerLevel)
public GlobalParameters(IDhServerLevel lodLevel)
{
this.dhServerLevel = dhServerLevel;
this.lodLevel = lodLevel;
this.mcServerLevel = ((ServerLevelWrapper) dhServerLevel.getServerLevelWrapper()).getWrappedMcObject();
MinecraftServer server = this.mcServerLevel.getServer();
level = ((ServerLevelWrapper) lodLevel.getServerLevelWrapper()).getWrappedMcObject();
MinecraftServer server = level.getServer();
WorldData worldData = server.getWorldData();
this.registry = server.registryAccess();
registry = server.registryAccess();
#if MC_VER < MC_1_19_4
this.worldGenSettings = worldData.worldGenSettings();
this.biomes = registry.registryOrThrow(Registry.BIOME_REGISTRY);
this.worldSeed = worldGenSettings.seed();
#elif MC_VER < MC_1_21_3
this.worldOptions = worldData.worldGenOptions();
this.biomes = registry.registryOrThrow(Registries.BIOME);
this.worldSeed = worldOptions.seed();
worldGenSettings = worldData.worldGenSettings();
biomes = registry.registryOrThrow(Registry.BIOME_REGISTRY);
worldSeed = worldGenSettings.seed();
#else
this.worldOptions = worldData.worldGenOptions();
this.biomes = this.registry.lookupOrThrow(Registries.BIOME);
this.worldSeed = this.worldOptions.seed();
worldOptions = worldData.worldGenOptions();
biomes = registry.registryOrThrow(Registries.BIOME);
worldSeed = worldOptions.seed();
#endif
#if MC_VER >= MC_1_18_2
this.biomeManager = new BiomeManager(this.mcServerLevel, BiomeManager.obfuscateSeed(this.worldSeed));
this.chunkScanner = this.mcServerLevel.getChunkSource().chunkScanner();
biomeManager = new BiomeManager(level, BiomeManager.obfuscateSeed(worldSeed));
chunkScanner = level.getChunkSource().chunkScanner();
#endif
this.structures = server.getStructureManager();
this.generator = this.mcServerLevel.getChunkSource().getGenerator();
this.dataFixer = server.getFixerUpper();
structures = server.getStructureManager();
generator = level.getChunkSource().getGenerator();
fixerUpper = server.getFixerUpper();
#if MC_VER >= MC_1_19_2
this.randomState = this.mcServerLevel.getChunkSource().randomState();
randomState = level.getChunkSource().randomState();
#endif
}
}
@@ -1,311 +0,0 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.GlobalWorldGenParams;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IC2meAccessor;
import com.seibel.distanthorizons.coreapi.ModInfo;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Function;
public class InternalServerGenerator
{
public static final DhLogger LOGGER = new DhLoggerBuilder()
.name("LOD World Gen - Internal Server")
.fileLevelConfig(Config.Common.Logging.logWorldGenEventToFile)
.build();
public static final DhLogger CHUNK_LOAD_LOGGER = new DhLoggerBuilder()
.name("LOD Chunk Loading")
.fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile)
.build();
private static final IC2meAccessor C2ME_ACCESSOR = ModAccessorInjector.INSTANCE.get(IC2meAccessor.class);
/**
* Used to revert the ignore logic in {@link SharedApi} so
* that a given chunk pos can be handled again.
* A timer is used so we don't have to inject into MC's code and it works sell enough
* most of the time.
* If a chunk does get through due the timeout not being long enough that isn't the end of the world.
*/
private static final int MS_TO_IGNORE_CHUNK_AFTER_COMPLETION = 5_000;
#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));
#elif MC_VER < MC_1_21_9
private static final TicketType DH_SERVER_GEN_TICKET = new TicketType(/* timeout, 0 = disabled*/0L, /* persist */ false, TicketType.TicketUse.LOADING);
#else
private static final TicketType DH_SERVER_GEN_TICKET = new TicketType(/* timeout, 0 = disabled*/0L, /* flags */TicketType.FLAG_LOADING);
#endif
private static boolean c2meMissingWarningLogged = false;
private final GlobalWorldGenParams params;
private final IDhServerLevel dhServerLevel;
private final Timer chunkSaveIgnoreTimer = TimerUtil.CreateTimer("ChunkSaveIgnoreTimer");
//=============//
// constructor //
//=============//
public InternalServerGenerator(GlobalWorldGenParams params, IDhServerLevel dhServerLevel)
{
this.params = params;
this.dhServerLevel = dhServerLevel;
}
//============//
// generation //
//============//
public void generateChunksViaInternalServer(GenerationEvent genEvent)
{
this.runValidation();
try
{
//=====================//
// create gen requests //
//=====================//
ArrayList<CompletableFuture<ChunkAccess>> getChunkFutureList = new ArrayList<>();
{
Iterator<ChunkPos> chunkPosIterator = ChunkPosGenStream.getIterator(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0);
while (chunkPosIterator.hasNext())
{
ChunkPos chunkPos = chunkPosIterator.next();
CompletableFuture<ChunkAccess> requestChunkFuture =
this.requestChunkFromServerAsync(chunkPos)
// log errors if necessary
.whenCompleteAsync(
(chunk, throwable) ->
{
// unwrap the CompletionException if necessary
Throwable actualThrowable = throwable;
while (actualThrowable instanceof CompletionException)
{
actualThrowable = actualThrowable.getCause();
}
if (actualThrowable != null)
{
// ignore expected shutdown exceptions
boolean isShutdownException =
ExceptionUtil.isShutdownException(actualThrowable)
|| actualThrowable.getMessage().contains("Unloaded chunk");
if (!isShutdownException)
{
CHUNK_LOAD_LOGGER.warn("DistantHorizons: Couldn't load chunk [" + chunkPos + "] from server, error: [" + actualThrowable.getMessage() + "].", actualThrowable);
}
}
});
getChunkFutureList.add(requestChunkFuture);
}
}
//==============================//
// wait for generation requests //
//==============================//
// Join-ing each thread will prevent DH from working on anything else
// but will also prevent over-queuing world gen tasks.
// If C2ME is present the CPU will still be well utilized.
ArrayList<IChunkWrapper> chunkWrappers = new ArrayList<>();
for (int i = 0; i < getChunkFutureList.size(); i++)
{
CompletableFuture<ChunkAccess> getChunkFuture = getChunkFutureList.get(i);
ChunkAccess chunk = getChunkFuture.join();
if (chunk != null)
{
ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, this.dhServerLevel.getLevelWrapper());
chunkWrapper.createDhHeightMaps();
chunkWrappers.add(chunkWrapper);
}
}
//==========================//
// process generated chunks //
//==========================//
int maxSkyLight = this.dhServerLevel.getServerLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT;
for (int i = 0; i < chunkWrappers.size(); i++)
{
ChunkWrapper chunkWrapper = (ChunkWrapper)chunkWrappers.get(i);
// pre-generated chunks should have lighting but new ones won't
if (!chunkWrapper.isDhBlockLightingCorrect())
{
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, chunkWrappers, maxSkyLight);
}
this.dhServerLevel.updateBeaconBeamsForChunk(chunkWrapper, chunkWrappers);
genEvent.resultConsumer.accept(chunkWrapper);
}
}
finally
{
// release all chunks from the server to prevent out of memory issues
Iterator<ChunkPos> chunkPosIterator = ChunkPosGenStream.getIterator(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0);
while (chunkPosIterator.hasNext())
{
ChunkPos chunkPos = chunkPosIterator.next();
this.releaseChunkFromServer(this.params.mcServerLevel, chunkPos);
}
}
}
private void runValidation()
{
// DH thread check
if (!DhApi.isDhThread()
&& ModInfo.IS_DEV_BUILD)
{
throw new IllegalStateException("Internal server generation should be called from one of DH's world gen thread. Current thread: ["+Thread.currentThread().getName()+"]");
}
// C2ME present?
if (C2ME_ACCESSOR == null
&& !c2meMissingWarningLogged)
{
c2meMissingWarningLogged = true;
String c2meWarning = "C2ME missing, \n" +
"low CPU usage and slow world gen speeds expected. \n" +
"DH is set to use MC's internal server for world gen \n" +
"this mode is less efficient unless a mod like C2ME is present."
;
if (Config.Common.Logging.Warning.showSlowWorldGenSettingWarnings.get())
{
String message =
MinecraftTextFormat.ORANGE + "Distant Horizons: slow world gen." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
c2meWarning;
ClientApi.INSTANCE.showChatMessageNextFrame(message);
}
LOGGER.warn(c2meWarning);
}
}
private CompletableFuture<ChunkAccess> requestChunkFromServerAsync(ChunkPos chunkPos)
{
return CompletableFuture.supplyAsync(() ->
{
ServerLevel level = this.params.mcServerLevel;
// ignore chunk update events for this position
SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.addPosToIgnore(new DhChunkPos(chunkPos.x, chunkPos.z));
#if MC_VER < MC_1_21_5
int chunkLevel = 33; // 33 is equivalent to FULL Chunk
level.getChunkSource().distanceManager.addTicket(DH_SERVER_GEN_TICKET, chunkPos, chunkLevel, chunkPos);
#else
level.getChunkSource().addTicketWithRadius(DH_SERVER_GEN_TICKET, chunkPos, 0);
#endif
// probably not the most optimal to run updates here, but fast enough
level.getChunkSource().distanceManager.runAllUpdates(level.getChunkSource().chunkMap);
ChunkHolder chunkHolder = level.getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong());
if (chunkHolder == null)
{
throw new IllegalStateException("No chunk chunkHolder for pos ["+chunkPos+"] after ticket has been added.");
}
#if MC_VER <= MC_1_20_4
return chunkHolder.getOrScheduleFuture(ChunkStatus.FEATURES, level.getChunkSource().chunkMap)
.thenApply(result -> result.left().orElseThrow(() -> new RuntimeException(result.right().get().toString()))); // can throw if the server is shutting down
#elif MC_VER <= MC_1_20_6
return chunkHolder.getOrScheduleFuture(ChunkStatus.FEATURES, level.getChunkSource().chunkMap)
.thenApply(result -> result.orElseThrow(() -> new RuntimeException(result.toString()))); // can throw if the server is shutting down
#else
return chunkHolder.scheduleChunkGenerationTask(ChunkStatus.FEATURES, level.getChunkSource().chunkMap)
.thenApply(result -> result.orElseThrow(() -> new RuntimeException(result.getError()))); // can throw if the server is shutting down
#endif
}, this.params.mcServerLevel.getChunkSource().chunkMap.mainThreadExecutor)
.thenCompose(Function.identity());
}
/**
* mitigates out of memory issues in the vanilla chunk system. <br>
* See: https://github.com/pop4959/Chunky/pull/383
*/
private void releaseChunkFromServer(ServerLevel level, ChunkPos chunkPos)
{
level.getChunkSource().chunkMap.mainThreadExecutor.execute(() ->
{
try
{
#if MC_VER < MC_1_21_5
int chunkLevel = 33; // 33 is equivalent to FULL Chunk
level.getChunkSource().distanceManager.removeTicket(DH_SERVER_GEN_TICKET, chunkPos, chunkLevel, chunkPos);
#else
level.getChunkSource().removeTicketWithRadius(DH_SERVER_GEN_TICKET, chunkPos, 0);
#endif
level.getChunkSource().chunkMap.tick(() -> false);
#if MC_VER > MC_1_16_5
level.entityManager.tick();
#endif
// give MC a few seconds to save the chunk before
// we can process update events there again
this.chunkSaveIgnoreTimer.schedule(new TimerTask()
{
@Override
public void run() { SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.removePosToIgnore(new DhChunkPos(chunkPos.x, chunkPos.z)); }
}, MS_TO_IGNORE_CHUNK_AFTER_COMPLETION);
}
catch (Exception e)
{
LOGGER.warn("Failed to release chunk back to internal server. Error: ["+e.getMessage()+"]", e);
}
});
}
}
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -17,16 +17,40 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.neoforge.wrappers.modAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IC2meAccessor;
package com.seibel.distanthorizons.common.wrappers.worldGeneration;
public class C2meAccessor implements IC2meAccessor
//FIXME: Move this outside the WorldGenerationStep thingy
public class Rolling
{
@Override
public String getModName()
private final int size;
private double total = 0d;
private int index = 0;
private final double[] samples;
public Rolling(int size)
{
return "c2me";
this.size = size;
samples = new double[size];
for (int i = 0; i < size; i++)
{
samples[i] = 0d;
}
}
public void add(double x)
{
total -= samples[index];
samples[index] = x;
total += x;
if (++index == size)
index = 0; // cheaper than modulus
}
public double getAverage()
{
return size == 0 ? 0 : total / size;
}
}
@@ -0,0 +1,109 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.worldGeneration;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment.PerfCalculator;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.WorldGenStructFeatManager;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
#if MC_VER >= MC_1_18_2
import net.minecraft.world.level.levelgen.structure.StructureCheck;
#endif
public final class ThreadedParameters
{
private static final ThreadLocal<ThreadedParameters> LOCAL_PARAM = new ThreadLocal<>();
final ServerLevel level;
public WorldGenStructFeatManager structFeat = null;
#if MC_VER >= MC_1_18_2
public StructureCheck structCheck;
#endif
boolean isValid = true;
public final PerfCalculator perf = new PerfCalculator();
private static GlobalParameters previousGlobalParameters = null;
public static ThreadedParameters getOrMake(GlobalParameters param)
{
ThreadedParameters tParam = LOCAL_PARAM.get();
if (tParam != null && tParam.isValid && tParam.level == param.level)
{
return tParam;
}
tParam = new ThreadedParameters(param);
LOCAL_PARAM.set(tParam);
return tParam;
}
private ThreadedParameters(GlobalParameters param)
{
previousGlobalParameters = param;
this.level = param.level;
#if MC_VER < MC_1_18_2
this.structFeat = new WorldGenStructFeatManager(param.worldGenSettings, level);
#elif MC_VER < MC_1_19_2
this.structCheck = this.createStructureCheck(param);
#else
this.structCheck = new StructureCheck(param.chunkScanner, param.registry, param.structures,
param.level.dimension(), param.generator, param.randomState, level, param.generator.getBiomeSource(), param.worldSeed,
param.fixerUpper);
#endif
}
public void markAsInvalid() { isValid = false; }
public void makeStructFeat(WorldGenLevel genLevel, GlobalParameters param)
{
#if MC_VER < MC_1_19_4
structFeat = new WorldGenStructFeatManager(param.worldGenSettings, genLevel #if MC_VER >= MC_1_18_2 , structCheck #endif );
#else
structFeat = new WorldGenStructFeatManager(param.worldOptions, genLevel, structCheck);
#endif
}
#if MC_VER >= MC_1_18_2 && MC_VER < MC_1_19_2
public void recreateStructureCheck()
{
if (previousGlobalParameters != null)
{
this.structCheck = createStructureCheck(previousGlobalParameters);
}
}
private StructureCheck createStructureCheck(GlobalParameters param)
{
return new StructureCheck(param.chunkScanner, param.registry, param.structures,
param.level.dimension(), param.generator, this.level, param.generator.getBiomeSource(), param.worldSeed,
param.fixerUpper);
}
#else
public void recreateStructureCheck() { /* do nothing */ }
#endif
}
@@ -1,745 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling;
import com.mojang.serialization.Codec;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import net.minecraft.core.Registry;
#if MC_VER >= MC_1_19_4
import net.minecraft.core.registries.Registries;
#endif
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
#if MC_VER < MC_1_21_3
import net.minecraft.world.level.chunk.storage.ChunkSerializer;
#else
#endif
#if MC_VER < MC_1_21_9
import net.minecraft.world.level.block.Blocks;
#else
#endif
import net.minecraft.world.level.levelgen.Heightmap;
#if MC_VER >= MC_1_18_2
#if MC_VER < MC_1_19_2
import net.minecraft.world.level.levelgen.feature.StructureFeature;
#endif
import net.minecraft.world.ticks.LevelChunkTicks;
#endif
#if MC_VER >= MC_1_18_2
import net.minecraft.core.Holder;
#if MC_VER < MC_1_19_2
import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
#endif
#endif
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
import net.minecraft.world.level.material.Fluids;
#endif
#if MC_VER == MC_1_20_6
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.status.ChunkType;
#elif MC_VER >= MC_1_21_1
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
import net.minecraft.world.level.material.Fluid;
public class ChunkCompoundTagParser
{
public static final DhLogger LOGGER = new DhLoggerBuilder()
.name("LOD Chunk Reader")
.fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile)
.build();
private static final AtomicBoolean ZERO_CHUNK_POS_ERROR_LOGGED_REF = new AtomicBoolean(false);
private static final ConcurrentHashMap<String, Object> LOGGED_ERROR_MESSAGE_MAP = new ConcurrentHashMap<>();
private static boolean lightingSectionErrorLogged = false;
//============//
// read chunk //
//============//
public static ChunkWrapper createFromTag(
WorldGenLevel mcWorldGenLevel, IDhServerLevel dhServerLevel,
ChunkPos chunkPos, CompoundTag chunkData)
{
#if MC_VER < MC_1_18_2
CompoundTag tagLevel = chunkData.getCompound("Level");
#else
CompoundTag tagLevel = chunkData;
#endif
//=======================//
// validate the chunkPos //
//=======================//
int chunkX = CompoundTagUtil.getInt(tagLevel,"xPos");
int chunkZ = CompoundTagUtil.getInt(tagLevel, "zPos");
ChunkPos actualChunkPos = new ChunkPos(chunkX, chunkZ);
// confirm chunk pos is correct
if (!Objects.equals(chunkPos, actualChunkPos))
{
if (chunkX == 0 && chunkZ == 0)
{
if (!ZERO_CHUNK_POS_ERROR_LOGGED_REF.getAndSet(true))
{
// 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" +
"This might happen if the world was created using an external program. \n" +
"DH will attempt to parse the chunk anyway and won't log this message again.\n" +
"If issues arise please try optimizing your world to fix this issue. \n" +
"World optimization can be done from the singleplayer world selection screen." +
" ");
}
}
else
{
LOGGER.error("Chunk file at ["+chunkPos.toString()+"] is in the wrong location. \n" +
"Please try optimizing your world to fix this issue. \n" +
"World optimization can be done from the singleplayer world selection screen. \n" +
"(Expected pos: ["+chunkPos.toString()+"], actual ["+actualChunkPos.toString()+"])" +
" ");
return null;
}
}
//===========//
// get ticks //
//===========//
#if MC_VER < MC_1_18_2
ChunkBiomeContainer chunkBiomeContainer = new ChunkBiomeContainer(
mcWorldGenLevel.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), #if MC_VER >= MC_1_17_1 mcWorldGenLevel, #endif
chunkPos, mcWorldGenLevel.getLevel().getChunkSource().getGenerator().getBiomeSource(),
tagLevel.contains("Biomes", 11) ? tagLevel.getIntArray("Biomes") : null);
String BLOCK_TICKS_TAG_PRE18 = "TileTicks";
TickList<Block> blockTicks = tagLevel.contains(BLOCK_TICKS_TAG_PRE18, 9)
? ChunkTickList.create(tagLevel.getList(BLOCK_TICKS_TAG_PRE18, 10), Registry.BLOCK::getKey, Registry.BLOCK::get)
: new ProtoTickList<Block>(block -> (block == null || block.defaultBlockState().isAir()), chunkPos,
tagLevel.getList("ToBeTicked", 9) #if MC_VER >= MC_1_17_1 , mcWorldGenLevel #endif );
String FLUID_TICKS_TAG_PRE18 = "LiquidTicks";
TickList<Fluid> fluidTicks = tagLevel.contains(FLUID_TICKS_TAG_PRE18, 9)
? ChunkTickList.create(tagLevel.getList(FLUID_TICKS_TAG_PRE18, 10), Registry.FLUID::getKey, Registry.FLUID::get)
: new ProtoTickList<Fluid>(fluid -> (fluid == null || fluid == Fluids.EMPTY), chunkPos,
tagLevel.getList("LiquidsToBeTicked", 9) #if MC_VER >= MC_1_17_1 , mcWorldGenLevel #endif );
#else
// ticks shouldn't be needed so ignore them for MC versions after 1.18.2
LevelChunkTicks<Block> blockTicks = new LevelChunkTicks<>();
LevelChunkTicks<Fluid> fluidTicks = new LevelChunkTicks<>();
#endif
//=====================//
// get misc properties //
//=====================//
int sectionYCount = #if MC_VER < MC_1_17_1 16; #else mcWorldGenLevel.getSectionsCount(); #endif
LevelChunkSection[] chunkSections = new LevelChunkSection[sectionYCount];
boolean hasBlocks = readAndPopulateSections(mcWorldGenLevel, chunkPos, tagLevel, chunkSections);
if (!hasBlocks)
{
return null;
}
long inhabitedTime = CompoundTagUtil.getLong(tagLevel, "InhabitedTime");
boolean isLightOn = CompoundTagUtil.getBoolean(tagLevel, "isLightOn");
//============//
// make chunk //
//============//
#if MC_VER < MC_1_18_2
LevelChunk chunk = new LevelChunk((Level) mcWorldGenLevel.getLevel(), chunkPos, chunkBiomeContainer, UpgradeData.EMPTY, blockTicks,
fluidTicks, inhabitedTime, chunkSections, null);
#else
LevelChunk chunk = new LevelChunk((Level) mcWorldGenLevel, chunkPos, UpgradeData.EMPTY, blockTicks,
fluidTicks, inhabitedTime, chunkSections, null, null);
#endif
// Set some states after object creation
chunk.setLightCorrect(isLightOn);
boolean hasHeightmapData = readHeightmaps(chunk, chunkData);
// chunk wrapper so we can pass along extra data more easily
ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, dhServerLevel.getServerLevelWrapper());
chunkWrapper.createDhHeightMaps();
//===========================//
// check if chunk has blocks //
//===========================//
// in some MC versions all the NBT data will be there
// but the chunk will be totally empty,
// usually this means the chunk was only partially generated.
// If that happens we should try to generate the chunk from scratch
// otherwise we can end up with large empty holes in the world.
// walking through the heightmap (recreated by DH if missing)
// is a fast way to check if there are any blocks in the chunk
boolean chunkHasBlocks = false;
int serverMinHeight = dhServerLevel.getServerLevelWrapper().getMinHeight();
for (int x = 0; x < 16 && !chunkHasBlocks; x++)
{
for (int z = 0; z < 16 && !chunkHasBlocks; z++)
{
int heightMap = Math.max(
// max between both heightmaps just in case there's a discrepancy
chunkWrapper.getLightBlockingHeightMapValue(x, z),
chunkWrapper.getSolidHeightMapValue(x, z)
);
if (heightMap != serverMinHeight)
{
chunkHasBlocks = true;
}
}
}
if (chunkHasBlocks)
{
return chunkWrapper;
}
else
{
// no blocks detected, this chunk should be generated from scratch
return null;
}
}
//=================//
// chunk sections //
// (Blocks/biomes) //
//=================//
/** handles both blocks and biomes */
private static boolean readAndPopulateSections(
LevelAccessor level, ChunkPos chunkPos, CompoundTag chunkData,
LevelChunkSection[] chunkSections)
{
int sectionYCount = #if MC_VER < MC_1_17_1 16; #else level.getSectionsCount(); #endif
ListTag tagSections = CompoundTagUtil.getListTag(chunkData, "Sections", 10);
// try lower-case "sections" if capital "Sections" is missing
if (tagSections == null
|| tagSections.isEmpty())
{
tagSections = CompoundTagUtil.getListTag(chunkData, "sections", 10);
}
boolean blocksFound = false;
if (tagSections != null)
{
for (int i = 0; i < tagSections.size(); ++i)
{
CompoundTag tagSection = CompoundTagUtil.getCompoundTag(tagSections, i);
if (tagSection == null)
{
continue;
}
final int sectionYPos = CompoundTagUtil.getByte(tagSection, "Y");
//===================//
// get blocks/biomes //
//===================//
#if MC_VER < MC_1_18_2
if (tagSection.contains("Palette", 9)
&& tagSection.contains("BlockStates", 12))
{
LevelChunkSection levelChunkSection = new LevelChunkSection(sectionYPos << 4);
levelChunkSection.getStates().read(tagSection.getList("Palette", 10), tagSection.getLongArray("BlockStates"));
levelChunkSection.recalcBlockCounts();
if (!levelChunkSection.isEmpty())
{
int sectionIndex;
#if MC_VER < MC_1_17_1
sectionIndex = sectionYPos;
#else
sectionIndex = level.getSectionIndexFromSectionY(sectionYPos);
#endif
chunkSections[sectionIndex] = levelChunkSection;
}
}
#else
int sectionId = level.getSectionIndexFromSectionY(sectionYPos);
if (sectionId >= 0
&& sectionId < chunkSections.length)
{
//========//
// blocks //
//========//
PalettedContainer<BlockState> blockStateContainer;
boolean containsBlockStates = CompoundTagUtil.contains(tagSection, "block_states", 10);
if (containsBlockStates)
{
Codec<PalettedContainer<BlockState>> blockStateCodec = getBlockStateCodec(level);
#if MC_VER < MC_1_20_6
blockStateContainer = blockStateCodec
.parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "block_states"))
.promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
.getOrThrow(false, (message) -> logParsingWarningOnce(message));
#else
blockStateContainer = blockStateCodec
.parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "block_states"))
.promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
.getOrThrow((message) -> logErrorAndReturnException(message));
#endif
blocksFound = true;
}
else
{
#if MC_VER < MC_1_21_9
blockStateContainer = new PalettedContainer<BlockState>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
#else
blockStateContainer = PalettedContainerFactory.create(level.registryAccess()).createForBlockStates();
#endif
}
//========//
// biomes //
//========//
Registry<Biome> biomeRegistry = getBiomeRegistry(level);
#if MC_VER < MC_1_19_2
Codec<PalettedContainer<Biome>> biomeCodec;
#else
Codec<PalettedContainer<Holder<Biome>>> biomeCodec;
#endif
biomeCodec = getBiomeCodec(level, biomeRegistry);
#if MC_VER < MC_1_18_2
PalettedContainer<Biome> biomeContainer;
#else
PalettedContainer<Holder<Biome>> biomeContainer;
#endif
#if MC_VER < MC_1_18_2
biomeContainer = tagSection.contains("biomeRegistry", 10)
? biomeCodec.parse(NbtOps.INSTANCE, tagSection.getCompound("biomeRegistry")).promotePartial(string -> logErrors(chunkPos, sectionYPos, string)).getOrThrow(false, (message) -> logWarningOnce(message))
: new PalettedContainer<Biome>(biomeRegistry, biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
#else
{
CompoundTag biomeTag = CompoundTagUtil.getCompoundTag(tagSection, "biomeRegistry");
if (biomeTag == null)
{
biomeTag = CompoundTagUtil.getCompoundTag(tagSection, "biomes");
}
if (biomeTag != null
&& !biomeTag.isEmpty())
{
#if MC_VER < MC_1_20_6
biomeContainer = new PalettedContainer<Holder<Biome>>(
biomeRegistry.asHolderIdMap(),
biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
#else
biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, biomeTag)
.promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYCount, (String) string))
.getOrThrow((message) -> logErrorAndReturnException(message));
#endif
}
else
{
// no biomes found, use the default (probably plains)
#if MC_VER < MC_1_21_3
biomeContainer = new PalettedContainer<Holder<Biome>>(
biomeRegistry.asHolderIdMap(),
biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
#elif MC_VER < MC_1_21_9
biomeContainer = new PalettedContainer<Holder<Biome>>(biomeRegistry.asHolderIdMap(),
biomeRegistry.getOrThrow(Biomes.PLAINS),
PalettedContainer.Strategy.SECTION_BIOMES);
#else
biomeContainer = PalettedContainerFactory.create(level.registryAccess()).createForBiomes();
#endif
}
}
#endif
#if MC_VER < MC_1_20_1
chunkSections[sectionId] = new LevelChunkSection(sectionYPos, blockStateContainer, biomeContainer);
#else
chunkSections[sectionId] = new LevelChunkSection(blockStateContainer, biomeContainer);
#endif
}
#endif
}
}
return blocksFound;
}
private static Codec<PalettedContainer<BlockState>> getBlockStateCodec(LevelAccessor level)
{
#if MC_VER < MC_1_18_2
return null; // unused for older MC versions
#elif MC_VER < MC_1_19_2
return PalettedContainer.codec(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState());
#elif MC_VER <= MC_1_21_8
return PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState());
#else
return PalettedContainerFactory.create(level.registryAccess()).blockStatesContainerCodec();
#endif
}
private static Registry<Biome> getBiomeRegistry(LevelAccessor level)
{
#if MC_VER < MC_1_18_2
// not needed
return null;
#elif MC_VER < MC_1_19_4
return level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
#elif MC_VER < MC_1_21_3
return level.registryAccess().registryOrThrow(Registries.BIOME);
#else
return level.registryAccess().lookupOrThrow(Registries.BIOME);
#endif
}
private static
#if MC_VER < MC_1_19_2 Codec<PalettedContainer<Biome>>
#else Codec<PalettedContainer<Holder<Biome>>>
#endif
getBiomeCodec(LevelAccessor level, Registry<Biome> biomeRegistry)
{
#if MC_VER < MC_1_18_2
return null; // unused for older MC versions
#elif MC_VER < MC_1_19_2
return PalettedContainer.codec(
biomeRegistry, biomeRegistry.byNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getOrThrow(Biomes.PLAINS));
#elif MC_VER < MC_1_19_2
return PalettedContainer.codec(
biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
#elif MC_VER < MC_1_21_3
return PalettedContainer.codecRW(
biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
#elif MC_VER < MC_1_21_9
return PalettedContainer.codecRW(
biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getOrThrow(Biomes.PLAINS));
#else
return PalettedContainer.codecRW(
biomeRegistry.holderByNameCodec(), PalettedContainerFactory.create(level.registryAccess()).biomeStrategy(), biomeRegistry.getOrThrow(Biomes.PLAINS));
#endif
}
//============//
// heightmaps //
//============//
private static boolean readHeightmaps(LevelChunk chunk, CompoundTag chunkData)
{
CompoundTag tagHeightmaps = CompoundTagUtil.getCompoundTag(chunkData, "Heightmaps");
if (tagHeightmaps == null)
{
return false;
}
for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter())
{
String heightmapKey = type.getSerializationKey();
#if MC_VER < MC_1_21_5
if (tagHeightmaps.contains(heightmapKey, 12))
{
chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmapKey));
}
#else
if (tagHeightmaps.contains(heightmapKey))
{
Optional<long[]> optionalHeightmap = tagHeightmaps.getLongArray(heightmapKey);
if (optionalHeightmap.isPresent())
{
chunk.setHeightmap(type, optionalHeightmap.get());
}
}
#endif
}
Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter());
return true;
}
//================//
// chunk lighting //
//================//
/** source: https://minecraft.wiki/w/Chunk_format */
public static CombinedChunkLightStorage readLight(ChunkAccess chunk, CompoundTag chunkData)
{
#if MC_VER <= MC_1_17_1
// MC 1.16 and 1.17 doesn't have the necessary NBT info
return null;
#else
CombinedChunkLightStorage combinedStorage = new CombinedChunkLightStorage(ChunkWrapper.getInclusiveMinBuildHeight(chunk), ChunkWrapper.getExclusiveMaxBuildHeight(chunk));
ChunkLightStorage blockLightStorage = combinedStorage.blockLightStorage;
ChunkLightStorage skyLightStorage = combinedStorage.skyLightStorage;
boolean foundSkyLight = false;
//===================//
// get NBT tags info //
//===================//
Tag chunkSectionTags = chunkData.get("sections");
if (chunkSectionTags == null)
{
if (!lightingSectionErrorLogged)
{
lightingSectionErrorLogged = true;
LOGGER.error("No sections found for chunk at pos ["+chunk.getPos()+"] chunk data may be out of date.");
}
return null;
}
else if (!(chunkSectionTags instanceof ListTag))
{
if (!lightingSectionErrorLogged)
{
lightingSectionErrorLogged = true;
LOGGER.error("Chunk section tag list have unexpected type ["+chunkSectionTags.getClass().getName()+"], expected ["+ListTag.class.getName()+"].");
}
return null;
}
ListTag chunkSectionListTag = (ListTag) chunkSectionTags;
//===================//
// get lighting info //
//===================//
for (int sectionIndex = 0; sectionIndex < chunkSectionListTag.size(); sectionIndex++)
{
Tag chunkSectionTag = chunkSectionListTag.get(sectionIndex);
if (!(chunkSectionTag instanceof CompoundTag))
{
if (!lightingSectionErrorLogged)
{
lightingSectionErrorLogged = true;
LOGGER.error("Chunk section tag has an unexpected type ["+chunkSectionTag.getClass().getName()+"], expected ["+CompoundTag.class.getName()+"].");
}
return null;
}
CompoundTag chunkSectionCompoundTag = (CompoundTag) chunkSectionTag;
// if null all lights = 0
byte[] blockLightNibbleArray = CompoundTagUtil.getByteArray(chunkSectionCompoundTag, "BlockLight");
byte[] skyLightNibbleArray = CompoundTagUtil.getByteArray(chunkSectionCompoundTag, "SkyLight");
if (blockLightNibbleArray != null
&& skyLightNibbleArray != null)
{
// if any sky light was found then all lights above will be max brightness
if (skyLightNibbleArray.length != 0)
{
foundSkyLight = true;
}
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
// chunk sections are also 16 blocks tall
for (int relY = 0; relY < LodUtil.CHUNK_WIDTH; relY++)
{
int blockPosIndex = relY*16*16 + relZ*16 + relX;
byte blockLight = (blockLightNibbleArray.length == 0) ? 0 : getNibbleAtIndex(blockLightNibbleArray, blockPosIndex);
byte skyLight = (skyLightNibbleArray.length == 0) ? 0 : getNibbleAtIndex(skyLightNibbleArray, blockPosIndex);
if (skyLightNibbleArray.length == 0 && foundSkyLight)
{
skyLight = LodUtil.MAX_MC_LIGHT;
}
int y = relY + (sectionIndex * LodUtil.CHUNK_WIDTH) + ChunkWrapper.getInclusiveMinBuildHeight(chunk);
blockLightStorage.set(relX, y, relZ, blockLight);
skyLightStorage.set(relX, y, relZ, skyLight);
}
}
}
}
}
return combinedStorage;
#endif
}
/** source: https://minecraft.wiki/w/Chunk_format#Block_Format */
private static byte getNibbleAtIndex(byte[] arr, int index)
{
if (index % 2 == 0)
{
return (byte)(arr[index/2] & 0x0F);
}
else
{
return (byte)((arr[index/2]>>4) & 0x0F);
}
}
//=========//
// logging //
//=========//
private static void logBlockDeserializationWarning(ChunkPos chunkPos, int sectionYIndex, String message)
{
LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) ->
{
LOGGER.warn("Unable to deserialize blocks for chunk section [" + chunkPos.x + ", " + sectionYIndex + ", " + chunkPos.z + "], error: ["+newMessage+"]. " +
"This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem.");
return newMessage;
});
}
private static void logBiomeDeserializationWarning(ChunkPos chunkPos, int sectionYIndex, String message)
{
LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) ->
{
LOGGER.warn("Unable to deserialize biomes for chunk section [" + chunkPos.x + ", " + sectionYIndex + ", " + chunkPos.z + "], error: ["+newMessage+"]. " +
"This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem.");
return newMessage;
});
}
private static void logParsingWarningOnce(String message) { logParsingWarningOnce(message, null); }
private static void logParsingWarningOnce(String message, Exception e)
{
if (message == null)
{
return;
}
LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) ->
{
LOGGER.warn("Parsing error: ["+newMessage+"]. " +
"This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem.",
e);
return newMessage;
});
}
private static RuntimeException logErrorAndReturnException(String message)
{
LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) ->
{
LOGGER.warn("Parsing error: ["+newMessage+"]. " +
"This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem.");
return newMessage;
});
// Currently we want to ignore these errors, if returning null is a problem, we can change this later
return null; //new RuntimeException(message);
}
//================//
// helper classes //
//================//
public static class CombinedChunkLightStorage
{
public ChunkLightStorage blockLightStorage;
public ChunkLightStorage skyLightStorage;
public CombinedChunkLightStorage(int minY, int maxY)
{
this.blockLightStorage = ChunkLightStorage.createBlockLightStorage(minY, maxY);
this.skyLightStorage = ChunkLightStorage.createSkyLightStorage(minY, maxY);
}
}
}
@@ -1,343 +0,0 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.GlobalWorldGenParams;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.RegionFileStorageExternalCache;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.chunk.storage.IOWorker;
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
import java.io.IOException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicReference;
#if MC_VER <= MC_1_17_1
import net.minecraft.world.level.chunk.ChunkStatus;
#elif MC_VER <= MC_1_19_2
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.core.Registry;
#elif MC_VER <= MC_1_19_4
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.chunk.ChunkStatus;
#elif MC_VER <= MC_1_20_6
import net.minecraft.core.registries.Registries;
#elif MC_VER <= MC_1_21_3
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.chunk.status.ChunkStatus;
#elif MC_VER <= MC_1_21_8
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.chunk.status.ChunkStatus;
#elif MC_VER <= MC_1_21_9
import net.minecraft.world.level.chunk.PalettedContainerFactory;
#else
import net.minecraft.world.level.chunk.PalettedContainerFactory;
#endif
public class ChunkFileReader implements AutoCloseable
{
public static final DhLogger LOGGER = new DhLoggerBuilder()
.name("LOD World Gen")
.fileLevelConfig(Config.Common.Logging.logWorldGenEventToFile)
.build();
public static final DhLogger CHUNK_LOAD_LOGGER = new DhLoggerBuilder()
.name("LOD Chunk Loading")
.fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile)
.build();
private static final IModChecker MOD_CHECKER = SingletonInjector.INSTANCE.get(IModChecker.class);
public final GlobalWorldGenParams params;
/**
* will be true if C2ME is installed (since they require us to
* pull chunks using their async method), or if there
* was an issue with the sync pulling method.
*/
private boolean pullExistingChunkUsingMcAsyncMethod = false;
private final AtomicReference<RegionFileStorageExternalCache> regionFileStorageCacheRef = new AtomicReference<>();
public RegionFileStorageExternalCache getOrCreateRegionFileCache(RegionFileStorage storage)
{
RegionFileStorageExternalCache cache = this.regionFileStorageCacheRef.get();
if (cache == null)
{
cache = new RegionFileStorageExternalCache(storage);
if (!this.regionFileStorageCacheRef.compareAndSet(null, cache))
{
cache = this.regionFileStorageCacheRef.get();
}
}
return cache;
}
//=============//
// constructor //
//=============//
public ChunkFileReader(GlobalWorldGenParams params)
{
this.params = params;
if (MOD_CHECKER.isModLoaded("c2me"))
{
LOGGER.info("C2ME detected: DH's pre-existing chunk accessing will use methods handled by C2ME.");
this.pullExistingChunkUsingMcAsyncMethod = true;
}
}
//=============//
// constructor //
//=============//
/**
* If the given chunk pos already exists in the world, that chunk will be returned,
* otherwise this will return an empty chunk.
*/
public CompletableFuture<ChunkWrapper> createEmptyOrPreExistingChunkWrapperAsync(
int chunkX, int chunkZ,
Map<DhChunkPos, ChunkLightStorage> chunkSkyLightingByDhPos,
Map<DhChunkPos, ChunkLightStorage> chunkBlockLightingByDhPos,
Map<DhChunkPos, ChunkWrapper> generatedChunkWrapperByDhPos)
{
ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
DhChunkPos dhChunkPos = new DhChunkPos(chunkX, chunkZ);
if (generatedChunkWrapperByDhPos.containsKey(dhChunkPos))
{
return CompletableFuture.completedFuture(generatedChunkWrapperByDhPos.get(dhChunkPos));
}
return this.getChunkNbtDataAsync(chunkPos)
.thenApply((CompoundTag chunkData) ->
{
ChunkWrapper newChunkWrapper = this.loadOrMakeChunkWrapper(chunkPos, chunkData);
// attempt to get chunk lighting
ChunkCompoundTagParser.CombinedChunkLightStorage combinedLights = ChunkCompoundTagParser.readLight(newChunkWrapper.getChunk(), chunkData);
if (combinedLights != null)
{
// may be empty, empty checks are handled later
chunkSkyLightingByDhPos.put(dhChunkPos, combinedLights.skyLightStorage);
chunkBlockLightingByDhPos.put(dhChunkPos, combinedLights.blockLightStorage);
}
return newChunkWrapper;
})
// separate handle so we can cleanly handle missing chunks and/or thrown errors
.handle((ChunkWrapper newChunkWrapper, Throwable throwable) ->
{
if (newChunkWrapper != null)
{
return newChunkWrapper;
}
else
{
return this.CreateProtoChunkWrapper(this.params.mcServerLevel, chunkPos);
}
})
.thenApply((ChunkWrapper newChunkWrapper) ->
{
generatedChunkWrapperByDhPos.put(dhChunkPos, newChunkWrapper);
return newChunkWrapper;
});
}
private CompletableFuture<CompoundTag> getChunkNbtDataAsync(ChunkPos chunkPos)
{
ServerLevel level = this.params.mcServerLevel;
try
{
IOWorker ioWorker = level.getChunkSource().chunkMap.worker;
#if MC_VER <= MC_1_18_2
return CompletableFuture.completedFuture(ioWorker.load(chunkPos));
#else
// storage will be null if C2ME is installed
if (!this.pullExistingChunkUsingMcAsyncMethod
&& ioWorker.storage != null)
{
try
{
RegionFileStorage storage = this.params.mcServerLevel.getChunkSource().chunkMap.worker.storage;
RegionFileStorageExternalCache cache = this.getOrCreateRegionFileCache(storage);
return CompletableFuture.completedFuture(cache.read(chunkPos));
}
catch (NullPointerException e)
{
// this shouldn't happen, if anything is null it should be
// ioWorker.storage
// but just in case
LOGGER.error("Unexpected issue pulling pre-existing chunk ["+chunkPos+"], falling back to async chunk pulling. This may cause server-tick lag.", e);
this.pullExistingChunkUsingMcAsyncMethod = true;
// try again now using the async method
return this.getChunkNbtDataAsync(chunkPos);
}
}
else
{
// log if we unexpectedly weren't able to run the sync chunk pulling
if (!this.pullExistingChunkUsingMcAsyncMethod)
{
// this shouldn't happen, but just in case
LOGGER.info("Unable to pull pre-existing chunk using synchronous method. Falling back to async method. this may cause server-tick lag.");
this.pullExistingChunkUsingMcAsyncMethod = true;
}
//GET_CHUNK_COUNT_REF.incrementAndGet();
// When running in vanilla MC on versions before 1.21.4,
// DH would attempt to run loadAsync on this same thread via a threading mixin,
// to prevent causing lag on the server thread.
// However, if a mod like C2ME is installed this will run on a C2ME thread instead.
return ioWorker.loadAsync(chunkPos)
.thenApply(optional ->
{
// Debugging note:
// If there are reports of extreme memory use when C2ME is installed, that probably means
// this method is queuing a lot of tasks (1,000+), which causes C2ME to explode.
//GET_CHUNK_COUNT_REF.decrementAndGet();
//PREF_LOGGER.info("chunk getter count ["+F3Screen.NUMBER_FORMAT.format(GET_CHUNK_COUNT_REF.get())+"]");
return optional.orElse(null);
})
.exceptionally((throwable) ->
{
// unwrap the CompletionException if necessary
Throwable actualThrowable = throwable;
while (actualThrowable instanceof CompletionException completionException)
{
actualThrowable = completionException.getCause();
}
boolean isShutdownException = ExceptionUtil.isShutdownException(actualThrowable);
if (!isShutdownException)
{
CHUNK_LOAD_LOGGER.warn("DistantHorizons: Couldn't load or make chunk ["+chunkPos+"], error: ["+actualThrowable.getMessage()+"].", actualThrowable);
}
return null;
});
}
#endif
}
catch (ClosedByInterruptException ignore)
{
// this just means the world generator is being shut down
return CompletableFuture.completedFuture(null);
}
catch (Exception e)
{
CHUNK_LOAD_LOGGER.warn("Couldn't load or make chunk [" + chunkPos + "]. Error: [" + e.getMessage() + "].", e);
return CompletableFuture.completedFuture(null);
}
}
private ChunkWrapper loadOrMakeChunkWrapper(ChunkPos chunkPos, CompoundTag chunkTagData)
{
ServerLevel mcServerLevel = this.params.mcServerLevel;
if (chunkTagData == null)
{
return this.CreateProtoChunkWrapper(mcServerLevel, chunkPos);
}
else
{
try
{
ChunkWrapper chunkWrapper = ChunkCompoundTagParser.createFromTag(mcServerLevel, this.params.dhServerLevel, chunkPos, chunkTagData);
if (chunkWrapper == null)
{
chunkWrapper = this.CreateProtoChunkWrapper(mcServerLevel, chunkPos);
}
return chunkWrapper;
}
catch (Exception e)
{
CHUNK_LOAD_LOGGER.error(
"DistantHorizons: couldn't load or make chunk at [" + chunkPos + "]." +
"Please try optimizing your world to fix this issue. \n" +
"World optimization can be done from the singleplayer world selection screen.\n" +
"Error: [" + e.getMessage() + "]."
, e);
return this.CreateProtoChunkWrapper(mcServerLevel, chunkPos);
}
}
}
public ChunkWrapper CreateProtoChunkWrapper(ServerLevel level, ChunkPos chunkPos)
{
ProtoChunk chunk = CreateProtoChunk(level, chunkPos);
return new ChunkWrapper(chunk, this.params.dhServerLevel.getLevelWrapper());
}
public static ProtoChunk CreateProtoChunk(ServerLevel level, ChunkPos chunkPos)
{
#if MC_VER <= MC_1_16_5
return new ProtoChunk(chunkPos, UpgradeData.EMPTY);
#elif MC_VER <= MC_1_17_1
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level);
#elif MC_VER <= MC_1_19_2
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), null);
#elif MC_VER <= MC_1_19_4
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registries.BIOME), null);
#elif MC_VER < MC_1_21_3
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registries.BIOME), null);
#elif MC_VER < MC_1_21_9
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().lookupOrThrow(Registries.BIOME), null);
#else
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, PalettedContainerFactory.create(level.registryAccess()), null);
#endif
}
//================//
// base overrides //
//================//
@Override
public void close()
{
RegionFileStorageExternalCache regionStorage = this.regionFileStorageCacheRef.get();
if (regionStorage != null)
{
try
{
regionStorage.close();
}
catch (ClosedChannelException ignore) { /* world generator is being shut down */ }
catch (IOException e)
{
LOGGER.error("Failed to close region file storage cache, error: ["+e.getMessage()+"].", e);
}
}
}
}
@@ -1,148 +0,0 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import org.jetbrains.annotations.Nullable;
/**
* these tag helpers are usedd to simplify tag accessing between MC versions
*/
public class CompoundTagUtil
{
/** defaults to "false" if the tag isn't present */
public static boolean getBoolean(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 */
public static byte getByte(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 */
public static short getShort(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 */
public static int getInt(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 */
public static long getLong(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 */
@Nullable
public static String getString(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 */
@Nullable
public static byte[] getByteArray(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
public static CompoundTag getCompoundTag(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
public static CompoundTag getCompoundTag(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
public static ListTag getListTag(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
public static ListTag getListTag(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getList(index);
#else
return tag.getList(index).orElse(null);
#endif
}
public static boolean contains(CompoundTag tag, String key, int index)
{
#if MC_VER < MC_1_21_5
return tag.contains(key, index);
#else
return tag.contains(key);
#endif
}
}
@@ -0,0 +1,525 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject;
import com.google.common.collect.Maps;
import com.mojang.serialization.Codec;
import com.mojang.serialization.Dynamic;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
#if MC_VER >= MC_1_19_4
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
#endif
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.chunk.storage.ChunkSerializer;
import net.minecraft.world.level.levelgen.Heightmap;
#if MC_VER >= MC_1_18_2
import net.minecraft.world.level.levelgen.blending.BlendingData;
#if MC_VER < MC_1_19_2
import net.minecraft.world.level.levelgen.feature.StructureFeature;
#endif
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext;
import net.minecraft.world.ticks.LevelChunkTicks;
#endif
#if MC_VER >= MC_1_18_2
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
#if MC_VER < MC_1_19_2
import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
#endif
#endif
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
import net.minecraft.world.level.material.Fluids;
#endif
#if MC_VER == MC_1_20_6
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.status.ChunkType;
#elif MC_VER == MC_1_21
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.status.ChunkType;
#endif
import net.minecraft.world.level.material.Fluid;
public class ChunkLoader
{
private static boolean zeroChunkPosErrorLogged = false;
#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());
#elif MC_VER >= MC_1_18_2
private static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codec(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState());
#endif
private static final String TAG_UPGRADE_DATA = "UpgradeData";
private static final String BLOCK_TICKS_TAG_18 = "block_ticks";
private static final String FLUID_TICKS_TAG_18 = "fluid_ticks";
private static final String BLOCK_TICKS_TAG_PRE18 = "TileTicks";
private static final String FLUID_TICKS_TAG_PRE18 = "LiquidTicks";
private static final ConfigBasedLogger LOGGER = BatchGenerationEnvironment.LOAD_LOGGER;
private static boolean lightingSectionErrorLogged = false;
//============//
// read chunk //
//============//
public static LevelChunk read(WorldGenLevel level, ChunkPos chunkPos, CompoundTag chunkData)
{
#if MC_VER < MC_1_18_2
CompoundTag tagLevel = chunkData.getCompound("Level");
#else
CompoundTag tagLevel = chunkData;
#endif
ChunkPos actualPos = new ChunkPos(tagLevel.getInt("xPos"), tagLevel.getInt("zPos"));
if (!Objects.equals(chunkPos, actualPos))
{
#if MC_VER > MC_1_17_1
if (actualPos.equals(ChunkPos.ZERO))
#else
if (actualPos.equals(ChunkPos.INVALID_CHUNK_POS))
#endif
{
if (!zeroChunkPosErrorLogged)
{
zeroChunkPosErrorLogged = true;
// 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" +
"This might happen if the world was created using an external program. \n" +
"DH will attempt to parse the chunk anyway and won't log this message again.\n" +
"If issues arise please try optimizing your world to fix this issue. \n" +
"World optimization can be done from the singleplayer world selection screen."+
"");
}
}
else
{
// everything is on one line to fix a JDK 17 compiler issue
// if the issue is ever resolved, feel free to make this multi-line for readability
LOGGER.error("Chunk file at ["+chunkPos.toString()+"] is in the wrong location. \nPlease try optimizing your world to fix this issue. \nWorld optimization can be done from the singleplayer world selection screen. \n(Expected pos: ["+chunkPos.toString()+"], actual ["+actualPos.toString()+"])");
return null;
}
}
#if MC_VER < MC_1_20_6
ChunkStatus.ChunkType chunkType;
#else
ChunkType chunkType;
#endif
chunkType = readChunkType(tagLevel);
#if MC_VER < MC_1_18_2
if (chunkType != ChunkStatus.ChunkType.LEVELCHUNK)
return null;
#else
BlendingData blendingData = readBlendingData(tagLevel);
#if MC_VER < MC_1_19_2
if (chunkType == ChunkStatus.ChunkType.PROTOCHUNK && (blendingData == null || !blendingData.oldNoise()))
return null;
#else
if (chunkType == #if MC_VER < MC_1_20_6 ChunkStatus.ChunkType.PROTOCHUNK #else ChunkType.PROTOCHUNK #endif && blendingData == null)
return null;
#endif
#endif
long inhabitedTime = tagLevel.getLong("InhabitedTime");
//================== 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");
#if MC_VER < MC_1_18_2
ChunkBiomeContainer chunkBiomeContainer = new ChunkBiomeContainer(
level.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY)#if MC_VER >= MC_1_17_1 , level #endif ,
chunkPos, level.getLevel().getChunkSource().getGenerator().getBiomeSource(),
tagLevel.contains("Biomes", 11) ? tagLevel.getIntArray("Biomes") : null);
TickList<Block> blockTicks = tagLevel.contains(BLOCK_TICKS_TAG_PRE18, 9)
? ChunkTickList.create(tagLevel.getList(BLOCK_TICKS_TAG_PRE18, 10), Registry.BLOCK::getKey, Registry.BLOCK::get)
: new ProtoTickList<Block>(block -> (block == null || block.defaultBlockState().isAir()), chunkPos,
tagLevel.getList("ToBeTicked", 9)#if MC_VER >= MC_1_17_1 , level #endif );
TickList<Fluid> fluidTicks = tagLevel.contains(FLUID_TICKS_TAG_PRE18, 9)
? ChunkTickList.create(tagLevel.getList(FLUID_TICKS_TAG_PRE18, 10), Registry.FLUID::getKey, Registry.FLUID::get)
: new ProtoTickList<Fluid>(fluid -> (fluid == null || fluid == Fluids.EMPTY), chunkPos,
tagLevel.getList("LiquidsToBeTicked", 9)#if MC_VER >= MC_1_17_1 , level #endif );
#else
#if MC_VER < MC_1_19_4
LevelChunkTicks<Block> blockTicks = LevelChunkTicks.load(tagLevel.getList(BLOCK_TICKS_TAG_18, 10),
string -> Registry.BLOCK.getOptional(ResourceLocation.tryParse(string)), chunkPos);
LevelChunkTicks<Fluid> fluidTicks = LevelChunkTicks.load(tagLevel.getList(FLUID_TICKS_TAG_18, 10),
string -> Registry.FLUID.getOptional(ResourceLocation.tryParse(string)), chunkPos);
#else
LevelChunkTicks<Block> blockTicks = LevelChunkTicks.load(tagLevel.getList(BLOCK_TICKS_TAG_18, 10),
(string -> BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(string))), chunkPos);
LevelChunkTicks<Fluid> fluidTicks = LevelChunkTicks.load(tagLevel.getList(FLUID_TICKS_TAG_18, 10),
string -> BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(string)), chunkPos);
#endif
#endif
LevelChunkSection[] levelChunkSections = readSections(level, chunkPos, tagLevel);
// ====================== Make the chunk =========================
#if MC_VER < MC_1_18_2
LevelChunk chunk = new LevelChunk((Level) level.getLevel(), chunkPos, chunkBiomeContainer, upgradeData, blockTicks,
fluidTicks, inhabitedTime, levelChunkSections, null);
#else
LevelChunk chunk = new LevelChunk((Level) level, chunkPos, upgradeData, blockTicks,
fluidTicks, inhabitedTime, levelChunkSections, null, blendingData);
#endif
// Set some states after object creation
chunk.setLightCorrect(isLightOn);
readHeightmaps(chunk, chunkData);
readPostPocessings(chunk, chunkData);
return chunk;
}
private static LevelChunkSection[] readSections(LevelAccessor level, ChunkPos chunkPos, CompoundTag chunkData)
{
#if MC_VER >= MC_1_18_2
#if MC_VER < MC_1_19_4
Registry<Biome> biomes = level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
#else
Registry<Biome> biomes = level.registryAccess().registryOrThrow(Registries.BIOME);
#endif
#if MC_VER < MC_1_18_2
Codec<PalettedContainer<Biome>> biomeCodec = PalettedContainer.codec(
biomes, biomes.byNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getOrThrow(Biomes.PLAINS));
#elif MC_VER < MC_1_19_2
Codec<PalettedContainer<Holder<Biome>>> biomeCodec = PalettedContainer.codec(
biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getHolderOrThrow(Biomes.PLAINS));
#else
Codec<PalettedContainer<Holder<Biome>>> biomeCodec = PalettedContainer.codecRW(
biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getHolderOrThrow(Biomes.PLAINS));
#endif
#endif
int sectionYIndex = #if MC_VER < MC_1_17_1 16; #else level.getSectionsCount(); #endif
LevelChunkSection[] chunkSections = new LevelChunkSection[sectionYIndex];
boolean isLightOn = chunkData.getBoolean("isLightOn");
boolean hasSkyLight = level.dimensionType().hasSkyLight();
ListTag tagSections = chunkData.getList("Sections", 10);
if (tagSections.isEmpty()) tagSections = chunkData.getList("sections", 10);
for (int j = 0; j < tagSections.size(); ++j)
{
CompoundTag tagSection = tagSections.getCompound(j);
int sectionYPos = tagSection.getByte("Y");
#if MC_VER < MC_1_18_2
if (tagSection.contains("Palette", 9) && tagSection.contains("BlockStates", 12))
{
LevelChunkSection levelChunkSection = new LevelChunkSection(sectionYPos << 4);
levelChunkSection.getStates().read(tagSection.getList("Palette", 10),
tagSection.getLongArray("BlockStates"));
levelChunkSection.recalcBlockCounts();
if (!levelChunkSection.isEmpty())
chunkSections[#if MC_VER < MC_1_17_1 sectionYPos #else level.getSectionIndexFromSectionY(sectionYPos) #endif ]
= levelChunkSection;
}
#else
int sectionId = level.getSectionIndexFromSectionY(sectionYPos);
if (sectionId >= 0 && sectionId < chunkSections.length)
{
PalettedContainer<BlockState> blockStateContainer;
#if MC_VER < MC_1_18_2
PalettedContainer<Biome> biomeContainer;
#else
PalettedContainer<Holder<Biome>> biomeContainer;
#endif
blockStateContainer = tagSection.contains("block_states", 10)
? BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagSection.getCompound("block_states")).promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
#if MC_VER < MC_1_20_6
.getOrThrow(false, LOGGER::error)
#else
.getOrThrow((message) -> (RuntimeException) LOGGER.errorAndThrow(message, null))
#endif
: new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
#if MC_VER < MC_1_18_2
biomeContainer = tagSection.contains("biomes", 10)
? biomeCodec.parse(NbtOps.INSTANCE, tagSection.getCompound("biomes")).promotePartial(string -> logErrors(chunkPos, sectionYPos, string)).getOrThrow(false, LOGGER::error)
: new PalettedContainer<Biome>(biomes, biomes.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
#else
biomeContainer = 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, LOGGER::error)
#else
.getOrThrow((message) -> (RuntimeException) LOGGER.errorAndThrow(message, null))
#endif
: new PalettedContainer<>(biomes.asHolderIdMap(), biomes.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
#endif
#if MC_VER < MC_1_20_1
chunkSections[sectionId] = new LevelChunkSection(sectionYPos, blockStateContainer, biomeContainer);
#else
chunkSections[sectionId] = new LevelChunkSection(blockStateContainer, biomeContainer);
#endif
}
#endif
}
return chunkSections;
}
private static
#if MC_VER < MC_1_20_6 ChunkStatus.ChunkType
#elif MC_VER < MC_1_21 ChunkType
#else ChunkType #endif
readChunkType(CompoundTag tagLevel)
{
ChunkStatus chunkStatus = ChunkStatus.byName(tagLevel.getString("Status"));
if (chunkStatus != null)
{
return chunkStatus.getChunkType();
}
return
#if MC_VER <= MC_1_20_4 ChunkStatus.ChunkType.PROTOCHUNK;
#else ChunkType.PROTOCHUNK; #endif
}
private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData)
{
CompoundTag tagHeightmaps = chunkData.getCompound("Heightmaps");
for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter())
{
String heightmap = type.getSerializationKey();
if (tagHeightmaps.contains(heightmap, 12))
chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmap));
}
Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter());
}
private static void readPostPocessings(LevelChunk chunk, CompoundTag chunkData)
{
ListTag tagPostProcessings = chunkData.getList("PostProcessing", 9);
for (int n = 0; n < tagPostProcessings.size(); ++n)
{
ListTag listTag3 = tagPostProcessings.getList(n);
for (int o = 0; o < listTag3.size(); ++o)
{
chunk.addPackedPostProcess(listTag3.getShort(o), n);
}
}
}
#if MC_VER >= MC_1_18_2
private static BlendingData readBlendingData(CompoundTag chunkData)
{
BlendingData blendingData = null;
if (chunkData.contains("blending_data", 10))
{
@SuppressWarnings({"unchecked", "rawtypes"})
Dynamic<CompoundTag> blendingDataTag = new Dynamic(NbtOps.INSTANCE, chunkData.getCompound("blending_data"));
blendingData = BlendingData.CODEC.parse(blendingDataTag).resultOrPartial(LOGGER::error).orElse(null);
}
return blendingData;
}
#endif
//=====================//
// read chunk lighting //
//=====================//
/**
* https://minecraft.wiki/w/Chunk_format
*/
public static CombinedChunkLightStorage readLight(ChunkAccess chunk, CompoundTag chunkData)
{
#if MC_VER <= MC_1_17_1
// MC 1.16 and 1.17 doesn't have the necessary NBT info
return null;
#else
CombinedChunkLightStorage combinedStorage = new CombinedChunkLightStorage(ChunkWrapper.getMinBuildHeight(chunk), ChunkWrapper.getMaxBuildHeight(chunk));
ChunkLightStorage blockLightStorage = combinedStorage.blockLightStorage;
ChunkLightStorage skyLightStorage = combinedStorage.skyLightStorage;
boolean foundSkyLight = false;
//===================//
// get NBT tags info //
//===================//
Tag chunkSectionTags = chunkData.get("sections");
if (chunkSectionTags == null)
{
if (!lightingSectionErrorLogged)
{
lightingSectionErrorLogged = true;
LOGGER.error("No sections found for chunk at pos ["+chunk.getPos()+"] chunk data may be out of date.");
}
return null;
}
else if (!(chunkSectionTags instanceof ListTag))
{
if (!lightingSectionErrorLogged)
{
lightingSectionErrorLogged = true;
LOGGER.error("Chunk section tag list have unexpected type ["+chunkSectionTags.getClass().getName()+"], expected ["+ListTag.class.getName()+"].");
}
return null;
}
ListTag chunkSectionListTag = (ListTag) chunkSectionTags;
//===================//
// get lighting info //
//===================//
for (int sectionIndex = 0; sectionIndex < chunkSectionListTag.size(); sectionIndex++)
{
Tag chunkSectionTag = chunkSectionListTag.get(sectionIndex);
if (!(chunkSectionTag instanceof CompoundTag chunkSectionCompoundTag))
{
if (!lightingSectionErrorLogged)
{
lightingSectionErrorLogged = true;
LOGGER.error("Chunk section tag has an unexpected type ["+chunkSectionTag.getClass().getName()+"], expected ["+CompoundTag.class.getName()+"].");
}
return null;
}
// if null all lights = 0
byte[] blockLightNibbleArray = chunkSectionCompoundTag.getByteArray("BlockLight");
byte[] skyLightNibbleArray = chunkSectionCompoundTag.getByteArray("SkyLight");
// if any sky light was found then all lights above will be max brightness
if (skyLightNibbleArray.length != 0)
{
foundSkyLight = true;
}
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
// chunk sections are also 16 blocks tall
for (int relY = 0; relY < LodUtil.CHUNK_WIDTH; relY++)
{
int blockPosIndex = relY*16*16 + relZ*16 + relX;
byte blockLight = (blockLightNibbleArray.length == 0) ? 0 : getNibbleAtIndex(blockLightNibbleArray, blockPosIndex);
byte skyLight = (skyLightNibbleArray.length == 0) ? 0 : getNibbleAtIndex(skyLightNibbleArray, blockPosIndex);
if (skyLightNibbleArray.length == 0 && foundSkyLight)
{
skyLight = LodUtil.MAX_MC_LIGHT;
}
int y = relY + (sectionIndex * LodUtil.CHUNK_WIDTH) + ChunkWrapper.getMinBuildHeight(chunk);
blockLightStorage.set(relX, y, relZ, blockLight);
skyLightStorage.set(relX, y, relZ, skyLight);
}
}
}
}
return combinedStorage;
#endif
}
/** source: https://minecraft.wiki/w/Chunk_format#Block_Format */
private static byte getNibbleAtIndex(byte[] arr, int index)
{
if (index % 2 == 0)
{
return (byte)(arr[index/2] & 0x0F);
}
else
{
return (byte)((arr[index/2]>>4) & 0x0F);
}
}
private static void logBlockDeserializationWarning(ChunkPos chunkPos, int sectionYIndex, String message)
{
LOGGER.warn("Unable to deserialize blocks for chunk section [" + chunkPos.x + ", " + sectionYIndex + ", " + chunkPos.z + "], error: ["+message+"]. This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem.");
}
private static void logBiomeDeserializationWarning(ChunkPos chunkPos, int sectionYIndex, String message)
{
LOGGER.warn("Unable to deserialize biomes for chunk section [" + chunkPos.x + ", " + sectionYIndex + ", " + chunkPos.z + "], error: ["+message+"]. This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem.");
}
//================//
// helper classes //
//================//
public static class CombinedChunkLightStorage
{
public ChunkLightStorage blockLightStorage;
public ChunkLightStorage skyLightStorage;
public CombinedChunkLightStorage(int minY, int maxY)
{
this.blockLightStorage = ChunkLightStorage.createBlockLightStorage(minY, maxY);
this.skyLightStorage = ChunkLightStorage.createSkyLightStorage(minY, maxY);
}
}
}
@@ -1,28 +1,23 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject;
#if MC_VER >= MC_1_21_1
#if MC_VER >= MC_1_21
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.world.level.ChunkPos;
import java.util.concurrent.CompletableFuture;
public class DhGenerationChunkHolder extends GenerationChunkHolder
{
public DhGenerationChunkHolder(ChunkPos pos) { super(pos); }
public DhGenerationChunkHolder(ChunkPos pos)
{
super(pos);
}
@Override
public int getTicketLevel() { return 0; }
@Override
public int getQueueLevel() { return 0; }
#if MC_VER < MC_1_21_3
#else
@Override
protected void addSaveDependency(CompletableFuture<?> completableFuture) { }
#endif
}
#endif
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject;
import java.lang.invoke.MethodHandles;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
@@ -28,7 +29,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.LodUtil;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.SpawnerBlock;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -62,36 +63,28 @@ import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.status.*;
#endif
#if MC_VER >= MC_1_21_1
#if MC_VER == MC_1_21
import net.minecraft.util.StaticCache2D;
import com.google.common.collect.ImmutableList;
import net.minecraft.server.level.GenerationChunkHolder;
#endif
#if MC_VER >= MC_1_18_2
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.ticks.BlackholeTickAccess;
import net.minecraft.world.ticks.LevelTickAccess;
#endif
public class DhLitWorldGenRegion extends WorldGenRegion
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
private static ChunkStatus debugTriggeredForStatus = null;
public final ServerLevel serverLevel;
public final DummyLightEngine lightEngine;
public final BatchGenerationEnvironment.IEmptyChunkRetrievalFunc generator;
public final BatchGenerationEnvironment.IEmptyChunkGeneratorFunc generator;
public final int writeRadius;
public final int size;
private final ChunkPos firstPos;
private final List<ChunkAccess> cache;
private final Long2ObjectOpenHashMap<ChunkAccess> chunkMap = new Long2ObjectOpenHashMap<ChunkAccess>();
private final Long2ObjectOpenHashMap<ChunkAccess> chunkMap = new Long2ObjectOpenHashMap<>();
/**
* Present to reduce the chance that we accidentally break underlying MC code that isn't thread safe,
@@ -130,11 +123,11 @@ public class DhLitWorldGenRegion extends WorldGenRegion
ChunkAccess centerChunk,
ServerLevel serverLevel, DummyLightEngine lightEngine,
List<ChunkAccess> chunkList, ChunkStatus chunkStatus, int writeRadius,
BatchGenerationEnvironment.IEmptyChunkRetrievalFunc generator)
BatchGenerationEnvironment.IEmptyChunkGeneratorFunc generator)
{
#if MC_VER == MC_1_16_5
super(serverLevel, chunkList);
#elif MC_VER < MC_1_21_1
#elif MC_VER < MC_1_21
super(serverLevel, chunkList, chunkStatus, writeRadius);
#else
super(serverLevel,
@@ -147,10 +140,9 @@ public class DhLitWorldGenRegion extends WorldGenRegion
new ChunkDependencies(ImmutableList.copyOf(ChunkStatus.getStatusList()).reverse()),
writeRadius, (WorldGenContext var1, ChunkStep var2, StaticCache2D<GenerationChunkHolder> var3, ChunkAccess var4) -> null),
centerChunk);
#endif
this.firstPos = chunkList.get(0).getPos();
this.serverLevel = serverLevel;
#endif
this.firstPos = chunkList.getFirst().getPos();
this.generator = generator;
this.lightEngine = lightEngine;
this.writeRadius = writeRadius;
@@ -179,18 +171,7 @@ public class DhLitWorldGenRegion extends WorldGenRegion
if (center.isUpgrading())
{
LevelHeightAccessor levelHeightAccessor = center.getHeightAccessorForGeneration();
int minY;
int maxY;
#if MC_VER < MC_1_21_3
minY = levelHeightAccessor.getMinBuildHeight();
maxY = levelHeightAccessor.getMaxBuildHeight();
#else
minY = levelHeightAccessor.getMinY();
maxY = levelHeightAccessor.getMaxY();
#endif
if (blockPos.getY() < minY || blockPos.getY() >= maxY)
if (blockPos.getY() < levelHeightAccessor.getMinBuildHeight() || blockPos.getY() >= levelHeightAccessor.getMaxBuildHeight())
{
return false;
}
@@ -200,22 +181,6 @@ public class DhLitWorldGenRegion extends WorldGenRegion
}
#endif
#if MC_VER >= MC_1_18_2
@Override
@NotNull
public LevelTickAccess<Block> getBlockTicks()
{
// DH world gen doesn't need ticking, so return the BlackholeTickAccess list (which causes all ticks to be ignored).
// If this isn't done the server may attempt to tick chunks outside the vanilla render distance,
// which can throw warnings or cause other issues
return BlackholeTickAccess.emptyLevelList();
}
@Override
@NotNull
public LevelTickAccess<Fluid> getFluidTicks() { return BlackholeTickAccess.emptyLevelList(); }
#endif
// TODO Check this
// @Override
// public List<? extends StructureStart<?>> startsForFeature(SectionPos sectionPos,
@@ -229,16 +194,8 @@ public class DhLitWorldGenRegion extends WorldGenRegion
{
ChunkAccess chunkAccess = this.getChunk(blockPos);
if (chunkAccess instanceof LevelChunk)
{
return true;
}
#if MC_VER < MC_1_21_5
chunkAccess.setBlockState(blockPos, blockState, /*isBlockMoving*/false);
#else
chunkAccess.setBlockState(blockPos, blockState, /*flags*/0);
#endif
chunkAccess.setBlockState(blockPos, blockState, false);
// This is for post ticking for water on gen and stuff like that. Not enabled
// for now.
// if (blockState.hasPostProcess(this, blockPos))
@@ -278,22 +235,6 @@ public class DhLitWorldGenRegion extends WorldGenRegion
#endif
}
/**
* This needs to be manually overridden to make sure Lithium 0.11.2 and lower
* don't try to get null chunks. <br><br>
*
* Problematic Lithium code was removed in 0.13.0 (MC 1.21.1) and higher: <br>
* https://github.com/CaffeineMC/lithium-fabric/commit/b7cfd53a1ed0197e1d13dea2799b898eb52ecab3
*/
@NotNull
@Override
public BlockState getBlockState(BlockPos blockPos)
{
int chunkX = SectionPos.blockToSectionCoord(blockPos.getX());
int chunkZ = SectionPos.blockToSectionCoord(blockPos.getZ());
return this.getChunk(chunkX, chunkZ).getBlockState(blockPos);
}
/** Skip BlockEntity stuff. They aren't needed for our use case. */
@Override
public boolean addFreshEntity(@NotNull Entity entity) { return true; }
@@ -352,7 +293,7 @@ public class DhLitWorldGenRegion extends WorldGenRegion
ChunkAccess chunk = this.getChunkAccess(chunkX, chunkZ, chunkStatus, returnNonNull);
if (chunk instanceof LevelChunk)
{
chunk = new ImposterProtoChunk((LevelChunk) chunk #if MC_VER >= MC_1_18_2 ,/* allow writes */ false #endif );
chunk = new ImposterProtoChunk((LevelChunk) chunk #if MC_VER >= MC_1_18_2 , true #endif );
}
return chunk;
}
@@ -383,7 +324,7 @@ public class DhLitWorldGenRegion extends WorldGenRegion
if (chunk == null)
{
// chunk isn't in memory, generate a new one
chunk = this.generator.getChunk(chunkX, chunkZ);
chunk = this.generator.generate(chunkX, chunkZ);
if (chunk == null)
{
throw new NullPointerException("The provided generator should not return null!");
@@ -392,11 +333,10 @@ public class DhLitWorldGenRegion extends WorldGenRegion
}
}
if (chunkStatus != ChunkStatus.EMPTY
&& chunkStatus != debugTriggeredForStatus)
if (chunkStatus != ChunkStatus.EMPTY && chunkStatus != debugTriggeredForStatus)
{
LOGGER.info("WorldGen requiring [" + chunkStatus + "]"
+ " is outside the expected range. Returning EMPTY chunk.");
LOGGER.info("WorldGen requiring " + chunkStatus
+ " outside expected range detected. Force passing EMPTY chunk and seeing if it works.");
debugTriggeredForStatus = chunkStatus;
}
@@ -435,10 +375,14 @@ public class DhLitWorldGenRegion extends WorldGenRegion
/** Overriding allows us to use our own lighting engine */
@Override
public boolean canSeeSky(@NotNull BlockPos blockPos)
{ return (this.getBrightness(LightLayer.SKY, blockPos) >= LodUtil.MAX_MC_LIGHT); }
{
return (this.getBrightness(LightLayer.SKY, blockPos) >= this.getMaxLightLevel());
}
public int getBlockTint(@NotNull BlockPos blockPos, @NotNull ColorResolver colorResolver)
{ return this.calculateBlockTint(blockPos, colorResolver); }
{
return this.calculateBlockTint(blockPos, colorResolver);
}
private Biome _getBiome(BlockPos pos)
{
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -2,7 +2,7 @@
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by

Some files were not shown because too many files have changed in this diff Show More