Compare commits

...

157 Commits

Author SHA1 Message Date
Ran 8e7f82d3e0 Update Forgix 2025-05-08 12:44:29 +10:00
Ran 931aa2974e Better CI formatting 2025-05-06 14:00:27 +10:00
Ran 1f77a12fe3 attempt #3 on fixing the CI 2025-05-06 13:14:34 +10:00
Ran dcf546a163 attempt #2 on fixing the CI 2025-05-06 12:37:46 +10:00
Ran 81f050dc8b try to fix CI 2025-05-06 11:33:49 +10:00
Ran 9cd1ada37f multiversion test 2025-05-06 11:27:16 +10:00
s809 d29e9085a1 Fix getWorldFolderName crash 2025-05-03 13:44:54 +05:00
Ran d392de3c0d Update Forgix
fixes a critical bug
2025-05-03 17:40:58 +10:00
Ran 7209193f8f Fix gradle versioning 2025-05-03 11:21:17 +10:00
s809 ed4f644a3f Bump protocol version 2025-05-03 00:08:12 +05:00
Ran 34038684a7 Fix gradle versioning for core application 2025-05-02 12:45:18 +10:00
s809 6fe6694c82 Merge branch 'feature/adaptive-data-rate' 2025-04-27 21:57:47 +05:00
s809 8e52f1aca5 Merge branch 'refactor/remove-python-dependency' 2025-04-27 21:48:53 +05:00
s809 5ce3dda2d5 Clear up the comment a bit 2025-04-27 21:48:28 +05:00
s809 f4f81f4d7f Account for forge byte when encoding protocol version instead of shifting the entire packet on pre-1.20.6 2025-04-27 21:40:50 +05:00
s809 7c37a5c370 Run prepare only when needed 2025-04-27 00:40:12 +05:00
s809 b495ac4799 remove python dependency for building with correct sqlite natives 2025-04-27 00:13:24 +05:00
s809 2ddeaf50eb test builds 2025-04-21 00:00:01 +05:00
Ran 3721ebea6e Improve LodDataBuilder.java
- Use bitwise modulo
- Don't compute certain things 256 times when they can be computed once.
- Removed expressions that are always false
- Improved comments
2025-04-11 11:24:17 +10:00
Ran 98f8a87362 Improve LodDataBuilder.java
- Use bitwise modulo
- Don't compute certain things 256 times when they can be computed once.
- Removed expressions that are always false
- Improved comments
2025-04-11 11:20:05 +10:00
Ran 10a743ddef Don't check for Indium for Sodium version >= 0.6 2025-04-07 23:53:02 +10:00
James Seibel 95c896f964 maybe break n-sized rendering but fix LOD loading getting stuck 2025-04-07 06:56:58 -05:00
James Seibel 040bc16874 re-add comment to getWorldFolderName() 2025-04-07 06:55:45 -05:00
James Seibel 35d3fdb473 Revert "bandaid fix for Forge 1.20.1 UI crashing"
This reverts commit 2b519a826f.
2025-04-07 06:55:01 -05:00
Ran 549f7510f7 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/TintWithoutLevelOverrider.java
2025-04-07 13:52:25 +10:00
Ran fab64d8477 Fix white foliage issue 2025-04-07 13:52:02 +10:00
Ran 4a6a35f617 Fix white foliage issue 2025-04-07 13:47:42 +10:00
James Seibel 2b519a826f bandaid fix for Forge 1.20.1 UI crashing 2025-04-05 09:19:36 -05:00
James Seibel 445c01b5ae up version number 2.3.2 -> 2.3.3 2025-04-05 09:11:45 -05:00
James Seibel 0f08bd540e remove dev from the version number 2025-04-05 09:10:23 -05:00
James Seibel 77088465f9 Improve DH world gen progress message 2025-04-02 07:25:19 -05:00
James Seibel affe014433 Fix auto updater for MC 1.21.4 and 1.21.5 2025-03-31 06:56:00 -05:00
James Seibel 4c06bf6dbd change iris incompat MC 1.21.5 1.8.12 -> 1.8.10 2025-03-30 17:31:51 -05:00
James Seibel dcab616385 Fix memory leaks due to un-closed thread pools and worlds 2025-03-30 17:31:04 -05:00
James Seibel 89b7d08e9b Fix the sun/moon and stars not rendering
Closes #986
2025-03-30 16:50:43 -05:00
James Seibel 724e318221 Fix beacon beams now going through some blocks 2025-03-30 15:23:27 -05:00
James Seibel a40d62d46a Fix flashing on MC 1.21.5 in non-overworld dimensions 2025-03-30 14:36:56 -05:00
James Seibel 2f6b4c079b don't log InterruptedException during threadPool shutdown 2025-03-29 20:11:36 -05:00
James Seibel 951e3c0271 Fix accidentally removing required imports 2025-03-29 20:09:41 -05:00
James Seibel 84825c2d09 Add MC 1.21.5 to the auto build script 2025-03-29 19:23:53 -05:00
James Seibel ec627e2eba Fix fog for MC 1.16.5 2025-03-29 19:22:59 -05:00
James Seibel 06bc9a349f Fix MC 1.16.5 compiling 2025-03-29 18:48:00 -05:00
James Seibel ff6c4e227b level wrapper weak refs to fix leak on bad shutdown 2025-03-29 18:18:23 -05:00
James Seibel a4d46ffe94 Mark Iris 1.8.12 and lower broken for MC 1.21.5
Older MC versions are unaffected and function correctly
2025-03-29 16:51:46 -05:00
James Seibel bd5c140782 Fix import for fabric mixin texture 2025-03-29 16:45:32 -05:00
James Seibel 229c3f7c91 Add neoforge 1.21.5 2025-03-29 16:11:04 -05:00
James Seibel 693369bc08 MC 1.21.5 changes, lighting fix, and world gen dup fix 2025-03-29 15:45:26 -05:00
James Seibel d109fe6c43 comment out LOD bias option for MC 1.21.5+ 2025-03-29 15:44:54 -05:00
James Seibel 3c9d3707cf update world gen chunk loading for MC 1.21.5 2025-03-29 15:38:42 -05:00
James Seibel 6e53564835 Fix getting block colors for MC 1.21.5 2025-03-29 12:34:35 -05:00
James Seibel 2480fe0d86 Add basic MC 1.21.5 rendering (block colors and world gen broken) 2025-03-29 10:40:36 -05:00
James Seibel 691c9d3f45 up version number 2.3.1 -> 2.3.2 2025-03-25 07:17:34 -05:00
James Seibel 3faf25636d remove dev from version number 2025-03-25 07:16:56 -05:00
James Seibel ab3bfbefb4 remove version from clean in buildall.bat 2025-03-25 07:16:07 -05:00
James Seibel 890e802de4 add execution policy for python script 2025-03-25 07:15:52 -05:00
James Seibel c13bc0cd6e Fix forge 1.18.2 dedicated server crash on startup 2025-03-20 07:08:21 -05:00
James Seibel 7143b7de08 Add config to only log GL errors once 2025-03-19 22:02:57 -05:00
James Seibel d136d782f5 Attempt to fix Linux complaining about glIsFramebuffer() 2025-03-19 18:34:06 -05:00
James Seibel 29a160316c Potentially fix LAN connections on neo/forge 2025-03-19 17:34:03 -05:00
James Seibel 1f6f64d322 Potentially fix GL errors when accessing the default FBO on Linux 2025-03-19 17:00:36 -05:00
James Seibel 37c0af529d Fix restoring textures to the default FBO 2025-03-18 20:18:30 -05:00
James Seibel 1341ea3f3d Attempt to fix GL errors on Linux during buffer cleanup
Attempt to fix #950
2025-03-18 19:51:22 -05:00
James Seibel c0bb120669 Add stack tracing to GL error logging 2025-03-18 18:10:25 -05:00
James Seibel f8887e403f fix passing in the wrong flags to glBufferStorage()
Might Resolve #964 and #950
2025-03-18 07:43:23 -05:00
James Seibel 949ee423c8 Fix changing graphics settings on world load via API 2025-03-16 14:30:04 -05:00
James Seibel b19ed3f30c Fix GL error logging 2025-03-14 10:18:03 -05:00
James Seibel 2d085e1074 Add additional error checking/handling to Shader compiling 2025-03-13 21:12:34 -05:00
James Seibel 6ba0490cf7 Closes !950 (Texture name does not refer to a texture object) 2025-03-13 18:09:43 -05:00
James Seibel b6a0878241 up version number 2.3.0-b -> 2.3.1-b-dev
Also fix compiling for release builds
2025-03-08 08:11:14 -06:00
James Seibel 50f5371084 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons 2025-03-06 07:43:29 -06:00
James Seibel df74b8d243 Update coreSubProjects 2025-03-06 07:43:22 -06:00
James Seibel fce2868c62 remove dev from version number for release 2025-03-06 07:41:52 -06:00
s809 a36cd0763b Add some debugging info for DTOs 2025-03-02 20:09:11 +05:00
James Seibel 0fba015f54 Fix crashing on MC 1.20.1 and older when updates aren't found 2 2025-03-01 09:19:52 -06:00
James Seibel f251c90472 Fix crashing on MC 1.20.1 and older when updates aren't found 2025-03-01 09:13:47 -06:00
s809 492a051a3b Replace chunk counts with speed in pregen 2025-02-27 21:08:31 +05:00
s809 0aa4743c1b Should be division instead of multiplication 2025-02-26 23:17:05 +05:00
s809 85f16944b2 Offset generation bounds by teleportation scale 2025-02-26 22:13:43 +05:00
James Seibel dddb0be2ac duct tape fix to reduce chance of LOD uploading requiring MC reboot 2025-02-25 07:26:12 -06:00
s809 4a3effa2f5 Remember split section responses temporarily 2025-02-22 20:55:18 +05:00
s809 a0a9151bfd Fix foreground thread sometimes blocking server shutdown 2025-02-19 21:17:42 +05:00
s809 aa3d15f38f Show section numbers in pregen 2025-02-19 20:37:29 +05:00
James Seibel adcb2a3a05 Fix IDhApiConfigValue.clearValue() failing for some deprecated functions 2025-02-17 21:16:27 -06:00
James Seibel 78f2cb24cc Fix DB leaks in FullDataV2Repo 2025-02-16 20:07:13 -06:00
James Seibel 67945509ed Fix errors related to player pos being unloaded 2025-02-16 19:54:11 -06:00
James Seibel c653e526a5 Revert 10 minute memoization for world gen 2025-02-15 11:56:25 -06:00
James Seibel 49b50c4c88 Fix beacon culling with auto overdraw prevention 2025-02-15 11:12:57 -06:00
James Seibel 7449f46c5e Add missing cave blocks for cave culling 2025-02-15 11:06:57 -06:00
James Seibel 069fc39aad up fabric api version for 1.21.1 to allow Immersive Portals testing 2025-02-14 07:48:32 -06:00
s809 4979ccf3e2 Invert generateOnlyInHighestDetail and rename to enableNSizedGeneration 2025-02-11 22:08:29 +05:00
James Seibel dd7f9c20b6 Put N-sized generation and upsampling behind experimental configs 2025-02-11 07:47:36 -06:00
James Seibel e96f9de1f0 Fix dimension wrapper creating duplicates for the same name 2025-02-11 07:05:34 -06:00
James Seibel c902e1957f Fix auto updater failing for nightly builds 2025-02-10 07:47:03 -06:00
James Seibel d40afb7a2a Fix C2ME slowdown again 2025-02-08 21:43:49 -06:00
James Seibel 01474d72e3 remove unneeded IVersionConstant methods 2025-02-08 11:39:26 -06:00
James Seibel 7c5af1836b add FIXME comment related to getChunkNbtDataAsync()
this method appears to be called much more often than it should be, very often 25 times per chunk position.
Very strange.
2025-02-08 11:12:06 -06:00
James Seibel a9bf6ae7e4 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons 2025-02-08 10:46:16 -06:00
James Seibel aef3162246 reduce test N-sized generator height 2025-02-08 10:45:55 -06:00
James Seibel 97ce869076 Fix C2ME causing memory use to explode with DH world gen 2025-02-08 10:45:38 -06:00
s809 91b3c83ffd Update core 2025-02-07 23:23:25 +05:00
James Seibel 1522df19cb Attempt to fix threadpool shutdown rejection exception 2025-02-07 07:26:07 -06:00
James Seibel 3845564128 Reduce world gen down time when using extremely fast generators 2025-02-07 07:15:31 -06:00
s809 23ef7cf27a Fix incorrect distance being used in update propagation SQL and reduce queue size 2025-02-07 01:05:18 +05:00
James Seibel bec28a5694 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons 2025-02-05 17:32:37 -06:00
James Seibel 8f20f103ad Fix empty data sources when moving in multiplayer or with N-sized world gen
Increases networking protocol from 9 -> 10
2025-02-05 17:32:30 -06:00
James Seibel 246e77cc56 Fix render enabled config getting set by world gen progress config 2025-02-02 19:55:06 -06:00
James Seibel 87fa29c77a Fix compiling with missing "E" 2025-02-02 15:52:20 -06:00
s809 5010256ce6 Update core 2025-02-02 20:30:57 +05:00
s809 a66ad19343 Balance tasks in thread pool using elapsed time instead of priorities 2025-02-02 20:30:04 +05:00
s809 913a458a1a Auto-move old save data to new location 2025-02-02 15:38:06 +05:00
s809 093d3a801e Remove generationProgressDisableMessageDisplayTimeInSeconds from server config command 2025-02-01 19:38:46 +05:00
s809 61ccf7bf60 Decrease delay between missing generation rechecks 2025-01-31 14:54:32 +05:00
s809 f948072253 Decrease delay between missing generation rechecks 2025-01-31 14:53:57 +05:00
s809 b748f27a1c Fix beacon beams flickering 2025-01-30 22:30:08 +05:00
s809 4dd4bb9ef0 Fix nightly self-updater after moving jars into zip root 2025-01-30 18:11:39 +05:00
s809 5051bde3b0 Add pycache into gitignore 2025-01-30 00:22:03 +05:00
s809 42cf639acc Merge branch 'test/artifacts-in-zip-root' 2025-01-30 00:21:02 +05:00
s809 9e6953a596 Fix relocation breaking runClient & runServer 2025-01-29 23:59:23 +05:00
s809 7f4f8a40eb Merge branch 'experimental/relocate_sqlite' 2025-01-29 23:23:36 +05:00
s809 89ca535a6f Add all the extra comments 2025-01-29 23:23:21 +05:00
s809 145182502e Do not relocate when python is not installed 2025-01-29 23:01:28 +05:00
s809 d61dfc9e03 Revert "Improve chunk processing throughput" 2025-01-28 22:56:52 +05:00
James Seibel 611d7d87ae Fix compiling for MC 1.19.2 and below 2025-01-26 18:12:01 -06:00
James Seibel 2f6a2d99ab Remove unneeded MixinLevelTicks (!73)
https://gitlab.com/distant-horizons-team/distant-horizons/-/merge_requests/73#note_2281882248
2025-01-26 17:47:32 -06:00
James Seibel d88ca0c98d Improve CPU usage and chunk update throughput 2025-01-26 17:13:28 -06:00
s809 0f64df7be0 Add missing enabled check 2025-01-26 22:08:17 +05:00
s809 23a1f0b025 Clean up code 2025-01-26 17:52:30 +05:00
s809 4a72e02550 Sign natives for mac 2025-01-25 18:25:58 +05:00
James Seibel 521bcdcc0f fix recalculate heightmap breaking stairs, slabs, and glass 2025-01-24 07:24:43 -06:00
s809 4eb20d5ce8 Fix using wrong path on linux 2025-01-24 11:28:51 +05:00
s809 3ad68aaf42 Merge branch 'main' into experimental/relocate_sqlite 2025-01-24 11:27:41 +05:00
s809 2a9a03771e Check if session is ready before ignoring local chunks 2025-01-24 11:20:49 +05:00
James Seibel 8f7823a4d2 Fix holes when moving with N-sized world gen/server side support 2025-01-23 19:45:25 -06:00
James Seibel cc4b965966 Speed up PhantomArrayListPool for large checkouts 2025-01-23 19:21:13 -06:00
s809 a6418de927 Merge branch 'main' into experimental/relocate_sqlite 2025-01-23 23:23:12 +05:00
s809 5303415d05 Ignore local chunks if realtime updates are enabled 2025-01-23 23:21:40 +05:00
s809 836515934f Fix column order check breaking on tiny columns 2 2025-01-23 00:18:38 +05:00
s809 228dc46d6b Fix column order check breaking on tiny columns 2025-01-23 00:15:18 +05:00
James Seibel a91f9670dc Show instructions to disable world gen progress message for short time 2025-01-21 07:49:36 -06:00
James Seibel 81313252f2 fix links in issue templates 2025-01-21 07:07:53 -06:00
s809 f65d411978 Fix task splitting causing generation of already generated sections 2025-01-21 17:26:58 +05:00
James Seibel 8c8a5ffeaf Fix cached RenderSource closing while in use 2025-01-20 21:51:24 -06:00
s809 68793fbe8d Process elf correctly 2025-01-21 00:33:43 +05:00
s809 d8401a8f49 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons into experimental/relocate_sqlite 2025-01-20 23:39:16 +05:00
s809 07ff00f7c9 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	coreSubProjects
2025-01-20 11:00:29 +05:00
s809 fadaff1113 Add generation bounds 2025-01-20 10:59:33 +05:00
James Seibel ff6bf7b4c9 Fix beacons disappearing and not updating correctly
Note: there appears to still be some off-by-one errors, although they happen relatively infrequently in my testing.
2025-01-19 17:48:36 -06:00
s809 082b1224a8 Relocate sqlite library 2025-01-19 03:26:53 +05:00
s809 bc475373fc Add a check for duplicate config command names and fix duplicate name 2025-01-18 16:27:16 +05:00
s809 498e958eca Add a check for duplicate config command names and fix duplicate name 2025-01-18 16:26:42 +05:00
s809 82e0cfe0b4 Fix server not shutting down, again 2025-01-15 23:34:48 +05:00
James Seibel 31d89e3349 Reduce duplicate warning logs when handling old worlds 2025-01-14 21:17:57 -06:00
James Seibel a3775c1f88 remove unneeded debug log 2025-01-14 19:48:30 -06:00
James Seibel 834269da67 Reduce holes when flying around a partially loaded world 2025-01-14 07:35:39 -06:00
James Seibel a9bebf03d5 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons 2025-01-14 07:04:24 -06:00
James Seibel 939f6304bf Put several queries in try-finally blocks 2025-01-14 07:04:11 -06:00
James Seibel a0b5cc7a5c Fix potential world gen error if center chunk is missing 2025-01-13 07:31:50 -06:00
85 changed files with 1766 additions and 584 deletions
+4
View File
@@ -26,6 +26,10 @@ Merged/
# Folder created by the buildAll scripts
buildAllJars/
relocate_natives/.venv/
relocate_natives/__pycache__/
relocate_natives/apple-codesign/
# file from notepad++
*.bak
+30 -6
View File
@@ -6,6 +6,7 @@ image: eclipse-temurin:21
# TODO: Make stages depend on what is in versionProperties
stages:
- build
- multiversion
- api
- pages
@@ -35,26 +36,49 @@ build:
stage: build
parallel:
matrix:
- MC_VER: ["1.16.5", "1.17.1", "1.18.2", "1.19.2", "1.19.4", "1.20.1", "1.20.2", "1.20.4", "1.20.6", "1.21.1", "1.21.3", "1.21.4"]
- MC_VER: ["1.16.5", "1.17.1", "1.18.2", "1.19.2", "1.19.4", "1.20.1", "1.20.2", "1.20.4", "1.20.6", "1.21.1", "1.21.3", "1.21.4", "1.21.5"]
script:
# 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/* ./Merged/* . || true
- cp ./build/forgix/* . || true
- mkdir -p ./builds
- cp ./fabric/build/libs/* ./forge/build/libs/* ./neoforge/build/libs/* ./builds || true
# When the archive is created, the merged jar will be the main jar and a subfolder named "builds" will contain the other jars
artifacts:
name: "NightlyBuild_${MC_VER}-${CI_COMMIT_SHORT_SHA}-${CI_COMMIT_TIMESTAMP}"
paths:
- ./*.jar
- ./builds/*.jar
exclude:
- ./*-all.jar
- ./*-dev.jar
- ./*-sources.jar
- ./builds/*-all.jar
- ./builds/*-dev.jar
- ./builds/*-sources.jar
expire_in: 14 days
when: always
extends: .build_java
multiversion:
stage: multiversion
needs: [build]
script:
# Create a semicolon-separated list of jar paths
- MC_VER_PATHS=$(find . -maxdepth 1 -name "*.jar" -type f | tr '\n' ';')
# Run the mergeVersions task
- ./gradlew mergeVersions -PmergeVersions="${MC_VER_PATHS}" -PinfoGitCommit="${CI_COMMIT_SHA}" -PinfoGitBranch="${CI_COMMIT_BRANCH}" -PinfoBuildSource="GitLab CI (${CI_PIPELINE_ID})" --gradle-user-home cache/;
# Clean up existing jars and make sure only the multiversion jar remains which will be uploaded
- rm -f ./*.jar
- cp ./build/forgix/multiversion/* .
artifacts:
name: "NightlyBuild_Multiversion-${CI_COMMIT_SHORT_SHA}-${CI_COMMIT_TIMESTAMP}"
paths:
- ./*.jar
expire_in: 30 days
when: always
extends: .build_java
api:
stage: api
needs: []
+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/jeseibel/distant-horizons/-/wikis/2-frequently-asked-questions/2-problems-and-solutions/Problems-and-Solutions)
[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/jeseibel/distant-horizons/-/wikis/2-frequently-asked-questions/4-mod-support/Mod-Support)
[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/jeseibel/distant-horizons/-/issues/)
[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` \
+1 -1
View File
@@ -1,4 +1,4 @@
- [ ] 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.
- [ ] 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.
1. **Describe the feature**:
@@ -1,3 +1,3 @@
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.
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.
2. **Describe the improvement**:
+84 -61
View File
@@ -1,3 +1,11 @@
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
plugins {
id "java"
@@ -5,15 +13,23 @@ plugins {
id "com.github.johnrengelman.shadow" version '8.1.1' apply false
// Plugin to create merged jars
id "io.github.pacifistmc.forgix" version "1.2.9"
id "io.github.pacifistmc.forgix" version "2.0.0-SNAPSHOT.2"
// 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.7-SNAPSHOT" apply false
id "dev.architectury.loom" version "1.10-SNAPSHOT" apply false
}
// Sets up Forgix for multiversion merging
forgix {
if (project.hasProperty('mergeVersions')) { // This is set using -PmergeVersions by the GitLab CI
multiversion {
inputJars = project.files(project.property('mergeVersions').toString().split(';'))
}
}
}
/**
* Creates the list of preprocessors to use.
@@ -59,41 +75,64 @@ 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 {
String loaderHyphenSeparatedList = ((String)gradle.builds_for).replaceAll(",", "-");
group = "com.seibel.distanthorizons"
mergedJarName = "DistantHorizons-${loaderHyphenSeparatedList}-${rootProject.versionStr}.jar"
if (findProject(":forge"))
forge {
jarLocation = "build/libs/DistantHorizons-forge-${rootProject.versionStr}.jar"
}
if (findProject(":neoforge"))
custom {
projectName = "neoforge"
jarLocation = "build/libs/DistantHorizons-neoforge-${rootProject.versionStr}.jar"
class NativeTransformer implements Transformer {
private final HashMap<String, String> replacements = new HashMap()
private final HashMap<String, byte[]> rewrittenFiles = new HashMap()
private var nativeRelocator
public File rootDir
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)
}
@Override
boolean canTransformResource(@Nonnull FileTreeElement element) {
return replacements.keySet().stream().anyMatch {
element.name.startsWith(it as String)
}
}
@Override
void transform(@Nonnull TransformerContext context) {
byte[] content = context.is.readAllBytes()
if (nativeRelocator == null) {
nativeRelocator = new NativeRelocator(rootDir.toPath().resolve("relocate_natives"))
}
if (findProject(":quilt"))
quilt {
jarLocation = "build/libs/DistantHorizons-quilt-${rootProject.versionStr}.jar"
}
try {
Map.Entry<String, String> pathReplacement = replacements.entrySet().stream().filter {
context.path.startsWith(it.key as String)
}.findFirst().orElseThrow()
removeDuplicate "com.seibel.distanthorizons"
String path = context.path.replace(pathReplacement.key as String, pathReplacement.value as String)
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)
}
}
}
subprojects { p ->
@@ -306,26 +345,20 @@ subprojects { p ->
// Logging
relocate "org.slf4j", "${librariesLocation}.slf4j"
// // Sqlite Database
// // James can't determine how to relocate the library correctly so this is commented out
// relocate ("org.sqlite", "${librariesLocation}.sqlite") {
// exclude("org/sqlite/core/NativeDB/**")
//
// exclude("org/sqlite/native/FreeBSD/**")
// exclude("org/sqlite/native/Linux-Android/**")
// exclude("org/sqlite/native/Linux-Musl/**")
// exclude("org/sqlite/native/Linux/arm/**")
// exclude("org/sqlite/native/Linux/aarch64/**")
// exclude("org/sqlite/native/Linux/armv6/**")
// exclude("org/sqlite/native/Linux/x86/**")
// exclude("org/sqlite/native/Linux/armv7/**")
// exclude("org/sqlite/native/Linux/ppc64/**")
// exclude("org/sqlite/native/Linux/riscv64/**")
// exclude("org/sqlite/native/Windows/armv7/**")
// exclude("org/sqlite/native/Windows/aarch64/**")
// exclude("org/sqlite/native/Windows/armv7/**")
// }
// 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
relocateNative "org/sqlite", "dh_sqlite"
relocateNative "org_sqlite", "dh_1sqlite"
}
// JOML
@@ -500,8 +533,9 @@ 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.name + "-" + rootProject.versionStr
version = (project == rootProject ? "" : project.name + "-") + rootProject.versionStr
group = rootProject.maven_group
// this is the text that appears at the top of the overview (home) page
@@ -666,14 +700,3 @@ allprojects { p ->
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)
+1 -1
View File
@@ -14,7 +14,7 @@ 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 -PmcVer="!version!"
call .\gradlew.bat clean
echo ==================== Building !version! ====================
call .\gradlew.bat build -PmcVer="!version!"
echo ==================== Merging !version! ====================
+211
View File
@@ -0,0 +1,211 @@
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"))
{
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);
}
}
@@ -1,6 +1,7 @@
package com.seibel.distanthorizons.common;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
import com.seibel.distanthorizons.core.network.event.internal.ProtocolErrorInternalEvent;
@@ -30,6 +31,16 @@ public abstract class AbstractPluginPacketSender implements IPluginPacketSender
public static final ResourceLocation WRAPPER_PACKET_RESOURCE = new ResourceLocation(ModInfo.RESOURCE_NAMESPACE, ModInfo.WRAPPER_PACKET_PATH);
#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)
@@ -41,7 +52,7 @@ public abstract class AbstractPluginPacketSender implements IPluginPacketSender
@Override
public abstract void sendToServer(AbstractNetworkMessage message);
public static AbstractNetworkMessage decodeMessage(FriendlyByteBuf in)
public AbstractNetworkMessage decodeMessage(FriendlyByteBuf in)
{
AbstractNetworkMessage message = null;
@@ -49,7 +60,7 @@ public abstract class AbstractPluginPacketSender implements IPluginPacketSender
{
in.markReaderIndex();
int protocolVersion = in.readShort();
int protocolVersion = this.forgeByteInProtocolVersion ? in.readByte() : in.readShort();
if (protocolVersion != ModInfo.PROTOCOL_VERSION)
{
return new IncompatibleMessageInternalEvent(protocolVersion);
@@ -82,11 +93,19 @@ public abstract class AbstractPluginPacketSender implements IPluginPacketSender
}
}
public static void encodeMessage(FriendlyByteBuf out, AbstractNetworkMessage message)
public void encodeMessage(FriendlyByteBuf out, AbstractNetworkMessage message)
{
// This is intentionally unhandled, because errors related to this are unlikely to appear in wild
Objects.requireNonNull(message);
out.writeShort(ModInfo.PROTOCOL_VERSION);
if (this.forgeByteInProtocolVersion)
{
out.writeByte(ModInfo.PROTOCOL_VERSION);
}
else
{
out.writeShort(ModInfo.PROTOCOL_VERSION);
}
try
{
@@ -2,7 +2,9 @@ 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;
@@ -12,6 +14,7 @@ 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
@@ -23,11 +26,11 @@ public record CommonPacketPayload(@Nullable AbstractNetworkMessage message) impl
@NotNull
@Override
public CommonPacketPayload decode(@NotNull FriendlyByteBuf in)
{ return new CommonPacketPayload(AbstractPluginPacketSender.decodeMessage(in)); }
{ return new CommonPacketPayload(PACKET_SENDER.decodeMessage(in)); }
@Override
public void encode(@NotNull FriendlyByteBuf out, CommonPacketPayload payload)
{ AbstractPluginPacketSender.encodeMessage(out, payload.message()); }
{ PACKET_SENDER.encodeMessage(out, payload.message()); }
}
@@ -9,6 +9,7 @@ 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;
@@ -40,6 +41,7 @@ public class ConfigCommand extends AbstractCommand
public LiteralArgumentBuilder<CommandSourceStack> buildCommand()
{
LiteralArgumentBuilder<CommandSourceStack> builder = literal("config");
HashSet<String> addedCommands = new HashSet<>();
for (AbstractConfigType<?, ?> type : ConfigBase.INSTANCE.entries)
{
@@ -56,6 +58,11 @@ public class ConfigCommand extends AbstractCommand
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" +
@@ -20,8 +20,6 @@
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
@@ -38,32 +36,48 @@ 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()
{
#if MC_VER < MC_1_19_2
return Minecraft.getInstance().getGame().getVersion().getId();
// 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";
#else
return SharedConstants.getCurrentVersion().getId();
ERROR MC version constant missing
#endif
}
}
@@ -43,6 +43,8 @@ import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
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;
@@ -84,26 +86,25 @@ public class BlockStateWrapper implements IBlockStateWrapper
public static HashSet<IBlockStateWrapper> rendererIgnoredCaveBlocks = null;
/** keep track of broken blocks so we don't log every time */
private static final HashSet<ResourceLocation> BrokenResourceLocations = new HashSet<>();
private static final HashSet<ResourceLocation> BROKEN_RESOURCE_LOCATIONS = 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;
/**
* 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;
/** Should be between {@link LodUtil#BLOCK_FULLY_OPAQUE} and {@link LodUtil#BLOCK_FULLY_OPAQUE} */
private final int opacity;
/** 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 Color mapColor;
@@ -160,9 +161,12 @@ public class BlockStateWrapper implements IBlockStateWrapper
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 blocks
String lowercaseSerial = this.serialString.toLowerCase();
boolean isBeaconBaseBlock = false;
for (int i = 0; i < LodUtil.BEACON_BASE_BLOCK_NAME_LIST.size(); i++)
{
@@ -198,6 +202,39 @@ public class BlockStateWrapper implements IBlockStateWrapper
this.beaconTintColor = beaconTintColor;
// allow/deny beacon beam passage
boolean allowsBeaconBeamPassage;
if (this.blockState != null)
{
// get block properties (defaults to the values used by air)
boolean canOcclude = this.getCanOcclude();
boolean propagatesSkyLightDown = this.getPropagatesSkyLightDown();
if (lowercaseSerial.contains("minecraft:bedrock"))
{
// bedrock is a special case fully opaque block that does allow beacons through
allowsBeaconBeamPassage = true;
}
else if (propagatesSkyLightDown || !canOcclude)
{
// stairs, cake, fences, etc.
allowsBeaconBeamPassage = true;
}
else
{
// non-opaque blocks (glass, mob spawners, etc.)
// all allow beacons through
allowsBeaconBeamPassage = (this.opacity != LodUtil.BLOCK_FULLY_OPAQUE);
}
}
else
{
// air allows beacons through
allowsBeaconBeamPassage = true;
}
this.allowsBeaconBeamPassage = allowsBeaconBeamPassage;
int mcColor = 0;
if (this.blockState != null)
{
@@ -341,13 +378,13 @@ public class BlockStateWrapper implements IBlockStateWrapper
//=================//
@Override
public int getOpacity()
public int getOpacity() { return this.opacity; }
private int calculateOpacity()
{
// use the cached opacity value if possible
if (this.opacity != -1)
{
return this.opacity;
}
// get block properties (defaults to the values used by air)
boolean canOcclude = this.getCanOcclude();
boolean propagatesSkyLightDown = this.getPropagatesSkyLightDown();
// this method isn't perfect, but works well enough for our use case
@@ -356,19 +393,20 @@ public class BlockStateWrapper implements IBlockStateWrapper
{
opacity = LodUtil.BLOCK_FULLY_TRANSPARENT;
}
else if (this.isLiquid() && !this.blockState.canOcclude())
else if (this.isLiquid() && !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;
}
#if MC_VER < MC_1_21_3
else if (this.blockState.propagatesSkylightDown(EmptyBlockGetter.INSTANCE, BlockPos.ZERO))
#else
else if (this.blockState.propagatesSkylightDown())
#endif
else if (propagatesSkyLightDown && !canOcclude)
{
// 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
@@ -378,9 +416,37 @@ public class BlockStateWrapper implements IBlockStateWrapper
}
this.opacity = opacity;
return this.opacity;
return 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; }
@@ -453,6 +519,8 @@ public class BlockStateWrapper implements IBlockStateWrapper
public boolean isBeaconBaseBlock() { return this.isBeaconBaseBlock; }
@Override
public boolean isBeaconTintBlock() { return this.beaconTintColor != null; }
@Override
public boolean allowsBeaconBeamPassage() { return this.allowsBeaconBeamPassage; }
@Override
public Color getMapColor() { return this.mapColor; }
@@ -596,9 +664,9 @@ public class BlockStateWrapper implements IBlockStateWrapper
if (block == null)
{
// shouldn't normally happen, but here to make the compiler happy
if (!BrokenResourceLocations.contains(resourceLocation))
if (!BROKEN_RESOURCE_LOCATIONS.contains(resourceLocation))
{
BrokenResourceLocations.add(resourceLocation);
BROKEN_RESOURCE_LOCATIONS.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.");
}
@@ -628,9 +696,9 @@ public class BlockStateWrapper implements IBlockStateWrapper
if (blockStatePropertiesString != null)
{
// we should have found a blockstate, but didn't
if (!BrokenResourceLocations.contains(resourceLocation))
if (!BROKEN_RESOURCE_LOCATIONS.contains(resourceLocation))
{
BrokenResourceLocations.add(resourceLocation);
BROKEN_RESOURCE_LOCATIONS.add(resourceLocation);
LOGGER.warn("Unable to find BlockState for Block [" + resourceLocation + "] with properties: [" + blockStatePropertiesString + "]. Using the default block state.");
}
}
@@ -40,11 +40,18 @@ import java.util.Random;
#endif
import net.minecraft.world.level.block.state.BlockState;
import org.apache.logging.log4j.Logger;
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
@@ -196,9 +203,7 @@ public class ClientBlockStateColorCache
List<BakedQuad> quads = null;
for (Direction direction : COLOR_RESOLUTION_DIRECTION_ORDER)
{
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(this.blockState).getQuads(this.blockState, direction, RANDOM);
quads = this.getQuadsForDirection(direction);
if (quads != null && !quads.isEmpty()
&& !(
this.blockState.getBlock() instanceof RotatedPillarBlock
@@ -212,19 +217,37 @@ public class ClientBlockStateColorCache
if (quads == null || quads.isEmpty())
{
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(this.blockState).getQuads(this.blockState, null, RANDOM);
quads = this.getUnculledQuads();
}
if (quads != null && !quads.isEmpty())
if (quads != null
&& !quads.isEmpty()
&& quads.get(0) != null)
{
this.needPostTinting = quads.get(0).isTinted();
this.needShade = quads.get(0).isShade();
this.tintIndex = quads.get(0).getTintIndex();
BakedQuad firstQuad = quads.get(0);
this.needPostTinting = firstQuad.isTinted();
#if MC_VER <= MC_1_21_4
this.needShade = firstQuad.isShade();
this.tintIndex = firstQuad.getTintIndex();
#else
this.needShade = firstQuad.shade();
this.tintIndex = firstQuad.tintIndex();
#endif
#if MC_VER < MC_1_17_1
this.baseColor = calculateColorFromTexture(
#if MC_VER < MC_1_17_1 quads.get(0).sprite,
#else quads.get(0).getSprite(), #endif
ColorMode.getColorMode(this.blockState.getBlock()));
firstQuad.sprite,
ColorMode.getColorMode(this.blockState.getBlock()));
#elif MC_VER < MC_1_21_5
this.baseColor = calculateColorFromTexture(
firstQuad.getSprite(),
ColorMode.getColorMode(this.blockState.getBlock()));
#else
this.baseColor = calculateColorFromTexture(
firstQuad.sprite(),
ColorMode.getColorMode(this.blockState.getBlock()));
#endif
}
else
{
@@ -232,8 +255,7 @@ public class ClientBlockStateColorCache
this.needPostTinting = false;
this.needShade = false;
this.tintIndex = 0;
this.baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(this.blockState),
ColorMode.getColorMode(this.blockState.getBlock()));
this.baseColor = this.getParticleIconColor();
}
}
else
@@ -242,8 +264,7 @@ public class ClientBlockStateColorCache
this.needPostTinting = true;
this.needShade = false;
this.tintIndex = 0;
this.baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(this.blockState),
ColorMode.getColorMode(this.blockState.getBlock()));
this.baseColor = this.getParticleIconColor();
}
this.isColorResolved = true;
@@ -253,6 +274,35 @@ public class ClientBlockStateColorCache
RESOLVE_LOCK.unlock();
}
}
@Nullable
private List<BakedQuad> getUnculledQuads() { return this.getQuadsForDirection(null); }
@Nullable
private List<BakedQuad> getQuadsForDirection(@Nullable Direction direction)
{
List<BakedQuad> quads = null;
#if MC_VER < MC_1_21_5
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(this.blockState).getQuads(this.blockState, direction, RANDOM);
#else
List<BlockModelPart> blockModelPartList = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(this.blockState).collectParts(RANDOM);
quads = new ArrayList<>();
if (blockModelPartList != null)
{
for (int i = 0; i < blockModelPartList.size(); i++)
{
// if direction is null this will return the unculled quads
quads.addAll(blockModelPartList.get(i).getQuads(direction));
}
}
#endif
return quads;
}
//TODO: Perhaps make this not just use the first frame?
private static int calculateColorFromTexture(TextureAtlasSprite texture, ColorMode colorMode)
{
@@ -383,6 +433,13 @@ public class ClientBlockStateColorCache
return (bias + (scale * t)) >>> 16;
}
private int getParticleIconColor()
{
return calculateColorFromTexture(
Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(this.blockState),
ColorMode.getColorMode(this.blockState.getBlock()));
}
//===============//
@@ -19,19 +19,16 @@
package com.seibel.distanthorizons.common.wrappers.block;
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.*;
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;
@@ -46,46 +43,74 @@ public class TintWithoutLevelOverrider implements BlockAndTintGetter
* but {@link Nullable} is there just in case.
*/
@Nullable
private final Biome biome;
#if MC_VER >= MC_1_18_2
public final Holder<Biome> biome;
#else
public final Biome biome;
#endif
/**
* Constructs the TintWithoutLevelOverrider, storing the provided Biome Holder for late-binding access.
*
* <p>Previously, this class might have immediately unwrapped the Holder like this:</p>
* <pre>{@code
* // Inside constructor (OLD WAY - PROBLEMATIC):
* Holder<Biome> biomeHolder = getTheHolderFromSomewhere();
* this.biome = biomeHolder.value(); // <-- PROBLEM HERE
* }</pre>
*
* <p>This approach is problematic because the {@link net.minecraft.core.Holder} system,
* particularly {@code Holder.Reference}, is designed for <strong>late binding</strong>. Here's why storing
* the Holder itself is now necessary:</p>
* <ol>
* <li>A {@code Holder.Reference<Biome>} might be created initially just with a
* {@link net.minecraft.resources.ResourceKey} (like {@code minecraft:plains}), but its actual
* {@link net.minecraft.core.Holder#value() value()} (the {@code Biome} object itself) might be {@code null}
* at construction time.</li>
* <li>Later, during game loading, registry population, or potentially due to modifications by other mods
* (e.g., Polytone), the system calls internal binding methods (like {@code bindValue(Biome)})
* on the {@code Holder} instance. This sets or <strong>updates</strong> the internal reference to the
* actual {@code Biome} object.</li>
* <li>Crucially, the binding process might assign a completely <strong>new</strong> {@code Biome} object
* instance to the {@code Holder} reference, replacing any previous one.</li>
* </ol>
*
* <p>If we unwrapped the {@code Holder} using {@code .value()} within the constructor (the old way),
* our class's internal {@code biome} field would permanently store a reference to whatever {@code Biome}
* object the {@code Holder} pointed to *at that exact moment*. It would have no link back to the
* {@code Holder} and would be unaware if the {@code Holder} was later updated to point to a different
* (or the initially missing) {@code Biome} object. This would lead to using stale or even {@code null} data.</p>
*
* <p>By storing the {@code Holder<Biome>} itself, this class can call {@link net.minecraft.core.Holder#value()}
* whenever the biome information is needed, ensuring it always retrieves the most current {@code Biome}
* instance associated with the holder at that time.</p>
*/
public TintWithoutLevelOverrider(BiomeWrapper biomeWrapper, IClientLevelWrapper clientLevelWrapper)
{
// try to get the wrapped biome
Biome unwrappedBiome = null;
if (biomeWrapper.biome != null)
#if MC_VER >= MC_1_18_2 Holder<Biome> #else Biome #endif biome = biomeWrapper.biome;
if (biome == null) // We are looking at the empty biome wrapper
{
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);
biome = plainsBiomeWrapper.biome;
}
}
this.biome = unwrappedBiome;
this.biome = biome;
}
@Override
public int getBlockTint(@NotNull BlockPos blockPos, @NotNull ColorResolver colorResolver)
{
if (this.biome != null)
{
return colorResolver.getColor(this.biome, blockPos.getX(), blockPos.getZ());
}
else
public int getBlockTint(@NotNull BlockPos blockPos, @NotNull ColorResolver colorResolver)
{
if (this.biome == null)
{
// hopefully unneeded debug color
return ColorUtil.CYAN;
}
return colorResolver.getColor(unwrap(biome), blockPos.getX(), blockPos.getZ());
}
private static Biome unwrap(#if MC_VER >= MC_1_18_2 Holder<Biome> #else Biome #endif biome)
@@ -278,14 +278,14 @@ public class ChunkWrapper implements IChunkWrapper
{
// is this block solid?
if (solidHeight == minInclusiveBuildHeight
&& block.isSolid())
&& block.isSolid())
{
solidHeight = y;
}
// is this block light blocking?
if (lightBlockingHeight == minInclusiveBuildHeight
&& block.getOpacity() != LodUtil.BLOCK_FULLY_TRANSPARENT)
&& block.getOpacity() != LodUtil.BLOCK_FULLY_TRANSPARENT)
{
lightBlockingHeight = y;
}
@@ -214,17 +214,29 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
@Override
public DhBlockPos getPlayerBlockPos()
{
BlockPos playerPos = this.getPlayer().blockPosition();
LocalPlayer player = this.getPlayer();
if (player == null)
{
return new DhBlockPos(0, 0, 0);
}
BlockPos playerPos = player.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(this.getPlayer().blockPosition());
ChunkPos playerPos = new ChunkPos(player.blockPosition());
#else
ChunkPos playerPos = this.getPlayer().chunkPosition();
ChunkPos playerPos = player.chunkPosition();
#endif
return new DhChunkPos(playerPos.x, playerPos.z);
}
@@ -19,7 +19,11 @@
package com.seibel.distanthorizons.common.wrappers.minecraft;
#if MC_VER < MC_1_21_5
import com.mojang.blaze3d.platform.GlStateManager;
#elif MC_VER == MC_1_21_5
import com.mojang.blaze3d.opengl.GlStateManager;
#endif
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
@@ -148,7 +152,10 @@ public class MinecraftGLWrapper implements IMinecraftGLWrapper
public void glBlendFunc(int sfactor, int dfactor)
{
GL32.glBlendFunc(sfactor, dfactor);
GlStateManager._blendFunc(sfactor, dfactor);
#if MC_VER < MC_1_21_5
GlStateManager._blendFunc(sfactor, dfactor);
#endif
}
/** @see GL32#glBlendFuncSeparate */
@Override
@@ -180,7 +187,15 @@ public class MinecraftGLWrapper implements IMinecraftGLWrapper
/** @see GL32#glDeleteBuffers(int) */
@Override
public void glDeleteBuffers(int buffer)
{ GlStateManager._glDeleteBuffers(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 //
@@ -71,6 +71,10 @@ import net.minecraft.world.phys.Vec3;
import org.apache.logging.log4j.Logger;
import org.joml.Vector4f;
#if MC_VER >= MC_1_21_5
import com.mojang.blaze3d.opengl.GlTexture;
import org.lwjgl.opengl.GL32;
#endif
/**
* A singleton that contains everything
@@ -102,8 +106,15 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
*/
public int finalLevelFrameBufferId = -1;
public boolean colorTextureCastFailLogged = false;
public boolean depthTextureCastFailLogged = false;
//=========//
// methods //
//=========//
@Override
public Vec3f getLookAtVector()
{
@@ -250,6 +261,26 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
private RenderTarget getRenderTarget() { return MC.getMainRenderTarget(); }
@Override
public boolean mcRendersToFrameBuffer()
{
#if MC_VER < MC_1_21_5
return true;
#else
return false;
#endif
}
@Override
public boolean runningLegacyOpenGL()
{
#if MC_VER <= MC_1_16_5
return true;
#else
return false;
#endif
}
@Override
public int getTargetFrameBuffer()
{
@@ -259,27 +290,87 @@ 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() { return this.getRenderTarget().getDepthTextureId(); }
public int getDepthTextureId()
{
#if MC_VER < MC_1_21_5
return this.getRenderTarget().getDepthTextureId();
#else
try
{
GlTexture glTexture = (GlTexture) this.getRenderTarget().getDepthTexture();
if (glTexture == null)
{
// shouldn't happen, but just in case
return 0;
}
return glTexture.glId();
}
catch (ClassCastException e)
{
// only log this error once per session
if (!this.depthTextureCastFailLogged)
{
this.depthTextureCastFailLogged = true;
LOGGER.error("Unable to cast render Target depth texture to GlTexture. MC or a rendering mod may have changed the object type.", e);
}
return 0;
}
#endif
}
@Override
public int getColorTextureId() { return this.getRenderTarget().getColorTextureId(); }
public int getColorTextureId()
{
#if MC_VER < MC_1_21_5
return this.getRenderTarget().getColorTextureId();
#else
try
{
GlTexture glTexture = (GlTexture) this.getRenderTarget().getColorTexture();
if (glTexture == null)
{
// shouldn't happen, but just in case
return 0;
}
return glTexture.glId();
}
catch (ClassCastException e)
{
// only log this error once per session
if (!this.colorTextureCastFailLogged)
{
this.colorTextureCastFailLogged = true;
LOGGER.error("Unable to cast render Target color texture to GlTexture. MC or a rendering mod may have changed the object type.", e);
}
return 0;
}
#endif
}
@Override
public int getTargetFrameBufferViewportWidth()
{
return getRenderTarget().viewWidth;
return this.getRenderTarget().viewWidth;
}
@Override
public int getTargetFrameBufferViewportHeight()
{
return getRenderTarget().viewHeight;
return this.getRenderTarget().viewHeight;
}
@Override
@@ -21,8 +21,10 @@ 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 org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL32;
import java.nio.ByteBuffer;
@@ -30,6 +32,7 @@ import java.nio.ByteBuffer;
public class LightMapWrapper implements ILightMapWrapper
{
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private int textureId = 0;
@@ -49,6 +52,7 @@ public class LightMapWrapper implements ILightMapWrapper
public void uploadLightmap(NativeImage image)
{
#if MC_VER < MC_1_21_5
int currentTexture = GLMC.getActiveTexture();
if (this.textureId == 0)
{
@@ -59,14 +63,27 @@ public class LightMapWrapper implements ILightMapWrapper
GLMC.glBindTexture(this.textureId);
}
image.upload(0, 0, 0, false);
GLMC.glBindTexture(currentTexture);
// 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
}
private void createLightmap(NativeImage image)
{
#if MC_VER < MC_1_21_5
this.textureId = GLMC.glGenTextures();
GLMC.glBindTexture(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)
@@ -25,7 +25,6 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.phys.Vec3;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -33,6 +32,10 @@ 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;
#if MC_VER <= MC_1_20_4
@@ -42,6 +45,7 @@ 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
@@ -49,7 +53,12 @@ import com.seibel.distanthorizons.core.util.ColorUtil;
public class ClientLevelWrapper implements IClientLevelWrapper
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger(ClientLevelWrapper.class.getSimpleName());
private static final ConcurrentHashMap<ClientLevel, ClientLevelWrapper> LEVEL_WRAPPER_BY_CLIENT_LEVEL = new ConcurrentHashMap<>(); // TODO can leak
/**
* weak references are to prevent rare issues
* where, upon world closure, some levels aren't shutdown/removed properly
* and/or for servers were the level object isn't consistent
*/
private static final Map<ClientLevel, WeakReference<ClientLevelWrapper>> LEVEL_WRAPPER_REF_BY_CLIENT_LEVEL = Collections.synchronizedMap(new WeakHashMap<>());
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
private static final Minecraft MINECRAFT = Minecraft.getInstance();
@@ -72,9 +81,9 @@ public class ClientLevelWrapper implements IClientLevelWrapper
//===============//
// wrapper logic //
//===============//
//==================//
// instance methods //
//==================//
public static IClientLevelWrapper getWrapper(@NotNull ClientLevel level) { return getWrapper(level, false); }
@@ -96,7 +105,19 @@ public class ClientLevelWrapper implements IClientLevelWrapper
}
}
return LEVEL_WRAPPER_BY_CLIENT_LEVEL.computeIfAbsent(level, ClientLevelWrapper::new);
return LEVEL_WRAPPER_REF_BY_CLIENT_LEVEL.compute(level, (newLevel, levelRef) ->
{
if (levelRef != null)
{
ClientLevelWrapper oldLevelWrapper = levelRef.get();
if (oldLevelWrapper != null)
{
return levelRef;
}
}
return new WeakReference<>(new ClientLevelWrapper(newLevel));
}).get();
}
@Nullable
@@ -265,7 +286,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
@Override
public void onUnload()
{
LEVEL_WRAPPER_BY_CLIENT_LEVEL.remove(this.level);
LEVEL_WRAPPER_REF_BY_CLIENT_LEVEL.remove(this.level);
this.parentDhLevel = null;
}
@@ -28,72 +28,80 @@ import net.minecraft.world.level.dimension.DimensionType;
/**
* @author James Seibel
* @version 2022-9-16
*/
public class DimensionTypeWrapper implements IDimensionTypeWrapper
{
private static final ConcurrentMap<DimensionType, DimensionTypeWrapper> dimensionTypeWrapperMap = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, DimensionTypeWrapper> DIMENSION_WRAPPER_BY_NAME = new ConcurrentHashMap<>();
private final DimensionType dimensionType;
public DimensionTypeWrapper(DimensionType dimensionType)
{
this.dimensionType = dimensionType;
}
//=============//
// Constructor //
//=============//
public DimensionTypeWrapper(DimensionType dimensionType) { this.dimensionType = dimensionType; }
public static DimensionTypeWrapper getDimensionTypeWrapper(DimensionType dimensionType)
{
//first we check if the biome has already been wrapped
if (dimensionTypeWrapperMap.containsKey(dimensionType) && dimensionTypeWrapperMap.get(dimensionType) != null)
String dimName = getName(dimensionType);
// check if the dimension has already been wrapped
if (DIMENSION_WRAPPER_BY_NAME.containsKey(dimName)
&& DIMENSION_WRAPPER_BY_NAME.get(dimName) != null)
{
return dimensionTypeWrapperMap.get(dimensionType);
return DIMENSION_WRAPPER_BY_NAME.get(dimName);
}
//if it hasn't been created yet, we create it and save it in the map
// create the missing wrapper
DimensionTypeWrapper dimensionTypeWrapper = new DimensionTypeWrapper(dimensionType);
dimensionTypeWrapperMap.put(dimensionType, dimensionTypeWrapper);
//we return the newly created wrapper
DIMENSION_WRAPPER_BY_NAME.put(dimName, dimensionTypeWrapper);
return dimensionTypeWrapper;
}
public static void clearMap()
{
dimensionTypeWrapperMap.clear();
}
public static void clearMap() { DIMENSION_WRAPPER_BY_NAME.clear(); }
private String getDimensionName()
//=================//
// wrapper methods //
//=================//
@Override
public String getName() { return getName(this.dimensionType); }
public static String getName(DimensionType dimensionType)
{
#if MC_VER <= MC_1_16_5
// effectsLocation() is marked as client only, so using the backing field directly
return dimensionType.effectsLocation.getPath();
#else
return this.dimensionType.effectsLocation().getPath();
return dimensionType.effectsLocation().getPath();
#endif
}
@Override
public boolean hasCeiling()
{
return this.dimensionType.hasCeiling();
}
public boolean hasCeiling() { return this.dimensionType.hasCeiling(); }
@Override
public boolean hasSkyLight()
{
return this.dimensionType.hasSkyLight();
}
public boolean hasSkyLight() { return this.dimensionType.hasSkyLight(); }
@Override
public Object getWrappedMcObject()
{
return this.dimensionType;
}
public Object getWrappedMcObject() { return this.dimensionType; }
// there's definitely a better way of doing this, but it should work well enough for now
@Override
public boolean isTheEnd() { return this.getDimensionName().equalsIgnoreCase("the_end"); }
public boolean isTheEnd() { return this.getName().equalsIgnoreCase("the_end"); }
@Override
public double getCoordinateScale() { return this.dimensionType.coordinateScale(); }
//================//
// base overrides //
//================//
@Override
public boolean equals(Object obj)
@@ -105,9 +113,10 @@ public class DimensionTypeWrapper implements IDimensionTypeWrapper
else
{
DimensionTypeWrapper other = (DimensionTypeWrapper) obj;
return other.getDimensionName().equals(this.getDimensionName());
return other.getName().equals(this.getName());
}
}
}
@@ -20,6 +20,10 @@
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;
@@ -38,6 +42,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
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;
@@ -57,7 +62,11 @@ import org.apache.logging.log4j.Logger;
public class ServerLevelWrapper implements IServerLevelWrapper
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final ConcurrentHashMap<ServerLevel, ServerLevelWrapper> LEVEL_WRAPPER_BY_SERVER_LEVEL = new ConcurrentHashMap<>();
/**
* weak references are to prevent rare issues
* where, upon world closure, some levels aren't shutdown/removed properly
*/
private static final Map<ServerLevel, WeakReference<ServerLevelWrapper>> LEVEL_WRAPPER_REF_BY_SERVER_LEVEL = Collections.synchronizedMap(new WeakHashMap<>());
private final ServerLevel level;
@Deprecated // TODO circular references are bad
@@ -70,15 +79,29 @@ public class ServerLevelWrapper implements IServerLevelWrapper
//==============//
public static ServerLevelWrapper getWrapper(ServerLevel level)
{ return LEVEL_WRAPPER_BY_SERVER_LEVEL.computeIfAbsent(level, ServerLevelWrapper::new); }
{
return LEVEL_WRAPPER_REF_BY_SERVER_LEVEL.compute(level, (newLevel, levelRef) ->
{
if (levelRef != null)
{
ServerLevelWrapper oldLevelWrapper = levelRef.get();
if (oldLevelWrapper != null)
{
return levelRef;
}
}
return new WeakReference<>(new ServerLevelWrapper(newLevel));
}).get();
}
public ServerLevelWrapper(ServerLevel level) { this.level = level; }
//=========//
// methods //
//=========//
//==================//
// instance methods //
//==================//
@Override
public File getMcSaveFolder()
@@ -93,10 +116,12 @@ public class ServerLevelWrapper implements IServerLevelWrapper
@Override
public String getWorldFolderName()
{
#if MC_VER >= MC_1_17_1
return this.level.getServer().getWorldScreenshotFile().get().getParent().getFileName().toString();
#else // <= 1.16.5
return this.level.getServer().getWorldScreenshotFile().getParentFile().getName();
// Need specifically overworld since it's the only dimension that is stored in a 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
}
@@ -179,7 +204,7 @@ public class ServerLevelWrapper implements IServerLevelWrapper
public ServerLevel getWrappedMcObject() { return this.level; }
@Override
public void onUnload() { LEVEL_WRAPPER_BY_SERVER_LEVEL.remove(this.level); }
public void onUnload() { LEVEL_WRAPPER_REF_BY_SERVER_LEVEL.remove(this.level); }
@Override
@@ -113,7 +113,11 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
new ConfigBasedLogger(LogManager.getLogger("LodWorldGen"),
() -> Config.Common.Logging.logWorldGenLoadEvent.get());
#if MC_VER < MC_1_21_5
private static final TicketType<ChunkPos> DH_SERVER_GEN_TICKET = TicketType.create("dh_server_gen_ticket", Comparator.comparingLong(ChunkPos::toLong));
#else
private static final TicketType DH_SERVER_GEN_TICKET = new TicketType(/* timeout, 0 = disabled*/0L, /* persist */ false, TicketType.TicketUse.LOADING);
#endif
private static final IModChecker MOD_CHECKER = SingletonInjector.INSTANCE.get(IModChecker.class);
@@ -125,7 +129,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
* pull chunks using their async method), or if there
* was an issue with the sync pulling method.
*/
private boolean pullExistingChunkAsync = false;
private boolean pullExistingChunkUsingMcAsyncMethod = false;
@@ -241,19 +245,10 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
if (MOD_CHECKER.isModLoaded("c2me"))
{
EVENT_LOGGER.info("C2ME detected: DH's pre-existing chunk accessing will use async methods handled by C2ME.");
this.pullExistingChunkAsync = true;
EVENT_LOGGER.info("C2ME detected: DH's pre-existing chunk accessing will use methods handled by C2ME.");
this.pullExistingChunkUsingMcAsyncMethod = true;
}
//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 (ioWorker.storage != null)
this.params = new GlobalParameters(serverlevel);
}
@@ -342,6 +337,8 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
// world generation //
//==================//
// TODO this is already being run on a generator thread,
// why are we passing in an executor?
/** @throws RejectedExecutionException if the given {@link Executor} is cancelled. */
public CompletableFuture<Void> generateLodFromListAsync(GenerationEvent genEvent, Executor executor) throws RejectedExecutionException, InterruptedException
{
@@ -385,10 +382,12 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
.map((chunkPos) -> this.createEmptyOrPreExistingChunkAsync(chunkPos.x, chunkPos.z, chunkSkyLightingByDhPos, chunkBlockLightingByDhPos, generatedChunkByDhPos))
.toArray(CompletableFuture[]::new);
// join to prevent an issue where DH queues too many tasks or something(?)
// also allows file IO to run in parallel so no one thread is waiting on disk IO (this is only an issue when C2ME is present)
CompletableFuture.allOf(readFutures).join();
// future chain for generation
return CompletableFuture.allOf(readFutures)
.thenRunAsync(() ->
return CompletableFuture.runAsync(() ->
{
// offset 1 chunk in both X and Z direction so we can generate an even number of chunks wide
// while still submitting an odd number width to MC's internal generators
@@ -424,7 +423,8 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
ChunkAccess centerChunk = regionChunks.stream()
.filter((chunk) -> chunk.getPos().x == centerX && chunk.getPos().z == centerZ)
.findFirst().get();
.findFirst()
.orElseGet(() -> regionChunks.getFirst());
genEvent.refreshTimeout();
DhLitWorldGenRegion region = new DhLitWorldGenRegion(
@@ -591,6 +591,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
return newChunk;
});
}
// TODO FIXME this method can be called up to 25 times for the same chunk position, why?
private CompletableFuture<CompoundTag> getChunkNbtDataAsync(ChunkPos chunkPos)
{
ServerLevel level = this.params.level;
@@ -609,7 +610,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
#else
// storage will be null if C2ME is installed
if (!this.pullExistingChunkAsync && ioWorker.storage != null)
if (!this.pullExistingChunkUsingMcAsyncMethod && ioWorker.storage != null)
{
try
{
@@ -623,7 +624,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
// ioWorker.storage
// but just in case
EVENT_LOGGER.error("Unexpected issue pulling pre-existing chunk ["+chunkPos+"], falling back to async chunk pulling. This may cause server-tick lag.", e);
this.pullExistingChunkAsync = true;
this.pullExistingChunkUsingMcAsyncMethod = true;
// try again now using the async method
return this.getChunkNbtDataAsync(chunkPos);
@@ -632,32 +633,42 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
else
{
// log if we unexpectedly weren't able to run the sync chunk pulling
if (!this.pullExistingChunkAsync)
if (!this.pullExistingChunkUsingMcAsyncMethod)
{
// this shouldn't happen, but just in case
EVENT_LOGGER.info("Unable to pull pre-existing chunk using synchronous method. Falling back to async method. this may cause server-tick lag.");
this.pullExistingChunkAsync = true;
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 -> optional.orElse(null))
.exceptionally((throwable) ->
{
// unwrap the CompletionException if necessary
Throwable actualThrowable = throwable;
while (actualThrowable instanceof CompletionException completionException)
.thenApply(optional ->
{
actualThrowable = completionException.getCause();
}
LOAD_LOGGER.warn("DistantHorizons: Couldn't load or make chunk ["+chunkPos+"], error: ["+actualThrowable.getMessage()+"].", actualThrowable);
return null;
});
// 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();
}
LOAD_LOGGER.warn("DistantHorizons: Couldn't load or make chunk ["+chunkPos+"], error: ["+actualThrowable.getMessage()+"].", actualThrowable);
return null;
});
}
#endif
}
@@ -871,7 +882,11 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
chunkLevel = generateUpToFeatures ? ChunkLevel.byStatus(ChunkStatus.FEATURES) : 33;
#endif
#if MC_VER < MC_1_21_5
level.getChunkSource().distanceManager.addTicket(DH_SERVER_GEN_TICKET, pos, chunkLevel, pos);
#else
level.getChunkSource().addTicketWithRadius(DH_SERVER_GEN_TICKET, pos, 0);
#endif
level.getChunkSource().distanceManager.runAllUpdates(level.getChunkSource().chunkMap); // probably not the most optimal to run updates here, but fast enough
ChunkHolder holder = level.getChunkSource().chunkMap.getUpdatingChunkIfPresent(pos.toLong());
if (holder == null)
@@ -908,7 +923,11 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
chunkLevel = chunkWasGeneratedUpToFeatures ? ChunkLevel.byStatus(ChunkStatus.FEATURES) : 33;
#endif
#if MC_VER < MC_1_21_5
level.getChunkSource().distanceManager.removeTicket(DH_SERVER_GEN_TICKET, pos, chunkLevel, pos);
#else
level.getChunkSource().removeTicketWithRadius(DH_SERVER_GEN_TICKET, pos, 0);
#endif
// mitigate OOM issues in vanilla chunk system: see https://github.com/pop4959/Chunky/pull/383
level.getChunkSource().chunkMap.tick(() -> false);
@@ -29,6 +29,9 @@ 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 it.unimi.dsi.fastutil.shorts.ShortList;
@@ -83,11 +86,13 @@ import net.minecraft.world.level.chunk.status.ChunkType;
#endif
import net.minecraft.world.level.material.Fluid;
import org.jetbrains.annotations.Nullable;
public class ChunkLoader
{
private static boolean zeroChunkPosErrorLogged = false;
private static final AtomicBoolean ZERO_CHUNK_POS_ERROR_LOGGED_REF = new AtomicBoolean(false);
#if MC_VER >= MC_1_19_2
private static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState());
@@ -103,6 +108,8 @@ public class ChunkLoader
private static boolean lightingSectionErrorLogged = false;
private static final ConcurrentHashMap<String, Object> LOGGED_ERROR_MESSAGE_MAP = new ConcurrentHashMap<>();
//============//
@@ -117,19 +124,16 @@ public class ChunkLoader
CompoundTag tagLevel = chunkData;
#endif
ChunkPos actualPos = new ChunkPos(tagLevel.getInt("xPos"), tagLevel.getInt("zPos"));
int chunkX = tagGetInt(tagLevel,"xPos");
int chunkZ = tagGetInt(tagLevel, "zPos");
ChunkPos actualPos = new ChunkPos(chunkX, chunkZ);
if (!Objects.equals(chunkPos, actualPos))
{
#if MC_VER >= MC_1_18_2
if (actualPos.equals(ChunkPos.ZERO))
#else
if (actualPos.equals(ChunkPos.INVALID_CHUNK_POS))
#endif
if (chunkX == 0 && chunkZ == 0)
{
if (!zeroChunkPosErrorLogged)
if (!ZERO_CHUNK_POS_ERROR_LOGGED_REF.getAndSet(true))
{
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" +
@@ -171,14 +175,27 @@ public class ChunkLoader
#endif
long inhabitedTime = tagLevel.getLong("InhabitedTime");
long inhabitedTime = tagGetLong(tagLevel, "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");
UpgradeData upgradeData;
#if MC_VER < MC_1_17_1
upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10)
? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA))
: UpgradeData.EMPTY;
#elif MC_VER < MC_1_21_5
upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10)
? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level)
: UpgradeData.EMPTY;
#else
upgradeData = tagLevel.contains(TAG_UPGRADE_DATA)
? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level)
: UpgradeData.EMPTY;
#endif
boolean isLightOn = tagGetBoolean(tagLevel, "isLightOn");
#if MC_VER < MC_1_18_2
ChunkBiomeContainer chunkBiomeContainer = new ChunkBiomeContainer(
level.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY)#if MC_VER >= MC_1_17_1 , level #endif ,
@@ -195,17 +212,21 @@ public class ChunkLoader
: 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
#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);
#elif MC_VER < MC_1_21_4
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);
#else
// do we need the ticks for what we're doing?
LevelChunkTicks<Block> blockTicks = new LevelChunkTicks<>();
LevelChunkTicks<Fluid> fluidTicks = new LevelChunkTicks<>();
#endif
#endif
LevelChunkSection[] levelChunkSections = readSections(level, chunkPos, tagLevel);
@@ -215,7 +236,6 @@ public class ChunkLoader
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
@@ -249,18 +269,23 @@ public class ChunkLoader
biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getOrThrow(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)
ListTag tagSections = tagGetListTag(chunkData, "Sections", 10);
if (tagSections == null || tagSections.isEmpty())
{
CompoundTag tagSection = tagSections.getCompound(j);
int sectionYPos = tagSection.getByte("Y");
tagSections = tagGetListTag(chunkData, "sections", 10);
}
if (tagSections != null)
{
for (int j = 0; j < tagSections.size(); ++j)
{
CompoundTag tagSection = tagGetCompoundTag(tagSections, j);
int sectionYPos = tagGetByte(tagSection, "Y");
#if MC_VER < MC_1_18_2
if (tagSection.contains("Palette", 9) && tagSection.contains("BlockStates", 12))
@@ -274,63 +299,89 @@ public class ChunkLoader
= levelChunkSection;
}
#else
int sectionId = level.getSectionIndexFromSectionY(sectionYPos);
if (sectionId >= 0 && sectionId < chunkSections.length)
{
PalettedContainer<BlockState> blockStateContainer;
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;
PalettedContainer<Holder<Biome>> biomeContainer;
#endif
boolean containsBlockStates;
#if MC_VER < MC_1_21_5
containsBlockStates = tagSection.contains("block_states", 10);
#else
containsBlockStates = tagSection.contains("block_states");
#endif
if (containsBlockStates)
{
#if MC_VER < MC_1_20_6
blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states"))
.promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
.getOrThrow(false, (message) -> logParsingWarningOnce(message));
#else
blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states"))
.promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
.getOrThrow((message) -> logErrorAndReturnException(message));
#endif
}
else
{
blockStateContainer = new PalettedContainer<BlockState>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
}
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<BlockState>(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)
? biomeCodec.parse(NbtOps.INSTANCE, tagSection.getCompound("biomes")).promotePartial(string -> logErrors(chunkPos, sectionYPos, string)).getOrThrow(false, (message) -> logWarningOnce(message))
: new PalettedContainer<Biome>(biomes, biomes.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
#else
if (tagSection.contains("biomes", 10))
{
biomeContainer =
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
}
else
{
biomeContainer = new PalettedContainer<Holder<Biome>>(biomes.asHolderIdMap(),
boolean containsBiomes;
#if MC_VER < MC_1_21_5
containsBiomes = tagSection.contains("biomes", 10);
#else
containsBiomes = tagSection.contains("biomes");
#endif
if (containsBiomes)
{
#if MC_VER < MC_1_20_6
biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes"))
.promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string))
.getOrThrow(false, (message) -> logParsingWarningOnce(message));
#else
biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes"))
.promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string))
.getOrThrow((message) -> logErrorAndReturnException(message));
#endif
}
else
{
biomeContainer = new PalettedContainer<Holder<Biome>>(biomes.asHolderIdMap(),
#if MC_VER < MC_1_21_3
biomes.getHolderOrThrow(Biomes.PLAINS),
#else
biomes.getOrThrow(Biomes.PLAINS),
biomes.getOrThrow(Biomes.PLAINS),
#endif
PalettedContainer.Strategy.SECTION_BIOMES);
}
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);
chunkSections[sectionId] = new LevelChunkSection(blockStateContainer, biomeContainer);
#endif
}
}
#endif
}
}
return chunkSections;
}
@@ -340,39 +391,54 @@ public class ChunkLoader
#else ChunkType #endif
readChunkType(CompoundTag tagLevel)
{
ChunkStatus chunkStatus = ChunkStatus.byName(tagLevel.getString("Status"));
ChunkStatus chunkStatus = ChunkStatus.byName(tagGetString(tagLevel,"Status"));
if (chunkStatus != null)
{
return chunkStatus.getChunkType();
}
return
#if MC_VER <= MC_1_20_4 ChunkStatus.ChunkType.PROTOCHUNK;
#else ChunkType.PROTOCHUNK; #endif
#if MC_VER <= MC_1_20_4
return ChunkStatus.ChunkType.PROTOCHUNK;
#else
return ChunkType.PROTOCHUNK;
#endif
}
private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData)
{
CompoundTag tagHeightmaps = chunkData.getCompound("Heightmaps");
CompoundTag tagHeightmaps = tagGetCompoundTag(chunkData, "Heightmaps");
for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter())
{
String heightmap = type.getSerializationKey();
#if MC_VER < MC_1_21_5
if (tagHeightmaps.contains(heightmap, 12))
{
chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmap));
}
#else
if (tagHeightmaps.contains(heightmap))
{
Optional<long[]> optionalHeightmap = tagHeightmaps.getLongArray(heightmap);
if (optionalHeightmap.isPresent())
{
chunk.setHeightmap(type, optionalHeightmap.get());
}
}
#endif
}
Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter());
}
private static void readPostPocessings(LevelChunk chunk, CompoundTag chunkData)
{
ListTag tagPostProcessings = chunkData.getList("PostProcessing", 9);
ListTag tagPostProcessings = tagGetListTag(chunkData,"PostProcessing", 9);
for (int i = 0; i < tagPostProcessings.size(); ++i)
{
ListTag listTag3 = tagPostProcessings.getList(i);
ListTag listTag3 = tagGetListTag(tagPostProcessings, i);
for (int j = 0; j < listTag3.size(); ++j)
{
#if MC_VER < MC_1_21_3
chunk.addPackedPostProcess(listTag3.getShort(j), i);
#else
chunk.addPackedPostProcess(ShortList.of(listTag3.getShort(j)), i);
chunk.addPackedPostProcess(ShortList.of(tagGetShort(listTag3, j)), i);
#endif
}
}
@@ -381,16 +447,38 @@ public class ChunkLoader
private static BlendingData readBlendingData(CompoundTag chunkData)
{
BlendingData blendingData = null;
if (chunkData.contains("blending_data", 10))
boolean containsBlendingData;
#if MC_VER < MC_1_21_5
containsBlendingData = chunkData.contains("blending_data", 10);
#else
containsBlendingData = chunkData.contains("blending_data");
#endif
if (containsBlendingData)
{
@SuppressWarnings({"unchecked", "rawtypes"})
Dynamic<CompoundTag> blendingDataTag = new Dynamic(NbtOps.INSTANCE, chunkData.getCompound("blending_data"));
#if MC_VER < MC_1_21_3
blendingData = BlendingData.CODEC.parse(blendingDataTag).resultOrPartial(LOGGER::error).orElse(null);
#else
blendingData = BlendingData.unpack(BlendingData.Packed.CODEC.parse(blendingDataTag).resultOrPartial(LOGGER::error).orElse(null));
#endif
try
{
#if MC_VER < MC_1_21_3
blendingData = BlendingData.CODEC.parse(blendingDataTag).resultOrPartial((message) -> logParsingWarningOnce(message)).orElse(null);
#else
blendingData = BlendingData.unpack(BlendingData.Packed.CODEC.parse(blendingDataTag).resultOrPartial((message) -> logParsingWarningOnce(message)).orElse(null));
#endif
}
catch (Exception e)
{
String message = e.getMessage();
if (message == null || message.trim().isEmpty())
{
message = "Failed to parse blending data";
}
logParsingWarningOnce(message, e);
}
}
return blendingData;
}
@@ -467,33 +555,37 @@ public class ChunkLoader
// if null all lights = 0
byte[] blockLightNibbleArray = chunkSectionCompoundTag.getByteArray("BlockLight");
byte[] skyLightNibbleArray = chunkSectionCompoundTag.getByteArray("SkyLight");
byte[] blockLightNibbleArray = tagGetByteArray(chunkSectionCompoundTag, "BlockLight");
byte[] skyLightNibbleArray = tagGetByteArray(chunkSectionCompoundTag, "SkyLight");
// if any sky light was found then all lights above will be max brightness
if (skyLightNibbleArray.length != 0)
if (blockLightNibbleArray != null
&& skyLightNibbleArray != null)
{
foundSkyLight = true;
}
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
// if any sky light was found then all lights above will be max brightness
if (skyLightNibbleArray.length != 0)
{
// chunk sections are also 16 blocks tall
for (int relY = 0; relY < LodUtil.CHUNK_WIDTH; relY++)
foundSkyLight = true;
}
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
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)
// chunk sections are also 16 blocks tall
for (int relY = 0; relY < LodUtil.CHUNK_WIDTH; relY++)
{
skyLight = LodUtil.MAX_MC_LIGHT;
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);
}
int y = relY + (sectionIndex * LodUtil.CHUNK_WIDTH) + ChunkWrapper.getInclusiveMinBuildHeight(chunk);
blockLightStorage.set(relX, y, relZ, blockLight);
skyLightStorage.set(relX, y, relZ, skyLight);
}
}
}
@@ -515,13 +607,192 @@ public class ChunkLoader
}
}
//=========//
// logging //
//=========//
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.");
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)
{
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.");
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);
}
//====================//
// tag helper methods //
//====================//
// TODO move into separate file (this file is getting long)
// these tag helpers are to simplify tag accessing between MC versions
/** defaults to "false" if the tag isn't present */
private static boolean tagGetBoolean(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getBoolean(key);
#else
return tag.getBoolean(key).orElse(false);
#endif
}
/** defaults to "0" if the tag isn't present */
private static byte tagGetByte(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getByte(key);
#else
return tag.getByte(key).orElse((byte)0);
#endif
}
/** defaults to "0" if the tag isn't present */
private static short tagGetShort(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getShort(index);
#else
return tag.getShort(index).orElse((short)0);
#endif
}
/** defaults to "0" if the tag isn't present */
private static int tagGetInt(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getInt(key);
#else
return tag.getInt(key).orElse(0);
#endif
}
/** defaults to "0" if the tag isn't present */
private static long tagGetLong(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getInt(key);
#else
return tag.getLong(key).orElse(0L);
#endif
}
/** defaults to null if the tag isn't present */
private static String tagGetString(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getString(key);
#else
return tag.getString(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
private static byte[] tagGetByteArray(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getByteArray(key);
#else
return tag.getByteArray(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
private static CompoundTag tagGetCompoundTag(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getCompound(key);
#else
return tag.getCompound(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
private static CompoundTag tagGetCompoundTag(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getCompound(index);
#else
return tag.getCompound(index).orElse(null);
#endif
}
/**
* defaults to null if the tag isn't present
* @param elementType unused after MC 1.21.5
*/
@Nullable
private static ListTag tagGetListTag(CompoundTag tag, String key, int elementType)
{
#if MC_VER < MC_1_21_5
return tag.getList(key, elementType);
#else
return tag.getList(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
private static ListTag tagGetListTag(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getList(index);
#else
return tag.getList(index).orElse(null);
#endif
}
@@ -229,8 +229,16 @@ 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
// This is for post ticking for water on gen and stuff like that. Not enabled
// for now.
// if (blockState.hasPostProcess(this, blockPos))
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id "fabric-loom" version "1.8-SNAPSHOT"
id "fabric-loom" version "1.10-SNAPSHOT"
}
loom {
@@ -33,6 +33,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.ISodiumAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.util.math.Mat4f;
@@ -82,6 +83,7 @@ public class FabricClientProxy implements AbstractModInitializer.IEventProxy
{
private final ClientApi clientApi = ClientApi.INSTANCE;
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final AbstractPluginPacketSender PACKET_SENDER = (AbstractPluginPacketSender) SingletonInjector.INSTANCE.get(IPluginPacketSender.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
// TODO we shouldn't be filtering keys on the Forge/Fabric side, only in ClientApi
@@ -316,9 +318,7 @@ public class FabricClientProxy implements AbstractModInitializer.IEventProxy
#else
ClientPlayNetworking.registerGlobalReceiver(AbstractPluginPacketSender.WRAPPER_PACKET_RESOURCE, (client, handler, buffer, packetSender) ->
{
// Forge packet ID
buffer.readByte();
AbstractNetworkMessage message = AbstractPluginPacketSender.decodeMessage(buffer);
AbstractNetworkMessage message = PACKET_SENDER.decodeMessage(buffer);
if (message != null)
{
ClientApi.INSTANCE.pluginMessageReceived(message);
@@ -90,8 +90,8 @@ public class FabricMain extends AbstractModInitializer implements ClientModIniti
{
ModAccessorInjector.INSTANCE.bind(ISodiumAccessor.class, new SodiumAccessor());
// If sodium is installed Indium is also necessary in order to use the Fabric rendering API
if (!modChecker.isModLoaded("indium"))
// If sodium is installed Indium is also necessary for versions 0.5 and less in order to use the Fabric rendering API
if (!modChecker.isModLoaded("indium") && SodiumAccessor.isSodiumV5OrLess)
{
String indiumMissingMessage = ModInfo.READABLE_NAME + " needs Indium to work with Sodium.\nPlease download Indium from https://modrinth.com/mod/indium";
LOGGER.fatal(indiumMissingMessage);
@@ -22,9 +22,7 @@ public class FabricPluginPacketSender extends AbstractPluginPacketSender
ClientPlayNetworking.send(new CommonPacketPayload(message));
#else // < 1.20.6
FriendlyByteBuf buffer = PacketByteBufs.create();
// Forge packet ID
buffer.writeByte(0);
AbstractPluginPacketSender.encodeMessage(buffer, message);
this.encodeMessage(buffer, message);
ClientPlayNetworking.send(WRAPPER_PACKET_RESOURCE, buffer);
#endif
}
@@ -36,9 +34,7 @@ public class FabricPluginPacketSender extends AbstractPluginPacketSender
ServerPlayNetworking.send(serverPlayer, new CommonPacketPayload(message));
#else // < 1.20.6
FriendlyByteBuf buffer = PacketByteBufs.create();
// Forge packet ID
buffer.writeByte(0);
AbstractPluginPacketSender.encodeMessage(buffer, message);
this.encodeMessage(buffer, message);
ServerPlayNetworking.send(serverPlayer, WRAPPER_PACKET_RESOURCE, buffer);
#endif
}
@@ -9,7 +9,10 @@ import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment;
import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.common.AbstractPluginPacketSender;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.fabric.testing.TestWorldGenBindingEvent;
@@ -32,7 +35,6 @@ import com.seibel.distanthorizons.common.CommonPacketPayload;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
#else
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.common.AbstractPluginPacketSender;
#endif
import java.util.function.Supplier;
@@ -48,6 +50,8 @@ import java.util.function.Supplier;
public class FabricServerProxy implements AbstractModInitializer.IEventProxy
{
private static final ServerApi SERVER_API = ServerApi.INSTANCE;
@SuppressWarnings("unused")
private static final AbstractPluginPacketSender PACKET_SENDER = (AbstractPluginPacketSender) SingletonInjector.INSTANCE.get(IPluginPacketSender.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private final boolean isDedicatedServer;
@@ -192,9 +196,7 @@ public class FabricServerProxy implements AbstractModInitializer.IEventProxy
#else
ServerPlayNetworking.registerGlobalReceiver(AbstractPluginPacketSender.WRAPPER_PACKET_RESOURCE, (server, serverPlayer, handler, buffer, packetSender) ->
{
// Forge packet ID
buffer.readByte();
AbstractNetworkMessage message = AbstractPluginPacketSender.decodeMessage(buffer);
AbstractNetworkMessage message = PACKET_SENDER.decodeMessage(buffer);
if (message != null)
{
ServerApi.INSTANCE.pluginMessageReceived(ServerPlayerWrapper.getWrapper(serverPlayer), message);
@@ -34,21 +34,34 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
#if MC_VER < MC_1_21_3
import com.mojang.blaze3d.platform.NativeImage;
#else
#elif MC_VER < MC_1_21_5
import com.mojang.blaze3d.pipeline.TextureTarget;
#else
import com.mojang.blaze3d.opengl.GlTexture;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.GpuTexture;
#endif
@Mixin(LightTexture.class)
public class MixinLightTexture
{
@Shadow
@Final
#if MC_VER < MC_1_21_3
@Shadow
@Final
private NativeImage lightPixels;
#else
#elif MC_VER < MC_1_21_5
@Shadow
@Final
private TextureTarget target;
#else
@Shadow
@Final
private GpuTexture texture;
#endif
@Inject(method = "updateLightTexture(F)V", at = @At("RETURN"))
public void updateLightTexture(float partialTicks, CallbackInfo ci)
{
@@ -63,8 +76,11 @@ public class MixinLightTexture
#if MC_VER < MC_1_21_3
MinecraftRenderWrapper.INSTANCE.updateLightmap(this.lightPixels, clientLevel);
#else
#elif MC_VER < MC_1_21_5
MinecraftRenderWrapper.INSTANCE.setLightmapId(this.target.getColorTextureId(), clientLevel);
#else
GlTexture glTexture = (GlTexture) this.texture;
MinecraftRenderWrapper.INSTANCE.setLightmapId(glTexture.glId(), clientLevel);
#endif
}
@@ -76,15 +76,25 @@ public abstract class MixinMinecraft
if (SelfUpdater.onStart() || DEBUG_ALWAYS_SHOW_UPDATER)
{
instance.setScreen(new UpdateModScreen(
new TitleScreen(false), // We don't want to use the vanilla title screen as it would fade the buttons
(Config.Client.Advanced.AutoUpdater.updateBranch.get() == EDhApiUpdateBranch.STABLE ? ModrinthGetter.getLatestIDForVersion(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion()): GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha"))
));
}
else
{
instance.setScreen(guiScreen); // Sets the screen back to the vanilla screen as if nothing ever happened
try
{
instance.setScreen(new UpdateModScreen(
new TitleScreen(false), // We don't want to use the vanilla title screen as it would fade the buttons
(Config.Client.Advanced.AutoUpdater.updateBranch.get() == EDhApiUpdateBranch.STABLE ? ModrinthGetter.getLatestIDForVersion(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion()): GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha"))
));
return;
}
catch (Exception e)
{
// info instead of error since this can be ignored and probably just means
// there isn't a new DH version available
LOGGER.info("Unable to show DH update screen, reason: ["+e.getMessage()+"].");
}
}
// Sets the screen back to the vanilla screen as if nothing ever happened
// if not done the game will crash
instance.setScreen(guiScreen);
}
#endif
@@ -124,14 +134,13 @@ public abstract class MixinMinecraft
{
try
{
Minecraft.getInstance().setScreen(new UpdateModScreen(
// TODO: Change to runnable, instead of tittle screen
new TitleScreen(false), // We don't want to use the vanilla title screen as it would fade the buttons
versionId
));
}
catch (IllegalArgumentException e)
catch (Exception e)
{
// info instead of error since this can be ignored and probably just means
// there isn't a new DH version available
@@ -1,12 +1,15 @@
package com.seibel.distanthorizons.fabric.mixins.client;
#if MC_VER < MC_1_21_5
import com.mojang.blaze3d.platform.GlStateManager;
#endif
import com.mojang.blaze3d.platform.TextureUtil;
import com.seibel.distanthorizons.core.config.Config;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
/**
* Sets Minecraft's LOD Bias (looks similar to mipmaps)
*
@@ -15,6 +18,9 @@ import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(TextureUtil.class)
public class MixinTextureUtil
{
// TODO fix for MC 1.21.5+
#if MC_VER < MC_1_21_5
@Redirect(method = "Lcom/mojang/blaze3d/platform/TextureUtil;prepareImage(Lcom/mojang/blaze3d/platform/NativeImage$InternalGlFormat;IIII)V",
at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/GlStateManager;_texParameter(IIF)V", #if MC_VER == MC_1_16_5 remap = true #else remap = false #endif))
private static void setLodBias(int target, int pname, float param)
@@ -27,5 +33,6 @@ public class MixinTextureUtil
GlStateManager._texParameter(target, pname, biasValue);
}
}
#endif
}
@@ -1,55 +0,0 @@
package com.seibel.distanthorizons.fabric.mixins.server;
#if MC_VER < MC_1_21_3
import net.minecraft.Util;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
/**
* {@link MixinUtilBackgroundThread} was used for versions before 1.21.3
* This is just a dummy class/mixin to make the compiler happy.
*
* @see MixinUtilBackgroundThread
*/
//@Mixin(net.minecraft.minecraft.class) // TODO we should allow version specific mixins so we don't have to create dummy mixins that exist for all MC versions
@Mixin(Util.class)
public class MixinLevelTicks
{
}
#else
import com.seibel.distanthorizons.common.wrappers.DependencySetupDoneCheck;
import net.minecraft.world.ticks.LevelTicks;
import net.minecraft.world.ticks.ScheduledTick;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LevelTicks.class)
public class MixinLevelTicks<T>
{
// TODO put in a common location
private static boolean isWorldGenThread()
{ return DependencySetupDoneCheck.isDone && DependencySetupDoneCheck.getIsCurrentThreadDistantGeneratorThread.get(); }
@Inject(method = "schedule", at = @At(value = "HEAD"), cancellable = true)
private void schedule(ScheduledTick<T> tick, CallbackInfo ci)
{
// In MC 1.21.4 an error check was added to log attempting to schedule ticks for unloaded chunks
// this caused a lot of unnecessary errors when generating sand (FallingBlock.class).
if (isWorldGenThread())
{
ci.cancel();
}
}
}
#endif
@@ -46,8 +46,8 @@ public class TestGenericWorldGenerator implements IDhApiWorldGenerator
public byte getSmallestDataDetailLevel() { return (byte) (EDhApiDetailLevel.BLOCK.detailLevel); }
@Override
public byte getLargestDataDetailLevel()
//{ return (byte) (EDhApiDetailLevel.BLOCK.detailLevel + 12); }
{ return (byte) (EDhApiDetailLevel.BLOCK.detailLevel); }
{ return (byte) (EDhApiDetailLevel.BLOCK.detailLevel + 12); }
//{ return (byte) (EDhApiDetailLevel.BLOCK.detailLevel); }
@Override
@@ -105,43 +105,43 @@ public class TestGenericWorldGenerator implements IDhApiWorldGenerator
{
case 0:
blockResourceLocation = "minecraft:red_wool";
maxHeight = 60;
maxHeight = 20;
break;
case 1:
blockResourceLocation = "minecraft:orange_wool";
maxHeight = 70;
maxHeight = 30;
break;
case 2:
blockResourceLocation = "minecraft:yellow_wool";
maxHeight = 80;
maxHeight = 40;
break;
case 3:
blockResourceLocation = "minecraft:lime_wool";
maxHeight = 90;
maxHeight = 50;
break;
case 4:
blockResourceLocation = "minecraft:cyan_wool";
maxHeight = 100;
maxHeight = 60;
break;
case 5:
blockResourceLocation = "minecraft:blue_wool";
maxHeight = 100;
maxHeight = 70;
break;
case 6:
blockResourceLocation = "minecraft:magenta_wool";
maxHeight = 110;
maxHeight = 80;
break;
case 7:
blockResourceLocation = "minecraft:white_wool";
maxHeight = 120;
maxHeight = 90;
break;
case 8:
blockResourceLocation = "minecraft:gray_wool";
maxHeight = 120;
maxHeight = 100;
break;
default:
blockResourceLocation = "minecraft:black_wool";
maxHeight = 140;
maxHeight = 110;
break;
}
@@ -25,7 +25,7 @@ public class TestWorldGenBindingEvent extends DhApiLevelLoadEvent
ServerLevel level = (ServerLevel) event.value.levelWrapper.getWrappedMcObject();
// override the core DH world generator for this level
//IDhApiWorldGenerator exampleWorldGen = new TestChunkWorldGenerator(level);
//IDhApiWorldGenerator exampleWorldGen = new TestChunkWorldGenerator(level); // TODO biomes are broken for some reason
IDhApiWorldGenerator exampleWorldGen = new TestGenericWorldGenerator(event.value.levelWrapper);
DhApi.worldGenOverrides.registerWorldGeneratorOverride(event.value.levelWrapper, exampleWorldGen);
}
@@ -43,11 +43,22 @@ import net.minecraft.world.phys.AABB;
public class SodiumAccessor implements ISodiumAccessor
{
/**
* True if sodium 0.5 or less is present. <br>
* This field is public because it's also used to check if we need Indium to be present. <br>
* We need Indium if Sodium 0.5 or less is present.
*/
public static final boolean isSodiumV5OrLess;
#if MC_VER >= MC_1_20_1
private static MethodHandle setFogOcclusionMethod;
private static Object sodiumPerformanceOptions;
#endif
static {
isSodiumV5OrLess = !classPresent("net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer");
}
//======================//
@@ -72,8 +83,7 @@ public class SodiumAccessor implements ISodiumAccessor
{
if (sodiumPerformanceOptions == null)
{
boolean sodiumV6 = classPresent("net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer");
if (!sodiumV6)
if (isSodiumV5OrLess)
{
// sodium 0.5
@@ -8,8 +8,7 @@
"server.MixinEntity",
"server.MixinServerPlayer",
"server.MixinTracingExecutor",
"server.MixinUtilBackgroundThread",
"server.MixinLevelTicks"
"server.MixinUtilBackgroundThread"
],
"client": [
"client.MixinClientLevel",
@@ -24,13 +24,17 @@ import com.seibel.distanthorizons.common.util.ProxyUtil;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftRenderWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import net.minecraft.world.level.LevelAccessor;
@@ -52,6 +56,7 @@ import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraftforge.common.MinecraftForge;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
@@ -75,6 +80,7 @@ import java.util.concurrent.ThreadPoolExecutor;
public class ForgeClientProxy implements AbstractModInitializer.IEventProxy
{
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final ForgePluginPacketSender PACKET_SENDER = (ForgePluginPacketSender) SingletonInjector.INSTANCE.get(IPluginPacketSender.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -90,7 +96,13 @@ public class ForgeClientProxy implements AbstractModInitializer.IEventProxy
public void registerEvents()
{
MinecraftForge.EVENT_BUS.register(this);
ForgePluginPacketSender.setPacketHandler(ClientApi.INSTANCE::pluginMessageReceived);
// handles singleplayer, LAN, and connecting to a server
PACKET_SENDER.setPacketHandler((IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message) ->
{
ClientApi.INSTANCE.pluginMessageReceived(message);
ServerApi.INSTANCE.pluginMessageReceived(player, message);
});
}
@@ -45,16 +45,18 @@ public class ForgePluginPacketSender extends AbstractPluginPacketSender
);
#endif
public static void setPacketHandler(Consumer<AbstractNetworkMessage> consumer)
public ForgePluginPacketSender() { super(true); }
public void setPacketHandler(Consumer<AbstractNetworkMessage> consumer)
{
setPacketHandler((player, message) -> consumer.accept(message));
this.setPacketHandler((player, message) -> consumer.accept(message));
}
public static void setPacketHandler(BiConsumer<IServerPlayerWrapper, AbstractNetworkMessage> consumer)
public void setPacketHandler(BiConsumer<IServerPlayerWrapper, AbstractNetworkMessage> consumer)
{
#if MC_VER >= MC_1_20_2
PLUGIN_CHANNEL.messageBuilder(MessageWrapper.class, 0)
.encoder((wrapper, out) -> AbstractPluginPacketSender.encodeMessage(out, wrapper.message))
.decoder(in -> new MessageWrapper(AbstractPluginPacketSender.decodeMessage(in)))
.encoder((wrapper, out) -> this.encodeMessage(out, wrapper.message))
.decoder(in -> new MessageWrapper(this.decodeMessage(in)))
.consumerNetworkThread((wrapper, context) ->
{
if (wrapper.message != null)
@@ -73,8 +75,8 @@ public class ForgePluginPacketSender extends AbstractPluginPacketSender
.add();
#else // < 1.20.2
PLUGIN_CHANNEL.registerMessage(0, MessageWrapper.class,
(wrapper, out) -> AbstractPluginPacketSender.encodeMessage(out, wrapper.message),
in -> new MessageWrapper(AbstractPluginPacketSender.decodeMessage(in)),
(wrapper, out) -> this.encodeMessage(out, wrapper.message),
in -> new MessageWrapper(this.decodeMessage(in)),
(wrapper, context) ->
{
if (wrapper.message != null)
@@ -7,7 +7,9 @@ import com.seibel.distanthorizons.common.wrappers.misc.ServerPlayerWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment;
import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
@@ -51,6 +53,8 @@ import java.util.function.Supplier;
public class ForgeServerProxy implements AbstractModInitializer.IEventProxy
{
private static final ForgePluginPacketSender PACKET_SENDER = (ForgePluginPacketSender) SingletonInjector.INSTANCE.get(IPluginPacketSender.class);
#if MC_VER < MC_1_19_2
private static LevelAccessor GetEventLevel(WorldEvent e) { return e.getWorld(); }
#else
@@ -68,7 +72,7 @@ public class ForgeServerProxy implements AbstractModInitializer.IEventProxy
MinecraftForge.EVENT_BUS.register(this);
if (this.isDedicated)
{
ForgePluginPacketSender.setPacketHandler(ServerApi.INSTANCE::pluginMessageReceived);
PACKET_SENDER.setPacketHandler(ServerApi.INSTANCE::pluginMessageReceived);
}
}
@@ -78,10 +82,7 @@ public class ForgeServerProxy implements AbstractModInitializer.IEventProxy
// constructor //
//=============//
public ForgeServerProxy(boolean isDedicated)
{
this.isDedicated = isDedicated;
}
public ForgeServerProxy(boolean isDedicated) { this.isDedicated = isDedicated; }
@@ -57,15 +57,25 @@ public class MixinMinecraft
if (SelfUpdater.onStart())
{
instance.setScreen(new UpdateModScreen(
new TitleScreen(false), // We don't want to use the vanilla title screen as it would fade the buttons
(Config.Client.Advanced.AutoUpdater.updateBranch.get() == EDhApiUpdateBranch.STABLE ? ModrinthGetter.getLatestIDForVersion(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion()): GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha"))
));
}
else
{
instance.setScreen(guiScreen); // Sets the screen back to the vanilla screen as if nothing ever happened
try
{
instance.setScreen(new UpdateModScreen(
new TitleScreen(false), // We don't want to use the vanilla title screen as it would fade the buttons
(Config.Client.Advanced.AutoUpdater.updateBranch.get() == EDhApiUpdateBranch.STABLE ? ModrinthGetter.getLatestIDForVersion(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion()): GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha"))
));
return;
}
catch (Exception e)
{
// info instead of error since this can be ignored and probably just means
// there isn't a new DH version available
LOGGER.info("Unable to show DH update screen, reason: ["+e.getMessage()+"].");
}
}
// Sets the screen back to the vanilla screen as if nothing ever happened
// if not done the game will crash
instance.setScreen(guiScreen);
}
#endif
@@ -108,7 +118,7 @@ public class MixinMinecraft
versionId
));
}
catch (IllegalArgumentException e)
catch (Exception e)
{
// info instead of error since this can be ignored and probably just means
// there isn't a new DH version available
+4 -3
View File
@@ -5,7 +5,7 @@ org.gradle.caching=true
# Mod Info
mod_name=DistantHorizons
mod_version=2.3.0-b-dev
mod_version=2.3.3-b-dev
api_version=4.0.0
maven_group=com.seibel.distanthorizons
mod_readable_name=Distant Horizons
@@ -23,9 +23,9 @@ manifold_version=2024.1.37
nightconfig_version=3.6.6
lz4_version=1.8.0
xz_version=1.9
# Before updating, read relocate_natives/README.md
sqlite_jdbc_version=3.47.2.0
# 8.2.1 is the newest version we can use since that's the version MC 1.16.5 uses
# (at least until we can fix the gradle script so core and main can use/shade different fastutil versions)
fastutil_version=8.2.1
#svgSalamander_version=1.1.3
@@ -48,10 +48,11 @@ infoBuildSource=User
# Internal Properties (These are set at runtime for Forgix to merge jar's)
versionStr=
mergeVersions=
# This defines what MC version Intellij will use for the preprocessor
# and what version is used automatically by build and run commands
mcVer=1.21.4
mcVer=1.21.5
# Defines the maximum amount of memory Minecraft is allowed when run in a development environment
#minecraftMemoryJavaArg="-Xmx4G"
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -26,7 +26,9 @@ import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
import com.seibel.distanthorizons.coreapi.ModInfo;
@@ -51,6 +53,7 @@ import java.util.function.Consumer;
import net.neoforged.neoforge.client.ConfigScreenHandler;
#else
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
import org.jetbrains.annotations.NotNull;
#endif
/**
@@ -64,11 +67,16 @@ public class NeoforgeMain extends AbstractModInitializer
{
public NeoforgeMain(IEventBus eventBus)
{
eventBus.addListener((FMLClientSetupEvent e) -> {
// handles singleplayer, LAN, and connecting to a server
eventBus.addListener((FMLClientSetupEvent e) ->
{
this.onInitializeClient();
eventBus.addListener(this::registerNetworkingClient);
eventBus.addListener(this::registerNetworkingClientServer);
});
eventBus.addListener((FMLDedicatedServerSetupEvent e) -> {
// handles dedicated servers
eventBus.addListener((FMLDedicatedServerSetupEvent e) ->
{
this.onInitializeServer();
eventBus.addListener(this::registerNetworkingServer);
});
@@ -79,13 +87,22 @@ public class NeoforgeMain extends AbstractModInitializer
//============//
// networking //
//============//
public void registerNetworkingClient(RegisterPayloadHandlersEvent event)
{ NeoforgePluginPacketSender.setPacketHandler(event, ClientApi.INSTANCE::pluginMessageReceived); }
public void registerNetworkingClientServer(RegisterPayloadHandlersEvent event)
{
NeoforgePluginPacketSender.setPacketHandler(event, (IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message) ->
{
ClientApi.INSTANCE.pluginMessageReceived(message);
ServerApi.INSTANCE.pluginMessageReceived(player, message);
});
}
public void registerNetworkingServer(RegisterPayloadHandlersEvent event)
{ NeoforgePluginPacketSender.setPacketHandler(event, ServerApi.INSTANCE::pluginMessageReceived); }
@Override
protected IEventProxy createServerProxy(boolean isDedicated) { return new NeoforgeServerProxy(isDedicated); }
@@ -19,17 +19,12 @@
package com.seibel.distanthorizons.neoforge.mixins.client;
import com.mojang.blaze3d.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.NativeImage;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftRenderWrapper;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import net.minecraft.client.renderer.LightTexture;
import org.lwjgl.opengl.GL32;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -37,30 +32,33 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
#if MC_VER < MC_1_21_3
import com.mojang.blaze3d.platform.NativeImage;
#elif MC_VER < MC_1_21_5
import com.mojang.blaze3d.pipeline.TextureTarget;
#else
import com.mojang.blaze3d.opengl.GlTexture;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.GpuTexture;
#endif
@Mixin(LightTexture.class)
public class MixinLightTexture
{
#if MC_VER < MC_1_21_3
@Shadow
@Shadow
@Final
private NativeImage lightPixels;
@Inject(method = "updateLightTexture(F)V", at = @At("RETURN"))
public void updateLightTexture(float partialTicks, CallbackInfo ci)
{
IMinecraftClientWrapper mc = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
if (mc == null || mc.getWrappedClientLevel() == null)
{
return;
}
IClientLevelWrapper clientLevel = mc.getWrappedClientLevel();
MinecraftRenderWrapper.INSTANCE.updateLightmap(this.lightPixels, clientLevel);
}
#elif MC_VER < MC_1_21_5
@Shadow
@Final
private TextureTarget target;
#else
@Shadow
@Final
private GpuTexture texture;
#endif
@Shadow @Final private TextureTarget target;
@Inject(method = "updateLightTexture(F)V", at = @At("RETURN"))
public void updateLightTexture(float partialTicks, CallbackInfo ci)
{
@@ -70,9 +68,17 @@ public class MixinLightTexture
return;
}
IClientLevelWrapper clientLevel = mc.getWrappedClientLevel();
#if MC_VER < MC_1_21_3
MinecraftRenderWrapper.INSTANCE.updateLightmap(this.lightPixels, clientLevel);
#elif MC_VER < MC_1_21_5
MinecraftRenderWrapper.INSTANCE.setLightmapId(this.target.getColorTextureId(), clientLevel);
#else
GlTexture glTexture = (GlTexture) this.texture;
MinecraftRenderWrapper.INSTANCE.setLightmapId(glTexture.glId(), clientLevel);
#endif
}
#endif
}
@@ -76,14 +76,13 @@ public class MixinMinecraft
{
try
{
Minecraft.getInstance().setScreen(new UpdateModScreen(
// TODO: Change to runnable, instead of tittle screen
new TitleScreen(false), // We don't want to use the vanilla title screen as it would fade the buttons
versionId
));
}
catch (IllegalArgumentException e)
catch (Exception e)
{
// info instead of error since this can be ignored and probably just means
// there isn't a new DH version available
@@ -1,6 +1,8 @@
package com.seibel.distanthorizons.neoforge.mixins.client;
#if MC_VER < MC_1_21_5
import com.mojang.blaze3d.platform.GlStateManager;
#endif
import com.mojang.blaze3d.platform.TextureUtil;
import com.seibel.distanthorizons.core.config.Config;
import org.spongepowered.asm.mixin.Mixin;
@@ -15,6 +17,9 @@ import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(TextureUtil.class)
public class MixinTextureUtil
{
// TODO fix for MC 1.21.5+
#if MC_VER < MC_1_21_5
@Redirect(method = "prepareImage(Lcom/mojang/blaze3d/platform/NativeImage$InternalGlFormat;IIII)V",
at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/GlStateManager;_texParameter(IIF)V"), remap = false)
private static void setLodBias(int target, int pname, float param)
@@ -27,5 +32,6 @@ public class MixinTextureUtil
GlStateManager._texParameter(target, pname, biasValue);
}
}
#endif
}
@@ -1,53 +0,0 @@
package com.seibel.distanthorizons.neoforge.mixins.server;
#if MC_VER < MC_1_21_3
import net.minecraft.Util;
import org.spongepowered.asm.mixin.Mixin;
/**
* {@link MixinUtilBackgroundThread} was used for versions before 1.21.3
* This is just a dummy class/mixin to make the compiler happy.
*
* @see MixinUtilBackgroundThread
*/
@Mixin(Util.class) // TODO we should allow version specific mixins so we don't have to create dummy mixins that exist for all MC versions
public class MixinLevelTicks<T>
{
}
#else
import com.seibel.distanthorizons.common.wrappers.DependencySetupDoneCheck;
import net.minecraft.world.ticks.LevelTicks;
import net.minecraft.world.ticks.ScheduledTick;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LevelTicks.class)
public class MixinLevelTicks<T>
{
// TODO put in a common location
private static boolean isWorldGenThread()
{ return DependencySetupDoneCheck.isDone && DependencySetupDoneCheck.getIsCurrentThreadDistantGeneratorThread.get(); }
@Inject(method = "schedule", at = @At(value = "HEAD"), cancellable = true)
private void schedule(ScheduledTick<T> tick, CallbackInfo ci)
{
// In MC 1.21.4 an error check was added to log attempting to schedule ticks for unloaded chunks
// this caused a lot of unnecessary errors when generating sand (FallingBlock.class).
if (isWorldGenThread())
{
ci.cancel();
}
}
}
#endif
@@ -8,8 +8,7 @@
"server.MixinServerPlayer",
"server.MixinTFChunkGenerator",
"server.MixinTracingExecutor",
"server.MixinUtilBackgroundThread",
"server.MixinLevelTicks"
"server.MixinUtilBackgroundThread"
],
"client": [
"client.MixinClientPacketListener",
+46
View File
@@ -0,0 +1,46 @@
This directory contains the native files of libraries that were modified for relocation. They will be copied from here during the normal build steps.
Before adding/updating a library, make sure you have Python 3.8+ installed and check the instructions below.
How to add a library's natives:
1. In `build.gradle`:
- Make sure the target package is the same length or shorter (untested) than the source package. Underscores in native methods will be mapped to `_1` so account for that as well.
- Exclude the native files and add them as `relocateNative` (see example).
Example:
```groovy
relocate "org.sqlite", "dh_sqlite", {
exclude "org/sqlite/native/**"
}
transform(NativeTransformer) {
// NativeTransformer configuration
rootDir = project.rootDir
// Replace native strings, e.g. used in calls back to Java
relocateNative "org/sqlite", "dh_sqlite"
// Rename native methods used when calling from Java
relocateNative "org_sqlite", "dh_1sqlite"
}
```
How to update a library's natives:
1. Delete the library's folder in cache/.
2. It will repopulate during the next build.
Why does this step exist?
- Native files are not handled by the shadow plugin correctly.
- I preferred it as a more streamlined approach, although a bit hacky.
- Possible alternatives:
- Use edited libraries' source code: although more straightforward, it requires maintaining and updating the repositories for the libraries being added
- Interfacing with the necessary libraries directly: an absolute mess for technical reasons
What are libraries used?
- LIEF: for fixing binaries after replacing strings
- apple-codesign: for re-signing Mac binaries, since their signatures get invalidated after previous steps
+110
View File
@@ -0,0 +1,110 @@
import os
import platform
import requests
import tarfile
import zipfile
from pathlib import Path
def get_platform_specific_filename():
system = platform.system()
machine = platform.machine()
if system == "Darwin":
if machine == "arm64":
return "apple-codesign-*-aarch64-apple-darwin.tar.gz"
else:
return "apple-codesign-*-x86_64-apple-darwin.tar.gz"
elif system == "Linux":
if machine == "aarch64":
return "apple-codesign-*-aarch64-unknown-linux-musl.tar.gz"
else:
return "apple-codesign-*-x86_64-unknown-linux-musl.tar.gz"
elif system == "Windows":
if machine.endswith("64"):
return "apple-codesign-*-x86_64-pc-windows-msvc.zip"
else:
return "apple-codesign-*-i686-pc-windows-msvc.zip"
else:
raise Exception(f"Unsupported platform: {system} {machine}")
def download_and_unpack():
dest_dir = Path("./apple-codesign")
repo_url = "https://api.github.com/repos/indygreg/apple-platform-rs/releases/latest"
dest_dir.mkdir(exist_ok=True)
# Fetch the latest release info from GitHub
print("Fetching latest release information...")
response = requests.get(repo_url)
response.raise_for_status()
release_data = response.json()
# Ensure release data has assets
if "assets" not in release_data:
raise Exception("Release data does not contain assets.")
# Determine the correct asset
platform_filename = get_platform_specific_filename()
asset = next((asset for asset in release_data["assets"] if asset["name"].startswith("apple-codesign-") and asset["name"].endswith(platform_filename.split("*")[-1])), None)
if not asset:
raise Exception(f"No matching asset found for platform: {platform_filename}")
# Download the archive
print(f"Downloading {asset['name']}...")
download_url = asset["browser_download_url"]
archive_path = dest_dir / asset["name"]
with requests.get(download_url, stream=True) as r:
r.raise_for_status()
with open(archive_path, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
print(f"Downloaded to {archive_path}")
# Extract the archive
print("Extracting archive...")
temp_extract_dir = dest_dir / "temp_extract"
temp_extract_dir.mkdir(parents=True, exist_ok=True)
if archive_path.suffix == ".zip":
with zipfile.ZipFile(archive_path, "r") as zip_ref:
zip_ref.extractall(temp_extract_dir)
elif archive_path.suffixes[-2:] == [".tar", ".gz"]:
with tarfile.open(archive_path, "r:gz") as tar_ref:
tar_ref.extractall(temp_extract_dir)
else:
raise Exception(f"Unknown archive format: {archive_path}")
# Move contents of the root directory inside the archive to dest_dir
root_dir = next(temp_extract_dir.iterdir()) # Assuming only one root directory
for item in root_dir.iterdir():
target_path = dest_dir / item.name
if target_path.exists():
if target_path.is_dir():
os.rmdir(target_path)
else:
os.remove(target_path)
item.rename(target_path)
# Clean up temporary directories
for item in temp_extract_dir.iterdir():
if item.is_dir():
os.rmdir(item)
temp_extract_dir.rmdir()
print(f"Extracted to {dest_dir}")
# Clean up the archive
os.remove(archive_path)
print(f"Removed archive {archive_path}")
if __name__ == "__main__":
try:
download_and_unpack()
except Exception as e:
print(f"Error: {e}")
+31
View File
@@ -0,0 +1,31 @@
import sys
import lief
import subprocess
import download_codesign
from pathlib import Path
# Parse the input binary & xit if binary is invalid
output_path = sys.argv[1]
binary = lief.parse(sys.stdin.buffer.read())
if binary is None:
exit(1)
# Remove signature from Mac binaries
if isinstance(binary, lief.MachO.Binary):
binary.remove_signature()
# Write the modified binary to the output path
binary.write(output_path)
# Sign Mac binaries (required to make them usable because apple)
if isinstance(binary, lief.MachO.Binary):
print(f"Signing {output_path}...")
# Check if the Apple code-signing files are available, if not, download them
if not Path("./apple-codesign/COPYING").exists():
download_codesign.download_and_unpack()
# Run the code-signing process
sign_process = subprocess.Popen(["./apple-codesign/rcodesign", "sign", output_path], shell=False,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
sign_process.wait()
+5
View File
@@ -0,0 +1,5 @@
$ErrorActionPreference = "Stop"
python -m venv .venv
.\.venv\Scripts\activate
pip install -r requirements.txt
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
set -e
python -m venv .venv
. ./.venv/bin/activate
pip install -r requirements.txt
Binary file not shown.
+1 -1
View File
@@ -58,7 +58,7 @@ If you still need help with compiling, please read the Readme.md
/** Loads the VersionProperties fiel for the currently selected Minecraft version. */
/** Loads the VersionProperties field for the currently selected Minecraft version. */
def loadProperties() {
def defaultMcVersion = "1.20.1" // 1.20.1 is our current most stable version so we use that if no version was defined
+1 -1
View File
@@ -12,7 +12,7 @@ netty_version=4.1.97.Final
# Fabric loader
fabric_loader_version=0.16.9
fabric_api_version=0.107.0+1.21.1
fabric_api_version=0.115.0+1.21.1
# Fabric mod versions
modmenu_version=11.0.0-beta.1
starlight_version_fabric=
+56
View File
@@ -0,0 +1,56 @@
# 1.21.4 version
java_version=21
minecraft_version=1.21.5
parchment_version=1.21:2024.07.28
compatible_minecraft_versions=["1.21.5"]
accessWidenerVersion=1_21_4
builds_for=fabric,neoforge
# forge is broken due to gradle/build script issues
# Netty
netty_version=4.1.97.Final
# Fabric loader
fabric_loader_version=0.16.10
fabric_api_version=0.119.5+1.21.5
modmenu_version=14.0.0-rc.2
starlight_version_fabric=
phosphor_version_fabric=
lithium_version=
sodium_version=mc1.21.5-0.6.11-fabric
iris_version=1.8.10+1.21.5-fabric
bclib_version=
immersive_portals_version=
canvas_version=
# some versions of 1.8.11 nightly builds may not work, but the ones after 2025-03-30 should
fabric_incompatibility_list={ "iris": "<=1.8.10" }
fabric_recommend_list={}
# Fabric mod run
# 0 = Don't enable and don't run
# 1 = Can be referenced in code but doesn't run
# 2 = Can be referenced in code and runs in client
enable_starlight=0
enable_phosphor=0
enable_sodium=1
enable_lithium=0
enable_iris=1
enable_bclib=0
enable_immersive_portals=0
enable_canvas=0
# (Neo)Forge loader
forge_version=
neoforge_version=21.5.0-beta
# (Neo)Forge mod versions
starlight_version_forge=
terraforged_version=
# (Neo)Forge mod run
# 0 = Don't enable and don't run
# 1 = Can be referenced in code but doesn't run
# 2 = Can be referenced in code and runs in client
enable_starlight_forge=0
enable_terraforged=0
enable_terrafirmacraft=0