Compare commits

..

216 Commits

Author SHA1 Message Date
James Seibel 89b959d3f5 Up version number 2.1.3-dev -> 2.2.0 2024-08-20 17:45:42 -05:00
James Seibel d62e50d6f4 Fix Legacy GL causing fog to smear 2024-08-20 17:45:31 -05:00
James Seibel 16836a2b49 Move MixinChunkMap shared code to common 2024-08-18 14:46:49 -05:00
James Seibel f5651f26a5 Merge !65 (fix neo/forge chunk update events) 2024-08-18 14:31:24 -05:00
James Seibel 82ff59c857 Fix fog and SSAO being broken by some mods 2024-08-17 22:29:42 -05:00
James Seibel 8af61041f0 Remove 1.21 from CI build script
1.21.1 also covers 1.21
2024-08-16 17:28:36 -05:00
James Seibel 2a9136b56f Merge 1.21.1 and 1.21 2024-08-15 07:21:19 -05:00
James Seibel 64da6c811d revert temporary 1.17.1 breakage 2024-08-13 17:24:18 -05:00
James Seibel e6b93e0d92 forgot to close test preprocessor 2024-08-13 07:46:49 -05:00
James Seibel f874219a64 Wrong 1.17.1 perpreocessor used 2024-08-13 07:33:39 -05:00
James Seibel b4822740f4 temporarily break 1.17 compiling to test gitlab bages 2024-08-13 07:33:04 -05:00
James Seibel af205a50b4 Deprecate IDhApiWorldGenerator.isBusy(), task queuing is now handled internally 2024-08-12 22:20:14 -05:00
James Seibel 2f6eaf79bd Add optional DhApiChunk validation for world gen 2024-08-12 21:47:58 -05:00
James Seibel 625f1e700f Fix MC 1.21 / 1.21.1 2024-08-12 21:05:15 -05:00
James Seibel 897d5b0b11 Change MC 1.21 -> 1.21.1 in CI version 2024-08-12 19:53:48 -05:00
James Seibel 95641e2f4e Allow adding empty lists to DhApiChunk 2024-08-11 22:01:29 -05:00
James Seibel cd856b86c7 Fix DhApiChunk setDataPoints failing for empty lists 2024-08-11 21:55:19 -05:00
James Seibel c00aa6d627 Add MC 1.21.1 2024-08-11 20:05:24 -05:00
James Seibel 398a3fb0bc Add alex's cave warning message 2024-08-11 09:55:12 -05:00
James Seibel e0fa638ad9 Fix beacons not enabling/disabling correctly 2024-08-09 07:26:20 -05:00
James Seibel 4e42cbd4ce Fix frustum culling when the screen is warped 2024-08-07 18:55:09 -05:00
James Seibel b6c6be77cd Fix beacons not being updated 2024-08-07 07:47:30 -05:00
James Seibel 0964293a72 Fix direct memory leak and remove config for GpuUpload 2024-08-07 07:30:01 -05:00
James Seibel c8b6141ce0 Improve LOD detail level detection and hole filling 2024-08-04 08:31:03 -05:00
James Seibel 948540369f Attempt to improve LOD building speed and reduce broken lighting on servers 2024-08-03 17:11:22 -05:00
James Seibel 363df0ad6f Fix MC 1.16/1.17 compiling 2024-08-03 11:38:02 -05:00
James Seibel a37e105434 Add (disabled) test API world generator 2024-08-03 09:52:06 -05:00
James Seibel aeea0c00c3 Allow DhApiChunk to accept top down or bottom up data point orders 2024-08-03 09:33:05 -05:00
James Seibel 137352674e Fix off by 1 error in Render data transformer 2024-08-02 18:30:59 -05:00
James Seibel 4734552954 Fix MC 1.16 compiling 2024-08-02 18:21:47 -05:00
James Seibel 879c2f1ec4 Fix out of bounds exception in Full Data Transformer 2024-08-02 17:56:33 -05:00
James Seibel 7dc9d2a352 Clean up faster world gen and fix even offset gen events 2024-08-02 08:25:32 -05:00
James Seibel cabc470ebd Temporary Test removing world gen boarder chunks 2024-08-01 07:44:46 -05:00
James Seibel 0bf1f493aa Change some world gen info logs to debug 2024-08-01 07:06:47 -05:00
James Seibel 705bd14ee4 Fix cave culling affecting floating islands and add LOD reload to some configs 2024-07-31 19:06:47 -05:00
James Seibel 155955e49b Mark Iris 1.7.4 and lower as incompatible (as recommended by IMS) 2024-07-30 17:13:54 -05:00
James Seibel c76a793b18 Remove deprecated methods and move method to StringUtil 2024-07-30 17:07:16 -05:00
James Seibel 50cc8501a0 Remove unused sodium and McRenderWrapper methods
Removed methods were originally used to cull LODs if MC had loaded chunks, however this turned out to be more trouble than it was worth and caused more problems than it solved.
2024-07-30 17:01:09 -05:00
James Seibel 209279e3e4 Merge branch 'distant-horizons-m2' 2024-07-30 16:06:39 -05:00
James Seibel 41239572a5 Fix presets only using "custom" after any value was changed 2024-07-30 15:47:52 -05:00
James Seibel 106ab47c3d Fix default logging debug to file 2024-07-29 20:40:54 -05:00
James Seibel a84f9b60e5 Fix rapidly changing dimensions causing the game to crash 2024-07-29 07:29:56 -05:00
James Seibel 4481e8634a Fix incorrect DhApiChunk create constructor parameter order (again) 2024-07-28 20:18:31 -05:00
James Seibel 3e432682fb fix incorrect positions being fed into biome color code 2024-07-28 09:34:15 -05:00
James Seibel 05569c03a4 Revert and Deprecate DhApiChunk and DhApiTerrainDataPoint constructors 2024-07-28 08:56:26 -05:00
James Seibel 2d567b84be Fix holes in LODs boarding different detail levels 2024-07-27 21:06:55 -05:00
James Seibel e2a378250f Fix LOD upload warning 2024-07-27 20:25:58 -05:00
James Seibel e2083a1836 Fix LODs flashing twice when changing configs 2024-07-27 20:11:49 -05:00
James Seibel 334946ab59 Potentially fix thread warnings in ClientBlockStateColorCache 2024-07-27 19:15:00 -05:00
James Seibel 8c9bb98125 Update IDhApiRenderProxy.clearRenderDataCache() to also clear cached block colors 2024-07-27 17:36:57 -05:00
James Seibel 726f0f3d3c Remove unused ServerBlockStateCache 2024-07-27 16:51:14 -05:00
James Seibel 50e5898692 Rename ClientBlockStateCache -> ClientBlockStateColorCache
And do some additional cleanup
2024-07-27 16:44:47 -05:00
James Seibel de05a5f674 Refactor and cleanup ClientBlockStateCache 2024-07-27 16:25:27 -05:00
James Seibel 31b57fae50 fix 1.16.5 compiling 2024-07-27 16:24:31 -05:00
James Seibel 2f686057f3 Fix ice/water vertical LOD lighting 2024-07-27 09:30:51 -05:00
James Seibel 132251341f Fix replay mod not showing LODs 2024-07-21 20:06:40 -05:00
James Seibel 2bac5f933a remove unused clientLevelWrapper.getGameDirectory() 2024-07-21 19:29:01 -05:00
James Seibel 2e565aa83a Improve cave culling and add config for ignored/cave blocks 2024-07-21 17:27:26 -05:00
James Seibel 4e9d0f4861 Fix ConfigEntry String value saving 2024-07-21 16:13:55 -05:00
James Seibel 7216b193e8 Fix API chunk world gen 2024-07-20 17:58:39 -05:00
James Seibel c33a5bf814 Add IDhApiWrapperFactory resourceLocation string methods for block/biomes 2024-07-20 11:21:01 -05:00
James Seibel 97756a5196 Add AbstractDhApiChunkWorldGenerator.generateApiChunk() 2024-07-20 10:45:55 -05:00
James Seibel 377f7d23e3 Remove render param from DhApiAfterRenderEvent 2024-07-14 09:31:03 -05:00
James Seibel 7005202384 Add a optional memory cache to the IDhApiTerrainDataRepo 2024-07-14 08:41:22 -05:00
James Seibel 99e8f57bac add missing genericRendering to IDhApiGraphicsConfig 2024-07-14 07:25:49 -05:00
James Seibel afddf4168e Change some chunk deserialization errors to warnings 2024-07-13 12:59:43 -05:00
James Seibel fbffdc0c9f Fix fog for Mac and remove near fog limitation 2024-07-13 12:17:30 -05:00
James Seibel e6d3647490 Increase default fog start distance 2024-07-13 08:14:33 -05:00
James Seibel 13363ff363 make clouds smaller and thinner 2024-07-12 21:47:16 -05:00
James Seibel 7f98e4b1eb Fix potential chunkWrapper null pointer 2024-07-12 21:31:12 -05:00
James Seibel 408460b0ae Fix missing imports for MC 1.19 and below 2024-07-12 20:31:36 -05:00
James Seibel b69ef5835d Fix repo connections not getting closed 2024-07-12 20:22:02 -05:00
James Seibel 0428fa0912 Clone API event parameters to reduce listener contamination 2024-07-12 19:22:25 -05:00
James Seibel 9f3124fa56 Add renderEventParam to generic rendering shader binding by IMS request 2024-07-12 17:27:32 -05:00
James Seibel fbbdab73c6 Attempt to fix lag spikes when right clicking blocks 2024-07-12 17:24:45 -05:00
James Seibel ee9441c521 Fix world gen not skipping already complete stages 2024-07-12 07:41:18 -05:00
James Seibel a9e0fd5d9b Add generic object setup/cleanup events 2024-07-12 07:16:06 -05:00
James Seibel 98464889ca Fix material typo 2 2024-07-11 22:51:47 -05:00
James Seibel eed5fd60c6 Fix material typo 2024-07-11 22:07:15 -05:00
James Seibel ac43cd5496 Add generic object materials 2024-07-11 18:13:07 -05:00
James Seibel 1f16a7c808 Fix generic rendering and add EDhApiBlockMaterial 2024-07-11 17:58:05 -05:00
James Seibel 39e4c70754 Add api for generic rendering config 2024-07-11 17:39:01 -05:00
James Seibel 82eb27af4c Add DhApiBeforeGenericObjectRenderEvent 2024-07-11 17:32:26 -05:00
IMS212 3aaab94b39 Support both Sodium 0.5 and 0.6 with reflection 2024-07-10 21:02:53 -07:00
James Seibel 07a0779ca4 Fix potential light map crashing and memory leak 2024-07-10 18:57:09 -05:00
James Seibel 2adba02a38 Add "IP Only" to multiplayer tooltip 2024-07-10 07:45:12 -05:00
James Seibel 9dd76db3fc Fix generic rendering at extreme distances 2024-07-10 07:37:18 -05:00
James Seibel 97dacf2429 Add toggleable logging for GL Buffer garbage collection
Will need to be tested by someone who is experiencing issue #718, so far I've been unable to reproduce anything meaningful.
2024-07-09 17:40:27 -05:00
James Seibel 1c189e162a fix sub MC 1.20.1 compiling 2024-07-09 16:39:29 -05:00
James Seibel f7a0fff869 Move IBlockStateWrapper constants into LodUtil 2024-07-09 16:39:04 -05:00
James Seibel 2f985d0926 Add beacon colors 2024-07-09 07:33:30 -05:00
James Seibel 2a3c544fba Increase cloud rendering performance 2024-07-08 19:56:29 -05:00
James Seibel 09d133b994 Add generic rendering localization 2024-07-08 07:45:03 -05:00
James Seibel 26a4223ecf Fix double unloading beacons 2024-07-07 19:54:25 -05:00
James Seibel e2943fdcaf Fix beacons un-rendering when unloading LODs 2024-07-07 19:45:47 -05:00
James Seibel f1053251b4 Add missing generic rendering config options 2024-07-07 18:13:58 -05:00
James Seibel be1dcaf43c Add cloud rendering 2024-07-07 18:03:11 -05:00
James Seibel a899d988fc Fix concurrent modification for GenericObjectRenderer 2024-07-04 21:43:15 -05:00
James Seibel 06b5b2c514 Fix potential null pointer in auto updater 2024-07-04 17:37:30 -05:00
James Seibel 864a19b79f Remove useless IServerLevelWrapper.tryGetClientLevelWrapper() 2024-07-04 16:31:04 -05:00
James Seibel 8974323406 Fix Api client level not containing the generic renderer 2024-07-04 16:15:51 -05:00
James Seibel 46c9e0103a Improve world gen timeout warning message 2024-07-04 16:01:33 -05:00
James Seibel 02203466ed Move generic rendering to the level API 2024-07-03 22:38:14 -05:00
James Seibel 87b22ea1cc Add a config to use pre-existing lighting 2024-07-03 20:30:56 -05:00
James Seibel d26327a930 fix max chunk Y position for empty chunks 2024-07-03 19:14:47 -05:00
James Seibel 469d2bdcb7 Add improved beacon logic 2024-07-02 17:51:26 -05:00
James Seibel 5516603a0c Add temporary proof-of-concept beacon rendering 2024-06-30 18:08:55 -05:00
James Seibel b737adc3da Up API version 2.1.0 -> 3.0.0 2024-06-30 16:36:49 -05:00
James Seibel f3a8afeee3 Up version 2.1.2 -> 2.1.3-dev 2024-06-25 19:25:35 -05:00
James Seibel a4501f86e9 Update coreSubProjects 2024-06-25 19:24:13 -05:00
James Seibel 095fff96ff Up version 2.1.1-dev -> 2.1.2 2024-06-24 20:53:45 -05:00
James Seibel a23211d061 Fix NeoForge not running 2024-06-24 20:52:14 -05:00
James Seibel b57ea41686 neoforge build script cleanup 2024-06-23 08:52:55 -05:00
James Seibel 62fb5ffb73 Add DB file lock checking 2024-06-23 08:36:48 -05:00
James Seibel 99c713967b Temporary spongepowered.vanillagradle fix/workaround 2024-06-22 16:21:19 -05:00
James Seibel 9f3de07bd8 Increase default world gen timeout to 3 minutes (from 60 sec) 2024-06-18 07:12:01 -05:00
James Seibel cd74117de3 Fix file handler tooltip 2024-06-17 07:42:35 -05:00
James Seibel e7d7033548 Improve F3 menu logic and visuals 2024-06-15 19:20:25 -05:00
James Seibel 34db7c9dac Lower the default CPU presets 2024-06-15 11:26:05 -05:00
James Seibel 272841aae9 Add a startup low memory warning 2024-06-15 11:05:10 -05:00
James Seibel 389b09a5cd Prevent creating LODs for already processed chunks 2024-06-15 09:42:49 -05:00
James Seibel 84bd876c71 Refactor ChunkWrapper 2024-06-15 08:11:26 -05:00
James Seibel 7e45051ffd Fix more MC version compiles 2024-06-14 22:21:52 -05:00
James Seibel 5570f3a313 Fix some compiling issues 2024-06-14 19:31:21 -05:00
James Seibel f4e71f7012 Add NeoForge 1.21 2024-06-14 19:05:45 -05:00
James Seibel 601d4e6e3a Fix CI not picking up 1.21 2024-06-14 07:40:29 -05:00
James Seibel a12092c1a1 Add fabric 1.21 support 2024-06-14 07:36:25 -05:00
James Seibel 94ad118c5d Minor memory optimization thanks to littlewolf 2024-06-13 07:30:42 -05:00
James Seibel 48e2978438 Fixes #713 (Forge/Neo level unload events not being called) 2024-06-13 07:15:11 -05:00
James Seibel 96b4c1a9e8 Use existing lighting for pre-generated chunks 2024-06-11 20:22:13 -05:00
James Seibel cc4a69c10c Move shared ChunkWrapper code form Main to Core 2024-06-11 18:35:02 -05:00
James Seibel 7293677ddb Batch Generation Environment refactoring 2024-06-10 21:32:14 -05:00
James Seibel 0f2ff20375 Re-arange ChunkLoader 2024-06-10 21:17:57 -05:00
James Seibel 7706240acb Remove OpenGL multithreading 2024-06-08 12:49:17 -05:00
James Seibel 4cf48fd997 Try changing LZMA preset from 4 -> 3 (faster, less compressed)
won't require any lod regeneration since the decompressor is the same
2024-06-08 11:06:42 -05:00
James Seibel 2708c1ee11 Improve config comment spacing 2024-06-08 08:33:41 -05:00
James Seibel ebb0f6ebad Up the manifold version 2023.1.17 -> 2024.1.15 2024-06-08 08:12:03 -05:00
James Seibel 2c263a2549 Up the API version 2.0.0 -> 2.1.0 2024-06-08 08:11:48 -05:00
James Seibel 955524c632 Remove blendium from the list of suggested fabric mods 2024-06-08 08:11:34 -05:00
James Seibel 564e0d3263 Add update branch config "auto" 2024-06-08 08:11:26 -05:00
James Seibel c533b2e8ea Fix config screen blur on 1.20.6 2024-06-08 07:19:50 -05:00
James Seibel 6073d8122a Up the version number 2.1.0-a -> 2.1.1-a-dev 2024-06-07 17:42:46 -05:00
James Seibel 71ca26bba9 Up the version number 2.0.4-a-dev -> 2.1.0-a 2024-05-30 20:14:04 -05:00
James Seibel 75a51be28c remove unused lightmapBindingIndex in DhApiRenderParam 2024-05-27 17:57:58 -05:00
James Seibel a66e4ba157 Potentially fix memory leaks when rendering is disabled 2024-05-27 17:38:00 -05:00
James Seibel f2b9e428d3 Re-add a missing import to fix compiling 2024-05-21 18:24:30 -05:00
James Seibel 5b2497b9d4 Minor MixinMinecraft reformatting 2024-05-21 17:16:36 -05:00
Yeshi0 e78424def4 typo 2024-05-21 20:58:44 +02:00
Yeshi0 e2c94de6e6 fix blurry text on auto update screen 2024-05-21 20:57:12 +02:00
Yeshi0 daa3caf684 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons 2024-05-21 15:12:52 +02:00
James Seibel 5991aa42d9 Revert "Add JVM Downgrader (DH now uses Java version Ω)"
This reverts commit af6dca6e5e.
2024-05-21 07:45:33 -05:00
James Seibel ff6a5aae69 Revert "Set the core to use Java version Ω"
This reverts commit c4a9e7a2a7.
2024-05-21 07:45:25 -05:00
James Seibel 80d9b4540b Fix LZ4 in retail MC 2024-05-21 07:43:45 -05:00
James Seibel 4998991ebe Fix option button in 1.20.6 being on the wrong side 2024-05-21 07:12:04 -05:00
James Seibel 14343569fe Fix neoforge config button position 2024-05-21 06:56:27 -05:00
James Seibel be6cc5ff4e Fix some old MC version compiling 2024-05-20 22:19:04 -05:00
James Seibel 0ad3391bea Put config button hide option only in file 2024-05-20 22:15:45 -05:00
James Seibel 582d998e2e Fix GuiHelper rename for MC 1.19.4 and below 2024-05-20 22:12:09 -05:00
James Seibel c00ee26075 Properly shade libraries when using Java version Ω 2024-05-20 22:10:48 -05:00
James Seibel 4c9f70a52f Merge branch 'main' of gitlab.com:jeseibel/distant-horizons 2024-05-20 22:06:59 -05:00
James Seibel 29481bc123 Fix 1.20.6 config page and config button 2024-05-20 22:06:25 -05:00
Cutiepie e274c9e004 Properly shade libraries when using Java version Ω 2024-05-21 11:53:00 +10:00
Yeshi 73988f0308 Merge branch distant-horizons:main into main 2024-05-20 20:06:23 +00:00
Cutiepie c4a9e7a2a7 Set the core to use Java version Ω 2024-05-21 01:56:33 +10:00
Cutiepie af6dca6e5e Add JVM Downgrader (DH now uses Java version Ω) 2024-05-21 01:26:03 +10:00
Yeshi0 a49720a221 fix gradle.properties typos (it was bothering me) 2024-05-20 17:04:10 +02:00
Yeshi0 12a66e70c9 remove unnecessary references to zstd 2024-05-20 17:01:45 +02:00
James Seibel 00d8aa356b minor ClassicConfigGUI reformat 2024-05-20 07:52:46 -05:00
James Seibel d40d94a565 Add probably broken AT OptionsScreen code
Will probably break 1.20.2 and 1.20.4
2024-05-20 07:52:38 -05:00
James Seibel c1f798793e roll back manifold version to fix mystery compiler issues 2024.1.15 -> 2023.1.17 2024-05-19 21:25:26 -05:00
James Seibel 8fe4ad454c update CI JDK 17 -> 21 2024-05-19 20:31:51 -05:00
James Seibel 17022f2df2 Document GuiHelper args 2024-05-19 14:26:27 -05:00
James Seibel 7fa4bc35f6 remove forge from 1.20.6 to fix CI/CD 2024-05-19 14:16:40 -05:00
James Seibel 85df9c5ef4 Add 1.20.6 to the CI build script 2024-05-18 20:32:33 -05:00
James Seibel 45d4f390a9 Fix a bunch of compiler errors 2024-05-18 20:31:15 -05:00
James Seibel e7b60b7562 Fix neoforge 1.20.6 compiling 2024-05-18 11:14:13 -05:00
James Seibel 2615177907 Fix fabric 1.20.6 compiling 2024-05-18 08:07:48 -05:00
James Seibel a83d7e2a26 Replace DhSectionPos with long's for GC performance
What could possibly go wrong?
2024-05-16 22:15:43 -05:00
James Seibel 8a2182e238 Merge branch 'main' of gitlab.com:jeseibel/distant-horizons 2024-05-15 20:40:11 -05:00
James Seibel d45455092c Replace QuadTree iterator linked list with ArrayDeque
Thanks JustALittleWolf!
2024-05-15 07:36:42 -04:00
James Seibel da18469fd4 Fix resource locations in biome/block wrappers 2024-05-15 07:24:08 -04:00
James Seibel 6b5bae9bee Cache block and biome wrapper deserialization values 2024-05-13 20:26:47 -04:00
James Seibel e29a7786e4 Potentially fix LODs not loading in 2024-05-11 16:23:50 -05:00
James Seibel 55a837ca5e Attempt to prevent thread starvation due to world gen 2024-05-10 22:27:29 -05:00
James Seibel 94cba6cf67 Fix compiling 2024-05-10 17:25:52 -05:00
James Seibel 294685df00 Remove indium recommended dependency
A lot of people were reading fabric's warning as a required dependency
2024-05-10 07:05:40 -05:00
James Seibel 2642b7a9a4 disable sql timeout 2024-05-09 23:22:50 -05:00
James Seibel 45594e4e47 Handle missing/corrupted block/biome ID's in the full data 2024-05-09 19:46:33 -05:00
James Seibel 54cd1a2e48 Fix monoliths due to duplicate IDs 2024-05-09 19:45:50 -05:00
James Seibel a20fb982ec Potential fix for NaN multiverse similarity 2024-05-09 07:35:00 -05:00
James Seibel 184d61e637 Up the API version 1.1.0 -> 2.0.0
There were several breaking changes and I forgot to up the major version number appropriately.
2024-05-04 18:17:10 -05:00
James Seibel 06ea56767f Up manifold version 2024.1.12 -> 2024.1.13 2024-05-04 15:37:20 -05:00
James Seibel 1f6e137759 Fix F3 levels not closing with multiverse 2024-05-04 15:36:51 -05:00
James Seibel c7cf7885ae Fix #670 Remove outdated world gen options from tooltip 2024-05-04 09:48:40 -05:00
James Seibel 8e98444887 Update coreSubProjects 2024-05-04 09:23:02 -05:00
James Seibel 9bfe2e8233 Up 1.20 fabric loader versions 0.14.24/0.15.1 -> 0.15.6 2024-05-04 09:22:44 -05:00
James Seibel 21f4adc769 Minor 1.20.6 preprocessor updates 2024-05-02 17:28:30 -05:00
James Seibel 3b10ca5809 Update arch loom 1.5-snapshot -> 1.6-snapshot 2024-05-02 17:27:04 -05:00
James Seibel 6cc8284747 Start adding 1.20.6 2024-05-01 07:45:23 -05:00
James Seibel 6254f7156f Improve nightly build and migration messages 2024-04-30 21:59:17 -05:00
James Seibel 0fa03701a4 Fix debug wireframes rendering on top of LODs 2024-04-30 21:24:08 -05:00
James Seibel 49125cae47 Remove ZStd compression option
Any ZStd data will be automatically deleted and re-generated
2024-04-30 21:17:54 -05:00
James Seibel 3298857d0c Remove references to FastUtil 8.5.13 2024-04-30 20:30:51 -05:00
James Seibel d939cbeb96 remove unused MixinWorldupgrader files 2024-04-30 19:44:44 -05:00
James Seibel 54d254be73 Fix optifine 1.16 support 2024-04-30 19:44:23 -05:00
James Seibel d433fdea62 Fix white grass/water if the biome is null 2024-04-28 17:35:10 -05:00
James Seibel ffa1c54ff3 Fix warning about BiomeWrapper null level on startup 2024-04-28 16:08:08 -05:00
James Seibel 019ac6dec3 Add corrupt data read handling 2024-04-28 15:52:11 -05:00
James Seibel 08d3da47f4 Fix fastutil relocation issues with world gen 2024-04-27 16:46:22 -05:00
James Seibel 348ac2b734 Closes #638 (optifine not rendering on 1.16 + 1.17) 2024-04-27 13:20:18 -05:00
James Seibel fe014b4985 revert b1c6a5c1 2024-04-27 12:56:20 -05:00
James Seibel b7f6f3b900 Remove (hopefully) unused MixinThreadingDetector 2024-04-27 11:55:27 -05:00
James Seibel 3c76ed71d8 Fix some lib shading issues 2024-04-27 11:35:16 -05:00
James Seibel 5de1998913 up the version number 2.0.3 -> 2.0.4 2024-04-26 07:33:48 -05:00
James Seibel 05c0f030cb Fix issues with compressors not appearing at runtime 2024-04-26 07:33:26 -05:00
James Seibel bd85329589 Merge Data_source_rewrite into main 2024-04-26 07:22:03 -05:00
100 changed files with 3652 additions and 3169 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# use Eclipse's JDK
# The ci should always use a unix(-like) OS to work
image: eclipse-temurin:17
image: eclipse-temurin:21
# all stages need to be defined here
# TODO: Make stages depend on what is in versionProperties
@@ -30,7 +30,7 @@ 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"]
- 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"]
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/;
+58 -24
View File
@@ -2,16 +2,16 @@ plugins {
id "java"
// Plugin to put dependencies inside our final jar
id "com.github.johnrengelman.shadow" version '7.1.2' apply false
id "com.github.johnrengelman.shadow" version '8.1.1' apply false
// Plugin to create merged jars
id "io.github.pacifistmc.forgix" version "1.2.6"
id "io.github.pacifistmc.forgix" version "1.2.9"
// Manifold preprocessor
id "systems.manifold.manifold-gradle-plugin" version "0.0.2-alpha"
// Architectury is used here only as a replacement for forge's own loom
id "dev.architectury.loom" version "1.5-SNAPSHOT" apply false
id "dev.architectury.loom" version "1.6-SNAPSHOT" apply false
}
@@ -54,7 +54,7 @@ def writeBuildGradlePredefine(List<String> mcVers, int mcIndex)
// Transfers the values set in settings.gradle to the rest of the project
project.gradle.ext.getProperties().each { prop ->
rootProject.ext.set(prop.key, prop.value)
// println "Added prop [key:" + prop.key + ", value:" + prop.value + "]"
//println "Added prop [key:" + prop.key + ", value:" + prop.value + "]"
}
// Sets up manifold stuff
writeBuildGradlePredefine(rootProject.mcVers, rootProject.mcIndex)
@@ -106,11 +106,12 @@ subprojects { p ->
apply plugin: "systems.manifold.manifold-gradle-plugin"
// Apply forge's loom
if (
(findProject(":forge") && p == project(":forge")) ||
(findProject(":neoforge") && p == project(":neoforge"))
)
apply plugin: "dev.architectury.loom"
if ((findProject(":forge") && p == project(":forge")) ||
(findProject(":neoforge") && p == project(":neoforge"))
)
{
apply plugin: "dev.architectury.loom"
}
// Set the manifold version (may not be required tough)
@@ -196,17 +197,29 @@ subprojects { p ->
implementation("org.junit.jupiter:junit-jupiter-engine:5.8.2")
implementation("junit:junit:4.13")
// FastUtil
// Note: MC 1.16 uses 8.2.1, and versions after use 8.5.12
// We cannot relocate this library since we call some MC classes that reference it
implementation("it.unimi.dsi:fastutil:${rootProject.fastutil_version}")
// Compression
forgeShadowMe("org.lz4:lz4-java:${rootProject.lz4_version}") // LZ4
forgeShadowMe("org.tukaani:xz:${rootProject.xz_version}") // LZMA
// Sqlite Database
forgeShadowMe("org.xerial:sqlite-jdbc:${rootProject.sqlite_jdbc_version}")
// NightConfig (includes Toml & Json)
// needs to be here and in core to prevent compiler errors
forgeShadowMe("com.electronwill.night-config:toml:${rootProject.nightconfig_version}")
forgeShadowMe("com.electronwill.night-config:json:${rootProject.nightconfig_version}")
// Compression
// needs to be here and in core to prevent compiler errors
forgeShadowMe("org.lz4:lz4-java:${rootProject.lz4_version}") // LZ4
// SVG (not needed atm)
// forgeShadowMe("com.formdev:svgSalamander:${rootProject.svgSalamander_version}")
// Netty
// Breaks 1.16.5
//forgeShadowMe("io.netty:netty-all:${rootProject.netty_version}")
// Remember, for lwjgl dependencies that arent included in Minecraft, you need to also need to add it to the ShadowJar thing
forgeShadowMe("org.lwjgl:lwjgl-jawt:${rootProject.lwjgl_version}") {
@@ -270,16 +283,32 @@ subprojects { p ->
relocate "com.seibel.distanthorizons.fabriclike", "loaderCommon.${p.name}.com.seibel.distanthorizons.fabriclike" // Move the loader files to a different location
}
}
def librariesLocation = "distanthorizons.libraries"
def librariesLocation = "DistantHorizons.libraries"
// SVG (not needed atm)
// relocate "com.kitfox.svg", "${librariesLocation}.kitfox.svg"
// LWJGL
// Only ever shadow the dependencies we use otherwise some stuff would break when running on an external client
relocate "org.lwjgl.system.jawt", "${librariesLocation}.lwjgl.system.jawt"
// Compression (LZ4)
relocate "net.jpountz", "${librariesLocation}.jpountz"
// Sqlite Database
//At the moment, there is a bug in this library which doesnt allow it to be relocated
// relocate "org.sqlite", "${librariesLocation}.sqlite"
// JOML
if (project.hasProperty("embed_joml") && embed_joml == "true")
relocate "org.joml", "${librariesLocation}.joml"
// NightConfig (includes Toml & Json)
relocate "com.electronwill.nightconfig", "${librariesLocation}.electronwill.nightconfig"
// SVG (not needed atm)
// relocate "com.kitfox.svg", "${librariesLocation}.kitfox.svg"
// Netty
relocate "io.netty", "${librariesLocation}.netty"
mergeServiceFiles()
}
// Using jar.finalizedBy(shadowJar) causes issues so we do this scuffed bypass
@@ -297,6 +326,7 @@ subprojects { p ->
"fabric.mod.json",
"quilt.mod.json",
"META-INF/mods.toml",
"META-INF/neoforge.mods.toml",
// The mixins for each of the loaders
"DistantHorizons."+ p.name +".fabricLike.mixins.json"
@@ -401,9 +431,11 @@ subprojects { p ->
jar {
from "LICENSE.txt"
manifest {
attributes 'Implementation-Title': rootProject.mod_name,
'Implementation-Version': rootProject.mod_version,
'Main-Class': 'com.seibel.distanthorizons.core.jar.JarMain' // When changing the main of the jar change this line
attributes(
'Implementation-Title': rootProject.mod_name,
'Implementation-Version': rootProject.mod_version,
'Main-Class': 'com.seibel.distanthorizons.core.jar.JarMain' // When changing the main of the jar change this line
)
}
}
@@ -492,14 +524,16 @@ allprojects { p ->
}
}
// Required for ModMenu
maven { url "https://maven.terraformersmc.com/" }
// Required for Mixins & VanillaGradle
// VanillaGradle and Mixins in common
maven { url "https://repo.spongepowered.org/maven/" }
// Required for Canvas (mod)
// Canvas mod
maven { url "https://maven.vram.io/" }
// ModMenu mod
maven { url "https://maven.terraformersmc.com/" }
// neoforge
maven { url "https://maven.neoforged.net/releases/" }
// These 3 are for importing mods that arnt on CursedForge, Modrinth, GitHub, GitLab or anywhere opensource
flatDir {
+9
View File
@@ -1,4 +1,13 @@
// temporary fix for broken spongepowered version
buildscript {
configurations.configureEach {
resolutionStrategy {
force 'org.spongepowered:vanillagradle:0.2.1-20240507.024226-82'
}
}
}
plugins {
id "org.spongepowered.gradle.vanilla" version "0.2.1-SNAPSHOT"
}
@@ -0,0 +1,85 @@
package com.seibel.distanthorizons.common.commonMixins;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
public class MixinChunkMapCommon
{
public static void onChunkSave(ServerLevel level, ChunkAccess chunk, CallbackInfoReturnable<Boolean> ci)
{
// is this position already being updated?
if (SharedApi.isChunkAtChunkPosAlreadyUpdating(chunk.getPos().x, chunk.getPos().z))
{
return;
}
// is this chunk being saved to disk?
boolean savingChunkToDisk = ci.getReturnValue();
// true means a chunk was saved to disk
if (!savingChunkToDisk)
{
return;
}
// TODO are the following validations necessary since we are checking above if
// the callback return value should state if the chunk was actually saved or not?
// Do we trust it to always be correct?
// corrupt/incomplete chunk validation //
// MC has a tendency to try saving incomplete or corrupted chunks (which show up as empty or black chunks)
// this logic should prevent that from happening
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
if (chunk.isUnsaved() || chunk.getUpgradeData() != null || !chunk.isLightCorrect())
{
return;
}
#else
if (chunk.isUnsaved() || chunk.isUpgrading() || !chunk.isLightCorrect())
{
return;
}
#endif
// biome validation //
// some chunks may be missing their biomes, which cause issues when attempting to save them
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
if (chunk.getBiomes() == null)
{
return;
}
#else
try
{
// this will throw an exception if the biomes aren't set up
chunk.getNoiseBiome(0,0,0);
}
catch (Exception e)
{
return;
}
#endif
// submit the update event
ServerApi.INSTANCE.serverChunkSaveEvent(
new ChunkWrapper(chunk, level, ServerLevelWrapper.getWrapper(level)),
ServerLevelWrapper.getWrapper(level)
);
}
}
@@ -26,7 +26,7 @@ import java.util.function.Consumer;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@@ -32,6 +32,7 @@ import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -54,9 +55,6 @@ import java.util.HashSet;
/**
* This handles creating abstract wrapper objects.
*
* @author James Seibel
* @version 2022-12-5
*/
public class WrapperFactory implements IWrapperFactory
{
@@ -81,17 +79,56 @@ public class WrapperFactory implements IWrapperFactory
}
}
@Override
public IDhApiBiomeWrapper getBiomeWrapper(String resourceLocationString, IDhApiLevelWrapper levelWrapper) throws IOException, ClassCastException
{
if (!(levelWrapper instanceof ILevelWrapper))
{
throw new ClassCastException("levelWrapper must be returned by DH and of type ["+ILevelWrapper.class.getName()+"].");
}
return BiomeWrapper.deserialize(resourceLocationString, (ILevelWrapper)levelWrapper);
}
@Override
public IDhApiBlockStateWrapper getDefaultBlockStateWrapper(String resourceLocationString, IDhApiLevelWrapper levelWrapper) throws IOException, ClassCastException
{
if (!(levelWrapper instanceof ILevelWrapper))
{
throw new ClassCastException("Invalid ["+IDhApiLevelWrapper.class.getSimpleName()+"] value given. Level wrapper object must be one given by the DH API (it can't be a custom implementation), specifically of type ["+ILevelWrapper.class.getName()+"].");
}
return BlockStateWrapper.deserialize(resourceLocationString, (ILevelWrapper)levelWrapper);
}
@Override
public IBiomeWrapper deserializeBiomeWrapper(String str, ILevelWrapper levelWrapper) throws IOException { return BiomeWrapper.deserialize(str, levelWrapper); }
@Override
public IBiomeWrapper getPlainsBiomeWrapper(ILevelWrapper levelWrapper) // TODO is there a way we could get this without the levelWrapper? it isn't necessary but would clean up the code a bit
{
try
{
return BiomeWrapper.deserialize(BiomeWrapper.PLAINS_RESOURCE_LOCATION_STRING, levelWrapper);
}
catch (IOException e)
{
throw new LodUtil.AssertFailureException("Unable to parse plains resource string ["+BiomeWrapper.PLAINS_RESOURCE_LOCATION_STRING+"], error:\n " + e.getMessage());
}
}
@Override
public IBlockStateWrapper deserializeBlockStateWrapper(String str, ILevelWrapper levelWrapper) throws IOException { return BlockStateWrapper.deserialize(str, levelWrapper); }
@Override
public IBlockStateWrapper getAirBlockStateWrapper() { return BlockStateWrapper.AIR; }
@Override
public HashSet<IBlockStateWrapper> getRendererIgnoredBlocks(ILevelWrapper levelWrapper) { return BlockStateWrapper.getRendererIgnoredBlocks(levelWrapper); }
@Override
public HashSet<IBlockStateWrapper> getRendererIgnoredCaveBlocks(ILevelWrapper levelWrapper) { return BlockStateWrapper.getRendererIgnoredCaveBlocks(levelWrapper); }
@Override
public void resetRendererIgnoredCaveBlocks() { BlockStateWrapper.clearRendererIgnoredCaveBlocks(); }
@Override
public void resetRendererIgnoredBlocksSet() { BlockStateWrapper.clearRendererIgnoredBlocks(); }
/**
@@ -118,7 +155,7 @@ public class WrapperFactory implements IWrapperFactory
}
}
#if MC_VER <= MC_1_20_4
//#if MC_VER <= MC_1_XX_X
else if (objectArray.length == 2)
{
// correct number of parameters from the API
@@ -164,16 +201,7 @@ public class WrapperFactory implements IWrapperFactory
{
throw new ClassCastException(createChunkWrapperErrorMessage(objectArray));
}
#else
// Intentional compiler error to bring attention to the missing wrapper function.
// If you need to work on an unimplemented version but don't have the ability to implement this yet
// you can comment it out, but please don't commit it. Someone will have to implement it.
// After implementing the new version please read this method's javadocs for instructions
// on what other locations also need to be updated, the DhAPI specifically needs to
// be updated to state which objects this method accepts.
not implemented for this version of Minecraft!
#endif
//#endif
}
/**
* Note: when this is updated for different MC versions,
@@ -183,16 +211,13 @@ public class WrapperFactory implements IWrapperFactory
{
String[] expectedClassNames;
#if MC_VER <= MC_1_20_4
//#if MC_VER <= MC_1_XX_X
expectedClassNames = new String[]
{
ChunkAccess.class.getName(),
ServerLevel.class.getName() + "] or [" + ClientLevel.class.getName()
};
#else
// See preprocessor comment in createChunkWrapper() for full documentation
not implemented for this version of Minecraft!
#endif
//#endif
return createWrapperErrorMessage("Chunk wrapper", expectedClassNames, objectArray);
}
@@ -210,7 +235,7 @@ public class WrapperFactory implements IWrapperFactory
// confirm the API level wrapper is also a Core wrapper
if (!(levelWrapper instanceof ILevelWrapper))
{
throw new ClassCastException("Unable to cast... only DH provided IDhApiLevelWrapper's can be used."); // TODO
throw new ClassCastException("Invalid ["+IDhApiLevelWrapper.class.getSimpleName()+"] value given. Level wrapper object must be one given by the DH API (it can't be a custom implementation), specifically of type ["+ILevelWrapper.class.getName()+"].");
}
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
@@ -231,7 +256,7 @@ public class WrapperFactory implements IWrapperFactory
Biome biome = (Biome) objectArray[0];
return BiomeWrapper.getBiomeWrapper(biome, coreLevelWrapper);
#elif MC_VER <= MC_1_20_4
#else
if (!(objectArray[0] instanceof Holder) || !(((Holder<?>) objectArray[0]).value() instanceof Biome))
{
throw new ClassCastException(createBiomeWrapperErrorMessage(objectArray));
@@ -239,9 +264,6 @@ public class WrapperFactory implements IWrapperFactory
Holder<Biome> biomeHolder = (Holder<Biome>) objectArray[0];
return BiomeWrapper.getBiomeWrapper(biomeHolder, coreLevelWrapper);
#else
// See preprocessor comment in createChunkWrapper() for full documentation (not a typo, check createChunkWrapper()'s else statement for full documentation)
not implemented for this version of Minecraft!
#endif
}
/**
@@ -254,11 +276,8 @@ public class WrapperFactory implements IWrapperFactory
#if MC_VER < MC_1_18_2
expectedClassNames = new String[] { Biome.class.getName() };
#elif MC_VER <= MC_1_20_4
expectedClassNames = new String[] { Holder.class.getName()+"<"+Biome.class.getName()+">" };
#else
// See preprocessor comment in createChunkWrapper() for full documentation
not implemented for this version of Minecraft!
expectedClassNames = new String[] { Holder.class.getName()+"<"+Biome.class.getName()+">" };
#endif
return createWrapperErrorMessage("Biome wrapper", expectedClassNames, objectArray);
@@ -269,13 +288,13 @@ public class WrapperFactory implements IWrapperFactory
// confirm the API level wrapper is also a Core wrapper
if (!(levelWrapper instanceof ILevelWrapper))
{
throw new ClassCastException("Unable to cast... only DH provided IDhApiLevelWrapper's can be used."); // TODO
throw new ClassCastException("Invalid ["+IDhApiLevelWrapper.class.getSimpleName()+"] value given. Level wrapper object must be one given by the DH API (it can't be a custom implementation), specifically of type ["+ILevelWrapper.class.getName()+"].");
}
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
#if MC_VER <= MC_1_20_4
//#if MC_VER <= MC_1_XX_X
if (objectArray.length != 1)
{
throw new ClassCastException(createBlockStateWrapperErrorMessage(objectArray));
@@ -287,10 +306,7 @@ public class WrapperFactory implements IWrapperFactory
BlockState blockState = (BlockState) objectArray[0];
return BlockStateWrapper.fromBlockState(blockState, coreLevelWrapper);
#else
// See preprocessor comment in createChunkWrapper() for full documentation (not a typo, check createChunkWrapper()'s else statement for full documentation)
not implemented for this version of Minecraft!
#endif
//#endif
}
/**
* Note: when this is updated for different MC versions,
@@ -302,11 +318,8 @@ public class WrapperFactory implements IWrapperFactory
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
expectedClassNames = new String[] { Biome.class.getName() };
#elif MC_VER <= MC_1_20_4
expectedClassNames = new String[] { Holder.class.getName()+"<"+Biome.class.getName()+">" };
#else
// See preprocessor comment in createChunkWrapper() for full documentation
not implemented for this version of Minecraft!
expectedClassNames = new String[] { Holder.class.getName()+"<"+Biome.class.getName()+">" };
#endif
return createWrapperErrorMessage("BlockState wrapper", expectedClassNames, objectArray);
@@ -32,8 +32,6 @@ import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import net.minecraft.client.Minecraft;
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
import net.minecraft.core.Registry;
#elif MC_VER == MC_1_18_2 || MC_VER == MC_1_19_2
@@ -66,9 +64,13 @@ public class BiomeWrapper implements IBiomeWrapper
public static final ConcurrentMap<Holder<Biome>, BiomeWrapper> WRAPPER_BY_BIOME = new ConcurrentHashMap<>();
#endif
public static final String EMPTY_STRING = "EMPTY";
public static final ConcurrentHashMap<String, BiomeWrapper> WRAPPER_BY_RESOURCE_LOCATION = new ConcurrentHashMap<>();
public static final String EMPTY_BIOME_STRING = "EMPTY";
public static final BiomeWrapper EMPTY_WRAPPER = new BiomeWrapper(null, null);
public static final String PLAINS_RESOURCE_LOCATION_STRING = "minecraft:plains";
/** keep track of broken biomes so we don't log every time */
private static final HashSet<String> brokenResourceLocationStrings = new HashSet<>();
@@ -131,7 +133,7 @@ public class BiomeWrapper implements IBiomeWrapper
private BiomeWrapper()
{
this.biome = null;
this.serialString = EMPTY_STRING;
this.serialString = EMPTY_BIOME_STRING;
this.hashCode = Objects.hash(this.serialString);
}
@@ -146,7 +148,7 @@ public class BiomeWrapper implements IBiomeWrapper
{
if (this == EMPTY_WRAPPER)
{
return EMPTY_STRING;
return EMPTY_BIOME_STRING;
}
#if MC_VER < MC_1_18_2
@@ -193,22 +195,23 @@ public class BiomeWrapper implements IBiomeWrapper
public String serialize(ILevelWrapper levelWrapper)
{
if (this.serialString != null)
if (this.biome == null)
{
return this.serialString;
return EMPTY_BIOME_STRING;
}
// we can't generate a serial string if the level is null
if (levelWrapper == null)
{
if (!emptyLevelSerializeFailLogged)
{
emptyLevelSerializeFailLogged = true;
LOGGER.warn("Unable to serialize biome: ["+this.biome+"] because the passed in level wrapper is null. Future errors won't be logged.");
LOGGER.warn("Unable to serialize biome: [" + this.biome + "] because the passed in level wrapper is null. Future errors of this type won't be logged.");
}
return EMPTY_STRING;
return EMPTY_BIOME_STRING;
}
@@ -248,14 +251,18 @@ public class BiomeWrapper implements IBiomeWrapper
return this.serialString;
}
// TODO would it be worth while to cache these objects in a ConcurrentHashMap<string, IBiomeWrapper>?
public static IBiomeWrapper deserialize(String resourceLocationString, ILevelWrapper levelWrapper) throws IOException
{
if (resourceLocationString.equals(EMPTY_STRING))
// we need the final string for the concurrent hash map later
final String finalResourceStateString = resourceLocationString;
if (resourceLocationString.equals(EMPTY_BIOME_STRING))
{
if (!emptyStringWarningLogged)
{
emptyStringWarningLogged = true;
LOGGER.warn("[" + EMPTY_STRING + "] biome string deserialized. This may mean the level was null when a save was attempted, a file saving error, or a biome saving error. Future errors will not be logged.");
LOGGER.warn("[" + EMPTY_BIOME_STRING + "] biome string deserialized. This may mean the level was null when a save was attempted, a file saving error, or a biome saving error. Future errors will not be logged.");
}
return EMPTY_WRAPPER;
}
@@ -265,53 +272,82 @@ public class BiomeWrapper implements IBiomeWrapper
return EMPTY_WRAPPER;
}
// parse the resource location
int separatorIndex = resourceLocationString.indexOf(":");
if (separatorIndex == -1)
if (WRAPPER_BY_RESOURCE_LOCATION.containsKey(finalResourceStateString))
{
throw new IOException("Unable to parse resource location string: [" + resourceLocationString + "].");
return WRAPPER_BY_RESOURCE_LOCATION.get(finalResourceStateString);
}
ResourceLocation resourceLocation = new ResourceLocation(resourceLocationString.substring(0, separatorIndex), resourceLocationString.substring(separatorIndex + 1));
// if no wrapper is found, default to the empty wrapper
BiomeWrapper foundWrapper = EMPTY_WRAPPER;
try
{
Level level = (Level)levelWrapper.getWrappedMcObject();
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
boolean success;
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
Biome biome = registryAccess.registryOrThrow(Registry.BIOME_REGISTRY).get(resourceLocation);
success = (biome != null);
#elif MC_VER == MC_1_18_2 || MC_VER == MC_1_19_2
Biome unwrappedBiome = registryAccess.registryOrThrow(Registry.BIOME_REGISTRY).get(resourceLocation);
success = (unwrappedBiome != null);
Holder<Biome> biome = new Holder.Direct<>(unwrappedBiome);
#else
Biome unwrappedBiome = registryAccess.registryOrThrow(Registries.BIOME).get(resourceLocation);
success = (unwrappedBiome != null);
Holder<Biome> biome = new Holder.Direct<>(unwrappedBiome);
#endif
if (!success)
// parse the resource location
int separatorIndex = resourceLocationString.indexOf(":");
if (separatorIndex == -1)
{
if (!brokenResourceLocationStrings.contains(resourceLocationString))
{
brokenResourceLocationStrings.add(resourceLocationString);
LOGGER.warn("Unable to deserialize biome from string: [" + resourceLocationString + "]");
}
return EMPTY_WRAPPER;
throw new IOException("Unable to parse resource location string: [" + resourceLocationString + "].");
}
return getBiomeWrapper(biome, levelWrapper);
ResourceLocation resourceLocation;
try
{
#if MC_VER < MC_1_21_1
resourceLocation = new ResourceLocation(resourceLocationString.substring(0, separatorIndex), resourceLocationString.substring(separatorIndex + 1));
#else
resourceLocation = ResourceLocation.fromNamespaceAndPath(resourceLocationString.substring(0, separatorIndex), resourceLocationString.substring(separatorIndex + 1));
#endif
}
catch (Exception e)
{
throw new IOException("No Resource Location found for the string: [" + resourceLocationString + "] Error: [" + e.getMessage() + "].");
}
try
{
Level level = (Level) levelWrapper.getWrappedMcObject();
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
boolean success;
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
Biome biome = registryAccess.registryOrThrow(Registry.BIOME_REGISTRY).get(resourceLocation);
success = (biome != null);
#elif MC_VER == MC_1_18_2 || MC_VER == MC_1_19_2
Biome unwrappedBiome = registryAccess.registryOrThrow(Registry.BIOME_REGISTRY).get(resourceLocation);
success = (unwrappedBiome != null);
Holder<Biome> biome = new Holder.Direct<>(unwrappedBiome);
#else
Biome unwrappedBiome = registryAccess.registryOrThrow(Registries.BIOME).get(resourceLocation);
success = (unwrappedBiome != null);
Holder<Biome> biome = new Holder.Direct<>(unwrappedBiome);
#endif
if (!success)
{
if (!brokenResourceLocationStrings.contains(resourceLocationString))
{
brokenResourceLocationStrings.add(resourceLocationString);
LOGGER.warn("Unable to deserialize biome from string: [" + resourceLocationString + "]");
}
return EMPTY_WRAPPER;
}
foundWrapper = (BiomeWrapper) getBiomeWrapper(biome, levelWrapper);
return foundWrapper;
}
catch (Exception e)
{
throw new IOException("Failed to deserialize the string [" + finalResourceStateString + "] into a BiomeWrapper: " + e.getMessage(), e);
}
}
catch (Exception e)
finally
{
throw new IOException("Failed to deserialize the string [" + resourceLocationString + "] into a BiomeWrapper: " + e.getMessage(), e);
WRAPPER_BY_RESOURCE_LOCATION.putIfAbsent(finalResourceStateString, foundWrapper);
}
}
@@ -19,7 +19,12 @@
package com.seibel.distanthorizons.common.wrappers.block;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
@@ -31,8 +36,10 @@ import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockState;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
@@ -65,15 +72,15 @@ public class BlockStateWrapper implements IBlockStateWrapper
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static final ConcurrentHashMap<BlockState, BlockStateWrapper> WRAPPER_BY_BLOCK_STATE = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<String, BlockStateWrapper> WRAPPER_BY_RESOURCE_LOCATION = new ConcurrentHashMap<>();
public static final String AIR_STRING = "AIR";
public static final BlockStateWrapper AIR = new BlockStateWrapper(null, null);
public static final String DIRT_RESOURCE_LOCATION_STRING = "minecraft:dirt";
// TODO: Make this changeable through the config
public static final String[] RENDERER_IGNORED_BLOCKS_RESOURCE_LOCATIONS = { AIR_STRING, "minecraft:barrier", "minecraft:structure_void", "minecraft:light", "minecraft:tripwire" };
public static HashSet<IBlockStateWrapper> rendererIgnoredBlocks = null;
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<>();
@@ -88,11 +95,16 @@ public class BlockStateWrapper implements IBlockStateWrapper
private final int hashCode;
/**
* Cached opacity value, -1 if not populated. <br>
* Should be between {@link IBlockStateWrapper#FULLY_OPAQUE} and {@link IBlockStateWrapper#FULLY_OPAQUE}
* Should be between {@link LodUtil#BLOCK_FULLY_OPAQUE} and {@link LodUtil#BLOCK_FULLY_OPAQUE}
*/
private int opacity = -1;
/** used by the Iris shader mod to determine how each LOD should be rendered */
private byte irisBlockMaterialId = 0;
private byte blockMaterialId = 0;
private final boolean isBeaconBlock;
private final boolean isBeaconBaseBlock;
private final boolean isGlassBlock;
private final Color mapColor;
@@ -125,16 +137,46 @@ public class BlockStateWrapper implements IBlockStateWrapper
this.blockState = blockState;
this.serialString = this.serialize(levelWrapper);
this.hashCode = Objects.hash(this.serialString);
this.irisBlockMaterialId = this.calculateIrisBlockMaterialId();
this.blockMaterialId = this.calculateEDhApiBlockMaterialId().index;
//LOGGER.trace("Created BlockStateWrapper ["+this.serialString+"] for ["+blockState+"] with material ID ["+this.irisBlockMaterialId+"]");
String lowercaseSerial = this.serialString.toLowerCase();
boolean isBeaconBaseBlock = false;
for (int i = 0; i < LodUtil.BEACON_BASE_BLOCK_NAME_LIST.size(); i++)
{
String baseBlockName = LodUtil.BEACON_BASE_BLOCK_NAME_LIST.get(i);
if (lowercaseSerial.contains(baseBlockName))
{
isBeaconBaseBlock = true;
break;
}
}
this.isBeaconBaseBlock = isBeaconBaseBlock;
this.isBeaconBlock = lowercaseSerial.contains("minecraft:beacon");
this.isGlassBlock = lowercaseSerial.contains("glass");
int mcColor = 0;
if (this.blockState != null)
{
#if MC_VER < MC_1_20_1
mcColor = this.blockState.getMaterial().getColor().col;
#else
mcColor = this.blockState.getMapColor(EmptyBlockGetter.INSTANCE, BlockPos.ZERO).col;
#endif
this.mapColor = ColorUtil.toColorObjRGB(mcColor);
}
else
{
this.mapColor = new Color(0,0,0,0);
}
//LOGGER.trace("Created BlockStateWrapper ["+this.serialString+"] for ["+blockState+"] with material ID ["+this.EDhApiBlockMaterialId+"]");
}
//================//
// helper methods //
//================//
//====================//
// LodBuilder methods //
//====================//
/**
* Requires a {@link ILevelWrapper} since {@link BlockStateWrapper#deserialize(String,ILevelWrapper)} also requires one.
@@ -148,37 +190,104 @@ public class BlockStateWrapper implements IBlockStateWrapper
return rendererIgnoredBlocks;
}
HashSet<String> baseIgnoredBlock = new HashSet<>();
baseIgnoredBlock.add(AIR_STRING);
rendererIgnoredBlocks = getBlockWrappers(Config.Client.Advanced.LodBuilding.ignoredRenderBlockCsv, baseIgnoredBlock, levelWrapper);
return rendererIgnoredBlocks;
}
/**
* Requires a {@link ILevelWrapper} since {@link BlockStateWrapper#deserialize(String,ILevelWrapper)} also requires one.
* This way the method won't accidentally be called before the deserialization can be completed.
*/
public static HashSet<IBlockStateWrapper> getRendererIgnoredCaveBlocks(ILevelWrapper levelWrapper)
{
// use the cached version if possible
if (rendererIgnoredCaveBlocks != null)
{
return rendererIgnoredCaveBlocks;
}
HashSet<String> baseIgnoredBlock = new HashSet<>();
baseIgnoredBlock.add(AIR_STRING);
rendererIgnoredCaveBlocks = getBlockWrappers(Config.Client.Advanced.LodBuilding.ignoredRenderCaveBlockCsv, baseIgnoredBlock, levelWrapper);
return rendererIgnoredCaveBlocks;
}
public static void clearRendererIgnoredBlocks() { rendererIgnoredBlocks = null; }
public static void clearRendererIgnoredCaveBlocks() { rendererIgnoredCaveBlocks = null; }
// lod builder helpers //
private static HashSet<IBlockStateWrapper> getBlockWrappers(ConfigEntry<String> config, HashSet<String> baseResourceLocations, ILevelWrapper levelWrapper)
{
// get the base blocks
HashSet<String> blockStringList = new HashSet<>();
if (baseResourceLocations != null)
{
blockStringList.addAll(baseResourceLocations);
}
// get the config blocks
String ignoreBlockCsv = config.get();
if (ignoreBlockCsv != null)
{
blockStringList.addAll(Arrays.asList(ignoreBlockCsv.split(",")));
}
return getBlockWrappers(blockStringList, levelWrapper);
}
private static HashSet<IBlockStateWrapper> getBlockWrappers(HashSet<String> blockResourceLocationSet, ILevelWrapper levelWrapper)
{
// deserialize each of the given resource locations
HashSet<IBlockStateWrapper> blockStateWrappers = new HashSet<>();
for (String blockResourceLocation : RENDERER_IGNORED_BLOCKS_RESOURCE_LOCATIONS)
for (String blockResourceLocation : blockResourceLocationSet)
{
try
{
BlockStateWrapper DefaultBlockStateToIgnore = (BlockStateWrapper) deserialize(blockResourceLocation, levelWrapper);
blockStateWrappers.add(DefaultBlockStateToIgnore);
if (DefaultBlockStateToIgnore == AIR)
if (blockResourceLocation == null)
{
// shouldn't happen, but just in case
continue;
}
String cleanedResourceLocation = blockResourceLocation.trim();
if (cleanedResourceLocation.length() == 0)
{
continue;
}
// add all possible blockstates (to account for light blocks with different light values and such)
List<BlockState> blockStatesToIgnore = DefaultBlockStateToIgnore.blockState.getBlock().getStateDefinition().getPossibleStates();
for (BlockState blockState : blockStatesToIgnore)
BlockStateWrapper defaultBlockStateToIgnore = (BlockStateWrapper) deserialize(cleanedResourceLocation, levelWrapper);
blockStateWrappers.add(defaultBlockStateToIgnore);
if (defaultBlockStateToIgnore != AIR)
{
BlockStateWrapper newBlockToIgnore = BlockStateWrapper.fromBlockState(blockState, levelWrapper);
blockStateWrappers.add(newBlockToIgnore);
// add all possible blockstates (to account for light blocks with different light values and such)
List<BlockState> blockStatesToIgnore = defaultBlockStateToIgnore.blockState.getBlock().getStateDefinition().getPossibleStates();
for (BlockState blockState : blockStatesToIgnore)
{
BlockStateWrapper newBlockToIgnore = BlockStateWrapper.fromBlockState(blockState, levelWrapper);
blockStateWrappers.add(newBlockToIgnore);
}
}
else
{
// air is a special case so it must be handled separately
blockStateWrappers.add(AIR);
}
}
catch (IOException e)
{
LOGGER.warn("Unable to deserialize rendererIgnoredBlock with the resource location: ["+blockResourceLocation+"]. Error: "+e.getMessage(), e);
LOGGER.warn("Unable to deserialize block with the resource location: ["+blockResourceLocation+"]. Error: "+e.getMessage(), e);
}
catch (Exception e)
{
LOGGER.warn("Unexpected error deserializing block with the resource location: ["+blockResourceLocation+"]. Error: "+e.getMessage(), e);
}
}
rendererIgnoredBlocks = blockStateWrappers;
return rendererIgnoredBlocks;
return blockStateWrappers;
}
@@ -201,23 +310,23 @@ public class BlockStateWrapper implements IBlockStateWrapper
int opacity;
if (this.isAir())
{
opacity = FULLY_TRANSPARENT;
opacity = LodUtil.BLOCK_FULLY_TRANSPARENT;
}
else if (this.isLiquid() && !this.blockState.canOcclude())
{
// probably not a waterlogged block (which should block light entirely)
// +1 to indicate that the block is translucent (in between transparent and opaque)
opacity = FULLY_TRANSPARENT + 1;
opacity = LodUtil.BLOCK_FULLY_TRANSPARENT + 1;
}
else if (this.blockState.propagatesSkylightDown(EmptyBlockGetter.INSTANCE, BlockPos.ZERO))
{
opacity = FULLY_TRANSPARENT;
opacity = LodUtil.BLOCK_FULLY_TRANSPARENT;
}
else
{
// default for all other blocks
opacity = FULLY_OPAQUE;
opacity = LodUtil.BLOCK_FULLY_OPAQUE;
}
@@ -286,7 +395,17 @@ public class BlockStateWrapper implements IBlockStateWrapper
}
@Override
public byte getIrisBlockMaterialId() { return this.irisBlockMaterialId; }
public boolean isBeaconBlock() { return this.isBeaconBlock; }
@Override
public boolean isBeaconBaseBlock() { return this.isBeaconBaseBlock; }
@Override
public boolean isGlassBlock() { return this.isGlassBlock; }
@Override
public Color getMapColor() { return this.mapColor; }
@Override
public byte getMaterialId() { return this.blockMaterialId; }
@Override
public String toString() { return this.getSerialString(); }
@@ -339,103 +458,139 @@ public class BlockStateWrapper implements IBlockStateWrapper
/** will only work if a level is currently loaded */
public static IBlockStateWrapper deserialize(String resourceStateString, ILevelWrapper levelWrapper) throws IOException
{
if (resourceStateString.equals(AIR_STRING) || resourceStateString.equals("")) // the empty string shouldn't normally happen, but just in case
// we need the final string for the concurrent hash map later
final String finalResourceStateString = resourceStateString;
if (finalResourceStateString.equals(AIR_STRING) || finalResourceStateString.equals("")) // the empty string shouldn't normally happen, but just in case
{
return AIR;
}
// try to parse out the BlockState
String blockStatePropertiesString = null; // will be null if no properties were included
int stateSeparatorIndex = resourceStateString.indexOf(STATE_STRING_SEPARATOR);
if (stateSeparatorIndex != -1)
// attempt to use the existing wrapper
if (WRAPPER_BY_RESOURCE_LOCATION.containsKey(finalResourceStateString))
{
// blockstate properties found
blockStatePropertiesString = resourceStateString.substring(stateSeparatorIndex + STATE_STRING_SEPARATOR.length());
resourceStateString = resourceStateString.substring(0, stateSeparatorIndex);
return WRAPPER_BY_RESOURCE_LOCATION.get(finalResourceStateString);
}
// parse the resource location
int resourceSeparatorIndex = resourceStateString.indexOf(RESOURCE_LOCATION_SEPARATOR);
if (resourceSeparatorIndex == -1)
{
throw new IOException("Unable to parse Resource Location out of string: [" + resourceStateString + "].");
}
ResourceLocation resourceLocation = new ResourceLocation(resourceStateString.substring(0, resourceSeparatorIndex), resourceStateString.substring(resourceSeparatorIndex + 1));
// attempt to get the BlockState from all possible BlockStates
// if no wrapper is found, default to air
BlockStateWrapper foundWrapper = AIR;
try
{
#if MC_VER > MC_1_17_1
// use the given level if possible, otherwise try using the currently loaded one
Level level = (levelWrapper != null ? (Level)levelWrapper.getWrappedMcObject() : null);
level = (level == null ? Minecraft.getInstance().level : level);
#endif
Block block;
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
block = Registry.BLOCK.get(resourceLocation);
#elif MC_VER == MC_1_18_2 || MC_VER == MC_1_19_2
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
block = registryAccess.registryOrThrow(Registry.BLOCK_REGISTRY).get(resourceLocation);
#else
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
block = registryAccess.registryOrThrow(Registries.BLOCK).get(resourceLocation);
#endif
if (block == null)
// try to parse out the BlockState
String blockStatePropertiesString = null; // will be null if no properties were included
int stateSeparatorIndex = resourceStateString.indexOf(STATE_STRING_SEPARATOR);
if (stateSeparatorIndex != -1)
{
// shouldn't normally happen, but here to make the compiler happy
if (!BrokenResourceLocations.contains(resourceLocation))
{
BrokenResourceLocations.add(resourceLocation);
LOGGER.warn("Unable to find BlockState with the resourceLocation [" + resourceLocation + "] and properties: [" + blockStatePropertiesString + "]. Air will be used instead, some data may be lost.");
}
return AIR;
// blockstate properties found
blockStatePropertiesString = resourceStateString.substring(stateSeparatorIndex + STATE_STRING_SEPARATOR.length());
resourceStateString = resourceStateString.substring(0, stateSeparatorIndex);
}
// parse the resource location
int separatorIndex = resourceStateString.indexOf(RESOURCE_LOCATION_SEPARATOR);
if (separatorIndex == -1)
{
throw new IOException("Unable to parse Resource Location out of string: [" + resourceStateString + "].");
}
ResourceLocation resourceLocation;
try
{
#if MC_VER < MC_1_21_1
resourceLocation = new ResourceLocation(resourceStateString.substring(0, separatorIndex), resourceStateString.substring(separatorIndex + 1));
#else
resourceLocation = ResourceLocation.fromNamespaceAndPath(resourceStateString.substring(0, separatorIndex), resourceStateString.substring(separatorIndex + 1));
#endif
}
catch (Exception e)
{
throw new IOException("No Resource Location found for the string: [" + resourceStateString + "] Error: [" + e.getMessage() + "].");
}
// attempt to find the blockstate from all possibilities
BlockState foundState = null;
if (blockStatePropertiesString != null)
{
List<BlockState> possibleStateList = block.getStateDefinition().getPossibleStates();
for (BlockState possibleState : possibleStateList)
{
String possibleStatePropertiesString = serializeBlockStateProperties(possibleState);
if (possibleStatePropertiesString.equals(blockStatePropertiesString))
{
foundState = possibleState;
break;
}
}
}
// use the default if no state was found or given
if (foundState == null)
// attempt to get the BlockState from all possible BlockStates
try
{
if (blockStatePropertiesString != null)
#if MC_VER > MC_1_17_1
// use the given level if possible, otherwise try using the currently loaded one
Level level = (levelWrapper != null ? (Level) levelWrapper.getWrappedMcObject() : null);
level = (level == null ? Minecraft.getInstance().level : level);
#endif
Block block;
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
block = Registry.BLOCK.get(resourceLocation);
#elif MC_VER == MC_1_18_2 || MC_VER == MC_1_19_2
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
block = registryAccess.registryOrThrow(Registry.BLOCK_REGISTRY).get(resourceLocation);
#else
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
block = registryAccess.registryOrThrow(Registries.BLOCK).get(resourceLocation);
#endif
if (block == null)
{
// we should have found a blockstate, but didn't
// shouldn't normally happen, but here to make the compiler happy
if (!BrokenResourceLocations.contains(resourceLocation))
{
BrokenResourceLocations.add(resourceLocation);
LOGGER.warn("Unable to find BlockState for Block [" + resourceLocation + "] with properties: [" + blockStatePropertiesString + "]. Using the default block state.");
LOGGER.warn("Unable to find BlockState with the resourceLocation [" + resourceLocation + "] and properties: [" + blockStatePropertiesString + "]. Air will be used instead, some data may be lost.");
}
return AIR;
}
// attempt to find the blockstate from all possibilities
BlockState foundState = null;
if (blockStatePropertiesString != null)
{
List<BlockState> possibleStateList = block.getStateDefinition().getPossibleStates();
for (BlockState possibleState : possibleStateList)
{
String possibleStatePropertiesString = serializeBlockStateProperties(possibleState);
if (possibleStatePropertiesString.equals(blockStatePropertiesString))
{
foundState = possibleState;
break;
}
}
}
foundState = block.defaultBlockState();
// use the default if no state was found or given
if (foundState == null)
{
if (blockStatePropertiesString != null)
{
// we should have found a blockstate, but didn't
if (!BrokenResourceLocations.contains(resourceLocation))
{
BrokenResourceLocations.add(resourceLocation);
LOGGER.warn("Unable to find BlockState for Block [" + resourceLocation + "] with properties: [" + blockStatePropertiesString + "]. Using the default block state.");
}
}
foundState = block.defaultBlockState();
}
foundWrapper = new BlockStateWrapper(foundState, levelWrapper);
return foundWrapper;
}
catch (Exception e)
{
throw new IOException("Failed to deserialize the string [" + finalResourceStateString + "] into a BlockStateWrapper: " + e.getMessage(), e);
}
return new BlockStateWrapper(foundState, levelWrapper);
}
catch (Exception e)
finally
{
throw new IOException("Failed to deserialize the string [" + resourceStateString + "] into a BlockStateWrapper: " + e.getMessage(), e);
// put if absent in case two threads deserialize at the same time
// unfortunately we can't put everything in a computeIfAbsent() since we also throw exceptions
WRAPPER_BY_RESOURCE_LOCATION.putIfAbsent(finalResourceStateString, foundWrapper);
}
}
@@ -475,11 +630,11 @@ public class BlockStateWrapper implements IBlockStateWrapper
// Iris methods //
//==============//
private byte calculateIrisBlockMaterialId()
private EDhApiBlockMaterial calculateEDhApiBlockMaterialId()
{
if (this.blockState == null)
{
return IrisBlockMaterial.AIR;
return EDhApiBlockMaterial.AIR;
}
@@ -492,15 +647,15 @@ public class BlockStateWrapper implements IBlockStateWrapper
|| serialString.contains("mushroom")
)
{
return IrisBlockMaterial.LEAVES;
return EDhApiBlockMaterial.LEAVES;
}
else if (this.blockState.is(Blocks.LAVA))
{
return IrisBlockMaterial.LAVA;
return EDhApiBlockMaterial.LAVA;
}
else if (this.isLiquid() || this.blockState.is(Blocks.WATER))
{
return IrisBlockMaterial.WATER;
return EDhApiBlockMaterial.WATER;
}
else if (this.blockState.getSoundType() == SoundType.WOOD
|| serialString.contains("root")
@@ -509,7 +664,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
#endif
)
{
return IrisBlockMaterial.WOOD;
return EDhApiBlockMaterial.WOOD;
}
else if (this.blockState.getSoundType() == SoundType.METAL
#if MC_VER >= MC_1_19_2
@@ -521,11 +676,11 @@ public class BlockStateWrapper implements IBlockStateWrapper
#endif
)
{
return IrisBlockMaterial.METAL;
return EDhApiBlockMaterial.METAL;
}
else if (serialString.contains("grass_block"))
{
return IrisBlockMaterial.GRASS;
return EDhApiBlockMaterial.GRASS;
}
else if (
serialString.contains("dirt")
@@ -535,7 +690,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
|| serialString.contains("mycelium")
)
{
return IrisBlockMaterial.DIRT;
return EDhApiBlockMaterial.DIRT;
}
#if MC_VER >= MC_1_17_1
else if (this.blockState.getSoundType() == SoundType.DEEPSLATE
@@ -544,37 +699,37 @@ public class BlockStateWrapper implements IBlockStateWrapper
|| this.blockState.getSoundType() == SoundType.POLISHED_DEEPSLATE
|| serialString.contains("deepslate") )
{
return IrisBlockMaterial.DEEPSLATE;
return EDhApiBlockMaterial.DEEPSLATE;
}
#endif
else if (this.serialString.contains("snow"))
{
return IrisBlockMaterial.SNOW;
return EDhApiBlockMaterial.SNOW;
}
else if (serialString.contains("sand"))
{
return IrisBlockMaterial.SAND;
return EDhApiBlockMaterial.SAND;
}
else if (serialString.contains("terracotta"))
{
return IrisBlockMaterial.TERRACOTTA;
return EDhApiBlockMaterial.TERRACOTTA;
}
else if (this.blockState.is(BlockTags.BASE_STONE_NETHER))
{
return IrisBlockMaterial.NETHER_STONE;
return EDhApiBlockMaterial.NETHER_STONE;
}
else if (serialString.contains("stone")
|| serialString.contains("ore"))
{
return IrisBlockMaterial.STONE;
return EDhApiBlockMaterial.STONE;
}
else if (this.blockState.getLightEmission() > 0)
{
return IrisBlockMaterial.ILLUMINATED;
return EDhApiBlockMaterial.ILLUMINATED;
}
else
{
return IrisBlockMaterial.UNKOWN;
return EDhApiBlockMaterial.UNKNOWN;
}
}
@@ -0,0 +1,489 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block;
import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
import com.seibel.distanthorizons.common.wrappers.block.TextureAtlasSpriteWrapper;
import com.seibel.distanthorizons.common.wrappers.block.TintWithoutLevelOverrider;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.common.wrappers.block.*;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FlowerBlock;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.RotatedPillarBlock;
#if MC_VER >= MC_1_19_2
import net.minecraft.util.RandomSource;
#else
import java.util.Random;
#endif
import net.minecraft.world.level.block.state.BlockState;
import org.apache.logging.log4j.Logger;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
/**
* This stores and calculates the colors
* the given {@link BlockState} should have based
* on the given {@link IClientLevelWrapper}.
*
* @see ColorUtil
*/
public class ClientBlockStateColorCache
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final HashSet<BlockState> BLOCK_STATES_THAT_NEED_LEVEL = new HashSet<>();
private static final HashSet<BlockState> BROKEN_BLOCK_STATES = new HashSet<>();
/**
* Methods using MC's "RandomSource" object aren't thread safe <br>
* so we need to put locks around that logic. <br>
* specifically:
* <code>
* getBlockModel(this.blockState).getQuads(this.blockState, direction, RANDOM)
* </code>
*/
private static final ReentrantLock RESOLVE_LOCK = new ReentrantLock();
/** This is the order each direction on a block is processed when attempting to get the texture/color */
private static final Direction[] COLOR_RESOLUTION_DIRECTION_ORDER = { Direction.UP, Direction.NORTH, Direction.EAST, Direction.WEST, Direction.SOUTH, Direction.DOWN };
private static final int FLOWER_COLOR_SCALE = 5;
#if MC_VER < MC_1_19_2
private static final Random RANDOM = new Random(0);
#else
/** Note: this object isn't thread safe and must be put in a lock */
private static final RandomSource RANDOM = RandomSource.create();
#endif
private final IClientLevelWrapper levelWrapper;
private final BlockState blockState;
private final LevelReader level;
private boolean isColorResolved = false;
private int baseColor = 0;
private boolean needShade = true;
private boolean needPostTinting = false;
private int tintIndex = 0;
//===========//
// constants //
//===========//
private static final int MIN_SRGB_BITS = 0x39000000; // 2^(-13)
private static final int MAX_SRGB_BITS = 0x3f7fffff; // 1.0 - f32::EPSILON
private static final float MIN_SRGB_BOUND = Float.intBitsToFloat(MIN_SRGB_BITS);
private static final float MAX_SRGB_BOUND = Float.intBitsToFloat(MAX_SRGB_BITS);
private static final int[] linearToSrgbTable = new int[]
{
0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d,
0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a,
0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033,
0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067,
0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5,
0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2,
0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143,
0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af,
0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240,
0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300,
0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401,
0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559,
0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723,
};
private static final float[] srgbToLinearTable = new float[]
{
0.0f, 0.000303527f, 0.000607054f, 0.00091058103f, 0.001214108f, 0.001517635f, 0.0018211621f, 0.002124689f,
0.002428216f, 0.002731743f, 0.00303527f, 0.0033465356f, 0.003676507f, 0.004024717f, 0.004391442f,
0.0047769533f, 0.005181517f, 0.0056053917f, 0.0060488326f, 0.006512091f, 0.00699541f, 0.0074990317f,
0.008023192f, 0.008568125f, 0.009134057f, 0.009721218f, 0.010329823f, 0.010960094f, 0.011612245f,
0.012286487f, 0.012983031f, 0.013702081f, 0.014443844f, 0.015208514f, 0.015996292f, 0.016807375f,
0.017641952f, 0.018500218f, 0.019382361f, 0.020288562f, 0.02121901f, 0.022173883f, 0.023153365f,
0.02415763f, 0.025186857f, 0.026241222f, 0.027320892f, 0.028426038f, 0.029556843f, 0.03071345f, 0.03189604f,
0.033104774f, 0.03433981f, 0.035601325f, 0.036889452f, 0.038204376f, 0.039546248f, 0.04091521f, 0.042311423f,
0.043735042f, 0.045186214f, 0.046665095f, 0.048171833f, 0.049706575f, 0.051269468f, 0.052860655f, 0.05448028f,
0.056128494f, 0.057805434f, 0.05951124f, 0.06124607f, 0.06301003f, 0.06480328f, 0.06662595f, 0.06847818f,
0.07036011f, 0.07227186f, 0.07421358f, 0.07618539f, 0.07818743f, 0.08021983f, 0.082282715f, 0.084376216f,
0.086500466f, 0.088655606f, 0.09084173f, 0.09305898f, 0.095307484f, 0.09758736f, 0.09989874f, 0.10224175f,
0.10461649f, 0.10702311f, 0.10946172f, 0.111932434f, 0.11443538f, 0.116970696f, 0.11953845f, 0.12213881f,
0.12477186f, 0.12743773f, 0.13013652f, 0.13286836f, 0.13563336f, 0.13843165f, 0.14126332f, 0.1441285f,
0.1470273f, 0.14995982f, 0.15292618f, 0.1559265f, 0.15896086f, 0.16202943f, 0.16513224f, 0.16826946f,
0.17144115f, 0.17464745f, 0.17788847f, 0.1811643f, 0.18447503f, 0.1878208f, 0.19120172f, 0.19461787f,
0.19806935f, 0.2015563f, 0.20507877f, 0.2086369f, 0.21223079f, 0.21586053f, 0.21952623f, 0.22322798f,
0.22696589f, 0.23074007f, 0.23455065f, 0.23839766f, 0.2422812f, 0.2462014f, 0.25015837f, 0.25415218f,
0.2581829f, 0.26225072f, 0.26635566f, 0.27049786f, 0.27467737f, 0.27889434f, 0.2831488f, 0.2874409f,
0.2917707f, 0.29613832f, 0.30054384f, 0.30498737f, 0.30946895f, 0.31398875f, 0.31854683f, 0.32314324f,
0.32777813f, 0.33245158f, 0.33716366f, 0.34191445f, 0.3467041f, 0.3515327f, 0.35640025f, 0.36130688f,
0.3662527f, 0.37123778f, 0.37626222f, 0.3813261f, 0.38642952f, 0.39157256f, 0.3967553f, 0.40197787f,
0.4072403f, 0.4125427f, 0.41788515f, 0.42326775f, 0.42869055f, 0.4341537f, 0.43965724f, 0.44520125f,
0.45078585f, 0.45641106f, 0.46207705f, 0.46778384f, 0.47353154f, 0.47932023f, 0.48514998f, 0.4910209f,
0.49693304f, 0.5028866f, 0.50888145f, 0.5149178f, 0.5209957f, 0.52711535f, 0.5332766f, 0.5394797f,
0.5457247f, 0.5520116f, 0.5583406f, 0.5647117f, 0.57112503f, 0.57758063f, 0.5840786f, 0.590619f, 0.597202f,
0.60382754f, 0.61049575f, 0.61720675f, 0.62396055f, 0.63075733f, 0.637597f, 0.6444799f, 0.6514058f,
0.65837497f, 0.66538745f, 0.67244333f, 0.6795426f, 0.68668544f, 0.69387203f, 0.70110214f, 0.70837605f,
0.7156938f, 0.72305536f, 0.730461f, 0.7379107f, 0.7454045f, 0.75294244f, 0.76052475f, 0.7681514f, 0.77582246f,
0.78353804f, 0.79129815f, 0.79910296f, 0.8069525f, 0.8148468f, 0.822786f, 0.8307701f, 0.83879924f, 0.84687346f,
0.8549928f, 0.8631574f, 0.87136734f, 0.8796226f, 0.8879232f, 0.89626956f, 0.90466136f, 0.913099f, 0.92158204f,
0.93011117f, 0.9386859f, 0.9473069f, 0.9559735f, 0.9646866f, 0.9734455f, 0.98225087f, 0.9911022f, 1.0f
};
//=============//
// constructor //
//=============//
public ClientBlockStateColorCache(BlockState blockState, IClientLevelWrapper samplingLevel)
{
this.blockState = blockState;
this.levelWrapper = samplingLevel;
this.level = (LevelReader) samplingLevel.getWrappedMcObject();
this.resolveColors();
}
//===================//
// color calculation //
//===================//
private void resolveColors()
{
if (this.isColorResolved)
{
return;
}
try
{
// getQuads() isn't thread safe so we need to put this logic in a lock
RESOLVE_LOCK.lock();
if (this.blockState.getFluidState().isEmpty())
{
// look for the first non-empty direction
List<BakedQuad> quads = null;
for (Direction direction : COLOR_RESOLUTION_DIRECTION_ORDER)
{
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(this.blockState).getQuads(this.blockState, direction, RANDOM);
if (quads != null && !quads.isEmpty()
&& !(
this.blockState.getBlock() instanceof RotatedPillarBlock
&& direction == Direction.UP
)
)
{
break;
}
}
if (quads == null || quads.isEmpty())
{
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(this.blockState).getQuads(this.blockState, null, RANDOM);
}
if (quads != null && !quads.isEmpty())
{
this.needPostTinting = quads.get(0).isTinted();
this.needShade = quads.get(0).isShade();
this.tintIndex = quads.get(0).getTintIndex();
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()));
}
else
{
// Backup method.
this.needPostTinting = false;
this.needShade = false;
this.tintIndex = 0;
this.baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(this.blockState),
ColorMode.getColorMode(this.blockState.getBlock()));
}
}
else
{
// Liquid Block
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.isColorResolved = true;
}
finally
{
RESOLVE_LOCK.unlock();
}
}
//TODO: Perhaps make this not just use the first frame?
private static int calculateColorFromTexture(TextureAtlasSprite texture, ColorMode colorMode)
{
int count = 0;
int alpha = 0;
double red = 0;
double green = 0;
double blue = 0;
int tempColor;
// don't render Chiseled blocks.
// Since ColorMode is set per block, you only need to check this once.
if (colorMode != ColorMode.Chisel)
{
// textures normally use u and v instead of x and y
for (int v = 0; v < getTextureHeight(texture); v++)
{
for (int u = 0; u < getTextureWidth(texture); u++)
{
//note: Minecraft color format is: 0xAA BB GG RR
//________ DH mod color format is: 0xAA RR GG BB
//OpenGL RGBA format native order: 0xRR GG BB AA
//_ OpenGL RGBA format Java Order: 0xAA BB GG RR
tempColor = TextureAtlasSpriteWrapper.getPixelRGBA(texture, 0, u, v);
int r = (tempColor & 0x000000FF);
int g = (tempColor & 0x0000FF00) >>> 8;
int b = (tempColor & 0x00FF0000) >>> 16;
int a = (tempColor & 0xFF000000) >>> 24;
int scale = 1;
if (colorMode == ColorMode.Leaves)
{
//switch (//FIXME add config option)
// case BLACK:
// a = 255; //simulate black background of fast leaves
// break;
// case IGNORE:
if (a == 0) {
continue; //same long grass
}
else
{
a = 255; //just in case there are semi transparent pixels
}
// break;
// case TRANSPARENT:
// break; //do nothing, let it count towards transparency
}
else if (a == 0 && colorMode != ColorMode.Glass)
{
continue;
}
else if (colorMode == ColorMode.Flower && (g + 25 < b || g + 25 < r))
{
scale = FLOWER_COLOR_SCALE;
}
count += scale;
//apparently alpha is linear
alpha += a * scale;
//gamma correction is complicated
red += srgbToLinearTable[r] * a * scale;
green += srgbToLinearTable[g] * a * scale;
blue += srgbToLinearTable[b] * a * scale;
}
}
}
if (count == 0)
{
// this block is entirely transparent
tempColor = ColorUtil.rgbToInt(0, 255, 255, 255);
}
else
{
// determine the average color
tempColor = ColorUtil.rgbToInt(
alpha / count,
linearToSrgb((float) (red / (double) alpha)),
linearToSrgb((float) (green / (double) alpha)),
linearToSrgb((float) (blue / (double) alpha)));
}
//check if not missing texture
if (tempColor == ColorUtil.rgbToInt(255, 182, 0, 182))
{
//make it not render at all
tempColor = ColorUtil.rgbToInt(0, 255, 255, 255);
}
return tempColor;
}
private static int getTextureWidth(TextureAtlasSprite texture)
{
#if MC_VER < MC_1_19_4
return texture.getWidth();
#else
return texture.contents().width();
#endif
}
private static int getTextureHeight(TextureAtlasSprite texture)
{
#if MC_VER < MC_1_19_4
return texture.getHeight();
#else
return texture.contents().height();
#endif
}
/**
* This method was suggested by IMS from the Iris/Sodium team.
* That's where the numbers and code are based.
*/
private static int linearToSrgb(float c)
{
if (!(c > MIN_SRGB_BOUND)) {
c = MIN_SRGB_BOUND;
}
if (c > MAX_SRGB_BOUND) {
c = MAX_SRGB_BOUND;
}
int inputBits = Float.floatToRawIntBits(c);
int entry = linearToSrgbTable[((inputBits - MIN_SRGB_BITS) >> 20)];
int bias = (entry >>> 16) << 9;
int scale = entry & 0xffff;
int t = (inputBits >>> 12) & 0xff;
return (bias + (scale * t)) >>> 16;
}
//===============//
// public getter //
//===============//
public int getColor(BiomeWrapper biome, DhBlockPos pos)
{
// only get the tint if the block needs to be tinted
if (!this.needPostTinting)
{
return this.baseColor;
}
// don't try tinting blocks that don't support our method of tint getting
if (BROKEN_BLOCK_STATES.contains(this.blockState))
{
return this.baseColor;
}
// attempt to get the tint
int tintColor = -1;
try
{
// try to use the fast tint getter logic first
if (!BLOCK_STATES_THAT_NEED_LEVEL.contains(this.blockState))
{
try
{
tintColor = Minecraft.getInstance().getBlockColors()
.getColor(this.blockState, new TintWithoutLevelOverrider(biome, this.levelWrapper), McObjectConverter.Convert(pos), this.tintIndex);
}
catch (UnsupportedOperationException e)
{
// this exception generally occurs if the tint requires other blocks besides itself
LOGGER.debug("Unable to use ["+TintWithoutLevelOverrider.class.getSimpleName()+"] to get the block tint for block: [" + this.blockState + "] and biome: [" + biome + "] at pos: " + pos + ". Error: [" + e.getMessage() + "]. Attempting to use backup method...", e);
BLOCK_STATES_THAT_NEED_LEVEL.add(this.blockState);
}
}
// use the level logic only if requested
if (BLOCK_STATES_THAT_NEED_LEVEL.contains(this.blockState))
{
// this logic can't be used all the time due to it breaking some blocks tinting
// specifically oceans don't render correctly
tintColor = Minecraft.getInstance().getBlockColors()
.getColor(this.blockState, new TintGetterOverrideFast(this.level), McObjectConverter.Convert(pos), this.tintIndex);
}
}
catch (Exception e)
{
// only display the error once per block/biome type to reduce log spam
if (!BROKEN_BLOCK_STATES.contains(this.blockState))
{
LOGGER.warn("Failed to get block color for block: [" + this.blockState + "] and biome: [" + biome + "] at pos: " + pos + ". Error: ["+e.getMessage() + "]. Note: future errors for this block/biome will be ignored.", e);
BROKEN_BLOCK_STATES.add(this.blockState);
}
}
if (tintColor != -1)
{
return ColorUtil.multiplyARGBwithRGB(this.baseColor, tintColor);
}
else
{
// unable to get the tinted color, use the base color instead
return this.baseColor;
}
}
//================//
// helper classes //
//================//
enum ColorMode
{
Default,
Flower,
Leaves,
Chisel,
Glass;
static ColorMode getColorMode(Block block)
{
if (block instanceof LeavesBlock) return Leaves;
if (block instanceof FlowerBlock) return Flower;
if (block.toString().contains("glass")) return Glass;
if (block.toString().equals("Block{chiselsandbits:chiseled}")) return Chisel;
return Default;
}
}
}
@@ -19,14 +19,20 @@
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;
#if MC_VER >= MC_1_18_2
@@ -35,18 +41,54 @@ import net.minecraft.core.Holder;
public class TintWithoutLevelOverrider implements BlockAndTintGetter
{
final BiomeWrapper biome;
/**
* This will only ever be null if there was an issue with {@link IClientLevelWrapper#getPlainsBiomeWrapper()}
* but {@link Nullable} is there just in case.
*/
@Nullable
private final Biome biome;
public TintWithoutLevelOverrider(BiomeWrapper biome)
public TintWithoutLevelOverrider(BiomeWrapper biomeWrapper, IClientLevelWrapper clientLevelWrapper)
{
this.biome = biome;
// try to get the wrapped biome
Biome unwrappedBiome = null;
if (biomeWrapper.biome != null)
{
unwrappedBiome = unwrap(biomeWrapper.biome);
}
if(unwrappedBiome == null)
{
// we are looking at the empty biome wrapper, try using plains as a backup
BiomeWrapper plainsBiomeWrapper = ((BiomeWrapper) clientLevelWrapper.getPlainsBiomeWrapper());
if (plainsBiomeWrapper != null)
{
unwrappedBiome = unwrap(plainsBiomeWrapper.biome);
}
}
this.biome = unwrappedBiome;
}
@Override
public int getBlockTint(BlockPos blockPos, ColorResolver colorResolver)
public int getBlockTint(@NotNull BlockPos blockPos, @NotNull ColorResolver colorResolver)
{
return colorResolver.getColor(_unwrap(biome.biome), blockPos.getX(), blockPos.getZ());
if (this.biome != null)
{
return colorResolver.getColor(this.biome, blockPos.getX(), blockPos.getZ());
}
else
{
// hopefully unneeded debug color
return ColorUtil.CYAN;
}
}
private Biome _unwrap(#if MC_VER >= MC_1_18_2 Holder<Biome> #else Biome #endif biome)
private static Biome unwrap(#if MC_VER >= MC_1_18_2 Holder<Biome> #else Biome #endif biome)
{
#if MC_VER >= MC_1_18_2
return biome.value();
@@ -55,30 +97,36 @@ public class TintWithoutLevelOverrider implements BlockAndTintGetter
#endif
}
//================//
// unused methods //
//================//
@Override
public float getShade(Direction direction, boolean shade)
public float getShade(@NotNull Direction direction, boolean shade)
{
throw new UnsupportedOperationException("ERROR: getShade() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Override
public LevelLightEngine getLightEngine()
public @NotNull LevelLightEngine getLightEngine()
{
throw new UnsupportedOperationException("ERROR: getLightEngine() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos)
public BlockEntity getBlockEntity(@NotNull BlockPos pos)
{
throw new UnsupportedOperationException("ERROR: getBlockEntity() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Override
public BlockState getBlockState(BlockPos pos)
public @NotNull BlockState getBlockState(@NotNull BlockPos pos)
{
throw new UnsupportedOperationException("ERROR: getBlockState() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@Override
public FluidState getFluidState(BlockPos pos)
public @NotNull FluidState getFluidState(@NotNull BlockPos pos)
{
throw new UnsupportedOperationException("ERROR: getFluidState() called on TintWithoutLevelOverrider. Object is for tinting only.");
}
@@ -1,48 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block.cache;
import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import net.minecraft.world.level.block.state.BlockState;
import java.util.concurrent.ConcurrentHashMap;
public class ClientBlockDetailMap
{
private final ConcurrentHashMap<BlockState, ClientBlockStateCache> blockCache = new ConcurrentHashMap<>();
//private final ConcurrentHashMap<#if MC_VER < MC_1_18_2 Biome #else Holder<Biome> #endif, Biome> biomeMap = new ConcurrentHashMap<>();
private final ClientLevelWrapper level;
public ClientBlockDetailMap(ClientLevelWrapper level) { this.level = level; }
public ClientBlockStateCache getBlockStateData(BlockState state, DhBlockPos pos)
{ //TODO: Allow a per pos unique setting
return blockCache.computeIfAbsent(state, (s) -> new ClientBlockStateCache(s, level, pos));
}
public void clear() { blockCache.clear(); }
public int getColor(BlockState state, BiomeWrapper biome, DhBlockPos pos)
{
return getBlockStateData(state, pos).getAndResolveFaceColor(biome, pos);
}
}
@@ -1,408 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block.cache;
import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
import com.seibel.distanthorizons.common.wrappers.block.TextureAtlasSpriteWrapper;
import com.seibel.distanthorizons.common.wrappers.block.TintWithoutLevelOverrider;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.common.wrappers.block.*;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FlowerBlock;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.RotatedPillarBlock;
#if MC_VER >= MC_1_19_2
import net.minecraft.util.RandomSource;
#else
import java.util.Random;
#endif
import net.minecraft.world.level.block.state.BlockState;
import org.apache.logging.log4j.Logger;
import java.util.HashSet;
import java.util.List;
/**
* @version 2022-9-16
*/
public class ClientBlockStateCache
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final HashSet<BlockState> BLOCK_STATES_THAT_NEED_LEVEL = new HashSet<>();
private static final HashSet<BlockState> BROKEN_BLOCK_STATES = new HashSet<>();
#if MC_VER < MC_1_19_2
public static final Random random = new Random(0);
#else
public static final RandomSource random = RandomSource.create();
#endif
public final BlockState blockState;
public final LevelReader level;
public final BlockPos pos;
public ClientBlockStateCache(BlockState blockState, IClientLevelWrapper samplingLevel, DhBlockPos samplingPos)
{
this.blockState = blockState;
level = (LevelReader) samplingLevel.getWrappedMcObject();
pos = McObjectConverter.Convert(samplingPos);
resolveColors();
//LOGGER.info("ClientBlocKCache created for {}", blockState);
}
boolean isColorResolved = false;
int baseColor = 0; //TODO: Impl per-face color
boolean needShade = true;
boolean needPostTinting = false;
int tintIndex = 0;
public static final int FLOWER_COLOR_SCALE = 5;
enum ColorMode
{
Default,
Flower,
Leaves,
Chisel,
Glass;
static ColorMode getColorMode(Block b)
{
if (b instanceof LeavesBlock) return Leaves;
if (b instanceof FlowerBlock) return Flower;
if (b.toString().contains("glass")) return Glass;
if (b.toString().equals("Block{chiselsandbits:chiseled}")) return Chisel;
return Default;
}
}
//Way to efficiently do this was suggested by IMS from sodium. This is where those numbers and support code was lifted from.
private static final int MIN_BITS = 0x39000000; // 2^(-13)
private static final int MAX_BITS = 0x3f7fffff; // 1.0 - f32::EPSILON
private static final float MIN_BOUND = Float.intBitsToFloat(MIN_BITS);
private static final float MAX_BOUND = Float.intBitsToFloat(MAX_BITS);
private static final int[] linearToSrgbTable = new int[] {
0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d,
0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a,
0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033,
0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067,
0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5,
0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2,
0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143,
0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af,
0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240,
0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300,
0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401,
0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559,
0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723,
};
private static final float[] srgbToLinearTable = new float[] {
0.0f, 0.000303527f, 0.000607054f, 0.00091058103f, 0.001214108f, 0.001517635f, 0.0018211621f, 0.002124689f,
0.002428216f, 0.002731743f, 0.00303527f, 0.0033465356f, 0.003676507f, 0.004024717f, 0.004391442f,
0.0047769533f, 0.005181517f, 0.0056053917f, 0.0060488326f, 0.006512091f, 0.00699541f, 0.0074990317f,
0.008023192f, 0.008568125f, 0.009134057f, 0.009721218f, 0.010329823f, 0.010960094f, 0.011612245f,
0.012286487f, 0.012983031f, 0.013702081f, 0.014443844f, 0.015208514f, 0.015996292f, 0.016807375f,
0.017641952f, 0.018500218f, 0.019382361f, 0.020288562f, 0.02121901f, 0.022173883f, 0.023153365f,
0.02415763f, 0.025186857f, 0.026241222f, 0.027320892f, 0.028426038f, 0.029556843f, 0.03071345f, 0.03189604f,
0.033104774f, 0.03433981f, 0.035601325f, 0.036889452f, 0.038204376f, 0.039546248f, 0.04091521f, 0.042311423f,
0.043735042f, 0.045186214f, 0.046665095f, 0.048171833f, 0.049706575f, 0.051269468f, 0.052860655f, 0.05448028f,
0.056128494f, 0.057805434f, 0.05951124f, 0.06124607f, 0.06301003f, 0.06480328f, 0.06662595f, 0.06847818f,
0.07036011f, 0.07227186f, 0.07421358f, 0.07618539f, 0.07818743f, 0.08021983f, 0.082282715f, 0.084376216f,
0.086500466f, 0.088655606f, 0.09084173f, 0.09305898f, 0.095307484f, 0.09758736f, 0.09989874f, 0.10224175f,
0.10461649f, 0.10702311f, 0.10946172f, 0.111932434f, 0.11443538f, 0.116970696f, 0.11953845f, 0.12213881f,
0.12477186f, 0.12743773f, 0.13013652f, 0.13286836f, 0.13563336f, 0.13843165f, 0.14126332f, 0.1441285f,
0.1470273f, 0.14995982f, 0.15292618f, 0.1559265f, 0.15896086f, 0.16202943f, 0.16513224f, 0.16826946f,
0.17144115f, 0.17464745f, 0.17788847f, 0.1811643f, 0.18447503f, 0.1878208f, 0.19120172f, 0.19461787f,
0.19806935f, 0.2015563f, 0.20507877f, 0.2086369f, 0.21223079f, 0.21586053f, 0.21952623f, 0.22322798f,
0.22696589f, 0.23074007f, 0.23455065f, 0.23839766f, 0.2422812f, 0.2462014f, 0.25015837f, 0.25415218f,
0.2581829f, 0.26225072f, 0.26635566f, 0.27049786f, 0.27467737f, 0.27889434f, 0.2831488f, 0.2874409f,
0.2917707f, 0.29613832f, 0.30054384f, 0.30498737f, 0.30946895f, 0.31398875f, 0.31854683f, 0.32314324f,
0.32777813f, 0.33245158f, 0.33716366f, 0.34191445f, 0.3467041f, 0.3515327f, 0.35640025f, 0.36130688f,
0.3662527f, 0.37123778f, 0.37626222f, 0.3813261f, 0.38642952f, 0.39157256f, 0.3967553f, 0.40197787f,
0.4072403f, 0.4125427f, 0.41788515f, 0.42326775f, 0.42869055f, 0.4341537f, 0.43965724f, 0.44520125f,
0.45078585f, 0.45641106f, 0.46207705f, 0.46778384f, 0.47353154f, 0.47932023f, 0.48514998f, 0.4910209f,
0.49693304f, 0.5028866f, 0.50888145f, 0.5149178f, 0.5209957f, 0.52711535f, 0.5332766f, 0.5394797f,
0.5457247f, 0.5520116f, 0.5583406f, 0.5647117f, 0.57112503f, 0.57758063f, 0.5840786f, 0.590619f, 0.597202f,
0.60382754f, 0.61049575f, 0.61720675f, 0.62396055f, 0.63075733f, 0.637597f, 0.6444799f, 0.6514058f,
0.65837497f, 0.66538745f, 0.67244333f, 0.6795426f, 0.68668544f, 0.69387203f, 0.70110214f, 0.70837605f,
0.7156938f, 0.72305536f, 0.730461f, 0.7379107f, 0.7454045f, 0.75294244f, 0.76052475f, 0.7681514f, 0.77582246f,
0.78353804f, 0.79129815f, 0.79910296f, 0.8069525f, 0.8148468f, 0.822786f, 0.8307701f, 0.83879924f, 0.84687346f,
0.8549928f, 0.8631574f, 0.87136734f, 0.8796226f, 0.8879232f, 0.89626956f, 0.90466136f, 0.913099f, 0.92158204f,
0.93011117f, 0.9386859f, 0.9473069f, 0.9559735f, 0.9646866f, 0.9734455f, 0.98225087f, 0.9911022f, 1.0f
};
private static int linearToSrgb(float c) {
if (!(c > MIN_BOUND)) {
c = MIN_BOUND;
}
if (c > MAX_BOUND) {
c = MAX_BOUND;
}
int inputBits = Float.floatToRawIntBits(c);
int entry = linearToSrgbTable[((inputBits - MIN_BITS) >> 20)];
int bias = (entry >>> 16) << 9;
int scale = entry & 0xffff;
int t = (inputBits >>> 12) & 0xff;
return (bias + (scale * t)) >>> 16;
}
//////////////
private static int getWidth(TextureAtlasSprite texture)
{
#if MC_VER < MC_1_19_4
return texture.getWidth();
#else
return texture.contents().width();
#endif
}
private static int getHeight(TextureAtlasSprite texture)
{
#if MC_VER < MC_1_19_4
return texture.getHeight();
#else
return texture.contents().height();
#endif
}
//TODO: Perhaps make this not just use the first frame?
private static int calculateColorFromTexture(TextureAtlasSprite texture, ColorMode colorMode)
{
int count = 0;
int alpha = 0;
double red = 0;
double green = 0;
double blue = 0;
int tempColor;
//make Chiseled block not render. Since ColorMode is set per block, you only need to check it once
if (colorMode != ColorMode.Chisel)
{
// textures normally use u and v instead of x and y
for (int v = 0; v < getHeight(texture); v++)
{
for (int u = 0; u < getWidth(texture); u++)
{
//note: Minecraft color format is: 0xAA BB GG RR
//________ DH mod color format is: 0xAA RR GG BB
//OpenGL RGBA format native order: 0xRR GG BB AA
//_ OpenGL RGBA format Java Order: 0xAA BB GG RR
tempColor = TextureAtlasSpriteWrapper.getPixelRGBA(texture, 0, u, v);
int r = (tempColor & 0x000000FF);
int g = (tempColor & 0x0000FF00) >>> 8;
int b = (tempColor & 0x00FF0000) >>> 16;
int a = (tempColor & 0xFF000000) >>> 24;
int scale = 1;
if (colorMode == ColorMode.Leaves)
{
//switch (//FIXME add config option)
// case BLACK:
// a = 255; //simulate black background of fast leaves
// break;
// case IGNORE:
if (a == 0) {
continue; //same long grass
}
else
{
a = 255; //just in case there are semi transparent pixels
}
// break;
// case TRANSPARENT:
// break; //do nothing, let it count towards transparency
}
else if (a == 0 && colorMode != ColorMode.Glass)
{
continue;
}
else if (colorMode == ColorMode.Flower && (g + 25 < b || g + 25 < r))
{
scale = FLOWER_COLOR_SCALE;
}
count += scale;
//apparently alpha is linear
alpha += a * scale;
//gamma correction is complicated
red += srgbToLinearTable[r] * a * scale;
green += srgbToLinearTable[g] * a * scale;
blue += srgbToLinearTable[b] * a * scale;
}
}
}
if (count == 0)
// this block is entirely transparent
tempColor = ColorUtil.rgbToInt(0, 255, 255, 255);
else
{
// determine the average color
tempColor = ColorUtil.rgbToInt(
alpha / count,
linearToSrgb((float) (red / (double) alpha)),
linearToSrgb((float) (green / (double) alpha)),
linearToSrgb((float) (blue / (double) alpha)));
}
//check if not missing texture
if (tempColor == ColorUtil.rgbToInt(255, 182, 0, 182))
{
//make it not render at all
tempColor = ColorUtil.rgbToInt(0, 255, 255, 255);
}
return tempColor;
}
private static final Direction[] DIRECTION_ORDER = {Direction.UP, Direction.NORTH, Direction.EAST, Direction.WEST, Direction.SOUTH, Direction.DOWN};
private void resolveColors()
{
if (isColorResolved) return;
if (blockState.getFluidState().isEmpty())
{
List<BakedQuad> quads = null;
for (Direction direction : DIRECTION_ORDER)
{
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(blockState).getQuads(blockState, direction, random); // TODO getQuads sometimes throws a "makeThreadingException", is there anything we can do about that? Note: this isn't a critical issue, it just prints an ugly error and the render data will need to be regenered.
if (quads != null && !quads.isEmpty() &&
!(blockState.getBlock() instanceof RotatedPillarBlock && direction == Direction.UP))
break;
} ;
if (quads == null || quads.isEmpty())
{
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
getBlockModel(blockState).getQuads(blockState, null, random);
}
if (quads != null && !quads.isEmpty())
{
needPostTinting = quads.get(0).isTinted();
needShade = quads.get(0).isShade();
tintIndex = quads.get(0).getTintIndex();
baseColor = calculateColorFromTexture(
#if MC_VER < MC_1_17_1 quads.get(0).sprite,
#else quads.get(0).getSprite(), #endif
ColorMode.getColorMode(blockState.getBlock()));
}
else
{ // Backup method.
needPostTinting = false;
needShade = false;
tintIndex = 0;
baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(blockState),
ColorMode.getColorMode(blockState.getBlock()));
}
}
else
{ // Liquid Block
needPostTinting = true;
needShade = false;
tintIndex = 0;
baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(blockState),
ColorMode.getColorMode(blockState.getBlock()));
}
isColorResolved = true;
}
public int getAndResolveFaceColor(BiomeWrapper biome, DhBlockPos pos)
{
// FIXME: impl per-face colors
// only get the tint if the block needs to be tinted
if (!this.needPostTinting)
{
return this.baseColor;
}
// don't try tinting blocks that don't support our method of tint getting
if (BROKEN_BLOCK_STATES.contains(this.blockState))
{
return this.baseColor;
}
// attempt to get the tint
int tintColor = -1;
try
{
// try to use the fast tint getter logic first
if (!BLOCK_STATES_THAT_NEED_LEVEL.contains(this.blockState))
{
try
{
tintColor = Minecraft.getInstance().getBlockColors()
.getColor(this.blockState, new TintWithoutLevelOverrider(biome), McObjectConverter.Convert(pos), this.tintIndex);
}
catch (UnsupportedOperationException e)
{
// this exception generally occurs if the tint requires other blocks besides itself
LOGGER.debug("Unable to use ["+TintWithoutLevelOverrider.class.getSimpleName()+"] to get the block tint for block: [" + this.blockState + "] and biome: [" + biome + "] at pos: " + pos + ". Error: [" + e.getMessage() + "]. Attempting to use backup method...", e);
BLOCK_STATES_THAT_NEED_LEVEL.add(this.blockState);
}
}
// use the level logic only if requested
if (BLOCK_STATES_THAT_NEED_LEVEL.contains(this.blockState))
{
// this logic can't be used all the time due to it breaking some blocks tinting
// specifically oceans don't render correctly
tintColor = Minecraft.getInstance().getBlockColors()
.getColor(this.blockState, new TintGetterOverrideFast(this.level), McObjectConverter.Convert(pos), this.tintIndex);
}
}
catch (Exception e)
{
// only display the error once per block/biome type to reduce log spam
if (!BROKEN_BLOCK_STATES.contains(this.blockState))
{
LOGGER.warn("Failed to get block color for block: [" + this.blockState + "] and biome: [" + biome + "] at pos: " + pos + ". Error: ["+e.getMessage() + "]. Note: future errors for this block/biome will be ignored.", e);
BROKEN_BLOCK_STATES.add(this.blockState);
}
}
if (tintColor != -1)
{
return ColorUtil.multiplyARGBwithRGB(this.baseColor, tintColor);
}
else
{
// unable to get the tinted color, use the base color instead
return this.baseColor;
}
}
}
@@ -1,43 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block.cache;
import java.util.concurrent.ConcurrentHashMap;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import net.minecraft.world.level.block.state.BlockState;
public class ServerBlockDetailMap
{
private final ConcurrentHashMap<BlockState, ServerBlockStateCache> blockCache = new ConcurrentHashMap<>();
//private final ConcurrentHashMap<#if MC_VER < MC_1_18_2 Biome #else Holder<Biome> #endif, Biome> biomeMap = new ConcurrentHashMap<>();
private final ServerLevelWrapper level;
public ServerBlockDetailMap(ServerLevelWrapper level) { this.level = level; }
public ServerBlockStateCache getBlockStateData(BlockState state, DhBlockPos pos)
{ //TODO: Allow a per pos unique setting
return blockCache.computeIfAbsent(state, (s) -> new ServerBlockStateCache(s, level, new DhBlockPos(0, 0, 0)));
}
public void clear() { blockCache.clear(); }
}
@@ -1,104 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.block.cache;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
/**
* @version 2022-9-16
*/
public class ServerBlockStateCache
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final BlockState state;
public final LevelReader level;
public final BlockPos pos;
public ServerBlockStateCache(BlockState blockState, ILevelWrapper samplingLevel, DhBlockPos samplingPos)
{
state = blockState;
level = (LevelReader) samplingLevel.getWrappedMcObject();
pos = McObjectConverter.Convert(samplingPos);
resolveShapes();
//LOGGER.info("ServerBlockState created for {}", blockState);
}
boolean noCollision = false;
boolean[] occludeFaces = null;
boolean[] fullFaces = null;
boolean isShapeResolved = false;
public void resolveShapes()
{
if (isShapeResolved) return;
if (state.getFluidState().isEmpty())
{
noCollision = state.getCollisionShape(level, pos).isEmpty();
occludeFaces = new boolean[6];
if (state.canOcclude())
{
for (Direction dir : Direction.values())
{
// Note: isEmpty() isn't quite correct... best would be a isFull() or something...
occludeFaces[McObjectConverter.Convert(dir).ordinal()]
= !state.getFaceOcclusionShape(level, pos, dir).isEmpty();
}
}
VoxelShape voxelShape = state.getShape(level, pos);
fullFaces = new boolean[6];
if (!voxelShape.isEmpty())
{
for (Direction dir : Direction.values())
{
VoxelShape faceShape = voxelShape.getFaceShape(dir);
AABB aabb = faceShape.bounds();
boolean xFull = aabb.minX <= 0.01 && aabb.maxX >= 0.99;
boolean yFull = aabb.minY <= 0.01 && aabb.maxY >= 0.99;
boolean zFull = aabb.minZ <= 0.01 && aabb.maxZ >= 0.99;
fullFaces[McObjectConverter.Convert(dir).ordinal()] =
(xFull || dir.getAxis().equals(Direction.Axis.X))
&& (yFull || dir.getAxis().equals(Direction.Axis.Y))
&& (zFull || dir.getAxis().equals(Direction.Axis.Z));
}
}
}
else
{ // Liquid Block. Treat as full block
occludeFaces = new boolean[6];
Arrays.fill(occludeFaces, true);
fullFaces = new boolean[6];
Arrays.fill(fullFaces, true);
}
}
}
@@ -1,239 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.chunk;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Compact, efficient storage for light levels.
* all blocks only take up 4 bits in total,
* and if a 16x16x16 area is detected to have the same light level in all positions,
* then we store a single byte for that light level, instead of 2 kilobytes.
*
* @author Builderb0y
*/
public class ChunkLightStorage
{
/** the minimum Y level in the chunk which this storage is storing light levels for (inclusive). */
public int minY;
/** the maximum Y level in the chunk which this storage is storing light levels for (exclusive). */
public int maxY;
/** the data stored in this storage, split up into 16x16x16 areas. */
public LightSection[] lightSections;
/**
* If the get method is called on a Y position above what's stored
* this value will be returned. <br><br>
*
* This needs to be manually defined since sky and block lights behave differently
* for values both above and below what's defined.
*/
public int aboveMaxYValue;
/** @see ChunkLightStorage#aboveMaxYValue */
public int belowMinYValue;
//=============//
// constructor //
//=============//
public ChunkLightStorage(int minY, int maxY, int aboveMaxYValue, int belowMinYValue)
{
this.minY = minY;
this.maxY = maxY;
this.aboveMaxYValue = aboveMaxYValue;
this.belowMinYValue = belowMinYValue;
}
//=====================//
// getters and setters //
//=====================//
public int get(int x, int y, int z)
{
if (y < this.minY)
{
return this.belowMinYValue;
}
else if (y >= this.maxY)
{
return this.aboveMaxYValue;
}
if (this.lightSections != null)
{
LightSection lightSection = this.lightSections[BitShiftUtil.divideByPowerOfTwo(y - this.minY, 4)];
if (lightSection != null)
{
return lightSection.get(x, y, z);
}
}
return 0;
}
public void set(int x, int y, int z, int lightLevel)
{
if (y < this.minY || y >= this.maxY)
{
return;
}
//populate array if it doesn't exist.
if (this.lightSections == null)
{
this.lightSections = new LightSection[BitShiftUtil.divideByPowerOfTwo(this.maxY - this.minY, 4)];
}
int index = (y - this.minY) >> 4;
LightSection lightSection = this.lightSections[index];
//populate lightSection in array if it doesn't exist.
if (lightSection == null)
{
lightSection = new LightSection(0);
this.lightSections[index] = lightSection;
}
lightSection.set(x, y, z, lightLevel);
}
//================//
// helper classes //
//================//
public static class LightSection
{
public byte constantValue;
public long[] data;
public short[] counts;
public LightSection(int initialValue)
{
this.constantValue = (byte) (initialValue);
this.counts = new short[16];
this.counts[initialValue] = 16 * 16 * 16;
}
public int get(int x, int y, int z)
{
if (this.constantValue >= 0)
{
return this.constantValue;
}
x &= 15;
y &= 15;
z &= 15;
long bits = this.data[(z << 4) | x];
return ((int) (bits >>> (y << 2))) & 15;
}
public void set(int x, int y, int z, int lightLevel)
{
int oldLightLevel = -1;
if (this.constantValue >= 0)
{
oldLightLevel = this.constantValue;
//if the light level didn't change, then there's nothing to do.
if (oldLightLevel == lightLevel) return;
//if we are a constant value and need to change something,
//then that means we need to convert to a non-constant value.
this.data = DataRecycler.get();
//repeat oldLightLevel 16 times as a bit pattern.
long payload = oldLightLevel;
payload |= payload << 4;
payload |= payload << 8;
payload |= payload << 16;
payload |= payload << 32;
//fill our data with our constant value.
Arrays.fill(this.data, payload);
//we are no longer a constant value.
this.constantValue = -1;
}
x &= 15;
y &= 15;
z &= 15;
int index = (z << 4) | x;
long bits = this.data[index];
//if we weren't a constant value before, now's the time to initialize oldLightLevel.
if (oldLightLevel < 0)
{
oldLightLevel = ((int) (bits >>> (y << 2))) & 15;
}
//clear the 4 bits that correspond to the light level at x, y, z...
bits &= ~(15L << (y << 2));
//...and then re-populate those bits with the new light level.
bits |= ((long) (lightLevel)) << (y << 2);
//store the updated bits in our data.
this.data[index] = bits;
//we have one less of the old light level...
this.counts[oldLightLevel]--;
//...and one more of the new level.
//if the number associated with the new level is now 4096 (AKA 16 ^ 3),
//then this implies every position in this section has the same light level,
//and therefore we can convert back to a constant value.
if (++this.counts[lightLevel] == 4096)
{
this.constantValue = (byte) (lightLevel);
DataRecycler.reclaim(this.data);
this.data = null;
}
}
}
static class DataRecycler
{
private static final ArrayList<long[]> recycled = new ArrayList<>(256);
static synchronized long[] get()
{
if (recycled.isEmpty())
{
return new long[256];
}
else
{
return recycled.remove(recycled.size() - 1);
}
}
static synchronized void reclaim(long[] data) { if (recycled.size() < 256) recycled.add(data); }
}
}
@@ -25,20 +25,18 @@ import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.Dh
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.levelgen.Heightmap;
@@ -73,13 +71,17 @@ import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.core.SectionPos;
#endif
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public class ChunkWrapper implements IChunkWrapper
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/** useful for debugging, but can slow down chunk operations quite a bit due to being called every time. */
private static final boolean RUN_RELATIVE_POS_INDEX_VALIDATION = ModInfo.IS_DEV_BUILD;
/** can be used for interactions with the underlying chunk where creating new BlockPos objects could cause issues for the garbage collector. */
private static final ThreadLocal<BlockPos.MutableBlockPos> MUTABLE_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new BlockPos.MutableBlockPos());
@@ -103,6 +105,8 @@ public class ChunkWrapper implements IChunkWrapper
private int minNonEmptyHeight = Integer.MIN_VALUE;
private int maxNonEmptyHeight = Integer.MAX_VALUE;
private int blockBiomeHashCode = 0;
/**
* Due to vanilla `isClientLightReady()` not being designed for use by a non-render thread, it may return 'true'
* before the light engine has ticked, (right after all light changes is marked by the engine to be processed).
@@ -142,30 +146,34 @@ public class ChunkWrapper implements IChunkWrapper
//=========//
// methods //
// getters //
//=========//
@Override
public int getHeight()
public int getHeight() { return getHeight(this.chunk); }
public static int getHeight(ChunkAccess chunk)
{
#if MC_VER < MC_1_17_1
return 255;
#else
return this.chunk.getHeight();
return chunk.getHeight();
#endif
}
@Override
public int getMinBuildHeight()
public int getMinBuildHeight() { return getMinBuildHeight(this.chunk); }
public static int getMinBuildHeight(ChunkAccess chunk)
{
#if MC_VER < MC_1_17_1
return 0;
#else
return this.chunk.getMinBuildHeight();
return chunk.getMinBuildHeight();
#endif
}
@Override
public int getMaxBuildHeight() { return this.chunk.getMaxBuildHeight(); }
public int getMaxBuildHeight() { return getMaxBuildHeight(this.chunk); }
public static int getMaxBuildHeight(ChunkAccess chunk) { return chunk.getMaxBuildHeight(); }
@Override
public int getMinNonEmptyHeight()
@@ -215,6 +223,9 @@ public class ChunkWrapper implements IChunkWrapper
LevelChunkSection[] sections = this.chunk.getSections();
for (int index = sections.length-1; index >= 0; index--)
{
// update at each position to fix using the max height if the chunk is empty
this.maxNonEmptyHeight = this.getChunkSectionMinHeight(index) + 16;
if (sections[index] == null)
{
continue;
@@ -222,7 +233,7 @@ public class ChunkWrapper implements IChunkWrapper
if (!isChunkSectionEmpty(sections[index]))
{
this.maxNonEmptyHeight = this.getChunkSectionMinHeight(index) + 16;
// non-empty section found
break;
}
}
@@ -239,15 +250,7 @@ public class ChunkWrapper implements IChunkWrapper
return section.hasOnlyAir();
#endif
}
private int getChunkSectionMinHeight(int index)
{
// convert from an index to a block coordinate
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
return this.chunk.getSections()[index].bottomBlockY();
#else
return this.chunk.getSectionYFromSectionIndex(index) * 16;
#endif
}
private int getChunkSectionMinHeight(int index) { return (index * 16) + this.getMinBuildHeight(); }
@Override
@@ -257,7 +260,6 @@ public class ChunkWrapper implements IChunkWrapper
public int getLightBlockingHeightMapValue(int xRel, int zRel) { return this.chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING).getFirstAvailable(xRel, zRel); }
@Override
public IBiomeWrapper getBiome(int relX, int relY, int relZ)
{
@@ -281,11 +283,35 @@ public class ChunkWrapper implements IChunkWrapper
#endif
}
@Override
public IBlockStateWrapper getBlockState(int relX, int relY, int relZ)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, relY, relZ);
BlockPos.MutableBlockPos blockPos = MUTABLE_BLOCK_POS_REF.get();
blockPos.setX(relX);
blockPos.setY(relY);
blockPos.setZ(relZ);
return BlockStateWrapper.fromBlockState(this.chunk.getBlockState(blockPos), this.wrappedLevel);
}
@Override
public DhChunkPos getChunkPos() { return this.chunkPos; }
public ChunkAccess getChunk() { return this.chunk; }
public ChunkStatus getStatus() { return getStatus(this.getChunk()); }
public static ChunkStatus getStatus(ChunkAccess chunk)
{
#if MC_VER < MC_1_21_1
return chunk.getStatus();
#else
return chunk.getPersistedStatus();
#endif
}
@Override
public int getMaxBlockX() { return this.chunk.getPos().getMaxBlockX(); }
@Override
@@ -295,8 +321,11 @@ public class ChunkWrapper implements IChunkWrapper
@Override
public int getMinBlockZ() { return this.chunk.getPos().getMinBlockZ(); }
@Override
public long getLongChunkPos() { return this.chunk.getPos().toLong(); }
//==========//
// lighting //
//==========//
@Override
public void setIsDhLightCorrect(boolean isDhLightCorrect) { this.isDhLightCorrect = isDhLightCorrect; }
@@ -304,8 +333,6 @@ public class ChunkWrapper implements IChunkWrapper
@Override
public void setUseDhLighting(boolean useDhLighting) { this.useDhLighting = useDhLighting; }
@Override
public boolean isLightCorrect()
{
@@ -358,13 +385,13 @@ public class ChunkWrapper implements IChunkWrapper
{
if (this.blockLightStorage == null)
{
this.blockLightStorage = new ChunkLightStorage(
this.getMinBuildHeight(), this.getMaxBuildHeight(),
// positions above and below the handled area should be unlit
LodUtil.MIN_MC_LIGHT, LodUtil.MIN_MC_LIGHT);
this.blockLightStorage = ChunkLightStorage.createBlockLightStorage(this);
}
return this.blockLightStorage;
}
public void setBlockLightStorage(ChunkLightStorage lightStorage) { this.blockLightStorage = lightStorage; }
@Override
public void clearDhBlockLighting() { this.getBlockLightStorage().clear(); }
@Override
@@ -379,18 +406,18 @@ public class ChunkWrapper implements IChunkWrapper
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
this.getSkyLightStorage().set(relX, y, relZ, lightValue);
}
@Override
public void clearDhSkyLighting() { this.getSkyLightStorage().clear(); }
private ChunkLightStorage getSkyLightStorage()
{
if (this.skyLightStorage == null)
{
this.skyLightStorage = new ChunkLightStorage(
this.getMinBuildHeight(), this.getMaxBuildHeight(),
// positions above should be lit but positions below should be unlit
LodUtil.MAX_MC_LIGHT, LodUtil.MIN_MC_LIGHT);
this.skyLightStorage = ChunkLightStorage.createSkyLightStorage(this);
}
return this.skyLightStorage;
}
public void setSkyLightStorage(ChunkLightStorage lightStorage) { this.skyLightStorage = lightStorage; }
@Override
@@ -460,55 +487,6 @@ public class ChunkWrapper implements IChunkWrapper
return this.blockLightPosList;
}
@Override
public boolean doNearbyChunksExist()
{
if (this.lightSource instanceof DhLitWorldGenRegion)
{
return true;
}
for (int dx = -1; dx <= 1; dx++)
{
for (int dz = -1; dz <= 1; dz++)
{
if (dx == 0 && dz == 0)
{
continue;
}
else if (this.lightSource.getChunk(dx + this.chunk.getPos().x, dz + this.chunk.getPos().z, ChunkStatus.BIOMES, false) == null)
{
return false;
}
}
}
return true;
}
public LevelReader getColorResolver() { return this.lightSource; }
@Override
public String toString() { return this.chunk.getClass().getSimpleName() + this.chunk.getPos(); }
@Override
public IBlockStateWrapper getBlockState(int relX, int relY, int relZ)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, relY, relZ);
BlockPos.MutableBlockPos blockPos = MUTABLE_BLOCK_POS_REF.get();
blockPos.setX(relX);
blockPos.setY(relY);
blockPos.setZ(relZ);
return BlockStateWrapper.fromBlockState(this.chunk.getBlockState(blockPos), this.wrappedLevel);
}
@Override
public boolean isStillValid() { return this.wrappedLevel.tryGetChunk(this.chunkPos) == this; }
public static void syncedUpdateClientLightStatus()
{
#if MC_VER < MC_1_18_2
@@ -568,64 +546,57 @@ public class ChunkWrapper implements IChunkWrapper
//================//
// helper methods //
//================//
//===============//
// other methods //
//===============//
/** used to prevent accidentally attempting to get/set values outside this chunk's boundaries */
private void throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(int x, int y, int z) throws IndexOutOfBoundsException
@Override
public boolean doNearbyChunksExist()
{
if (!RUN_RELATIVE_POS_INDEX_VALIDATION)
if (this.lightSource instanceof DhLitWorldGenRegion)
{
return;
return true;
}
// FIXME +1 is to handle the fact that LodDataBuilder adds +1 to all block lighting calculations, also done in the constructor
int minHeight = this.getMinBuildHeight();
int maxHeight = this.getMaxBuildHeight() + 1;
if (x < 0 || x >= LodUtil.CHUNK_WIDTH
|| z < 0 || z >= LodUtil.CHUNK_WIDTH
|| y < minHeight || y > maxHeight)
for (int dx = -1; dx <= 1; dx++)
{
String errorMessage = "Relative position [" + x + "," + y + "," + z + "] out of bounds. \n" +
"X/Z must be between 0 and 15 (inclusive) \n" +
"Y must be between [" + minHeight + "] and [" + maxHeight + "] (inclusive).";
throw new IndexOutOfBoundsException(errorMessage);
for (int dz = -1; dz <= 1; dz++)
{
if (dx == 0 && dz == 0)
{
continue;
}
else if (this.lightSource.getChunk(dx + this.chunk.getPos().x, dz + this.chunk.getPos().z, ChunkStatus.BIOMES, false) == null)
{
return false;
}
}
}
return true;
}
@Override
public boolean isStillValid() { return this.wrappedLevel.tryGetChunk(this.chunkPos) == this; }
/**
* Converts a 3D position into a 1D array index. <br><br>
*
* Source: <br>
* <a href="https://stackoverflow.com/questions/7367770/how-to-flatten-or-index-3d-array-in-1d-array">stackoverflow</a>
*/
public int relativeBlockPosToIndex(int xRel, int y, int zRel)
{
int yRel = y - this.getMinBuildHeight();
return (zRel * LodUtil.CHUNK_WIDTH * this.getHeight()) + (yRel * LodUtil.CHUNK_WIDTH) + xRel;
}
/**
* Converts a 3D position into a 1D array index. <br><br>
*
* Source: <br>
* <a href="https://stackoverflow.com/questions/7367770/how-to-flatten-or-index-3d-array-in-1d-array">stackoverflow</a>
*/
public DhBlockPos indexToRelativeBlockPos(int index)
{
final int zRel = index / (LodUtil.CHUNK_WIDTH * this.getHeight());
index -= (zRel * LodUtil.CHUNK_WIDTH * this.getHeight());
final int y = index / LodUtil.CHUNK_WIDTH;
final int yRel = y + this.getMinBuildHeight();
//================//
// base overrides //
//================//
final int xRel = index % LodUtil.CHUNK_WIDTH;
return new DhBlockPos(xRel, yRel, zRel);
}
@Override
public String toString() { return this.chunk.getClass().getSimpleName() + this.chunk.getPos(); }
//@Override
//public int hashCode()
//{
// if (this.blockBiomeHashCode == 0)
// {
// this.blockBiomeHashCode = this.getBlockBiomeHashCode();
// }
//
// return this.blockBiomeHashCode;
//}
}
@@ -98,6 +98,7 @@ public class ClassicConfigGUI
public static final int SpaceFromRightScreen = 10;
public static final int ButtonWidthSpacing = 5;
public static final int ResetButtonWidth = 40;
public static final int ResetButtonHeight = 20;
}
@@ -157,19 +158,22 @@ public class ClassicConfigGUI
// button.active = entries.stream().allMatch(e -> e.inLimits);
if (((ConfigEntry) info).isValid(value) == 0 && info.getType() != List.class)
if (info.getType() == String.class
|| info.getType() == List.class)
{
((ConfigEntry) info).uiSetWithoutSaving(stringValue);
}
else if (((ConfigEntry) info).isValid(value) == 0)
{
if (!cast)
{
((ConfigEntry) info).uiSetWithoutSaving(value);
}
else
{
((ConfigEntry) info).uiSetWithoutSaving(value.intValue());
}
}
// else if (((ConfigEntry) info).isValidMemoryAddress() == 0)
// {
// if (((List<String>) info.get()).size() == ((EntryInfo) info.guiValue).index)
// info.uiSet(((List<String>) info.get()).add(""));
// info.uiSet(((List<String>) info.get()).set(((EntryInfo) info.guiValue).index, Arrays.stream(((EntryInfo) info.guiValue).tempValue.replace("[", "").replace("]", "").split(", ")).collect(Collectors.toList()).get(0)));
// }
return true;
};
@@ -244,17 +248,23 @@ public class ClassicConfigGUI
}
// Changelog button
if (Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get() && Config.Client.Advanced.AutoUpdater.updateBranch.get() == EDhApiUpdateBranch.STABLE)
if (Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get() && !ModInfo.IS_DEV_BUILD) // we only have changelogs for stable builds
{
this.addBtn(new TexturedButtonWidget(
// Where the button is on the screen
this.width - 28, this.height - 28,
// Width and height of the button
20, 20,
// Offset
// texture UV Offset
0, 0,
// Some textuary stuff
0, new ResourceLocation(ModInfo.ID, "textures/gui/changelog.png"), 20, 20,
0,
#if MC_VER < MC_1_21_1
new ResourceLocation(ModInfo.ID, "textures/gui/changelog.png"),
#else
ResourceLocation.fromNamespaceAndPath(ModInfo.ID, "textures/gui/changelog.png"),
#endif
20, 20,
// Create the button and tell it where to go
(buttonWidget) -> {
ChangelogScreen changelogScreen = new ChangelogScreen(this);
@@ -269,18 +279,25 @@ public class ClassicConfigGUI
}
addBtn(MakeBtn(Translatable("distanthorizons.general.cancel"), this.width / 2 - 154, this.height - 28, 150, 20, button -> {
ConfigBase.INSTANCE.configFileINSTANCE.loadFromFile();
Objects.requireNonNull(minecraft).setScreen(parent);
}));
addBtn(MakeBtn(Translatable("distanthorizons.general.cancel"),
this.width / 2 - 154, this.height - 28,
150, 20,
button ->
{
ConfigBase.INSTANCE.configFileINSTANCE.loadFromFile();
Objects.requireNonNull(minecraft).setScreen(parent);
}));
doneButton = addBtn(MakeBtn(Translatable("distanthorizons.general.done"), this.width / 2 + 4, this.height - 28, 150, 20, (button) -> {
ConfigBase.INSTANCE.configFileINSTANCE.saveToFile();
Objects.requireNonNull(minecraft).setScreen(parent);
}));
this.list = new ConfigListWidget(this.minecraft, this.width * 2, this.height, 32, 32, 25);
#if MC_VER < MC_1_20_6 // no background is rendered in MC 1.20.6+
if (this.minecraft != null && this.minecraft.level != null)
this.list.setRenderBackground(false);
#endif
this.addWidget(this.list);
@@ -311,6 +328,7 @@ public class ClassicConfigGUI
initEntry(info, this.translationPrefix);
Component name = Translatable(translationPrefix + info.getNameWCategory());
if (ConfigEntry.class.isAssignableFrom(info.getClass()))
{
Button.OnPress btnAction = button -> {
@@ -319,12 +337,12 @@ public class ClassicConfigGUI
this.reload = true;
Objects.requireNonNull(minecraft).setScreen(this);
};
int a = this.width - ConfigScreenConfigs.SpaceFromRightScreen - 150 - ConfigScreenConfigs.ButtonWidthSpacing - ConfigScreenConfigs.ResetButtonWidth;
int b = 0;
int c = ConfigScreenConfigs.ResetButtonWidth;
int d = 20;
int posX = this.width - ConfigScreenConfigs.SpaceFromRightScreen - 150 - ConfigScreenConfigs.ButtonWidthSpacing - ConfigScreenConfigs.ResetButtonWidth;
int posZ = 0;
Button resetButton = MakeBtn(Translatable("distanthorizons.general.reset").withStyle(ChatFormatting.RED), a, b, c, d, btnAction);
Button resetButton = MakeBtn(Translatable("distanthorizons.general.reset").withStyle(ChatFormatting.RED),
posX, posZ, ConfigScreenConfigs.ResetButtonWidth, ConfigScreenConfigs.ResetButtonHeight,
btnAction);
if (((EntryInfo) info.guiValue).widget instanceof Map.Entry)
{
@@ -428,7 +446,9 @@ public class ClassicConfigGUI
String key = translationPrefix + (newInfo.category.isEmpty() ? "" : newInfo.category + ".") + newInfo.getName() + ".@tooltip";
if (((EntryInfo) newInfo.guiValue).error != null && text.equals(name))
{
DhRenderTooltip(matrices, font, ((EntryInfo) newInfo.guiValue).error.getValue(), mouseX, mouseY);
}
else if (I18n.exists(key) && (text != null && text.equals(name)))
{
List<Component> list = new ArrayList<>();
@@ -36,7 +36,7 @@ public class GetConfigScreen
case JavaFX:
return MinecraftScreen.getScreen(parent, new JavaScreenHandlerScreen(new ConfigScreen()), ModInfo.ID + ".title");
default:
return null;
throw new IllegalArgumentException("No config screen implementation defined for ["+useScreen+"].");
}
}
@@ -15,12 +15,12 @@ public class GuiHelper
/**
* Helper static methods for versional compat
*/
public static Button MakeBtn(Component base, int a, int b, int c, int d, Button.OnPress action)
public static Button MakeBtn(Component base, int posX, int posZ, int width, int height, Button.OnPress action)
{
#if MC_VER < MC_1_19_4
return new Button(a, b, c, d, base, action);
return new Button(posX, posZ, width, height, base, action);
#else
return Button.builder(base, action).bounds(a, b, c, d).build();
return Button.builder(base, action).bounds(posX, posZ, width, height).build();
#endif
}
@@ -60,8 +60,12 @@ public class MinecraftScreen
screen.init(); // Init our own config screen
this.list = new ConfigListWidget(this.minecraft, this.width, this.height, 0, 0, 25); // Select the area to tint
#if MC_VER < MC_1_20_6 // no background is rendered in MC 1.20.6+
if (this.minecraft != null && this.minecraft.level != null) // Check if in game
this.list.setRenderBackground(false); // Disable from rendering
#endif
this.addWidget(this.list); // Add the tint to the things to be rendered
}
@@ -101,11 +101,17 @@ public class ChangelogScreen extends DhScreen
this.changelog.add("");
this.changelog.add("");
String changelog = ModrinthGetter.changeLogs.get(versionID);
if (changelog == null)
{
// in case something goes wrong this will prevent null pointers
changelog = "";
}
// Get the release changelog and split it by the new lines
String[] unwrappedChangelog = // Arrays.asList could be used if a list object is desired here vs List.of which is only available for Java 9+
new MarkdownFormatter.MinecraftFormat().convertTo( // This formats markdown to minecraft's "§" characters
ModrinthGetter.changeLogs.get(versionID)
).split("\\n");
// This formats markdown to minecraft's "§" charactersnew MarkdownFormatter.MinecraftFormat().convertTo(
new MarkdownFormatter.MinecraftFormat().convertTo(changelog).split("\\n");
// Makes the words wrap around to not go off the screen
for (String str : unwrappedChangelog)
{
@@ -168,10 +174,9 @@ public class ChangelogScreen extends DhScreen
#endif
this.changelogArea.render(matrices, mouseX, mouseY, delta); // Render the changelog
// render order matters, otherwise on 1.20.6+ the blurred background will render on top of the text
super.render(matrices, mouseX, mouseY, delta); // Render the buttons
this.changelogArea.render(matrices, mouseX, mouseY, delta); // Render the changelog
DhDrawCenteredString(matrices, font, title, width / 2, 15, 0xFFFFFF); // Render title
}
@@ -42,15 +42,17 @@ public class UpdateModScreen extends DhScreen
this.parent = parent;
this.newVersionID = newVersionID;
switch (Config.Client.Advanced.AutoUpdater.updateBranch.get()) {
case STABLE:
currentVer = ModInfo.VERSION;
nextVer = ModrinthGetter.releaseNames.get(this.newVersionID);
break;
case NIGHTLY:
currentVer = ModJarInfo.Git_Commit.substring(0,7);
nextVer = this.newVersionID.substring(0,7);
break;
EDhApiUpdateBranch updateBranch = EDhApiUpdateBranch.convertAutoToStableOrNightly(Config.Client.Advanced.AutoUpdater.updateBranch.get());
if (updateBranch == EDhApiUpdateBranch.STABLE)
{
this.currentVer = ModInfo.VERSION;
this.nextVer = ModrinthGetter.releaseNames.get(this.newVersionID);
}
else
{
this.currentVer = ModJarInfo.Git_Commit.substring(0,7);
this.nextVer = this.newVersionID.substring(0,7);
}
}
@@ -73,7 +75,13 @@ public class UpdateModScreen extends DhScreen
// Offset
0, 0,
// Some textuary stuff
0, new ResourceLocation(ModInfo.ID, "logo.png"), 130, 65,
0,
#if MC_VER < MC_1_21_1
new ResourceLocation(ModInfo.ID, "logo.png"),
#else
ResourceLocation.fromNamespaceAndPath(ModInfo.ID, "logo.png"),
#endif
130, 65,
// Create the button and tell it where to go
// For now it goes to the client option by default
(buttonWidget) -> System.out.println("Nice, you found an easter egg :)"), // TODO: Add a proper easter egg to pressing the logo (maybe with confetti)
@@ -88,7 +96,7 @@ public class UpdateModScreen extends DhScreen
e.printStackTrace();
}
if (Config.Client.Advanced.AutoUpdater.updateBranch.get() == EDhApiUpdateBranch.STABLE)
if (!ModInfo.IS_DEV_BUILD)
{
this.addBtn(new TexturedButtonWidget(
// Where the button is on the screen
@@ -98,7 +106,13 @@ public class UpdateModScreen extends DhScreen
// Offset
0, 0,
// Some textuary stuff
0, new ResourceLocation(ModInfo.ID, "textures/gui/changelog.png"), 20, 20,
0,
#if MC_VER < MC_1_21_1
new ResourceLocation(ModInfo.ID, "textures/gui/changelog.png"),
#else
ResourceLocation.fromNamespaceAndPath(ModInfo.ID, "textures/gui/changelog.png"),
#endif
20, 20,
// Create the button and tell it where to go
(buttonWidget) -> Objects.requireNonNull(minecraft).setScreen(new ChangelogScreen(this, this.newVersionID)), // TODO: Add a proper easter egg to pressing the logo (maybe with confetti)
// Add a title to the button
@@ -147,17 +161,15 @@ public class UpdateModScreen extends DhScreen
this.renderBackground(matrices, mouseX, mouseY, delta); // Render background
#endif
// TODO: add the tooltips for the buttons
super.render(matrices, mouseX, mouseY, delta); // Render the buttons
// TODO: Add tooltips
// Render the text's
DhDrawCenteredString(matrices, this.font, Translatable(ModInfo.ID + ".updater.text1"), this.width / 2, this.height / 2 - 35, 0xFFFFFF);
DhDrawCenteredString(matrices, this.font,
Translatable(ModInfo.ID + ".updater.text2", currentVer, nextVer),
this.width / 2, this.height / 2 - 20, 0x52FD52);
// TODO: add the tooltips for the buttons
super.render(matrices, mouseX, mouseY, delta); // Render the buttons
// TODO: Add tooltips
}
@Override
@@ -31,6 +31,7 @@ import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -43,6 +44,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import net.minecraft.CrashReport;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.core.BlockPos;
@@ -59,16 +61,15 @@ import org.jetbrains.annotations.Nullable;
* A singleton that wraps the Minecraft object.
*
* @author James Seibel
* @version 3-5-2022
*/
//@Environment(EnvType.CLIENT)
public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecraftSharedWrapper
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
private static final Minecraft MINECRAFT = Minecraft.getInstance();
public static final MinecraftClientWrapper INSTANCE = new MinecraftClientWrapper();
public final Minecraft mc = Minecraft.getInstance();
/**
* The lightmap for the current:
@@ -99,10 +100,7 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
* This doesn't affect OpenGL objects in any way.
*/
@Override
public void clearFrameObjectCache()
{
lightMap = null;
}
public void clearFrameObjectCache() { this.lightMap = null; }
@@ -118,10 +116,10 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
{
default:
case AUTO:
if (this.mc.level != null)
if (MINECRAFT.level != null)
{
Direction mcDir = McObjectConverter.Convert(lodDirection);
return this.mc.level.getShade(mcDir, true);
return MINECRAFT.level.getShade(mcDir, true);
}
else
{
@@ -150,47 +148,63 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
}
@Override
public boolean hasSinglePlayerServer() { return mc.hasSingleplayerServer(); }
public boolean hasSinglePlayerServer() { return MINECRAFT.hasSingleplayerServer(); }
@Override
public boolean clientConnectedToDedicatedServer() { return mc.getCurrentServer() != null && !this.hasSinglePlayerServer(); }
public boolean clientConnectedToDedicatedServer() { return MINECRAFT.getCurrentServer() != null && !this.hasSinglePlayerServer(); }
@Override
public boolean connectedToReplay() { return !MINECRAFT.hasSingleplayerServer() && MINECRAFT.getCurrentServer() == null; }
@Override
public String getCurrentServerName() { return mc.getCurrentServer().name; }
public String getCurrentServerName()
{
if (this.connectedToReplay())
{
return ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME;
}
else
{
ServerData server = MINECRAFT.getCurrentServer();
return (server != null) ? server.name : "NULL";
}
}
@Override
public String getCurrentServerIp() { return mc.getCurrentServer().ip; }
public String getCurrentServerIp()
{
if (this.connectedToReplay())
{
return "";
}
else
{
ServerData server = MINECRAFT.getCurrentServer();
return (server != null) ? server.ip : "NA";
}
}
@Override
public String getCurrentServerVersion()
{
return mc.getCurrentServer().version.getString();
ServerData server = MINECRAFT.getCurrentServer();
return (server != null) ? server.version.getString() : "UNKOWN";
}
//=============//
// Simple gets //
//=============//
public LocalPlayer getPlayer()
{
return mc.player;
}
public LocalPlayer getPlayer() { return MINECRAFT.player; }
@Override
public boolean playerExists()
{
return mc.player != null;
}
public boolean playerExists() { return MINECRAFT.player != null; }
@Override
public UUID getPlayerUUID()
{
return getPlayer().getUUID();
}
public UUID getPlayerUUID() { return this.getPlayer().getUUID(); }
@Override
public DhBlockPos getPlayerBlockPos()
{
BlockPos playerPos = getPlayer().blockPosition();
BlockPos playerPos = this.getPlayer().blockPosition();
return new DhBlockPos(playerPos.getX(), playerPos.getY(), playerPos.getZ());
}
@@ -198,46 +212,38 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
public DhChunkPos getPlayerChunkPos()
{
#if MC_VER < MC_1_17_1
ChunkPos playerPos = new ChunkPos(getPlayer().blockPosition());
ChunkPos playerPos = new ChunkPos(this.getPlayer().blockPosition());
#else
ChunkPos playerPos = getPlayer().chunkPosition();
ChunkPos playerPos = this.getPlayer().chunkPosition();
#endif
return new DhChunkPos(playerPos.x, playerPos.z);
}
public ModelManager getModelManager()
{
return mc.getModelManager();
}
@Nullable
@Override
public IClientLevelWrapper getWrappedClientLevel()
{
if (this.mc.level == null)
if (MINECRAFT.level == null)
{
return null;
}
return ClientLevelWrapper.getWrapperIgnoringOverride(this.mc.level);
}
/** Please move over to getInstallationDirectory() */
@Deprecated
@Override
public File getGameDirectory()
{
return getInstallationDirectory();
return ClientLevelWrapper.getWrapperIgnoringOverride(MINECRAFT.level);
}
@Override
public IProfilerWrapper getProfiler()
{
if (profilerWrapper == null)
profilerWrapper = new ProfilerWrapper(mc.getProfiler());
else if (mc.getProfiler() != profilerWrapper.profiler)
profilerWrapper.profiler = mc.getProfiler();
return profilerWrapper;
if (this.profilerWrapper == null)
{
this.profilerWrapper = new ProfilerWrapper(MINECRAFT.getProfiler());
}
else if (MINECRAFT.getProfiler() != this.profilerWrapper.profiler)
{
this.profilerWrapper.profiler = MINECRAFT.getProfiler();
}
return this.profilerWrapper;
}
/** Returns all worlds available to the server */
@@ -246,7 +252,7 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
{
ArrayList<ILevelWrapper> worlds = new ArrayList<ILevelWrapper>();
Iterable<ServerLevel> serverWorlds = mc.getSingleplayerServer().getAllLevels();
Iterable<ServerLevel> serverWorlds = MINECRAFT.getSingleplayerServer().getAllLevels();
for (ServerLevel world : serverWorlds)
{
worlds.add(ServerLevelWrapper.getWrapper(world));
@@ -260,12 +266,12 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
@Override
public void sendChatMessage(String string)
{
LocalPlayer p = getPlayer();
if (p == null) return;
LocalPlayer player = this.getPlayer();
if (player == null) return;
#if MC_VER < MC_1_19_2
p.sendMessage(new TextComponent(string), getPlayer().getUUID());
player.sendMessage(new TextComponent(string), getPlayer().getUUID());
#else
p.sendSystemMessage(net.minecraft.network.chat.Component.translatable(string));
player.sendSystemMessage(net.minecraft.network.chat.Component.translatable(string));
#endif
}
@@ -290,24 +296,15 @@ public class MinecraftClientWrapper implements IMinecraftClientWrapper, IMinecra
}
@Override
public Object getOptionsObject()
{
return mc.options;
}
public Object getOptionsObject() { return MINECRAFT.options; }
@Override
public boolean isDedicatedServer()
{
return false;
}
public boolean isDedicatedServer() { return false; }
@Override
public File getInstallationDirectory()
{
return mc.gameDirectory;
}
public File getInstallationDirectory() { return MINECRAFT.gameDirectory; }
@Override
public void executeOnRenderThread(Runnable runnable) { this.mc.execute(runnable); }
public void executeOnRenderThread(Runnable runnable) { MINECRAFT.execute(runnable); }
}
@@ -12,16 +12,20 @@ public class MinecraftDedicatedServerWrapper implements IMinecraftSharedWrapper
private MinecraftDedicatedServerWrapper() { }
public DedicatedServer dedicatedServer = null;
@Override
public boolean isDedicatedServer()
{
return true;
}
public boolean isDedicatedServer() { return true; }
@Override
public File getInstallationDirectory()
{
if (dedicatedServer == null)
if (this.dedicatedServer == null)
{
throw new IllegalStateException("Trying to get Installation Direction before Dedicated server complete initialization!");
return dedicatedServer.getServerDirectory();
}
#if MC_VER < MC_1_21_1
return this.dedicatedServer.getServerDirectory();
#else
return this.dedicatedServer.getServerDirectory().toFile();
#endif
}
}
@@ -21,10 +21,9 @@ package com.seibel.distanthorizons.common.wrappers.minecraft;
import java.awt.Color;
import java.lang.invoke.MethodHandles;
import java.nio.FloatBuffer;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import com.mojang.blaze3d.pipeline.RenderTarget;
@@ -37,10 +36,10 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
#if MC_VER < MC_1_19_4
import org.joml.Matrix4f;
import org.joml.Vector3f;
#else
import org.joml.Matrix4f;
@@ -54,9 +53,9 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOpt
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.coreapi.util.math.Vec3d;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
@@ -80,7 +79,6 @@ import net.minecraft.world.level.material.FogType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
/**
@@ -105,7 +103,7 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
* In the case of immersive portals multiple levels may be active at once, causing conflicting lightmaps. <br>
* Requiring the use of multiple {@link LightMapWrapper}.
*/
public HashMap<IDimensionTypeWrapper, LightMapWrapper> lightmapByDimensionType = new HashMap<>();
public ConcurrentHashMap<IDimensionTypeWrapper, LightMapWrapper> lightmapByDimensionType = new ConcurrentHashMap<>();
/**
* Holds the render buffer that should be used when displaying levels to the screen.
@@ -122,14 +120,6 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
return new Vec3f(camera.getLookVector().x(), camera.getLookVector().y(), camera.getLookVector().z());
}
@Override
public DhBlockPos getCameraBlockPosition()
{
Camera camera = MC.gameRenderer.getMainCamera();
BlockPos blockPos = camera.getBlockPosition();
return new DhBlockPos(blockPos.getX(), blockPos.getY(), blockPos.getZ());
}
@Override
/** Unless you really need to know if the player is blind, use {@link MinecraftRenderWrapper#isFogStateSpecial()}/{@link IMinecraftRenderWrapper#isFogStateSpecial()} instead */
public boolean playerHasBlindingEffect()
@@ -150,43 +140,6 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
return new Vec3d(projectedView.x, projectedView.y, projectedView.z);
}
@Override
public Mat4f getWorldViewMatrix()
{
Camera camera = MC.gameRenderer.getMainCamera();
Vector3f cameraVec3 = new Vector3f(
(float)camera.getPosition().x,
(float)camera.getPosition().y,
(float)camera.getPosition().z);
cameraVec3 = cameraVec3.negate();
Matrix4f matWorldView = new Matrix4f()
.rotateX((float)Math.toRadians(camera.getXRot()))
.rotateY((float)Math.toRadians(camera.getYRot() + 180f))
.translate(cameraVec3);
return new Mat4f(matWorldView);
}
@Override
public Mat4f getDefaultProjectionMatrix(float partialTicks)
{
#if MC_VER < MC_1_17_1
return McObjectConverter.Convert(Minecraft.getInstance().gameRenderer.getProjectionMatrix(Minecraft.getInstance().gameRenderer.getMainCamera(), partialTicks, true));
#else
return McObjectConverter.Convert(MC.gameRenderer.getProjectionMatrix(MC.gameRenderer.getFov(MC.gameRenderer.getMainCamera(), partialTicks, true)));
#endif
}
@Override
public double getGamma()
{
#if MC_VER < MC_1_19_2
return MC.options.gamma;
#else
return MC.options.gamma().get();
#endif
}
@Override
public Color getFogColor(float partialTicks)
{
@@ -211,15 +164,24 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
{
if (MC.level.dimensionType().hasSkyLight())
{
#if MC_VER < MC_1_17_1
Vec3 colorValues = MC.level.getSkyColor(MC.gameRenderer.getMainCamera().getBlockPosition(), MC.getFrameTime());
float frameTime;
#if MC_VER < MC_1_21_1
frameTime = MC.getFrameTime();
#else
Vec3 colorValues = MC.level.getSkyColor(MC.gameRenderer.getMainCamera().getPosition(), MC.getFrameTime());
frameTime = MC.getTimer().getRealtimeDeltaTicks();
#endif
#if MC_VER < MC_1_17_1
Vec3 colorValues = MC.level.getSkyColor(MC.gameRenderer.getMainCamera().getBlockPosition(), frameTime);
#else
Vec3 colorValues = MC.level.getSkyColor(MC.gameRenderer.getMainCamera().getPosition(), frameTime);
#endif
return new Color((float) colorValues.x, (float) colorValues.y, (float) colorValues.z);
}
else
{
return new Color(0, 0, 0);
}
}
@Override
@@ -307,77 +269,6 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
return getRenderTarget().viewHeight;
}
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame. <br><br>
* <p>
*/
public boolean usingBackupGetVanillaRenderedChunks = false;
@Override
public HashSet<DhChunkPos> getVanillaRenderedChunks()
{
ISodiumAccessor sodium = ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class);
if (sodium != null)
{
return sodium.getNormalRenderedChunks();
}
IOptifineAccessor optifine = ModAccessorInjector.INSTANCE.get(IOptifineAccessor.class);
if (optifine != null)
{
HashSet<DhChunkPos> pos = optifine.getNormalRenderedChunks();
if (pos == null)
pos = getMaximumRenderedChunks();
return pos;
}
if (!usingBackupGetVanillaRenderedChunks)
{
try
{
#if MC_VER < MC_1_20_2
LevelRenderer levelRenderer = MC.levelRenderer;
Collection<LevelRenderer.RenderChunkInfo> chunks =
#if MC_VER < MC_1_18_2 levelRenderer.renderChunks;
#else levelRenderer.renderChunkStorage.get().renderChunks; #endif
return (chunks.stream().map((chunk) -> {
AABB chunkBoundingBox =
#if MC_VER < MC_1_18_2 chunk.chunk.bb;
#else chunk.chunk.getBoundingBox(); #endif
return new DhChunkPos(Math.floorDiv((int) chunkBoundingBox.minX, 16),
Math.floorDiv((int) chunkBoundingBox.minZ, 16));
}).collect(Collectors.toCollection(HashSet::new)));
#else
LevelRenderer levelRenderer = MC.levelRenderer;
Collection<SectionRenderDispatcher.RenderSection> chunks = levelRenderer.visibleSections;
return (chunks.stream().map((chunk) -> {
AABB chunkBoundingBox = chunk.getBoundingBox();
return new DhChunkPos(Math.floorDiv((int) chunkBoundingBox.minX, 16),
Math.floorDiv((int) chunkBoundingBox.minZ, 16));
}).collect(Collectors.toCollection(HashSet::new)));
#endif
}
catch (LinkageError e)
{
try
{
MinecraftClientWrapper.INSTANCE.sendChatMessage(
"\u00A7e\u00A7l\u00A7uWARNING: Distant Horizons: getVanillaRenderedChunks method failed."
+ " Using Backup Method.");
MinecraftClientWrapper.INSTANCE.sendChatMessage(
"\u00A7eOverdraw prevention will be worse than normal.");
}
catch (Exception e2)
{
}
LOGGER.error("getVanillaRenderedChunks Error: ", e);
usingBackupGetVanillaRenderedChunks = true;
}
}
return getMaximumRenderedChunks();
}
@Override
public ILightMapWrapper getLightmapWrapper(ILevelWrapper level) { return this.lightmapByDimensionType.get(level.getDimensionType()); }
@@ -405,11 +296,8 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
// so this will have to do for now
IDimensionTypeWrapper dimensionType = level.getDimensionType();
if (!this.lightmapByDimensionType.containsKey(dimensionType))
{
this.lightmapByDimensionType.put(dimensionType, new LightMapWrapper());
}
this.lightmapByDimensionType.get(dimensionType).uploadLightmap(lightPixels);
LightMapWrapper wrapper = this.lightmapByDimensionType.computeIfAbsent(dimensionType, (dimType) -> new LightMapWrapper());
wrapper.uploadLightmap(lightPixels);
}
}
@@ -36,14 +36,6 @@ public class LightMapWrapper implements ILightMapWrapper
public LightMapWrapper() { }
private void createLightmap(NativeImage image)
{
this.textureId = GL32.glGenTextures();
GL32.glBindTexture(GL32.GL_TEXTURE_2D, this.textureId);
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, image.format().glFormat(), image.getWidth(), image.getHeight(),
0, image.format().glFormat(), GL32.GL_UNSIGNED_BYTE, (ByteBuffer) null);
}
//=========//
@@ -53,14 +45,25 @@ public class LightMapWrapper implements ILightMapWrapper
public void uploadLightmap(NativeImage image)
{
int currentBind = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, this.textureId);
if (this.textureId == 0)
{
this.createLightmap(image);
}
else
{
GL32.glBindTexture(GL32.GL_TEXTURE_2D, this.textureId);
}
image.upload(0, 0, 0, false);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, currentBind);
}
private void createLightmap(NativeImage image)
{
this.textureId = GL32.glGenTextures();
GL32.glBindTexture(GL32.GL_TEXTURE_2D, this.textureId);
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, image.format().glFormat(), image.getWidth(), image.getHeight(),
0, image.format().glFormat(), GL32.GL_UNSIGNED_BYTE, (ByteBuffer) null);
}
@Override
public void bind()
@@ -1,31 +1,31 @@
package com.seibel.distanthorizons.common.wrappers.world;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiDimensionTypeWrapper;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper;
import com.seibel.distanthorizons.common.wrappers.block.cache.ClientBlockDetailMap;
import com.seibel.distanthorizons.common.wrappers.block.ClientBlockStateColorCache;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftClientWrapper;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
import com.seibel.distanthorizons.core.level.*;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.LightLayer;
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.level.chunk.ChunkStatus;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -33,16 +33,27 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
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<>();
private static final ConcurrentHashMap<ClientLevel, ClientLevelWrapper> LEVEL_WRAPPER_BY_CLIENT_LEVEL = new ConcurrentHashMap<>(); // TODO can leak
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
private static final Minecraft MINECRAFT = Minecraft.getInstance();
private final ClientLevel level;
private final ClientBlockDetailMap blockMap = new ClientBlockDetailMap(this);
private final ConcurrentHashMap<BlockState, ClientBlockStateColorCache> blockCache = new ConcurrentHashMap<>();
private BlockStateWrapper dirtBlockWrapper;
private BiomeWrapper plainsBiomeWrapper;
@Deprecated // TODO circular references are bad
private IDhLevel parentDhLevel;
@@ -82,7 +93,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
{
try
{
Iterable<ServerLevel> serverLevels = MinecraftClientWrapper.INSTANCE.mc.getSingleplayerServer().getAllLevels();
Iterable<ServerLevel> serverLevels = MINECRAFT.getSingleplayerServer().getAllLevels();
// attempt to find the server level with the same dimension type
// TODO this assumes only one level per dimension type, the SubDimensionLevelMatcher will need to be added for supporting multiple levels per dimension
@@ -102,7 +113,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
}
catch (Exception e)
{
LOGGER.error("Failed to get server side wrapper for client level: " + level);
LOGGER.error("Failed to get server side wrapper for client level: " + this.level);
return null;
}
}
@@ -114,9 +125,13 @@ public class ClientLevelWrapper implements IClientLevelWrapper
//====================//
@Override
public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper blockState)
public int getBlockColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper blockWrapper)
{
return this.blockMap.getColor(((BlockStateWrapper) blockState).blockState, (BiomeWrapper) biome, pos);
ClientBlockStateColorCache blockColorCache = this.blockCache.computeIfAbsent(
((BlockStateWrapper) blockWrapper).blockState,
(block) -> new ClientBlockStateColorCache(block, this));
return blockColorCache.getColor((BiomeWrapper) biome, pos);
}
@Override
@@ -136,7 +151,30 @@ public class ClientLevelWrapper implements IClientLevelWrapper
}
}
return this.blockMap.getColor(this.dirtBlockWrapper.blockState, BiomeWrapper.EMPTY_WRAPPER, DhBlockPos.ZERO);
return this.getBlockColor(DhBlockPos.ZERO,BiomeWrapper.EMPTY_WRAPPER, this.dirtBlockWrapper);
}
@Override
public void clearBlockColorCache() { this.blockCache.clear(); }
@Override
public IBiomeWrapper getPlainsBiomeWrapper()
{
if (this.plainsBiomeWrapper == null)
{
try
{
this.plainsBiomeWrapper = (BiomeWrapper) BiomeWrapper.deserialize(BiomeWrapper.PLAINS_RESOURCE_LOCATION_STRING, this);
}
catch (IOException e)
{
// shouldn't happen, but just in case
LOGGER.warn("Unable to get planes biome with resource location ["+BiomeWrapper.PLAINS_RESOURCE_LOCATION_STRING+"] with level ["+this+"].", e);
return null;
}
}
return this.plainsBiomeWrapper;
}
@Override
@@ -154,7 +192,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
public boolean hasSkyLight() { return this.level.dimensionType().hasSkyLight(); }
@Override
public int getHeight() { return this.level.getHeight(); }
public int getMaxHeight() { return this.level.getHeight(); }
@Override
public int getMinHeight()
@@ -203,7 +241,37 @@ public class ClientLevelWrapper implements IClientLevelWrapper
public ClientLevel getWrappedMcObject() { return this.level; }
@Override
public void onUnload() { LEVEL_WRAPPER_BY_CLIENT_LEVEL.remove(this.level); }
public void onUnload()
{
LEVEL_WRAPPER_BY_CLIENT_LEVEL.remove(this.level);
this.parentDhLevel = null;
}
//===================//
// generic rendering //
//===================//
@Override
public void setParentLevel(IDhLevel parentLevel) { this.parentDhLevel = parentLevel; }
@Override
public IDhApiCustomRenderRegister getRenderRegister()
{
if (this.parentDhLevel == null)
{
return null;
}
return this.parentDhLevel.getGenericRenderer();
}
//================//
// base overrides //
//================//
@Override
public String toString()
@@ -23,25 +23,29 @@ import java.io.File;
import java.util.concurrent.ConcurrentHashMap;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper;
import com.seibel.distanthorizons.common.wrappers.block.cache.ServerBlockDetailMap;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftClientWrapper;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
@@ -54,8 +58,9 @@ 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<>();
final ServerLevel level;
ServerBlockDetailMap blockMap = new ServerBlockDetailMap(this);
private final ServerLevel level;
@Deprecated // TODO circular references are bad
private IDhLevel parentDhLevel;
@@ -76,19 +81,6 @@ public class ServerLevelWrapper implements IServerLevelWrapper
// methods //
//=========//
@Nullable
@Override
public IClientLevelWrapper tryGetClientLevelWrapper()
{
MinecraftClientWrapper client = MinecraftClientWrapper.INSTANCE;
if (client.mc.level == null)
{
return null;
}
return ClientLevelWrapper.getWrapper(client.mc.level);
}
@Override
public File getSaveFolder()
{
@@ -122,7 +114,7 @@ public class ServerLevelWrapper implements IServerLevelWrapper
}
@Override
public int getHeight()
public int getMaxHeight()
{
return level.getHeight();
}
@@ -136,6 +128,7 @@ public class ServerLevelWrapper implements IServerLevelWrapper
return level.getMinBuildHeight();
#endif
}
@Override
public IChunkWrapper tryGetChunk(DhChunkPos pos)
{
@@ -166,18 +159,33 @@ public class ServerLevelWrapper implements IServerLevelWrapper
}
@Override
public ServerLevel getWrappedMcObject()
{
return level;
}
public ServerLevel getWrappedMcObject() { return this.level; }
@Override
public void onUnload() { LEVEL_WRAPPER_BY_SERVER_LEVEL.remove(this.level); }
@Override
public String toString()
public void setParentLevel(IDhLevel parentLevel) { this.parentDhLevel = parentLevel; }
@Override
public IDhApiCustomRenderRegister getRenderRegister()
{
return "Wrapped{" + level.toString() + "@" + getDimensionType().getDimensionName() + "}";
if (this.parentDhLevel == null)
{
return null;
}
return this.parentDhLevel.getGenericRenderer();
}
//================//
// base overrides //
//================//
@Override
public String toString() { return "Wrapped{" + this.level.toString() + "@" + this.getDimensionType().getDimensionName() + "}"; }
}
@@ -35,6 +35,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.objects.EventTimer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.gridList.ArrayGridList;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvironmentWrapper;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
@@ -66,7 +67,6 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
@@ -79,6 +79,12 @@ import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.nbt.CompoundTag;
import org.apache.logging.log4j.LogManager;
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
/*
Total: 3.135214124s
=====================================
@@ -104,8 +110,6 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
new ConfigBasedLogger(LogManager.getLogger("LodWorldGen"),
() -> Config.Client.Advanced.Logging.logWorldGenLoadEvent.get());
//TODO: Make actual proper support for StarLight
public static class PerfCalculator
{
private static final String[] TIME_NAMES = {
@@ -220,8 +224,8 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
// constructors //
//==============//
public static ImmutableMap<EDhApiWorldGenerationStep, Integer> BorderNeeded;
public static int MaxBorderNeeded;
public static final ImmutableMap<EDhApiWorldGenerationStep, Integer> WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP;
public static final int MAX_WORLD_GEN_CHUNK_BORDER_NEEDED;
static
{
@@ -249,8 +253,13 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
builder.put(EDhApiWorldGenerationStep.LIQUID_CARVERS, 0);
builder.put(EDhApiWorldGenerationStep.FEATURES, 0);
builder.put(EDhApiWorldGenerationStep.LIGHT, 0);
BorderNeeded = builder.build();
MaxBorderNeeded = BorderNeeded.values().stream().mapToInt(Integer::intValue).max().getAsInt();
WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP = builder.build();
// TODO this is a test to see if the additional boarder is actually necessary or not.
// If world generators end up having infinite loops or other unexplained issues,
// this should be set back to the commented out logic below
MAX_WORLD_GEN_CHUNK_BORDER_NEEDED = 0;
//MAX_WORLD_GEN_CHUNK_BORDER_NEEDED = WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP.values().stream().mapToInt(Integer::intValue).max().getAsInt();
}
public BatchGenerationEnvironment(IDhServerLevel serverlevel)
@@ -289,6 +298,9 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
//=================//
// synchronization //
//=================//
public <T> T joinSync(CompletableFuture<T> future)
{
@@ -339,8 +351,11 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
}
else if (event.hasTimeout(Config.Client.Advanced.WorldGenerator.worldGenerationTimeoutLengthInSeconds.get(), TimeUnit.SECONDS))
{
EVENT_LOGGER.error("Batching World Generator: " + event + " timed out and terminated!");
EVENT_LOGGER.info("Dump PrefEvent: " + event.timer);
EVENT_LOGGER.warn(
"Batching World Generator: [" + event + "] timed out and terminated after ["+Config.Client.Advanced.WorldGenerator.worldGenerationTimeoutLengthInSeconds.get()+"] seconds. " +
"\nYour computer might be overloaded or your world gen mods might be causing world gen to take longer than expected. " +
"\nEither increase DH's world gen timeout or reduce your computer's CPU load.");
EVENT_LOGGER.debug("Dump PrefEvent: " + event.timer);
try
{
if (!event.terminate())
@@ -363,31 +378,251 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
}
}
private static ProtoChunk EmptyChunk(ServerLevel level, ChunkPos chunkPos)
//==================//
// world generation //
//==================//
public void generateLodFromList(GenerationEvent genEvent) throws InterruptedException
{
return new ProtoChunk(chunkPos, UpgradeData.EMPTY
#if MC_VER >= MC_1_17_1 , level #endif
#if MC_VER >= MC_1_18_2 , level.registryAccess().registryOrThrow(
#if MC_VER < MC_1_19_4
Registry.BIOME_REGISTRY
#else
Registries.BIOME
#endif
), null #endif
);
EVENT_LOGGER.debug("Lod Generate Event: " + genEvent.minPos);
// Minecraft's generation events expect odd chunk width areas (3x3, 7x7, or 11x11),
// but DH submits square generation events (4x4).
// We handle this later, although that handling would need to change if the gen size ever changed.
LodUtil.assertTrue(genEvent.size % 2 == 0, "Generation events are expected to be an evan number of chunks wide.");
int borderSize = MAX_WORLD_GEN_CHUNK_BORDER_NEEDED;
// genEvent.size - 1 converts the even width size to an odd number for MC compatability
int refSize = (genEvent.size - 1) + (borderSize * 2);
int refPosX = genEvent.minPos.x - borderSize;
int refPosZ = genEvent.minPos.z - borderSize;
LightGetterAdaptor lightGetterAdaptor = new LightGetterAdaptor(this.params.level);
DummyLightEngine dummyLightEngine = new DummyLightEngine(lightGetterAdaptor);
//====================================//
// offset and generate odd width area //
//====================================//
// reused data between each offset
HashMap<DhChunkPos, ChunkLightStorage> chunkSkyLightingByDhPos = new HashMap<>();
HashMap<DhChunkPos, ChunkLightStorage> chunkBlockLightingByDhPos = new HashMap<>();
HashMap<DhChunkPos, ChunkAccess> generatedChunkByDhPos = new HashMap<>();
HashMap<DhChunkPos, ChunkWrapper> chunkWrappersByDhPos = new HashMap<>();
// offset 1 chunk in both X and Z direction so we can generate an even number of chunks wide
// while still submitting odd numbers to MC's internal generators
for (int xOffset = 0; xOffset < 2; xOffset++)
{
// final is so the offset can be used in lambdas
final int xOffsetFinal = xOffset;
for (int zOffset = 0; zOffset < 2; zOffset++)
{
final int zOffsetFinal = zOffset;
//================//
// variable setup //
//================//
int radius = refSize / 2;
int centerX = refPosX + radius + xOffset;
int centerZ = refPosZ + radius + zOffset;
// get/create the list of chunks we're going to generate
ArrayGridList<ChunkAccess> regionChunks = new ArrayGridList<>(
refSize,
(x, z) -> this.generateEmptyChunk(
x + refPosX + xOffsetFinal,
z + refPosZ + zOffsetFinal,
chunkSkyLightingByDhPos, chunkBlockLightingByDhPos, generatedChunkByDhPos));
ChunkAccess centerChunk = regionChunks.stream().filter(chunk -> chunk.getPos().x == centerX && chunk.getPos().z == centerZ).findFirst().get();
genEvent.refreshTimeout();
DhLitWorldGenRegion region = new DhLitWorldGenRegion(
centerX, centerZ,
centerChunk,
this.params.level, dummyLightEngine, regionChunks,
ChunkStatus.STRUCTURE_STARTS, radius,
// this method shouldn't be necessary since we're passing in a pre-populated
// list of chunks, but just in case
(x, z) -> this.generateEmptyChunk(x, z, chunkSkyLightingByDhPos, chunkBlockLightingByDhPos, generatedChunkByDhPos)
);
lightGetterAdaptor.setRegion(region);
genEvent.threadedParam.makeStructFeat(region, this.params);
//=========================//
// create chunk wrappers //
// and get existing chunks //
//=========================//
ArrayGridList<ChunkWrapper> chunkWrapperList = new ArrayGridList<>(regionChunks.gridSize);
regionChunks.forEachPos((relX, relZ) ->
{
// ArrayGridList's use relative positions and don't have a center position
// so we need to use the offsetFinal to select the correct position
DhChunkPos chunkPos = new DhChunkPos(relX + xOffsetFinal, relZ + zOffsetFinal);
ChunkAccess chunk = regionChunks.get(relX, relZ);
if (chunkWrappersByDhPos.containsKey(chunkPos))
{
chunkWrapperList.set(relX, relZ, chunkWrappersByDhPos.get(chunkPos));
}
else if (chunk != null)
{
// wrap the chunk
ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, region, this.serverlevel.getLevelWrapper());
chunkWrapperList.set(relX, relZ, chunkWrapper);
// try setting the wrapper's lighting
if (chunkBlockLightingByDhPos.containsKey(chunkWrapper.getChunkPos()))
{
chunkWrapper.setBlockLightStorage(chunkBlockLightingByDhPos.get(chunkWrapper.getChunkPos()));
chunkWrapper.setSkyLightStorage(chunkSkyLightingByDhPos.get(chunkWrapper.getChunkPos()));
chunkWrapper.setUseDhLighting(true);
chunkWrapper.setIsDhLightCorrect(true);
}
chunkWrappersByDhPos.put(chunkPos, chunkWrapper);
}
else //if (chunk == null)
{
LodUtil.assertNotReach("Programmer Error: No chunk found in grid list, position offset is likely wrong.");
}
});
//=================//
// generate chunks //
//=================//
this.generateDirect(genEvent, chunkWrapperList, borderSize, genEvent.targetGenerationStep, region);
genEvent.timer.nextEvent("cleanup");
}
}
//=========================//
// submit generated chunks //
//=========================//
for (DhChunkPos dhChunkPos : chunkWrappersByDhPos.keySet())
{
ChunkWrapper wrappedChunk = chunkWrappersByDhPos.get(dhChunkPos);
ChunkAccess target = wrappedChunk.getChunk();
if (target instanceof LevelChunk)
{
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
((LevelChunk) target).setLoaded(true);
#else
((LevelChunk) target).loaded = true;
#endif
}
if (!wrappedChunk.isLightCorrect())
{
throw new RuntimeException("The generated chunk somehow has isLightCorrect() returning false");
}
boolean isFull = ChunkWrapper.getStatus(target) == ChunkStatus.FULL || target instanceof LevelChunk;
#if MC_VER >= MC_1_18_2
boolean isPartial = target.isOldNoiseGeneration();
#endif
if (isFull)
{
LOAD_LOGGER.debug("Detected full existing chunk at {}", target.getPos());
genEvent.resultConsumer.accept(wrappedChunk);
}
#if MC_VER >= MC_1_18_2
else if (isPartial)
{
LOAD_LOGGER.debug("Detected old existing chunk at {}", target.getPos());
genEvent.resultConsumer.accept(wrappedChunk);
}
#endif
else if (ChunkWrapper.getStatus(target) == ChunkStatus.EMPTY)
{
genEvent.resultConsumer.accept(wrappedChunk);
}
else
{
genEvent.resultConsumer.accept(wrappedChunk);
}
}
genEvent.timer.complete();
genEvent.refreshTimeout();
if (PREF_LOGGER.canMaybeLog())
{
genEvent.threadedParam.perf.recordEvent(genEvent.timer);
PREF_LOGGER.debugInc("{}", genEvent.timer);
}
}
private ChunkAccess generateEmptyChunk(
int x, int z,
HashMap<DhChunkPos, ChunkLightStorage> chunkSkyLightingByDhPos,
HashMap<DhChunkPos, ChunkLightStorage> chunkBlockLightingByDhPos,
HashMap<DhChunkPos, ChunkAccess> generatedChunkByDhPos)
{
ChunkPos chunkPos = new ChunkPos(x, z);
DhChunkPos dhChunkPos = new DhChunkPos(x, z);
public ChunkAccess loadOrMakeChunk(ChunkPos chunkPos)
if (generatedChunkByDhPos.containsKey(dhChunkPos))
{
return generatedChunkByDhPos.get(dhChunkPos);
}
ChunkAccess newChunk = null;
try
{
// get the chunk
CompoundTag chunkData = this.getChunkNbtData(chunkPos);
newChunk = this.loadOrMakeChunk(chunkPos, chunkData);
if (Config.Client.Advanced.LodBuilding.pullLightingForPregeneratedChunks.get())
{
// attempt to get chunk lighting
ChunkLoader.CombinedChunkLightStorage combinedLights = ChunkLoader.readLight(newChunk, chunkData);
if (combinedLights != null)
{
chunkSkyLightingByDhPos.put(dhChunkPos, combinedLights.skyLightStorage);
chunkBlockLightingByDhPos.put(dhChunkPos, combinedLights.blockLightStorage);
}
}
}
catch (RuntimeException loadChunkError)
{
// Continue...
}
if (newChunk == null)
{
newChunk = new ProtoChunk(chunkPos, UpgradeData.EMPTY
#if MC_VER >= MC_1_17_1 , this.params.level #endif
#if MC_VER >= MC_1_18_2 , this.params.biomes, null #endif
);
}
generatedChunkByDhPos.put(dhChunkPos, newChunk);
return newChunk;
}
private CompoundTag getChunkNbtData(ChunkPos chunkPos)
{
ServerLevel level = this.params.level;
//====================//
// get the chunk data //
//====================//
CompoundTag chunkData = null;
try
{
@@ -420,175 +655,52 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
LOAD_LOGGER.error("DistantHorizons: Couldn't load or make chunk " + chunkPos + ". Error: " + e.getMessage(), e);
}
//========================//
// convert the chunk data //
//========================//
return chunkData;
}
private ChunkAccess loadOrMakeChunk(ChunkPos chunkPos, CompoundTag chunkData)
{
ServerLevel level = this.params.level;
if (chunkData == null)
{
return EmptyChunk(level, chunkPos);
return CreateEmptyChunk(level, chunkPos);
}
else
{
try
{
LOAD_LOGGER.info("DistantHorizons: Loading chunk [" + chunkPos + "] from disk.");
LOAD_LOGGER.debug("DistantHorizons: Loading chunk [" + chunkPos + "] from disk.");
return ChunkLoader.read(level, chunkPos, chunkData);
}
catch (Exception e)
{
LOAD_LOGGER.error(
"DistantHorizons: couldn't load or make chunk at ["+chunkPos+"]." +
"Please try optimizing your world to fix this issue. \n" +
"World optimization can be done from the singleplayer world selection screen.\n" +
"Error: ["+e.getMessage()+"]."
, e);
"DistantHorizons: couldn't load or make chunk at ["+chunkPos+"]." +
"Please try optimizing your world to fix this issue. \n" +
"World optimization can be done from the singleplayer world selection screen.\n" +
"Error: ["+e.getMessage()+"]."
, e);
return EmptyChunk(level, chunkPos);
return CreateEmptyChunk(level, chunkPos);
}
}
}
private static <T> ArrayGridList<T> GetCutoutFrom(ArrayGridList<T> total, int border)
private static ProtoChunk CreateEmptyChunk(ServerLevel level, ChunkPos chunkPos)
{
return new ArrayGridList<>(total, border, total.gridSize - border);
return new ProtoChunk(chunkPos, UpgradeData.EMPTY
#if MC_VER >= MC_1_17_1 , level #endif
#if MC_VER >= MC_1_18_2 , level.registryAccess().registryOrThrow(
#if MC_VER < MC_1_19_4
Registry.BIOME_REGISTRY
#else
Registries.BIOME
#endif
), null #endif
);
}
private static <T> ArrayGridList<T> GetCutoutFrom(ArrayGridList<T> total, EDhApiWorldGenerationStep step)
{
return GetCutoutFrom(total, MaxBorderNeeded - BorderNeeded.get(step));
}
public void generateLodFromList(GenerationEvent genEvent) throws InterruptedException
{
EVENT_LOGGER.debug("Lod Generate Event: " + genEvent.minPos);
ArrayGridList<ChunkWrapper> chunkWrapperList;
DhLitWorldGenRegion region;
DummyLightEngine lightEngine;
LightGetterAdaptor adaptor;
int borderSize = MaxBorderNeeded;
int refSize = genEvent.size + borderSize * 2;
int refPosX = genEvent.minPos.x - borderSize;
int refPosZ = genEvent.minPos.z - borderSize;
try
{
ArrayGridList<ChunkAccess> totalChunks;
adaptor = new LightGetterAdaptor(this.params.level);
lightEngine = new DummyLightEngine(adaptor);
EmptyChunkGenerator generator = (int x, int z) ->
{
ChunkPos chunkPos = new ChunkPos(x, z);
ChunkAccess target = null;
try
{
target = this.loadOrMakeChunk(chunkPos);
}
catch (RuntimeException e2)
{
// Continue...
}
if (target == null)
{
target = new ProtoChunk(chunkPos, UpgradeData.EMPTY
#if MC_VER >= MC_1_17_1 , params.level #endif
#if MC_VER >= MC_1_18_2 , params.biomes, null #endif
);
}
return target;
};
totalChunks = new ArrayGridList<>(refSize, (x, z) -> generator.generate(x + refPosX, z + refPosZ));
genEvent.refreshTimeout();
region = new DhLitWorldGenRegion(params.level, lightEngine, totalChunks,
ChunkStatus.STRUCTURE_STARTS, refSize / 2, generator);
adaptor.setRegion(region);
genEvent.threadedParam.makeStructFeat(region, params);
chunkWrapperList = new ArrayGridList<>(totalChunks.gridSize);
totalChunks.forEachPos((x, z) ->
{
ChunkAccess chunk = totalChunks.get(x, z);
if (chunk != null)
{
chunkWrapperList.set(x, z, new ChunkWrapper(chunk, region, serverlevel.getLevelWrapper()));
}
});
this.generateDirect(genEvent, chunkWrapperList, borderSize, genEvent.targetGenerationStep, region);
genEvent.timer.nextEvent("cleanup");
}
catch (StepStructureStart.StructStartCorruptedException f)
{
genEvent.threadedParam.markAsInvalid();
throw (RuntimeException) f.getCause();
}
ArrayGridList<ChunkWrapper> finalGenChunks = GetCutoutFrom(chunkWrapperList, borderSize);
for (int offsetY = 0; offsetY < finalGenChunks.gridSize; offsetY++)
{
for (int offsetX = 0; offsetX < finalGenChunks.gridSize; offsetX++)
{
ChunkWrapper wrappedChunk = finalGenChunks.get(offsetX, offsetY);
ChunkAccess target = wrappedChunk.getChunk();
if (target instanceof LevelChunk)
{
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
((LevelChunk) target).setLoaded(true);
#else
((LevelChunk) target).loaded = true;
#endif
}
if (!wrappedChunk.isLightCorrect())
{
throw new RuntimeException("The generated chunk somehow has isLightCorrect() returning false");
}
boolean isFull = target.getStatus() == ChunkStatus.FULL || target instanceof LevelChunk;
#if MC_VER >= MC_1_18_2
boolean isPartial = target.isOldNoiseGeneration();
#endif
if (isFull)
{
LOAD_LOGGER.info("Detected full existing chunk at {}", target.getPos());
genEvent.resultConsumer.accept(wrappedChunk);
}
#if MC_VER >= MC_1_18_2
else if (isPartial)
{
LOAD_LOGGER.info("Detected old existing chunk at {}", target.getPos());
genEvent.resultConsumer.accept(wrappedChunk);
}
#endif
else if (target.getStatus() == ChunkStatus.EMPTY)
{
genEvent.resultConsumer.accept(wrappedChunk);
}
else
{
genEvent.resultConsumer.accept(wrappedChunk);
}
}
}
genEvent.timer.complete();
genEvent.refreshTimeout();
if (PREF_LOGGER.canMaybeLog())
{
genEvent.threadedParam.perf.recordEvent(genEvent.timer);
PREF_LOGGER.infoInc("{}", genEvent.timer);
}
}
public void generateDirect(
GenerationEvent genEvent, ArrayGridList<ChunkWrapper> chunksToGenerate, int border,
@@ -689,7 +801,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
for (int i = 0; i < chunksToGenerate.size(); i++) // regular for loop since enhanced for loops increase GC pressure slightly
{
ChunkWrapper chunkWrapper = chunksToGenerate.get(i);
if (chunkWrapper.getChunk().getStatus() != ChunkStatus.EMPTY)
if (chunkWrapper.getStatus() != ChunkStatus.EMPTY)
{
iChunkWrapperList.add(chunkWrapper);
}
@@ -710,19 +822,20 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
// if this isn't done everything else afterward may fail
Heightmap.primeHeightmaps(((ChunkWrapper)centerChunk).getChunk(), ChunkStatus.FEATURES.heightmapsAfter());
// populate the lighting
DhLightingEngine.INSTANCE.lightChunk(centerChunk, iChunkWrapperList, maxSkyLight);
// pre-generated chunks should have lighting but new ones won't
if (!centerChunk.isLightCorrect())
{
DhLightingEngine.INSTANCE.lightChunk(centerChunk, iChunkWrapperList, maxSkyLight);
}
}
genEvent.refreshTimeout();
}
}
private static <T> ArrayGridList<T> GetCutoutFrom(ArrayGridList<T> total, int border) { return new ArrayGridList<>(total, border, total.gridSize - border); }
//private static <T> ArrayGridList<T> GetCutoutFrom(ArrayGridList<T> total, EDhApiWorldGenerationStep step) { return GetCutoutFrom(total, MaxBorderNeeded - WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP.get(step)); }
private static <T> ArrayGridList<T> GetCutoutFrom(ArrayGridList<T> total, EDhApiWorldGenerationStep step) { return GetCutoutFrom(total, 0); }
public interface EmptyChunkGenerator
{
ChunkAccess generate(int x, int z);
}
@Override
public int getEventCount() { return this.generationEventList.size(); }
@@ -771,6 +884,12 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
return genEvent.future;
}
//================//
// helper methods //
//================//
/**
* Called before code that may run for an extended period of time. <br>
* This is necessary to allow canceling world gen since waiting
@@ -784,4 +903,16 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
}
}
//================//
// helper classes //
//================//
@FunctionalInterface
public interface IEmptyChunkGeneratorFunc
{
ChunkAccess generate(int x, int z);
}
}
@@ -43,6 +43,7 @@ public final class GenerationEvent
public final int id;
public final ThreadedParameters threadedParam;
public final DhChunkPos minPos;
/** the number of chunks wide this event is */
public final int size;
public final EDhApiWorldGenerationStep targetGenerationStep;
public EventTimer timer = null;
@@ -73,10 +74,10 @@ public final class GenerationEvent
EDhApiWorldGenerationStep target, Consumer<IChunkWrapper> resultConsumer,
ExecutorService worldGeneratorThreadPool)
{
if (size % 2 == 0)
{
size += 1; // size must be odd for vanilla world gen regions to work
}
//if (size % 2 == 0)
//{
// size += 1; // size must be odd for vanilla world gen regions to work
//}
GenerationEvent generationEvent = new GenerationEvent(minPos, size, genEnvironment, target, resultConsumer);
@@ -93,9 +94,7 @@ public final class GenerationEvent
//LOGGER.info("generating [{}]", event.minPos);
genEnvironment.generateLodFromList(generationEvent);
}
catch (InterruptedException ignored)
{
}
catch (InterruptedException ignored) { }
finally
{
BatchGenerationEnvironment.isDistantGeneratorThread.remove();
@@ -126,21 +125,6 @@ public final class GenerationEvent
return this.future.isCancelled();
}
public boolean tooClose(int minX, int minZ, int width)
{
int aMinX = this.minPos.x;
int aMinZ = this.minPos.z;
int aSize = this.size;
// Account for required empty chunks in the border
aSize += 1;
width += 1;
// Do a AABB to AABB intersection test
return (aMinX + aSize >= minX &&
aMinX <= minX + width &&
aMinZ + aSize >= minZ &&
aMinZ <= minZ + width);
}
public void refreshTimeout()
{
this.timeoutTime = System.nanoTime();
@@ -22,9 +22,14 @@ package com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject;
import com.google.common.collect.Maps;
import com.mojang.serialization.Codec;
import com.mojang.serialization.Dynamic;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
@@ -44,6 +49,7 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.*;
@@ -76,6 +82,14 @@ import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
import net.minecraft.world.level.material.Fluids;
#endif
#if MC_VER == MC_1_20_6
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.status.ChunkType;
#elif MC_VER >= MC_1_21_1
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.status.ChunkType;
#endif
import net.minecraft.world.level.material.Fluid;
@@ -95,133 +109,13 @@ public class ChunkLoader
private static final String FLUID_TICKS_TAG_PRE18 = "LiquidTicks";
private static final ConfigBasedLogger LOGGER = BatchGenerationEnvironment.LOAD_LOGGER;
#if MC_VER >= MC_1_18_2
private static BlendingData readBlendingData(CompoundTag chunkData)
{
BlendingData blendingData = null;
if (chunkData.contains("blending_data", 10))
{
@SuppressWarnings({"unchecked", "rawtypes"})
Dynamic<CompoundTag> blendingDataTag = new Dynamic(NbtOps.INSTANCE, chunkData.getCompound("blending_data"));
blendingData = BlendingData.CODEC.parse(blendingDataTag).resultOrPartial(LOGGER::error).orElse(null);
}
return blendingData;
}
#endif
private static boolean lightingSectionErrorLogged = false;
private static LevelChunkSection[] readSections(LevelAccessor level, ChunkPos chunkPos, CompoundTag chunkData)
{
#if MC_VER >= MC_1_18_2
#if MC_VER < MC_1_19_4
Registry<Biome> biomes = level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
#else
Registry<Biome> biomes = level.registryAccess().registryOrThrow(Registries.BIOME);
#endif
#if MC_VER < MC_1_18_2
Codec<PalettedContainer<Biome>> biomeCodec = PalettedContainer.codec(
biomes, biomes.byNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getOrThrow(Biomes.PLAINS));
#elif MC_VER < MC_1_19_2
Codec<PalettedContainer<Holder<Biome>>> biomeCodec = PalettedContainer.codec(
biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getHolderOrThrow(Biomes.PLAINS));
#else
Codec<PalettedContainer<Holder<Biome>>> biomeCodec = PalettedContainer.codecRW(
biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getHolderOrThrow(Biomes.PLAINS));
#endif
#endif
int i = #if MC_VER < MC_1_17_1 16; #else level.getSectionsCount(); #endif
LevelChunkSection[] chunkSections = new LevelChunkSection[i];
boolean isLightOn = chunkData.getBoolean("isLightOn");
boolean hasSkyLight = level.dimensionType().hasSkyLight();
ListTag tagSections = chunkData.getList("Sections", 10);
if (tagSections.isEmpty()) tagSections = chunkData.getList("sections", 10);
for (int j = 0; j < tagSections.size(); ++j)
{
CompoundTag tagSection = tagSections.getCompound(j);
int sectionYPos = tagSection.getByte("Y");
#if MC_VER < MC_1_18_2
if (tagSection.contains("Palette", 9) && tagSection.contains("BlockStates", 12))
{
LevelChunkSection levelChunkSection = new LevelChunkSection(sectionYPos << 4);
levelChunkSection.getStates().read(tagSection.getList("Palette", 10),
tagSection.getLongArray("BlockStates"));
levelChunkSection.recalcBlockCounts();
if (!levelChunkSection.isEmpty())
chunkSections[#if MC_VER < MC_1_17_1 sectionYPos #else level.getSectionIndexFromSectionY(sectionYPos) #endif ]
= levelChunkSection;
}
#else
int sectionId = level.getSectionIndexFromSectionY(sectionYPos);
if (sectionId >= 0 && sectionId < chunkSections.length)
{
PalettedContainer<BlockState> blockStateContainer;
#if MC_VER < MC_1_18_2
PalettedContainer<Biome> biomeContainer;
#else
PalettedContainer<Holder<Biome>> biomeContainer;
#endif
blockStateContainer = tagSection.contains("block_states", 10)
? BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagSection.getCompound("block_states")).promotePartial(string -> logErrors(chunkPos, sectionYPos, string)).getOrThrow(false, LOGGER::error)
: 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)
: new PalettedContainer<Biome>(biomes, biomes.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
#else
biomeContainer = tagSection.contains("biomes", 10)
? biomeCodec.parse(NbtOps.INSTANCE, tagSection.getCompound("biomes")).promotePartial(string -> logErrors(chunkPos, i, (String) string)).getOrThrow(false, LOGGER::error)
: new PalettedContainer<Holder<Biome>>(biomes.asHolderIdMap(), biomes.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
#endif
#if MC_VER < MC_1_20_1
chunkSections[sectionId] = new LevelChunkSection(sectionYPos, blockStateContainer, biomeContainer);
#else
chunkSections[sectionId] = new LevelChunkSection(blockStateContainer, biomeContainer);
#endif
}
#endif
}
return chunkSections;
}
private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData)
{
CompoundTag tagHeightmaps = chunkData.getCompound("Heightmaps");
for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter())
{
String heightmap = type.getSerializationKey();
if (tagHeightmaps.contains(heightmap, 12))
chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmap));
}
Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter());
}
private static void readPostPocessings(LevelChunk chunk, CompoundTag chunkData)
{
ListTag tagPostProcessings = chunkData.getList("PostProcessing", 9);
for (int n = 0; n < tagPostProcessings.size(); ++n)
{
ListTag listTag3 = tagPostProcessings.getList(n);
for (int o = 0; o < listTag3.size(); ++o)
{
chunk.addPackedPostProcess(listTag3.getShort(o), n);
}
}
}
public static ChunkStatus.ChunkType readChunkType(CompoundTag tagLevel)
{
ChunkStatus chunkStatus = ChunkStatus.byName(tagLevel.getString("Status"));
if (chunkStatus != null)
{
return chunkStatus.getChunkType();
}
return ChunkStatus.ChunkType.PROTOCHUNK;
}
//============//
// read chunk //
//============//
public static LevelChunk read(WorldGenLevel level, ChunkPos chunkPos, CompoundTag chunkData)
{
@@ -262,19 +156,27 @@ public class ChunkLoader
}
}
ChunkStatus.ChunkType chunkType = readChunkType(tagLevel);
#if MC_VER < MC_1_18_2
if (chunkType != ChunkStatus.ChunkType.LEVELCHUNK)
return null;
#if MC_VER < MC_1_20_6
ChunkStatus.ChunkType chunkType;
#else
BlendingData blendingData = readBlendingData(tagLevel);
#if MC_VER < MC_1_19_2
if (chunkType == ChunkStatus.ChunkType.PROTOCHUNK && (blendingData == null || !blendingData.oldNoise()))
return null;
#else
if (chunkType == ChunkStatus.ChunkType.PROTOCHUNK && blendingData == null)
return null;
ChunkType chunkType;
#endif
chunkType = readChunkType(tagLevel);
#if MC_VER < MC_1_18_2
if (chunkType != ChunkStatus.ChunkType.LEVELCHUNK)
return null;
#else
BlendingData blendingData = readBlendingData(tagLevel);
#if MC_VER < MC_1_19_2
if (chunkType == ChunkStatus.ChunkType.PROTOCHUNK && (blendingData == null || !blendingData.oldNoise()))
return null;
#else
if (chunkType == #if MC_VER < MC_1_20_6 ChunkStatus.ChunkType.PROTOCHUNK #else ChunkType.PROTOCHUNK #endif && blendingData == null)
return null;
#endif
#endif
long inhabitedTime = tagLevel.getLong("InhabitedTime");
@@ -331,10 +233,293 @@ public class ChunkLoader
readPostPocessings(chunk, chunkData);
return chunk;
}
private static void logErrors(ChunkPos chunkPos, int i, String string)
private static LevelChunkSection[] readSections(LevelAccessor level, ChunkPos chunkPos, CompoundTag chunkData)
{
LOGGER.error("Distant Horizons: Recoverable errors when loading section [" + chunkPos.x + ", " + i + ", " + chunkPos.z + "]: " + string);
#if MC_VER >= MC_1_18_2
#if MC_VER < MC_1_19_4
Registry<Biome> biomes = level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
#else
Registry<Biome> biomes = level.registryAccess().registryOrThrow(Registries.BIOME);
#endif
#if MC_VER < MC_1_18_2
Codec<PalettedContainer<Biome>> biomeCodec = PalettedContainer.codec(
biomes, biomes.byNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getOrThrow(Biomes.PLAINS));
#elif MC_VER < MC_1_19_2
Codec<PalettedContainer<Holder<Biome>>> biomeCodec = PalettedContainer.codec(
biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getHolderOrThrow(Biomes.PLAINS));
#else
Codec<PalettedContainer<Holder<Biome>>> biomeCodec = PalettedContainer.codecRW(
biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getHolderOrThrow(Biomes.PLAINS));
#endif
#endif
int sectionYIndex = #if MC_VER < MC_1_17_1 16; #else level.getSectionsCount(); #endif
LevelChunkSection[] chunkSections = new LevelChunkSection[sectionYIndex];
boolean isLightOn = chunkData.getBoolean("isLightOn");
boolean hasSkyLight = level.dimensionType().hasSkyLight();
ListTag tagSections = chunkData.getList("Sections", 10);
if (tagSections.isEmpty()) tagSections = chunkData.getList("sections", 10);
for (int j = 0; j < tagSections.size(); ++j)
{
CompoundTag tagSection = tagSections.getCompound(j);
int sectionYPos = tagSection.getByte("Y");
#if MC_VER < MC_1_18_2
if (tagSection.contains("Palette", 9) && tagSection.contains("BlockStates", 12))
{
LevelChunkSection levelChunkSection = new LevelChunkSection(sectionYPos << 4);
levelChunkSection.getStates().read(tagSection.getList("Palette", 10),
tagSection.getLongArray("BlockStates"));
levelChunkSection.recalcBlockCounts();
if (!levelChunkSection.isEmpty())
chunkSections[#if MC_VER < MC_1_17_1 sectionYPos #else level.getSectionIndexFromSectionY(sectionYPos) #endif ]
= levelChunkSection;
}
#else
int sectionId = level.getSectionIndexFromSectionY(sectionYPos);
if (sectionId >= 0 && sectionId < chunkSections.length)
{
PalettedContainer<BlockState> blockStateContainer;
#if MC_VER < MC_1_18_2
PalettedContainer<Biome> biomeContainer;
#else
PalettedContainer<Holder<Biome>> biomeContainer;
#endif
blockStateContainer = tagSection.contains("block_states", 10)
? BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagSection.getCompound("block_states")).promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
#if MC_VER < MC_1_20_6
.getOrThrow(false, LOGGER::error)
#else
.getOrThrow((message) -> (RuntimeException) LOGGER.errorAndThrow(message, null))
#endif
: new PalettedContainer<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)
: new PalettedContainer<Biome>(biomes, biomes.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
#else
biomeContainer = tagSection.contains("biomes", 10)
? biomeCodec.parse(NbtOps.INSTANCE, tagSection.getCompound("biomes")).promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string))
#if MC_VER < MC_1_20_6
.getOrThrow(false, LOGGER::error)
#else
.getOrThrow((message) -> (RuntimeException) LOGGER.errorAndThrow(message, null))
#endif
: new PalettedContainer<Holder<Biome>>(biomes.asHolderIdMap(), biomes.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
#endif
#if MC_VER < MC_1_20_1
chunkSections[sectionId] = new LevelChunkSection(sectionYPos, blockStateContainer, biomeContainer);
#else
chunkSections[sectionId] = new LevelChunkSection(blockStateContainer, biomeContainer);
#endif
}
#endif
}
return chunkSections;
}
private static
#if MC_VER < MC_1_20_6 ChunkStatus.ChunkType
#elif MC_VER < MC_1_21_1 ChunkType
#else ChunkType #endif
readChunkType(CompoundTag tagLevel)
{
ChunkStatus chunkStatus = ChunkStatus.byName(tagLevel.getString("Status"));
if (chunkStatus != null)
{
return chunkStatus.getChunkType();
}
return
#if MC_VER <= MC_1_20_4 ChunkStatus.ChunkType.PROTOCHUNK;
#else ChunkType.PROTOCHUNK; #endif
}
private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData)
{
CompoundTag tagHeightmaps = chunkData.getCompound("Heightmaps");
for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter())
{
String heightmap = type.getSerializationKey();
if (tagHeightmaps.contains(heightmap, 12))
chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmap));
}
Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter());
}
private static void readPostPocessings(LevelChunk chunk, CompoundTag chunkData)
{
ListTag tagPostProcessings = chunkData.getList("PostProcessing", 9);
for (int n = 0; n < tagPostProcessings.size(); ++n)
{
ListTag listTag3 = tagPostProcessings.getList(n);
for (int o = 0; o < listTag3.size(); ++o)
{
chunk.addPackedPostProcess(listTag3.getShort(o), n);
}
}
}
#if MC_VER >= MC_1_18_2
private static BlendingData readBlendingData(CompoundTag chunkData)
{
BlendingData blendingData = null;
if (chunkData.contains("blending_data", 10))
{
@SuppressWarnings({"unchecked", "rawtypes"})
Dynamic<CompoundTag> blendingDataTag = new Dynamic(NbtOps.INSTANCE, chunkData.getCompound("blending_data"));
blendingData = BlendingData.CODEC.parse(blendingDataTag).resultOrPartial(LOGGER::error).orElse(null);
}
return blendingData;
}
#endif
//=====================//
// read chunk lighting //
//=====================//
/**
* https://minecraft.wiki/w/Chunk_format
*/
public static CombinedChunkLightStorage readLight(ChunkAccess chunk, CompoundTag chunkData)
{
#if MC_VER <= MC_1_17_1
// MC 1.16 and 1.17 doesn't have the necessary NBT info
return null;
#else
CombinedChunkLightStorage combinedStorage = new CombinedChunkLightStorage(ChunkWrapper.getMinBuildHeight(chunk), ChunkWrapper.getMaxBuildHeight(chunk));
ChunkLightStorage blockLightStorage = combinedStorage.blockLightStorage;
ChunkLightStorage skyLightStorage = combinedStorage.skyLightStorage;
boolean foundSkyLight = false;
//===================//
// get NBT tags info //
//===================//
Tag chunkSectionTags = chunkData.get("sections");
if (chunkSectionTags == null)
{
if (!lightingSectionErrorLogged)
{
lightingSectionErrorLogged = true;
LOGGER.error("No sections found for chunk at pos ["+chunk.getPos()+"] chunk data may be out of date.");
}
return null;
}
else if (!(chunkSectionTags instanceof ListTag))
{
if (!lightingSectionErrorLogged)
{
lightingSectionErrorLogged = true;
LOGGER.error("Chunk section tag list have unexpected type ["+chunkSectionTags.getClass().getName()+"], expected ["+ListTag.class.getName()+"].");
}
return null;
}
ListTag chunkSectionListTag = (ListTag) chunkSectionTags;
//===================//
// get lighting info //
//===================//
for (int sectionIndex = 0; sectionIndex < chunkSectionListTag.size(); sectionIndex++)
{
Tag chunkSectionTag = chunkSectionListTag.get(sectionIndex);
if (!(chunkSectionTag instanceof CompoundTag))
{
if (!lightingSectionErrorLogged)
{
lightingSectionErrorLogged = true;
LOGGER.error("Chunk section tag has an unexpected type ["+chunkSectionTag.getClass().getName()+"], expected ["+CompoundTag.class.getName()+"].");
}
return null;
}
CompoundTag chunkSectionCompoundTag = (CompoundTag) chunkSectionTag;
// if null all lights = 0
byte[] blockLightNibbleArray = chunkSectionCompoundTag.getByteArray("BlockLight");
byte[] skyLightNibbleArray = chunkSectionCompoundTag.getByteArray("SkyLight");
// if any sky light was found then all lights above will be max brightness
if (skyLightNibbleArray.length != 0)
{
foundSkyLight = true;
}
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
// chunk sections are also 16 blocks tall
for (int relY = 0; relY < LodUtil.CHUNK_WIDTH; relY++)
{
int blockPosIndex = relY*16*16 + relZ*16 + relX;
byte blockLight = (blockLightNibbleArray.length == 0) ? 0 : getNibbleAtIndex(blockLightNibbleArray, blockPosIndex);
byte skyLight = (skyLightNibbleArray.length == 0) ? 0 : getNibbleAtIndex(skyLightNibbleArray, blockPosIndex);
if (skyLightNibbleArray.length == 0 && foundSkyLight)
{
skyLight = LodUtil.MAX_MC_LIGHT;
}
int y = relY + (sectionIndex * LodUtil.CHUNK_WIDTH) + ChunkWrapper.getMinBuildHeight(chunk);
blockLightStorage.set(relX, y, relZ, blockLight);
skyLightStorage.set(relX, y, relZ, skyLight);
}
}
}
}
return combinedStorage;
#endif
}
/** source: https://minecraft.wiki/w/Chunk_format#Block_Format */
private static byte getNibbleAtIndex(byte[] arr, int index)
{
if (index % 2 == 0)
{
return (byte)(arr[index/2] & 0x0F);
}
else
{
return (byte)((arr[index/2]>>4) & 0x0F);
}
}
private static void logBlockDeserializationWarning(ChunkPos chunkPos, int sectionYIndex, String message)
{
LOGGER.warn("Unable to deserialize blocks for chunk section [" + chunkPos.x + ", " + sectionYIndex + ", " + chunkPos.z + "], error: ["+message+"]. This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem.");
}
private static void logBiomeDeserializationWarning(ChunkPos chunkPos, int sectionYIndex, String message)
{
LOGGER.warn("Unable to deserialize biomes for chunk section [" + chunkPos.x + ", " + sectionYIndex + ", " + chunkPos.z + "], error: ["+message+"]. This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem.");
}
//================//
// helper classes //
//================//
public static class CombinedChunkLightStorage
{
public ChunkLightStorage blockLightStorage;
public ChunkLightStorage skyLightStorage;
public CombinedChunkLightStorage(int minY, int maxY)
{
this.blockLightStorage = ChunkLightStorage.createBlockLightStorage(minY, maxY);
this.skyLightStorage = ChunkLightStorage.createSkyLightStorage(minY, maxY);
}
}
}
@@ -0,0 +1,23 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject;
#if MC_VER >= MC_1_21_1
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.world.level.ChunkPos;
public class DhGenerationChunkHolder extends GenerationChunkHolder
{
public DhGenerationChunkHolder(ChunkPos pos)
{
super(pos);
}
@Override
public int getTicketLevel() { return 0; }
@Override
public int getQueueLevel() { return 0; }
}
#endif
@@ -20,9 +20,10 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject;
import java.lang.invoke.MethodHandles;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -52,11 +53,23 @@ import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.lighting.LevelLightEngine;
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.*;
#endif
#if MC_VER >= MC_1_21_1
import net.minecraft.util.StaticCache2D;
import com.google.common.collect.ImmutableList;
import net.minecraft.server.level.GenerationChunkHolder;
#endif
public class DhLitWorldGenRegion extends WorldGenRegion
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
@@ -65,7 +78,7 @@ public class DhLitWorldGenRegion extends WorldGenRegion
public final DummyLightEngine lightEngine;
public final BatchGenerationEnvironment.EmptyChunkGenerator generator;
public final BatchGenerationEnvironment.IEmptyChunkGeneratorFunc generator;
public final int writeRadius;
public final int size;
@@ -106,11 +119,29 @@ public class DhLitWorldGenRegion extends WorldGenRegion
public DhLitWorldGenRegion(
int centerChunkX, int centerChunkZ,
ChunkAccess centerChunk,
ServerLevel serverLevel, DummyLightEngine lightEngine,
List<ChunkAccess> chunkList, ChunkStatus chunkStatus, int writeRadius,
BatchGenerationEnvironment.EmptyChunkGenerator generator)
BatchGenerationEnvironment.IEmptyChunkGeneratorFunc generator)
{
super(serverLevel, chunkList #if MC_VER >= MC_1_17_1 , chunkStatus, writeRadius #endif );
#if MC_VER == MC_1_16_5
super(serverLevel, chunkList);
#elif MC_VER < MC_1_21_1
super(serverLevel, chunkList, chunkStatus, writeRadius);
#else
super(serverLevel,
StaticCache2D.create(
centerChunkX, centerChunkZ,
writeRadius * 2, (x,z) -> new DhGenerationChunkHolder(new ChunkPos(x, z))),
new ChunkStep(chunkStatus,
// reverse is needed because MC uses the index of the chunkStatus to determine how many items are in the list instead of the actual list count
new ChunkDependencies(ImmutableList.copyOf(ChunkStatus.getStatusList()).reverse()),
new ChunkDependencies(ImmutableList.copyOf(ChunkStatus.getStatusList()).reverse()),
writeRadius, (WorldGenContext var1, ChunkStep var2, StaticCache2D<GenerationChunkHolder> var3, ChunkAccess var4) -> null),
centerChunk);
#endif
this.firstPos = chunkList.get(0).getPos();
this.generator = generator;
this.lightEngine = lightEngine;
@@ -274,7 +305,7 @@ public class DhLitWorldGenRegion extends WorldGenRegion
private ChunkAccess getChunkAccess(int chunkX, int chunkZ, ChunkStatus chunkStatus, boolean returnNonNull)
{
ChunkAccess chunk = this.superHasChunk(chunkX, chunkZ) ? this.superGetChunk(chunkX, chunkZ) : null;
if (chunk != null && chunk.getStatus().isOrAfter(chunkStatus))
if (chunk != null && ChunkWrapper.getStatus(chunk).isOrAfter(chunkStatus))
{
return chunk;
}
@@ -23,15 +23,24 @@ import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IStarlightAccessor;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.chunk.LightChunkGetter;
#if MC_VER >= MC_1_17_1
import net.minecraft.world.level.LevelHeightAccessor;
#endif
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LightChunkGetter;
#if MC_VER >= MC_1_20_1
import net.minecraft.world.level.chunk.LightChunk;
#endif
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public class LightGetterAdaptor implements LightChunkGetter
{
private final BlockGetter heightGetter;
@@ -17,6 +17,10 @@ import java.nio.file.Path;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantLock;
#if MC_VER >= MC_1_20_6
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
#endif
/**
* @deprecated should be replaced with net.minecraft.world.level.chunk.storage.IOWorker to
* prevent potential file corruption and issues with the C2ME mod.
@@ -180,8 +184,10 @@ public class RegionFileStorageExternalCache implements AutoCloseable
Path regionFilePath = storageFolderPath.resolve("r." + pos.getRegionX() + "." + pos.getRegionZ() + ".mca");
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
rFile = new RegionFile(regionFilePath.toFile(), storageFolderPath.toFile(), false);
#else
#elif MC_VER <= MC_1_20_4
rFile = new RegionFile(regionFilePath, storageFolderPath, false);
#else
rFile = new RegionFile(new RegionStorageInfo("level", null, "level type"), regionFilePath, storageFolderPath, false);
#endif
this.regionFileCache.add(new RegionFileCache(ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ()), rFile));
@@ -35,7 +35,6 @@ import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.levelgen.WorldGenSettings;
#if MC_VER < MC_1_19_2
import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
@@ -57,6 +56,12 @@ import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
#endif
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public class WorldGenStructFeatManager extends #if MC_VER < MC_1_19_2 StructureFeatureManager #else StructureManager #endif
@@ -27,15 +27,19 @@ import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGeneratio
import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParameters;
import net.minecraft.server.level.WorldGenRegion;
#if MC_VER < MC_1_19_2
#endif
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ProtoChunk;
#if MC_VER >= MC_1_18_2
import net.minecraft.world.level.levelgen.blending.Blender;
#endif
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public final class StepBiomes
{
public static final ChunkStatus STATUS = ChunkStatus.BIOMES;
@@ -52,29 +56,44 @@ public final class StepBiomes
List<ChunkWrapper> chunkWrappers)
{
ArrayList<ChunkAccess> chunksToDo = new ArrayList<ChunkAccess>();
ArrayList<ChunkAccess> chunksToDo = new ArrayList<>();
for (ChunkWrapper chunkWrapper : chunkWrappers)
{
ChunkAccess chunk = chunkWrapper.getChunk();
if (chunk.getStatus().isOrAfter(STATUS)) continue;
((ProtoChunk) chunk).setStatus(STATUS);
chunksToDo.add(chunk);
if (chunkWrapper.getStatus().isOrAfter(STATUS))
{
// this chunk has already generated this step
continue;
}
else if (chunk instanceof ProtoChunk)
{
#if MC_VER < MC_1_21_1
((ProtoChunk) chunk).setStatus(STATUS);
#else
((ProtoChunk) chunk).setPersistedStatus(STATUS);
#endif
chunksToDo.add(chunk);
}
}
for (ChunkAccess chunk : chunksToDo)
{
// System.out.println("StepBiomes: "+chunk.getPos());
#if MC_VER < MC_1_18_2
environment.params.generator.createBiomes(environment.params.biomes, chunk);
this.environment.params.generator.createBiomes(this.environment.params.biomes, chunk);
#elif MC_VER < MC_1_19_2
chunk = environment.joinSync(environment.params.generator.createBiomes(environment.params.biomes, Runnable::run, Blender.of(worldGenRegion),
chunk = this.environment.joinSync(this.environment.params.generator.createBiomes(this.environment.params.biomes, Runnable::run, Blender.of(worldGenRegion),
tParams.structFeat.forWorldGenRegion(worldGenRegion), chunk));
#elif MC_VER < MC_1_19_4
chunk = environment.joinSync(environment.params.generator.createBiomes(environment.params.biomes, Runnable::run, environment.params.randomState, Blender.of(worldGenRegion),
chunk = this.environment.joinSync(this.environment.params.generator.createBiomes(this.environment.params.biomes, Runnable::run, this.environment.params.randomState, Blender.of(worldGenRegion),
tParams.structFeat.forWorldGenRegion(worldGenRegion), chunk));
#elif MC_VER < MC_1_21_1
chunk = this.environment.joinSync(this.environment.params.generator.createBiomes(Runnable::run, this.environment.params.randomState, Blender.of(worldGenRegion),
tParams.structFeat.forWorldGenRegion(worldGenRegion), chunk));
#else
chunk = environment.joinSync(environment.params.generator.createBiomes(Runnable::run, environment.params.randomState, Blender.of(worldGenRegion),
chunk = this.environment.joinSync(this.environment.params.generator.createBiomes(this.environment.params.randomState, Blender.of(worldGenRegion),
tParams.structFeat.forWorldGenRegion(worldGenRegion), chunk));
#endif
}
@@ -27,11 +27,16 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.gridList.ArrayGridList;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.levelgen.Heightmap;
import org.apache.logging.log4j.Logger;
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public final class StepFeatures
{
@@ -54,14 +59,18 @@ public final class StepFeatures
for (ChunkWrapper chunkWrapper : chunkWrappers)
{
ChunkAccess chunk = chunkWrapper.getChunk();
if (chunk.getStatus().isOrAfter(STATUS))
if (chunkWrapper.getStatus().isOrAfter(STATUS))
{
// this chunk has already generated this step
continue;
}
if (chunk instanceof ProtoChunk)
else if (chunk instanceof ProtoChunk)
{
#if MC_VER < MC_1_21_1
((ProtoChunk) chunk).setStatus(STATUS);
#else
((ProtoChunk) chunk).setPersistedStatus(STATUS);
#endif
}
@@ -28,17 +28,19 @@ import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParame
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import net.minecraft.server.level.WorldGenRegion;
#if MC_VER >= MC_1_17_1
#endif
#if MC_VER < MC_1_19_2
#endif
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ProtoChunk;
#if MC_VER >= MC_1_18_2
import net.minecraft.world.level.levelgen.blending.Blender;
#endif
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public final class StepNoise
{
private static final ChunkStatus STATUS = ChunkStatus.NOISE;
@@ -56,13 +58,21 @@ public final class StepNoise
List<ChunkWrapper> chunkWrappers)
{
ArrayList<ChunkAccess> chunksToDo = new ArrayList<ChunkAccess>();
ArrayList<ChunkAccess> chunksToDo = new ArrayList<>();
for (ChunkWrapper chunkWrapper : chunkWrappers)
{
ChunkAccess chunk = chunkWrapper.getChunk();
if (chunk.getStatus().isOrAfter(STATUS)) continue;
if (chunkWrapper.getStatus().isOrAfter(STATUS))
{
continue;
}
#if MC_VER < MC_1_21_1
((ProtoChunk) chunk).setStatus(STATUS);
#else
((ProtoChunk) chunk).setPersistedStatus(STATUS);
#endif
chunksToDo.add(chunk);
}
@@ -70,15 +80,18 @@ public final class StepNoise
{
// System.out.println("StepNoise: "+chunk.getPos());
#if MC_VER < MC_1_17_1
environment.params.generator.fillFromNoise(worldGenRegion, tParams.structFeat, chunk);
this.environment.params.generator.fillFromNoise(worldGenRegion, tParams.structFeat, chunk);
#elif MC_VER < MC_1_18_2
chunk = environment.joinSync(environment.params.generator.fillFromNoise(Runnable::run,
chunk = this.environment.joinSync(this.environment.params.generator.fillFromNoise(Runnable::run,
tParams.structFeat.forWorldGenRegion(worldGenRegion), chunk));
#elif MC_VER < MC_1_19_2
chunk = environment.joinSync(environment.params.generator.fillFromNoise(Runnable::run, Blender.of(worldGenRegion),
chunk = this.environment.joinSync(this.environment.params.generator.fillFromNoise(Runnable::run, Blender.of(worldGenRegion),
tParams.structFeat.forWorldGenRegion(worldGenRegion), chunk));
#elif MC_VER < MC_1_21_1
chunk = this.environment.joinSync(this.environment.params.generator.fillFromNoise(Runnable::run, Blender.of(worldGenRegion), this.environment.params.randomState,
tParams.structFeat.forWorldGenRegion(worldGenRegion), chunk));
#else
chunk = environment.joinSync(environment.params.generator.fillFromNoise(Runnable::run, Blender.of(worldGenRegion), environment.params.randomState,
chunk = this.environment.joinSync(this.environment.params.generator.fillFromNoise(Blender.of(worldGenRegion), this.environment.params.randomState,
tParams.structFeat.forWorldGenRegion(worldGenRegion), chunk));
#endif
UncheckedInterruptedException.throwIfInterrupted();
@@ -27,12 +27,16 @@ import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGeneratio
import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParameters;
import net.minecraft.server.level.WorldGenRegion;
#if MC_VER < MC_1_19_2
#endif
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ProtoChunk;
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public final class StepStructureReference
{
private static final ChunkStatus STATUS = ChunkStatus.STRUCTURE_REFERENCES;
@@ -55,9 +59,20 @@ public final class StepStructureReference
for (ChunkWrapper chunkWrapper : chunkWrappers)
{
ChunkAccess chunk = chunkWrapper.getChunk();
if (chunk.getStatus().isOrAfter(STATUS)) continue;
((ProtoChunk) chunk).setStatus(STATUS);
chunksToDo.add(chunk);
if (chunkWrapper.getStatus().isOrAfter(STATUS))
{
// this chunk has already generated this step
continue;
}
else if (chunk instanceof ProtoChunk)
{
#if MC_VER < MC_1_21_1
((ProtoChunk) chunk).setStatus(STATUS);
#else
((ProtoChunk) chunk).setPersistedStatus(STATUS);
#endif
chunksToDo.add(chunk);
}
}
for (ChunkAccess chunk : chunksToDo)
@@ -30,10 +30,16 @@ import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParame
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ProtoChunk;
import org.apache.logging.log4j.Logger;
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public final class StepStructureStart
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -70,20 +76,29 @@ public final class StepStructureStart
for (ChunkWrapper chunkWrapper : chunkWrappers)
{
ChunkAccess chunk = chunkWrapper.getChunk();
if (!chunk.getStatus().isOrAfter(STATUS))
if (chunkWrapper.getStatus().isOrAfter(STATUS))
{
// this chunk has already generated this step
continue;
}
else if (chunk instanceof ProtoChunk)
{
#if MC_VER < MC_1_21_1
((ProtoChunk) chunk).setStatus(STATUS);
#else
((ProtoChunk) chunk).setPersistedStatus(STATUS);
#endif
chunksToDo.add(chunk);
}
}
#if MC_VER < MC_1_19_2
if (environment.params.worldGenSettings.generateFeatures())
if (this.environment.params.worldGenSettings.generateFeatures())
{
#elif MC_VER < MC_1_19_4
if (environment.params.worldGenSettings.generateStructures()) {
if (this.environment.params.worldGenSettings.generateStructures()) {
#else
if (environment.params.worldOptions.generateStructures())
if (this.environment.params.worldOptions.generateStructures())
{
#endif
for (ChunkAccess chunk : chunksToDo)
@@ -28,9 +28,14 @@ import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParame
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.levelgen.Heightmap;
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public final class StepSurface
{
@@ -53,9 +58,21 @@ public final class StepSurface
for (ChunkWrapper chunkWrapper : chunkWrappers)
{
ChunkAccess chunk = chunkWrapper.getChunk();
if (chunk.getStatus().isOrAfter(STATUS)) continue;
((ProtoChunk) chunk).setStatus(STATUS);
chunksToDo.add(chunk);
if (chunkWrapper.getStatus().isOrAfter(STATUS))
{
// this chunk has already generated this step
continue;
}
else if (chunk instanceof ProtoChunk)
{
#if MC_VER < MC_1_21_1
((ProtoChunk) chunk).setStatus(STATUS);
#else
((ProtoChunk) chunk).setPersistedStatus(STATUS);
#endif
chunksToDo.add(chunk);
}
}
for (ChunkAccess chunk : chunksToDo)
@@ -0,0 +1,46 @@
accessWidener v1 named
# used when determining where to save files to
accessible field net/minecraft/world/level/storage/DimensionDataStorage dataFolder Ljava/io/File;
# used when rendering
accessible method net/minecraft/client/renderer/GameRenderer getFov (Lnet/minecraft/client/Camera;FZ)D
# used for grabbing vanilla rendered chunks
accessible field net/minecraft/client/renderer/LevelRenderer visibleSections Lit/unimi/dsi/fastutil/objects/ObjectArrayList;
#accessible method net/minecraft/client/renderer/LevelRenderer renderSectionLayer (Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/PoseStack;DDDLorg/joml/Matrix4f;)V
# world generation
# accessible method net/minecraft/world/level/lighting/LayerLightEngine queueSectionData (JLnet/minecraft/world/level/chunk/DataLayer;Z)V
accessible field net/minecraft/world/level/chunk/LevelChunk loaded Z
accessible field net/minecraft/world/level/lighting/LightEngine storage Lnet/minecraft/world/level/lighting/LayerLightSectionStorage;
accessible method net/minecraft/world/level/lighting/LayerLightSectionStorage lightOnInSection (J)Z
# lod generation from save file
accessible field net/minecraft/world/level/chunk/storage/ChunkStorage worker Lnet/minecraft/world/level/chunk/storage/IOWorker;
accessible field net/minecraft/world/level/chunk/storage/IOWorker storage Lnet/minecraft/world/level/chunk/storage/RegionFileStorage;
accessible field net/minecraft/world/level/chunk/storage/RegionFileStorage regionCache Lit/unimi/dsi/fastutil/longs/Long2ObjectLinkedOpenHashMap;
accessible field net/minecraft/world/level/chunk/storage/RegionFileStorage folder Ljava/nio/file/Path;
# grabbing textures
accessible class net/minecraft/client/renderer/texture/SpriteContents$AnimatedTexture
accessible method net/minecraft/client/renderer/texture/SpriteContents$AnimatedTexture getFrameX (I)I
accessible method net/minecraft/client/renderer/texture/SpriteContents$AnimatedTexture getFrameY (I)I
accessible field net/minecraft/client/renderer/texture/SpriteContents animatedTexture Lnet/minecraft/client/renderer/texture/SpriteContents$AnimatedTexture;
accessible field net/minecraft/client/renderer/texture/SpriteContents originalImage Lcom/mojang/blaze3d/platform/NativeImage;
# UI stuff
accessible field net/minecraft/client/gui/components/AbstractButton SPRITES Lnet/minecraft/client/gui/components/WidgetSprites;
# Handles inserting the config button
accessible field net/minecraft/client/gui/layouts/HeaderAndFooterLayout headerFrame Lnet/minecraft/client/gui/layouts/FrameLayout;
accessible field net/minecraft/client/gui/layouts/FrameLayout children Ljava/util/List;
accessible class net/minecraft/client/gui/layouts/FrameLayout$ChildContainer
accessible field net/minecraft/client/gui/layouts/LinearLayout wrapped Lnet/minecraft/client/gui/layouts/GridLayout;
# hacky stuff
accessible field net/minecraft/util/ThreadingDetector lock Ljava/util/concurrent/Semaphore;
mutable field net/minecraft/util/ThreadingDetector lock Ljava/util/concurrent/Semaphore;
accessible field net/minecraft/client/gui/components/AbstractSelectionList scrollAmount D # Hack to bypass vanilla's setScrollAmount's clamp
+8 -5
View File
@@ -45,7 +45,7 @@ def addMod(path, enabled) {
}
dependencies {
minecraft "com.mojang:minecraft:${minecraft_version}"
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
mappings loom.layered() {
// Mojmap mappings
officialMojangMappings()
@@ -70,12 +70,14 @@ dependencies {
addModJar(fabricApi.module("fabric-rendering-v1", rootProject.fabric_api_version)) // TODO: Remove this as it is only needed in 1 line (FabricClientProxy)
addModJar(fabricApi.module("fabric-networking-api-v1", rootProject.fabric_api_version))
// used by mod menu in MC 1.20.6+
addModJar(fabricApi.module("fabric-screen-api-v1", rootProject.fabric_api_version))
addModJar(fabricApi.module("fabric-key-binding-api-v1", rootProject.fabric_api_version))
// Mod Menu
modImplementation("com.terraformersmc:modmenu:${rootProject.modmenu_version}")
// Starlight
addMod("curse.maven:starlight-521783:${rootProject.starlight_version_fabric}", rootProject.enable_starlight)
@@ -103,8 +105,9 @@ dependencies {
addMod("io.vram:canvas-fabric-${project.canvas_version}", rootProject.enable_canvas)
// Immersive Portals
if (rootProject.enable_immersive_portals == "1")
modCompileOnly ("com.github.iPortalTeam.ImmersivePortalsMod:imm_ptl_core:${project.immersive_portals_version}")
if (rootProject.enable_immersive_portals == "1") {
modCompileOnly("com.github.iPortalTeam.ImmersivePortalsMod:imm_ptl_core:${project.immersive_portals_version}")
}
else if (rootProject.enable_immersive_portals == "2") {
modImplementation ("com.github.iPortalTeam.ImmersivePortalsMod:imm_ptl_core:${project.immersive_portals_version}") {
exclude(group: "net.fabricmc.fabric-api")
@@ -19,6 +19,8 @@
package com.seibel.distanthorizons.fabric;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.seibel.distanthorizons.common.AbstractModInitializer;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
@@ -27,15 +29,14 @@ import com.mojang.blaze3d.platform.InputConstants;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.ISodiumAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.fabric.wrappers.modAccessor.SodiumAccessor;
//import io.netty.buffer.ByteBuf;
import net.fabricmc.api.EnvType;
@@ -52,12 +53,14 @@ import net.minecraft.client.gui.screens.TitleScreen;
import java.nio.FloatBuffer;
#endif
import java.util.HashSet;
import java.util.concurrent.ThreadPoolExecutor;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.phys.HitResult;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import org.lwjgl.glfw.GLFW;
/**
@@ -114,11 +117,15 @@ public class FabricClientProxy implements AbstractModInitializer.IEventProxy
// ClientChunkLoadEvent
ClientChunkEvents.CHUNK_LOAD.register((level, chunk) ->
{
IClientLevelWrapper wrappedLevel = ClientLevelWrapper.getWrapper(level);
SharedApi.INSTANCE.chunkLoadEvent(new ChunkWrapper(chunk, level, wrappedLevel), wrappedLevel);
if (MC.clientConnectedToDedicatedServer())
{
IClientLevelWrapper wrappedLevel = ClientLevelWrapper.getWrapper(level);
SharedApi.INSTANCE.chunkLoadEvent(new ChunkWrapper(chunk, level, wrappedLevel), wrappedLevel);
}
});
// (kinda) block break event
// Since fabric doesn't have a client-side break-block API event, this is the next best thing
AttackBlockCallback.EVENT.register((player, level, interactionHand, blockPos, direction) ->
{
// if we have access to the server, use the chunk save event instead
@@ -126,17 +133,26 @@ public class FabricClientProxy implements AbstractModInitializer.IEventProxy
{
if (SharedApi.isChunkAtBlockPosAlreadyUpdating(blockPos.getX(), blockPos.getZ()))
{
// Since fabric doesn't have a client-side break-block API event, this is the next best thing
ChunkAccess chunk = level.getChunk(blockPos);
if (chunk != null)
// executor to prevent locking up the render/event thread
// if the getChunk() takes longer than expected
// (which can be caused by certain mods)
ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor != null)
{
//LOGGER.trace("attack block at blockPos: " + blockPos);
executor.execute(() ->
{
ChunkAccess chunk = level.getChunk(blockPos);
if (chunk != null)
{
//LOGGER.trace("attack block at blockPos: " + blockPos);
IClientLevelWrapper wrappedLevel = ClientLevelWrapper.getWrapper((ClientLevel) level);
SharedApi.INSTANCE.chunkBlockChangedEvent(
new ChunkWrapper(chunk, level, wrappedLevel),
wrappedLevel
);
IClientLevelWrapper wrappedLevel = ClientLevelWrapper.getWrapper((ClientLevel) level);
SharedApi.INSTANCE.chunkBlockChangedEvent(
new ChunkWrapper(chunk, level, wrappedLevel),
wrappedLevel
);
}
});
}
}
}
@@ -146,27 +162,37 @@ public class FabricClientProxy implements AbstractModInitializer.IEventProxy
});
// (kinda) block place event
// Since fabric doesn't have a client-side place-block API event, this is the next best thing
UseBlockCallback.EVENT.register((player, level, hand, hitResult) ->
{
// if we have access to the server, use the chunk save event instead
if (MC.clientConnectedToDedicatedServer())
{
if (SharedApi.isChunkAtBlockPosAlreadyUpdating(hitResult.getBlockPos().getX(), hitResult.getBlockPos().getZ()))
if (hitResult.getType() == HitResult.Type.BLOCK
&& !hitResult.isInside())
{
// Since fabric doesn't have a client-side place-block API event, this is the next best thing
if (hitResult.getType() == HitResult.Type.BLOCK
&& !hitResult.isInside())
if (SharedApi.isChunkAtBlockPosAlreadyUpdating(hitResult.getBlockPos().getX(), hitResult.getBlockPos().getZ()))
{
ChunkAccess chunk = level.getChunk(hitResult.getBlockPos());
if (chunk != null)
// executor to prevent locking up the render/event thread
// if the getChunk() takes longer than expected
// (which can be caused by certain mods)
ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor != null)
{
//LOGGER.trace("use block at blockPos: " + hitResult.getBlockPos());
executor.execute(() ->
{
ChunkAccess chunk = level.getChunk(hitResult.getBlockPos());
if (chunk != null)
{
//LOGGER.trace("use block at blockPos: " + hitResult.getBlockPos());
IClientLevelWrapper wrappedLevel = ClientLevelWrapper.getWrapper((ClientLevel) level);
SharedApi.INSTANCE.chunkBlockChangedEvent(
new ChunkWrapper(chunk, level, wrappedLevel),
wrappedLevel
);
IClientLevelWrapper wrappedLevel = ClientLevelWrapper.getWrapper((ClientLevel) level);
SharedApi.INSTANCE.chunkBlockChangedEvent(
new ChunkWrapper(chunk, level, wrappedLevel),
wrappedLevel
);
}
});
}
}
}
@@ -177,14 +203,6 @@ public class FabricClientProxy implements AbstractModInitializer.IEventProxy
});
// Client Chunk Save
ClientChunkEvents.CHUNK_UNLOAD.register((level, chunk) ->
{
IClientLevelWrapper wrappedLevel = ClientLevelWrapper.getWrapper(level);
SharedApi.INSTANCE.chunkUnloadEvent(new ChunkWrapper(chunk, level, wrappedLevel), wrappedLevel);
});
//==============//
// render event //
@@ -192,10 +210,24 @@ public class FabricClientProxy implements AbstractModInitializer.IEventProxy
WorldRenderEvents.AFTER_SETUP.register((renderContext) ->
{
Mat4f projectionMatrix = McObjectConverter.Convert(renderContext.projectionMatrix());
Mat4f modelViewMatrix;
#if MC_VER < MC_1_20_6
modelViewMatrix = McObjectConverter.Convert(renderContext.matrixStack().last().pose());
#else
modelViewMatrix = McObjectConverter.Convert(renderContext.positionMatrix());
#endif
this.clientApi.renderLods(ClientLevelWrapper.getWrapper(renderContext.world()),
McObjectConverter.Convert(renderContext.matrixStack().last().pose()),
McObjectConverter.Convert(renderContext.projectionMatrix()),
renderContext.tickDelta());
modelViewMatrix,
projectionMatrix,
#if MC_VER < MC_1_21_1
renderContext.tickDelta()
#else
renderContext.tickCounter().getGameTimeDeltaTicks()
#endif
);
});
// Debug keyboard event
@@ -77,7 +77,7 @@ public class FabricMain extends AbstractModInitializer implements ClientModIniti
// If sodium is installed Indium is also necessary in order to use the Fabric rendering API
if (!modChecker.isModLoaded("indium"))
{
String indiumMissingMessage = ModInfo.READABLE_NAME + " now relies on Indium to work with Sodium.\nPlease download Indium from https://modrinth.com/mod/indium";
String indiumMissingMessage = ModInfo.READABLE_NAME + " needs Indium to work with Sodium.\nPlease download Indium from https://modrinth.com/mod/indium";
LOGGER.fatal(indiumMissingMessage);
if (!GraphicsEnvironment.isHeadless())
@@ -124,7 +124,7 @@ public class FabricMain extends AbstractModInitializer implements ClientModIniti
#if MC_VER >= MC_1_20_1
if (SingletonInjector.INSTANCE.get(IModChecker.class).isModLoaded("sodium"))
ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class).setFogOcclusion(false); // FIXME: This is a tmp fix for sodium 0.5.0, and 0.5.1. This is fixed in sodium 0.5.2
ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class).setFogOcclusion(false);
#endif
if (ConfigBase.INSTANCE == null)
@@ -1,5 +1,7 @@
package com.seibel.distanthorizons.fabric;
import com.seibel.distanthorizons.api.methods.events.DhApiEventRegister;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.common.AbstractModInitializer;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.misc.ServerPlayerWrapper;
@@ -10,6 +12,7 @@ import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.fabric.testing.TestWorldGenBindingEvent;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
@@ -75,6 +78,14 @@ public class FabricServerProxy implements AbstractModInitializer.IEventProxy
// ServerTickEvent
ServerTickEvents.END_SERVER_TICK.register((server) -> SERVER_API.serverTickEvent());
// can be enabled to test world gen overrides without having to build a separate API project
if (false)
{
DhApiEventRegister.on(DhApiLevelLoadEvent.class, new TestWorldGenBindingEvent());
}
// ServerWorldLoadEvent
//TODO: Check if both of these use the correct timed events. (i.e. is it 'ed' or 'ing' one?)
ServerLifecycleEvents.SERVER_STARTING.register((server) ->
@@ -1,69 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.fabric.mixins.client;
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 com.seibel.distanthorizons.common.util.ILightTextureMarker;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(DynamicTexture.class)
public class MixinDynamicTexture implements ILightTextureMarker
{
/** Used to prevent accidentally using other dynamic textures as a lightmap */
@Unique
private boolean isLightTexture = false;
@Shadow
@Final
private NativeImage pixels;
@Inject(method = "upload()V", at = @At("HEAD"))
public void updateLightTexture(CallbackInfo ci)
{
// since the light map is always updated on the client render thread we should be able to access the client level at the same time
IMinecraftClientWrapper mc = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
if (!this.isLightTexture
|| mc == null
|| mc.getWrappedClientLevel() == null
)
{
return;
}
//ApiShared.LOGGER.info("Lightmap update");
IClientLevelWrapper clientLevel = mc.getWrappedClientLevel();
MinecraftRenderWrapper.INSTANCE.updateLightmap(this.pixels, clientLevel);
}
public void markLightTexture() { this.isLightTexture = true; }
}
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.fabric.mixins.client;
import com.mojang.blaze3d.vertex.PoseStack;
#if MC_VER < MC_1_19_4
import com.mojang.math.Matrix4f;
import org.lwjgl.opengl.GL32;
#else
import org.joml.Matrix4f;
#endif
@@ -29,23 +30,17 @@ import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LightTexture;
import com.seibel.distanthorizons.core.config.Config;
import net.minecraft.client.Camera;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.level.lighting.LevelLightEngine;
import org.lwjgl.opengl.GL15;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -84,33 +79,49 @@ public class MixinLevelRenderer
method = "renderChunkLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/PoseStack;DDDLorg/joml/Matrix4f;)V",
cancellable = true)
private void renderChunkLayer(RenderType renderType, PoseStack modelViewMatrixStack, double cameraXBlockPos, double cameraYBlockPos, double cameraZBlockPos, Matrix4f projectionMatrix, CallbackInfo callback)
#else
#elif MC_VER < MC_1_20_6
@Inject(at = @At("HEAD"),
method = "Lnet/minecraft/client/renderer/LevelRenderer;renderSectionLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/PoseStack;DDDLorg/joml/Matrix4f;)V",
cancellable = true)
private void renderChunkLayer(RenderType renderType, PoseStack modelViewMatrixStack, double camX, double camY, double camZ, Matrix4f projectionMatrix, CallbackInfo callback)
#else
@Inject(at = @At("HEAD"),
method = "Lnet/minecraft/client/renderer/LevelRenderer;renderSectionLayer(Lnet/minecraft/client/renderer/RenderType;DDDLorg/joml/Matrix4f;Lorg/joml/Matrix4f;)V",
cancellable = true)
private void renderChunkLayer(RenderType renderType, double x, double y, double z, Matrix4f projectionMatrix, Matrix4f frustumMatrix, CallbackInfo callback)
#endif
{
#if MC_VER == MC_1_16_5
// get the matrices from the OpenGL fixed pipeline
float[] mcProjMatrixRaw = new float[16];
GL15.glGetFloatv(GL15.GL_PROJECTION_MATRIX, mcProjMatrixRaw);
GL32.glGetFloatv(GL32.GL_PROJECTION_MATRIX, mcProjMatrixRaw);
Mat4f mcProjectionMatrix = new Mat4f(mcProjMatrixRaw);
mcProjectionMatrix.transpose();
Mat4f mcModelViewMatrix = McObjectConverter.Convert(matrixStackIn.last().pose());
#else
#elif MC_VER <= MC_1_20_4
// get the matrices directly from MC
Mat4f mcModelViewMatrix = McObjectConverter.Convert(modelViewMatrixStack.last().pose());
Mat4f mcProjectionMatrix = McObjectConverter.Convert(projectionMatrix);
#else
// MC combined the model view and projection matricies
Mat4f mcModelViewMatrix = McObjectConverter.Convert(projectionMatrix);
Mat4f mcProjectionMatrix = new Mat4f();
mcProjectionMatrix.setIdentity();
#endif
if (renderType.equals(RenderType.translucent())) {
if (renderType.equals(RenderType.translucent()))
{
ClientApi.INSTANCE.renderDeferredLods(ClientLevelWrapper.getWrapper(this.level),
mcModelViewMatrix,
mcProjectionMatrix,
Minecraft.getInstance().getFrameTime());
#if MC_VER < MC_1_21_1
Minecraft.getInstance().getFrameTime()
#else
Minecraft.getInstance().getTimer().getRealtimeDeltaTicks()
#endif
);
}
// FIXME completely disables rendering when sodium is installed
@@ -126,9 +137,12 @@ public class MixinLevelRenderer
#elif MC_VER < MC_1_20_1
@Inject(at = @At(value = "TAIL", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runUpdates(IZZ)I"), method = "renderLevel")
public void callAfterRunUpdates(PoseStack poseStack, float partialTick, long finishNanoTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f projectionMatrix, CallbackInfo ci)
#else
#elif MC_VER < MC_1_20_6
@Inject(at = @At(value = "TAIL", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runLightUpdates()I"), method = "renderLevel")
private void callAfterRunUpdates(PoseStack poseStack, float partialTick, long finishNanoTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f projectionMatrix, CallbackInfo ci)
#else
@Inject(at = @At(value = "TAIL", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runLightUpdates()I"), method = "renderLevel")
private void callAfterRunUpdates(CallbackInfo ci)
#endif
{
ChunkWrapper.syncedUpdateClientLightStatus();
@@ -20,9 +20,14 @@
package com.seibel.distanthorizons.fabric.mixins.client;
import com.seibel.distanthorizons.common.util.ILightTextureMarker;
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 net.minecraft.client.renderer.texture.DynamicTexture;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -35,13 +40,20 @@ public class MixinLightTexture
{
@Shadow
@Final
private DynamicTexture lightTexture;
private NativeImage lightPixels;
@Inject(method = "<init>", at = @At("RETURN"))
public void markLightTexture(CallbackInfo ci)
@Inject(method = "updateLightTexture(F)V", at = @At("RETURN"))
public void updateLightTexture(float partialTicks, CallbackInfo ci)
{
//
((ILightTextureMarker) this.lightTexture).markLightTexture();
IMinecraftClientWrapper mc = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
if (mc == null || mc.getWrappedClientLevel() == null)
{
return;
}
IClientLevelWrapper clientLevel = mc.getWrappedClientLevel();
MinecraftRenderWrapper.INSTANCE.updateLightmap(this.lightPixels, clientLevel);
}
}
@@ -8,10 +8,12 @@ import com.seibel.distanthorizons.core.jar.installer.GitlabGetter;
import com.seibel.distanthorizons.core.jar.installer.ModrinthGetter;
import com.seibel.distanthorizons.core.jar.updater.SelfUpdater;
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
import net.minecraft.client.Minecraft;
import com.seibel.distanthorizons.coreapi.ModInfo;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.TitleScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
@@ -25,6 +27,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
public class MixinMinecraft
{
/**
* Can be enabled for testing the auto updater UI. <br/>
* will always show the auto updater if set to true.
*/
@Unique
private static final boolean DEBUG_ALWAYS_SHOW_UPDATER = false;
#if MC_VER < MC_1_20_2
#if MC_VER == MC_1_20_1
@Redirect(
@@ -41,13 +52,13 @@ public class MixinMinecraft
public void onOpenScreen(Minecraft instance, Screen guiScreen)
{
#endif
if (!Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get()) // Don't do anything if the user doesn't want it
if (!Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get() && !DEBUG_ALWAYS_SHOW_UPDATER) // Don't do anything if the user doesn't want it
{
instance.setScreen(guiScreen); // Sets the screen back to the vanilla screen as if nothing ever happened
return;
}
if (SelfUpdater.onStart())
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
@@ -69,17 +80,31 @@ public class MixinMinecraft
private void buildInitialScreens(Runnable runnable)
{
if (
Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get() // Don't do anything if the user doesn't want it
&& SelfUpdater.onStart()
)
DEBUG_ALWAYS_SHOW_UPDATER ||
(
// Don't do anything if the user doesn't want it
Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get()
&& SelfUpdater.onStart()
)
)
{
runnable = () -> {
runnable = () ->
{
String versionId;
EDhApiUpdateBranch updateBranch = EDhApiUpdateBranch.convertAutoToStableOrNightly(Config.Client.Advanced.AutoUpdater.updateBranch.get());
if (updateBranch == EDhApiUpdateBranch.STABLE)
{
versionId = ModrinthGetter.getLatestIDForVersion(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion());
}
else
{
versionId = GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha");
}
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
(Config.Client.Advanced.AutoUpdater.updateBranch.get() == EDhApiUpdateBranch.STABLE
? ModrinthGetter.getLatestIDForVersion(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion())
: GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha"))
versionId
));
};
}
@@ -23,7 +23,6 @@ import com.seibel.distanthorizons.common.wrappers.gui.GetConfigScreen;
import com.seibel.distanthorizons.common.wrappers.gui.TexturedButtonWidget;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.config.Config;
import net.minecraft.client.gui.screens.OptionsScreen;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
#if MC_VER < MC_1_19_2
@@ -31,51 +30,127 @@ import net.minecraft.network.chat.TranslatableComponent;
#endif
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
#if MC_VER >= MC_1_20_6
import net.minecraft.client.gui.layouts.LinearLayout;
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Shadow;
#endif
#if MC_VER < MC_1_21_1
import net.minecraft.client.gui.screens.OptionsScreen;
#else
import net.minecraft.client.gui.screens.options.OptionsScreen;
#endif
/**
* Adds a button to the menu to goto the config
*
* @author coolGi
* @version 12-02-2021
* @version 2024-5-20
*/
@Mixin(OptionsScreen.class)
public class MixinOptionsScreen extends Screen
{
// Get the texture for the button
private static final ResourceLocation ICON_TEXTURE = new ResourceLocation(ModInfo.ID, "textures/gui/button.png");
protected MixinOptionsScreen(Component title)
{
super(title);
}
/** Texture used for the config opening button */
@Unique
private static final ResourceLocation ICON_TEXTURE =
#if MC_VER < MC_1_21_1
new ResourceLocation(ModInfo.ID, "textures/gui/button.png");
#else
ResourceLocation.fromNamespaceAndPath(ModInfo.ID, "textures/gui/button.png");
#endif
@Inject(at = @At("HEAD"), method = "init")
@Unique
private TexturedButtonWidget optionsButton = null;
#if MC_VER >= MC_1_20_6
@Shadow
@Final
protected HeaderAndFooterLayout layout;
#endif
//==============//
// constructors //
//==============//
protected MixinOptionsScreen(Component title) { super(title); }
@Inject(at = @At("RETURN"), method = "init")
private void lodconfig$init(CallbackInfo ci)
{
if (Config.Client.optionsButton.get())
this. #if MC_VER < MC_1_17_1 addButton #else addRenderableWidget #endif
(new TexturedButtonWidget(
// Where the button is on the screen
this.width / 2 - 180, this.height / 6 - 12,
// Width and height of the button
20, 20,
// Offset
0, 0,
// Some textuary stuff
20, ICON_TEXTURE, 20, 40,
// Create the button and tell it where to go
// For now it goes to the client option by default
(buttonWidget) -> Objects.requireNonNull(minecraft).setScreen(GetConfigScreen.getScreen(this)),
// Add a title to the utton
#if MC_VER < MC_1_19_2
new TranslatableComponent(ModInfo.ID + ".title")));
#else
Component.translatable(ModInfo.ID + ".title")));
#endif
{
#if MC_VER < MC_1_17_1
this.addButton(this.getOptionsButton());
#elif MC_VER < MC_1_20_6
this.addRenderableWidget(this.getOptionsButton());
#else
// add the button so it's rendered
this.addRenderableWidget(this.getOptionsButton());
// add the button to the correct location in the UI
// TODO is there a better way to do this instead of using access transformers to inject into the exact UI elements?
LinearLayout layout = (LinearLayout) this.layout.headerFrame.children.get(0).child;
// determine how wide the other option buttons are so we can put our botton to the left of them all
AtomicInteger width = new AtomicInteger(0);
layout.visitChildren(x -> { width.addAndGet(x.getWidth()); });
width.addAndGet(-10); // padding between the DH button and the FOV slider
layout.wrapped.addChild(this.getOptionsButton(), 1, 2, (settings) -> { settings.paddingLeft(width.get() * -1); });
layout.arrangeElements();
#endif
}
}
//================//
// helper methods //
//================//
@Unique
public TexturedButtonWidget getOptionsButton()
{
if (this.optionsButton == null)
{
this.optionsButton
= new TexturedButtonWidget(
// Where the button is on the screen
this.width / 2 - 180, this.height / 6 - 12,
// Width and height of the button
20, 20,
// texture UV Offset
0, 0,
// Some textuary stuff
20, ICON_TEXTURE, 20, 40,
// Create the button and tell it where to go
// For now it goes to the client option by default
(buttonWidget) -> Objects.requireNonNull(this.minecraft).setScreen(GetConfigScreen.getScreen(this)),
// Add a title to the button
#if MC_VER < MC_1_19_2
new TranslatableComponent(ModInfo.ID + ".title"));
#else
Component.translatable(ModInfo.ID + ".title"));
#endif
}
return this.optionsButton;
}
}
@@ -1,8 +1,6 @@
package com.seibel.distanthorizons.fabric.mixins.server;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.common.commonMixins.MixinChunkMapCommon;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
@@ -32,62 +30,6 @@ public class MixinChunkMap
// don't need the chunk(s) before MC has finished saving them
@Inject(method = "save", at = @At(value = "RETURN", target = CHUNK_SERIALIZER_WRITE))
private void onChunkSave(ChunkAccess chunk, CallbackInfoReturnable<Boolean> ci)
{
// true means a chunk was saved to disk
if (ci.getReturnValue())
{
// TODO is this validation necessary since we are checking above if
// the callback return value should state if the chunk was actually saved or not?
// Do we trust it to always be correct?
//=====================================//
// corrupt/incomplete chunk validation //
//=====================================//
// MC has a tendency to try saving incomplete or corrupted chunks (which show up as empty or black chunks)
// this logic should prevent that from happening
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
if (chunk.isUnsaved() || chunk.getUpgradeData() != null || !chunk.isLightCorrect())
{
return;
}
#else
if (chunk.isUnsaved() || chunk.isUpgrading() || !chunk.isLightCorrect())
{
return;
}
#endif
//==================//
// biome validation //
//==================//
// some chunks may be missing their biomes, which cause issues when attempting to save them
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
if (chunk.getBiomes() == null)
{
return;
}
#else
try
{
// this will throw an exception if the biomes aren't set up
chunk.getNoiseBiome(0,0,0);
}
catch (Exception e)
{
return;
}
#endif
ServerApi.INSTANCE.serverChunkSaveEvent(
new ChunkWrapper(chunk, this.level, ServerLevelWrapper.getWrapper(this.level)),
ServerLevelWrapper.getWrapper(this.level)
);
}
}
{ MixinChunkMapCommon.onChunkSave(this.level, chunk, ci); }
}
@@ -1,60 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.fabric.mixins.server.unsafe;
import org.spongepowered.asm.mixin.Mixin;
//FIXME: Is this still needed?
#if MC_VER >= MC_1_18_2
import net.minecraft.util.ThreadingDetector;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.concurrent.Semaphore;
/**
* Why does this exist? But okay! (Will be probably removed when the experimental generator is done)
* FIXME: Recheck this
*/
@Mixin(ThreadingDetector.class)
public class MixinThreadingDetector
{
@Mutable
@Shadow
private Semaphore lock;
@Inject(method = "<init>", at = @At("RETURN"))
private void setSemaphore(CallbackInfo ci)
{
this.lock = new Semaphore(2);
}
}
#else
import net.minecraft.server.level.ServerLevel;
@Mixin(ServerLevel.class)
public class MixinThreadingDetector { } //FIXME: Is there some way to make this file just not be added?
#endif
@@ -0,0 +1,28 @@
package com.seibel.distanthorizons.fabric.testing;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import net.minecraft.server.level.ServerLevel;
import org.apache.logging.log4j.Logger;
public class TestWorldGenBindingEvent extends DhApiLevelLoadEvent
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@Override
public void onLevelLoad(DhApiEventParam<DhApiLevelLoadEvent.EventParam> event)
{
LOGGER.info("DH Level: ["+event.value.levelWrapper.getDimensionType()+"] loaded.");
// Note: whenever you use a wrapper method on a new Minecraft version it is recommended that you
// call wrapper.getClass() to determine which object the API will return before you try casting it.
ServerLevel level = (ServerLevel) event.value.levelWrapper.getWrappedMcObject();
// override the core DH world generator for this level
IDhApiWorldGenerator exampleWorldGen = new TestWorldGenerator(level);
DhApi.worldGenOverrides.registerWorldGeneratorOverride(event.value.levelWrapper, exampleWorldGen);
}
}
@@ -0,0 +1,114 @@
package com.seibel.distanthorizons.fabric.testing;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGeneratorReturnType;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.AbstractDhApiChunkWorldGenerator;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.core.config.Config;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
public class TestWorldGenerator extends AbstractDhApiChunkWorldGenerator
{
private final ServerLevel level;
private final IDhApiLevelWrapper levelWrapper;
//=============//
// constructor //
//=============//
public TestWorldGenerator(ServerLevel level)
{
this.level = level;
this.levelWrapper = ServerLevelWrapper.getWrapper(level);
}
//============//
// properties //
//============//
@Override
public EDhApiWorldGeneratorReturnType getReturnType() { return EDhApiWorldGeneratorReturnType.API_CHUNKS; }
@Override
public boolean runApiChunkValidation() { return true; }
//==================//
// chunk generation //
//==================//
@Override
public Object[] generateChunk(int chunkX, int chunkZ, EDhApiDistantGeneratorMode eDhApiDistantGeneratorMode)
{
ChunkAccess chunk = this.level.getChunk(chunkX, chunkZ);
return new Object[] { chunk, this.level };
}
@Override
public DhApiChunk generateApiChunk(int chunkPosX, int chunkPosZ, EDhApiDistantGeneratorMode generatorMode)
{
// this test is only validated for 1.18.2 and up
// (and it is only needed when testing world gen overrides/API chunks, so it isn't normally needed)
#if MC_VER >= MC_1_18_2
ChunkAccess chunk = this.level.getChunk(chunkPosX, chunkPosZ);
int minBuildHeight = this.level.getMinBuildHeight();
int maxBuildHeight = this.level.getMaxBuildHeight();
DhApiChunk apiChunk = DhApiChunk.create(chunkPosX, chunkPosZ, minBuildHeight, maxBuildHeight);
for (int x = 0; x < 16; x++)
{
for (int z = 0; z < 16; z++)
{
ArrayList<DhApiTerrainDataPoint> dataPoints = new ArrayList<>();
IDhApiBlockStateWrapper block = null;
IDhApiBiomeWrapper biome = null;
for (int y = minBuildHeight; y < maxBuildHeight; y++)
{
block = DhApi.Delayed.wrapperFactory.getBlockStateWrapper(new Object[]{chunk.getBlockState(new BlockPos(x, y, z))}, this.levelWrapper);
biome = DhApi.Delayed.wrapperFactory.getBiomeWrapper(new Object[]{chunk.getNoiseBiome(x, y, z)}, this.levelWrapper);
dataPoints.add(DhApiTerrainDataPoint.create((byte) 0, 0, 15, y, y + 1, block, biome));
}
apiChunk.setDataPoints(x, z, dataPoints);
}
}
return apiChunk;
#else
return null;
#endif
}
@Override
public void preGeneratorTaskStart() { /* do nothing */ }
//=========//
// cleanup //
//=========//
@Override
public void close() { /* do nothing */ }
}
@@ -1,11 +1,11 @@
package com.seibel.distanthorizons.fabric.wrappers.modAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IBCLibAccessor;
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 || MC_VER == MC_1_20_4 // These versions either don't have BCLib, or the implementation is different
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 || MC_VER == MC_1_20_4 || MC_VER == MC_1_20_6 // These versions either don't have BCLib, or the implementation is different
#elif MC_VER == MC_1_18_2
import ru.bclib.config.ClientConfig;
import ru.bclib.config.Configs;
#else
#elif MC_VER < MC_1_21_1
import org.betterx.bclib.config.ClientConfig;
import org.betterx.bclib.config.Configs;
#endif
@@ -17,7 +17,8 @@ public class BCLibAccessor implements IBCLibAccessor
public void setRenderCustomFog(boolean newValue)
{
#if !(MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 || MC_VER == MC_1_20_4) // These versions either don't have BCLib, or the implementation is different
// only some MC versions have BCLib and require this fix
#if (MC_VER > MC_1_17_1 && MC_VER < MC_1_20_4)
// Change the value of CUSTOM_FOG_RENDERING in the bclib client config
// This disabled fog from rendering within bclib
@@ -22,7 +22,11 @@ package com.seibel.distanthorizons.fabric.wrappers.modAccessor;
#if MC_VER >= MC_1_19_4
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
#if MC_VER <= MC_1_20_4
import net.coderbot.iris.Iris;
#else
import net.irisshaders.iris.Iris;
#endif
import net.irisshaders.iris.api.v0.IrisApi;
public class IrisAccessor implements IIrisAccessor
@@ -19,20 +19,18 @@
package com.seibel.distanthorizons.fabric.wrappers.modAccessor;
import java.util.HashSet;
import java.util.stream.Collectors;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.ISodiumAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
#if MC_VER < MC_1_20_1
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
import net.minecraft.client.Minecraft;
#endif
#if MC_VER < MC_1_17_1
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.Packet;
@@ -41,102 +39,99 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.phys.AABB;
#else
import net.minecraft.world.level.LevelHeightAccessor;
#endif
public class SodiumAccessor implements ISodiumAccessor
{
private final IWrapperFactory factory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
public IClientLevelWrapper levelWrapper;
public Mat4f mcModelViewMatrix;
public Mat4f mcProjectionMatrix;
public float partialTicks;
@Override
public String getModName()
{
return "Sodium-Fabric";
}
#if MC_VER >= MC_1_17_1
@Override
public HashSet<DhChunkPos> getNormalRenderedChunks()
{
SodiumWorldRenderer renderer = SodiumWorldRenderer.instance();
LevelHeightAccessor height = Minecraft.getInstance().level;
#if MC_VER >= MC_1_20_1
// TODO: This is just a tmp solution, use a proper solution later
return MC_RENDER.getMaximumRenderedChunks().stream().filter((DhChunkPos chunk) -> {
return (renderer.isBoxVisible(
chunk.getMinBlockX() + 1, height.getMinBuildHeight() + 1, chunk.getMinBlockZ() + 1,
chunk.getMinBlockX() + 15, height.getMaxBuildHeight() - 1, chunk.getMinBlockZ() + 15));
}).collect(Collectors.toCollection(HashSet::new));
#elif MC_VER >= MC_1_18_2
// 0b11 = Lighted chunk & loaded chunk
return renderer.getChunkTracker().getChunks(0b00).filter(
(long l) -> {
return true;
}).mapToObj(DhChunkPos::new).collect(Collectors.toCollection(HashSet::new));
#else
// TODO: Maybe use a mixin to make this more efficient, and maybe ignore changes behind the camera
return MC_RENDER.getMaximumRenderedChunks().stream().filter((DhChunkPos chunk) -> {
return (renderer.isBoxVisible(
chunk.getMinBlockX() + 1, height.getMinBuildHeight() + 1, chunk.getMinBlockZ() + 1,
chunk.getMinBlockX() + 15, height.getMaxBuildHeight() - 1, chunk.getMinBlockZ() + 15));
}).collect(Collectors.toCollection(HashSet::new));
#endif
}
#else
@Override
public HashSet<DhChunkPos> getNormalRenderedChunks() {
SodiumWorldRenderer renderer = SodiumWorldRenderer.getInstance();
LevelAccessor height = Minecraft.getInstance().level;
// TODO: Maybe use a mixin to make this more efficient
return MC_RENDER.getMaximumRenderedChunks().stream().filter((DhChunkPos chunk) -> {
FakeChunkEntity AABB = new FakeChunkEntity(chunk.x, chunk.z, height.getMaxBuildHeight());
return (renderer.isEntityVisible(AABB));
}).collect(Collectors.toCollection(HashSet::new));
}
private static class FakeChunkEntity extends Entity {
public int cx;
public int cz;
public int my;
public FakeChunkEntity(int chunkX, int chunkZ, int maxHeight) {
super(EntityType.AREA_EFFECT_CLOUD, null);
cx = chunkX;
cz = chunkZ;
my = maxHeight;
}
@Override
public AABB getBoundingBoxForCulling() {
return new AABB(cx*16+1, 1, cz*16+1,
cx*16+15, my-1, cz*16+15);
}
@Override
protected void defineSynchedData() {}
@Override
protected void readAdditionalSaveData(CompoundTag paramCompoundTag) {}
@Override
protected void addAdditionalSaveData(CompoundTag paramCompoundTag) {}
@Override
public Packet<?> getAddEntityPacket() {
throw new UnsupportedOperationException("This is a FAKE CHUNK ENTITY... For tricking the Sodium to check a AABB.");
}
}
#if MC_VER >= MC_1_20_1
private static MethodHandle setFogOcclusionMethod;
private static Object sodiumPerformanceOptions;
#endif
/** A temporary overwrite for a config in sodium 0.5 to fix their terrain from showing, will be removed once a proper fix is added */
// FIXME
//======================//
// mod accessor methods //
//======================//
@Override
public void setFogOcclusion(boolean b)
public String getModName() { return "Sodium-Fabric"; }
//================//
// sodium methods //
//================//
/** An overwrite for a config in sodium 0.5 to fix their terrain from showing */
@Override
public void setFogOcclusion(boolean occlusionEnabled)
{
#if MC_VER >= MC_1_20_1
me.jellysquid.mods.sodium.client.SodiumClientMod.options().performance.useFogOcclusion = b;
try
{
if (sodiumPerformanceOptions == null)
{
boolean sodiumV6 = classPresent("net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer");
if (!sodiumV6)
{
// sodium 0.5
Class<?> optionsClass = Class.forName("me.jellysquid.mods.sodium.client.gui.SodiumGameOptions");
Object basicOptions = MethodHandles.lookup().findStatic(
Class.forName("me.jellysquid.mods.sodium.client.SodiumClientMod"),
"options", MethodType.methodType(optionsClass)).invoke();
sodiumPerformanceOptions = optionsClass.getDeclaredField("performance").get(basicOptions);
setFogOcclusionMethod = MethodHandles.lookup()
.findSetter(Class.forName(
"me.jellysquid.mods.sodium.client.gui.SodiumGameOptions$PerformanceSettings"),
"useFogOcclusion", boolean.class);
// alternate option if referencing Sodium 0.5 directly
//me.jellysquid.mods.sodium.client.SodiumClientMod.options().performance.useFogOcclusion = b;
}
else
{
// sodium 0.6
Class<?> optionsClass = Class.forName("net.caffeinemc.mods.sodium.client.gui.SodiumGameOptions");
Object basicOptions = MethodHandles.lookup().findStatic(
Class.forName("net.caffeinemc.mods.sodium.client.SodiumClientMod"),
"options", MethodType.methodType(optionsClass)).invoke();
sodiumPerformanceOptions = optionsClass.getDeclaredField("performance").get(basicOptions);
setFogOcclusionMethod = MethodHandles.lookup()
.findSetter(Class.forName(
"net.caffeinemc.mods.sodium.client.gui.SodiumGameOptions$PerformanceSettings"),
"useFogOcclusion", boolean.class);
}
}
setFogOcclusionMethod.invoke(sodiumPerformanceOptions, occlusionEnabled);
}
catch (Throwable e)
{
throw new RuntimeException(e);
}
#endif
}
//================//
// helper methods //
//================//
private static boolean classPresent(String className)
{
try
{
Class.forName(className);
return true;
}
catch (ClassNotFoundException e)
{
return false;
}
}
}
@@ -3,7 +3,6 @@
"minVersion": "0.8",
"package": "com.seibel.distanthorizons.fabric.mixins",
"mixins": [
"server.unsafe.MixinThreadingDetector",
"server.MixinChunkGenerator",
"server.MixinChunkMap",
"server.MixinUtilBackgroundThread"
@@ -15,7 +14,6 @@
"client.MixinFogRenderer",
"client.MixinGameRenderer",
"client.MixinLevelRenderer",
"client.MixinDynamicTexture",
"client.MixinLightTexture",
"client.MixinOptionsScreen",
"client.MixinMinecraft",
@@ -56,7 +56,6 @@
},
"suggests": {
"blendium": "*"
},
"breaks": $fabric_incompatibility_list,
+13 -1
View File
@@ -4,7 +4,7 @@ plugins {
id "architectury-plugin" version "3.4-SNAPSHOT"
}
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_21
architectury {
platformSetupLoomIde()
@@ -78,6 +78,18 @@ dependencies {
addMod("curse.maven:TerraFirmaCraft-302973:4616004", rootProject.enable_terrafirmacraft)
if ( // Only run on MC 1.20.6 or later
// FIXME: Add an environment variable for the Major, Minor, and Patch version number of Minecraft
minecraft_version.split("\\.")[1].toInteger() >= 20 &&
(
minecraft_version.split("\\.").length > 1 && // Incase there isn't a minor version
minecraft_version.split("\\.")[2].toInteger() >= 6
)
) {
// (potential) hack fix, force jopt-simple to be exactly 5.0.4 because Mojang ships that version, but some transitive dependencies request 6.0+
implementation('net.sf.jopt-simple:jopt-simple:5.0.4') //{ version { strictly '5.0.4' } }
}
}
task deleteResources(type: Delete) {
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.api.internal.ClientApi;
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.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -65,6 +66,8 @@ import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import org.lwjgl.opengl.GL32;
import java.util.concurrent.ThreadPoolExecutor;
/**
* This handles all events sent to the client,
* and is the starting point for most of the mod.
@@ -144,7 +147,7 @@ public class ForgeClientProxy implements AbstractModInitializer.IEventProxy
#if MC_VER < MC_1_19_2
public void clientLevelUnloadEvent(WorldEvent.Unload event)
#else
public void clientLevelUnloadEvent(LevelEvent.Load event)
public void clientLevelUnloadEvent(LevelEvent.Unload event)
#endif
{
LOGGER.info("level unload");
@@ -173,40 +176,60 @@ public class ForgeClientProxy implements AbstractModInitializer.IEventProxy
@SubscribeEvent
public void rightClickBlockEvent(PlayerInteractEvent.RightClickBlock event)
{
if (SharedApi.isChunkAtBlockPosAlreadyUpdating(event.getPos().getX(), event.getPos().getZ()))
if (MC.clientConnectedToDedicatedServer())
{
return;
if (SharedApi.isChunkAtBlockPosAlreadyUpdating(event.getPos().getX(), event.getPos().getZ()))
{
return;
}
//LOGGER.trace("interact or block place event at blockPos: " + event.getPos());
#if MC_VER < MC_1_19_2
LevelAccessor level = event.getWorld();
#else
LevelAccessor level = event.getLevel();
#endif
ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor != null)
{
executor.execute(() ->
{
ChunkAccess chunk = level.getChunk(event.getPos());
this.onBlockChangeEvent(level, chunk);
});
}
}
//LOGGER.trace("interact or block place event at blockPos: " + event.getPos());
#if MC_VER < MC_1_19_2
LevelAccessor level = event.getWorld();
#else
LevelAccessor level = event.getLevel();
#endif
ChunkAccess chunk = level.getChunk(event.getPos());
this.onBlockChangeEvent(level, chunk);
}
@SubscribeEvent
public void leftClickBlockEvent(PlayerInteractEvent.LeftClickBlock event)
{
if (SharedApi.isChunkAtBlockPosAlreadyUpdating(event.getPos().getX(), event.getPos().getZ()))
if (MC.clientConnectedToDedicatedServer())
{
return;
if (SharedApi.isChunkAtBlockPosAlreadyUpdating(event.getPos().getX(), event.getPos().getZ()))
{
return;
}
//LOGGER.trace("break or block attack at blockPos: " + event.getPos());
#if MC_VER < MC_1_19_2
LevelAccessor level = event.getWorld();
#else
LevelAccessor level = event.getLevel();
#endif
ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor != null)
{
executor.execute(() ->
{
ChunkAccess chunk = level.getChunk(event.getPos());
this.onBlockChangeEvent(level, chunk);
});
}
}
//LOGGER.trace("break or block attack at blockPos: " + event.getPos());
#if MC_VER < MC_1_19_2
LevelAccessor level = event.getWorld();
#else
LevelAccessor level = event.getLevel();
#endif
ChunkAccess chunk = level.getChunk(event.getPos());
this.onBlockChangeEvent(level, chunk);
}
private void onBlockChangeEvent(LevelAccessor level, ChunkAccess chunk)
{
@@ -214,20 +237,15 @@ public class ForgeClientProxy implements AbstractModInitializer.IEventProxy
SharedApi.INSTANCE.chunkBlockChangedEvent(new ChunkWrapper(chunk, level, wrappedLevel), wrappedLevel);
}
@SubscribeEvent
public void clientChunkLoadEvent(ChunkEvent.Load event)
{
ILevelWrapper wrappedLevel = ProxyUtil.getLevelWrapper(GetEventLevel(event));
IChunkWrapper chunk = new ChunkWrapper(event.getChunk(), GetEventLevel(event), wrappedLevel);
SharedApi.INSTANCE.chunkLoadEvent(chunk, wrappedLevel);
}
@SubscribeEvent
public void clientChunkUnloadEvent(ChunkEvent.Unload event)
{
ILevelWrapper wrappedLevel = ProxyUtil.getLevelWrapper(GetEventLevel(event));
IChunkWrapper chunk = new ChunkWrapper(event.getChunk(), GetEventLevel(event), wrappedLevel);
SharedApi.INSTANCE.chunkUnloadEvent(chunk, wrappedLevel);
if (MC.clientConnectedToDedicatedServer())
{
ILevelWrapper wrappedLevel = ProxyUtil.getLevelWrapper(GetEventLevel(event));
IChunkWrapper chunk = new ChunkWrapper(event.getChunk(), GetEventLevel(event), wrappedLevel);
SharedApi.INSTANCE.chunkLoadEvent(chunk, wrappedLevel);
}
}
@@ -328,9 +346,7 @@ public class ForgeClientProxy implements AbstractModInitializer.IEventProxy
#elif MC_VER >= MC_1_18_2
if (event.getStage() == RenderLevelStageEvent.Stage.AFTER_SOLID_BLOCKS)
#else
// FIXME: Is this the correct location for 1.16 & 1.17???
// I couldnt find anything for rendering after the level, so is rendering after overlays ok?
if (event.type.equals(TickEvent.RenderTickEvent.Type.WORLD))
if (event.type.equals(TickEvent.RenderTickEvent.Type.RENDER))
#endif
{
try
@@ -22,6 +22,8 @@ package com.seibel.distanthorizons.forge;
import com.mojang.brigadier.CommandDispatcher;
import com.seibel.distanthorizons.common.AbstractModInitializer;
import com.seibel.distanthorizons.common.wrappers.gui.GetConfigScreen;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker;
import com.seibel.distanthorizons.coreapi.ModInfo;
@@ -104,6 +106,20 @@ public class ForgeMain extends AbstractModInitializer
ModLoadingContext.get().registerExtensionPoint(ConfigScreenHandler.ConfigScreenFactory.class,
() -> new ConfigScreenHandler.ConfigScreenFactory((client, parent) -> GetConfigScreen.getScreen(parent)));
#endif
if (Config.Client.Advanced.Logging.showModCompatibilityWarningsOnStartup.get())
{
IModChecker modChecker = SingletonInjector.INSTANCE.get(IModChecker.class);
if (modChecker.isModLoaded("alexscaves"))
{
String message =
// orange text
"\u00A76" + "Distant Horizons: Alex's Cave detected." + "\u00A7r\n" +
"You may have to change Alex's config for DH to render. ";
ClientApi.INSTANCE.showChatMessageNextFrame(message);
}
}
}
@Override
@@ -137,14 +137,6 @@ public class ForgeServerProxy implements AbstractModInitializer.IEventProxy
IChunkWrapper chunk = new ChunkWrapper(event.getChunk(), GetEventLevel(event), levelWrapper);
this.serverApi.serverChunkLoadEvent(chunk, levelWrapper);
}
@SubscribeEvent
public void serverChunkSaveEvent(ChunkEvent.Unload event)
{
ILevelWrapper levelWrapper = ProxyUtil.getLevelWrapper(GetEventLevel(event));
IChunkWrapper chunk = new ChunkWrapper(event.getChunk(), GetEventLevel(event), levelWrapper);
this.serverApi.serverChunkSaveEvent(chunk, levelWrapper);
}
@@ -1,74 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.forge.mixins.client;
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 com.seibel.distanthorizons.common.util.ILightTextureMarker;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.jetbrains.annotations.Nullable;
@Mixin(DynamicTexture.class)
public abstract class MixinDynamicTexture implements ILightTextureMarker
{
/** Used to prevent accidentally using other dynamic textures as a lightmap */
@Unique
private boolean isLightTexture = false;
@Shadow
@Final
private NativeImage pixels;
@Inject(method = "upload()V", at = @At("HEAD"))
public void updateLightTexture(CallbackInfo ci)
{
// since the light map is always updated on the client render thread we should be able to access the client level at the same time
IMinecraftClientWrapper mc = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
if (!this.isLightTexture
|| mc == null
|| mc.getWrappedClientLevel() == null
)
{
return;
}
//ApiShared.LOGGER.info("Lightmap update");
IClientLevelWrapper clientLevel = mc.getWrappedClientLevel();
MinecraftRenderWrapper.INSTANCE.updateLightmap(this.pixels, clientLevel);
}
public void markLightTexture() { this.isLightTexture = true; }
}
@@ -22,6 +22,10 @@ package com.seibel.distanthorizons.forge.mixins.client;
import com.mojang.blaze3d.vertex.PoseStack;
#if MC_VER < MC_1_19_4
import com.mojang.math.Matrix4f;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LightTexture;
#else
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
@@ -34,13 +38,9 @@ import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -49,7 +49,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.nio.FloatBuffer;
import javax.annotation.Nullable;
#if MC_VER < MC_1_17_1
import org.lwjgl.opengl.GL15;
@@ -68,7 +68,8 @@ import org.lwjgl.opengl.GL15;
@Mixin(LevelRenderer.class)
public class MixinLevelRenderer
{
@Shadow
@Nullable
@Shadow //# if MC_VER >= MC_1_20_4 (remap = false) # endif
private ClientLevel level;
@Unique
private static float previousPartialTicks = 0;
@@ -111,11 +112,16 @@ public class MixinLevelRenderer
method = "renderChunkLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/PoseStack;DDDLorg/joml/Matrix4f;)V",
cancellable = true)
private void renderChunkLayer(RenderType renderType, PoseStack modelViewMatrixStack, double cameraXBlockPos, double cameraYBlockPos, double cameraZBlockPos, Matrix4f projectionMatrix, CallbackInfo callback)
#else
#elif MC_VER < MC_1_20_4
@Inject(at = @At("HEAD"),
method = "Lnet/minecraft/client/renderer/LevelRenderer;renderSectionLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/PoseStack;DDDLorg/joml/Matrix4f;)V",
cancellable = true)
private void renderChunkLayer(RenderType renderType, PoseStack modelViewMatrixStack, double camX, double camY, double camZ, Matrix4f projectionMatrix, CallbackInfo callback)
#else
@Inject(at = @At("HEAD"),
method = "renderSectionLayer",
cancellable = true)
private void renderChunkLayer(RenderType renderType, PoseStack modelViewMatrixStack, double camX, double camY, double camZ, Matrix4f projectionMatrix, CallbackInfo callback)
#endif
{
// get MC's model view and projection matrices
@@ -20,10 +20,13 @@
package com.seibel.distanthorizons.forge.mixins.client;
import com.seibel.distanthorizons.common.util.ILightTextureMarker;
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 net.minecraft.client.renderer.texture.DynamicTexture;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -35,11 +38,22 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LightTexture.class)
public class MixinLightTexture
{
@Shadow
@Shadow //# if MC_VER >= MC_1_20_4 (remap = false) # endif
@Final
private DynamicTexture lightTexture;
private NativeImage lightPixels;
@Inject(method = "<init>", at = @At("RETURN"))
public void markLightTexture(CallbackInfo ci) { ((ILightTextureMarker) this.lightTexture).markLightTexture(); }
@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);
}
}
@@ -73,11 +73,23 @@ public class MixinMinecraft
&& SelfUpdater.onStart()
)
{
runnable = () -> {
runnable = () ->
{
String versionId;
EDhApiUpdateBranch updateBranch = EDhApiUpdateBranch.convertAutoToStableOrNightly(Config.Client.Advanced.AutoUpdater.updateBranch.get());
if (updateBranch == EDhApiUpdateBranch.STABLE)
{
versionId = ModrinthGetter.getLatestIDForVersion(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion());
}
else
{
versionId = GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha");
}
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
(Config.Client.Advanced.AutoUpdater.updateBranch.get() == EDhApiUpdateBranch.STABLE ? ModrinthGetter.getLatestIDForVersion(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion()) : GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha"))
versionId
));
};
}
@@ -1,156 +0,0 @@
package com.seibel.distanthorizons.forge.mixins.client;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiDimensionTypeWrapper;
import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper;
import com.seibel.distanthorizons.common.wrappers.world.DimensionTypeWrapper;
import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure;
import com.seibel.distanthorizons.core.level.DhServerLevel;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.worldupdate.WorldUpgrader;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.WorldGenSettings;
import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.io.File;
import java.nio.file.Path;
#if FALSE
@Mixin(WorldUpgrader.class)
public class MixinWorldUpgrader {
static class FakeLevelWrapper implements IServerLevelWrapper {
private Path saveFolder;
private LevelStem stem;
private DimensionType dimension;
private DimensionTypeWrapper dimensionTypeWrapper;
public FakeLevelWrapper(LevelStorageSource.LevelStorageAccess storage, WorldGenSettings gen, ResourceKey<Level> dim) {
saveFolder = storage.getDimensionPath(dim);
stem = gen.dimensions().getOrThrow(WorldGenSettings.levelToLevelStem(dim));
dimension = stem.typeHolder().value();
dimensionTypeWrapper = DimensionTypeWrapper.getDimensionTypeWrapper(dimension);
}
@Override
public EDhApiLevelType getLevelType() {
return EDhApiLevelType.SERVER_LEVEL;
}
@Override
public IDhApiDimensionTypeWrapper getDimensionType() {
return dimensionTypeWrapper;
}
@Override
public int getBlockLight(int x, int y, int z) {
return 0;
}
@Override
public int getSkyLight(int x, int y, int z) {
return 0;
}
@Override
public boolean hasCeiling() {
return dimension.hasCeiling();
}
@Override
public boolean hasSkyLight() {
return dimension.hasSkyLight();
}
@Override
public int getHeight() {
return dimension.height();
}
@Override
public int getMinHeight() {
return dimension.minY();
}
@Override
public boolean hasChunkLoaded(int chunkX, int chunkZ) {
return false;
}
@Override
public IBlockStateWrapper getBlockState(DhBlockPos pos) {
return BlockStateWrapper.AIR;
}
@Override
public IBiomeWrapper getBiome(DhBlockPos pos) {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public Object getWrappedMcObject() {
return null;
}
@Nullable
@Override
public IClientLevelWrapper tryGetClientLevelWrapper() {
return null;
}
@Override
public File getSaveFolder() {
return saveFolder.toFile();
}
}
@Unique
private DhServerLevel dhServerLevel;
@Unique
private FakeLevelWrapper fakeLevelWrapper;
@Unique
public LocalSaveStructure saveStructure;
@Shadow @Final
private DimensionDataStorage overworldDataStorage;
@Shadow @Final
private LevelStorageSource.LevelStorageAccess levelStorage;
@Shadow @Final
private WorldGenSettings worldGenSettings;
@Inject(method = "Lnet/minecraft/util/worldupdate/WorldUpgrader;work()V",
at = @At(value = "INVOKE")
)
private void initWorldUpgrade() {
saveStructure = new LocalSaveStructure();
}
@Inject(method = "Lnet/minecraft/util/worldupdate/WorldUpgrader;work()V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/util/worldupdate/WorldUpgrader;getAllChunkPos(Lnet/minecraft/resources/ResourceKey;)Ljava/util/List;", shift = At.Shift.AFTER),
locals = LocalCapture.CAPTURE_FAILSOFT
)
private void startWorldUpgrade(CallbackInfo info, ResourceKey resourceKey) {
ResourceKey<Level> key = resourceKey;
fakeLevelWrapper = new FakeLevelWrapper(levelStorage, worldGenSettings, key);
dhServerLevel = new DhServerLevel(saveStructure, fakeLevelWrapper);
}
}
#endif
@@ -0,0 +1,35 @@
package com.seibel.distanthorizons.forge.mixins.server;
import com.seibel.distanthorizons.common.commonMixins.MixinChunkMapCommon;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ChunkMap.class)
public class MixinChunkMap
{
@Unique
private static final String CHUNK_SERIALIZER_WRITE
= "Lnet/minecraft/world/level/chunk/storage/ChunkSerializer;write(" +
"Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ChunkAccess;)" +
"Lnet/minecraft/nbt/CompoundTag;";
@Shadow
@Final
ServerLevel level;
// firing at INVOKE causes issues with C2ME and is probably unnecessary since we
// don't need the chunk(s) before MC has finished saving them
@Inject(method = "save", at = @At(value = "RETURN", target = CHUNK_SERIALIZER_WRITE))
private void onChunkSave(ChunkAccess chunk, CallbackInfoReturnable<Boolean> ci)
{ MixinChunkMapCommon.onChunkSave(this.level, chunk, ci); }
}
@@ -1,59 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.forge.mixins.server.unsafe;
import org.spongepowered.asm.mixin.Mixin;
#if MC_VER >= MC_1_18_2
import net.minecraft.util.ThreadingDetector;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.concurrent.Semaphore;
/**
* Why does this exist? But okay! (Will be probably removed when the experimental generator is done)
* FIXME: Recheck this // STILL check this
*/
@Mixin(ThreadingDetector.class)
public class MixinThreadingDetector
{
@Mutable
@Shadow
private Semaphore lock;
@Inject(method = "<init>", at = @At("RETURN"))
private void setSemaphore(CallbackInfo ci)
{
this.lock = new Semaphore(2);
}
}
#else
import net.minecraft.world.level.chunk.ChunkGenerator;
@Mixin(ChunkGenerator.class)
public class MixinThreadingDetector { }
#endif
@@ -3,10 +3,10 @@
"minVersion": "0.8",
"package": "com.seibel.distanthorizons.forge.mixins",
"mixins": [
"server.unsafe.MixinThreadingDetector",
"server.MixinUtilBackgroundThread",
"server.MixinChunkGenerator",
"server.MixinTFChunkGenerator"
"server.MixinTFChunkGenerator",
"server.MixinChunkMap"
],
"client": [
"client.MixinClientPacketListener",
@@ -14,7 +14,6 @@
"client.MixinFogRenderer",
"client.MixinGameRenderer",
"client.MixinLevelRenderer",
"client.MixinDynamicTexture",
"client.MixinLightTexture",
"client.MixinOptionsScreen",
"client.MixinTextureUtil"
+12 -10
View File
@@ -5,12 +5,12 @@ org.gradle.caching=true
# Mod Info
mod_name=DistantHorizons
mod_version=2.0.3-a-dev
api_version=1.1.0
mod_version=2.2.0-a
api_version=3.0.0
maven_group=com.seibel.distanthorizons
mod_readable_name=Distant Horizons
mod_description=This mod generates and renders simplified terrain beyond the normal view distance at a low performance cost. Allowing you to see much farther without turning your game into a slideshow.
# Note: In forge's mods.toml this is hard coded because Architectury throws an error with setting it as a varuable
# Note: In forge's mods.toml this is hard coded because Architectury throws an error with setting it as a variable
mod_authors=["James Seibel", "Leonardo Amato", "Cola", "coolGi", "Ran", "Leetom"]
mod_homepage=https://modrinth.com/mod/distanthorizons
mod_source=https://gitlab.com/jeseibel/distant-horizons
@@ -18,17 +18,19 @@ mod_issues=https://gitlab.com/jeseibel/distant-horizons/-/issues
mod_discord=https://discord.gg/xAB8G4cENx
# Global Plugin Versions
manifold_version=2024.1.9
manifold_version=2023.1.17
# 2023.1.17 can be used if there are mystery Java compiler issues
nightconfig_version=3.6.6
lz4_version=1.8.0
zstd_version=1.5.5-11
xz_version=1.9
sqlite_jdbc_version=3.43.0.0
fastutil_version=8.5.13
# 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
# Minecraft related libaries (included in MC's jar)
log4j_version=2.20.0
# Minecraft related libraries (included in MC's jar)
log4j_version=2.23.1
netty_version=4.1.94.Final
lwjgl_version=3.3.1
joml_version=1.10.2
@@ -47,7 +49,7 @@ versionStr=
# This defines what MC version Intellij will use for the preprocessor
# and what version is used automatically by build and run commands
mcVer=1.20.4
mcVer=1.21.1
# Defines the maximum amount of memory Minecraft is allowed when run in a developement environment
# 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.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+16 -42
View File
@@ -11,41 +11,17 @@ architectury {
neoForge()
}
// TODO this is already defined in the main settings.gradle file, why doesn't it work unless also defined here? (If compiling does work without this block feel free to remove)
repositories {
maven {
name "Neoforge"
url "https://maven.neoforged.net/releases/"
}
}
//loom {
// forge {
// convertAccessWideners.set(true)
// extraAccessWideners.add("lod.accesswidener")
// mixinConfigs("DistantHorizons.mixins.json")
// }
//}
loom {
silentMojangMappingsLicense() // Shut the licencing warning
accessWidenerPath = project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener")
neoForge {
// Access wideners are now defined in the `remapJar.atAccessWideners`
// convertAccessWideners = true
// extraAccessWideners.add loom.accessWidenerPath.get().asFile.name
// Access wideners are defined in the `remapJar.atAccessWideners`
// Mixins are now defined in the `mods.toml`
// mixinConfigs = [
// "DistantHorizons.mixins.json"
// ]
// Mixins are defined in the `mods.toml`
}
mixin {
// Mixins are now defined in the `mods.toml`
// mixinConfigs = [
// "DistantHorizons.mixins.json"
// ]
// Mixins are defined in the `mods.toml`
}
// "runs" isn't required, but when we do need it then it can be useful
@@ -55,7 +31,7 @@ loom {
setConfigName("NeoForge Client")
ideConfigGenerated(true)
runDir("../run")
// vmArgs("-XX:-OmitStackTraceInFastThrow", minecraftMemoryJavaArg)
//vmArgs("-XX:-OmitStackTraceInFastThrow", minecraftMemoryJavaArg)
}
server {
server()
@@ -66,13 +42,7 @@ loom {
}
}
remapJar {
inputFile = shadowJar.archiveFile
dependsOn shadowJar
// classifier null
atAccessWideners.add("distanthorizons.accesswidener")
}
def addMod(path, enabled) {
if (enabled == "2")
@@ -80,21 +50,24 @@ def addMod(path, enabled) {
else if (enabled == "1")
dependencies { modCompileOnly(path) }
}
dependencies {
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
mappings loom.layered() {
mappings loom.layered()
{
// Mojmap mappings
officialMojangMappings()
// Parchment mappings (it adds parameter mappings & javadoc)
parchment("org.parchmentmc.data:parchment-${rootProject.parchment_version}@zip")
}
// Neoforge
neoForge "net.neoforged:neoforge:${rootProject.neoforge_version}"
addMod("curse.maven:TerraFirmaCraft-302973:4616004", rootProject.enable_terrafirmacraft)
}
task deleteResources(type: Delete) {
delete file("build/resources/main")
}
@@ -113,14 +86,15 @@ tasks.named('runClient') {
finalizedBy(deleteResources)
}
remapJar {
inputFile = shadowJar.archiveFile
dependsOn shadowJar
atAccessWideners.add("distanthorizons.accesswidener")
}
sourcesJar {
def commonSources = project(":common").sourcesJar
dependsOn commonSources
from commonSources.archiveFile.map { zipTree(it) }
}
//components.java {
// withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) {
// skip()
// }
//}
@@ -21,19 +21,21 @@ package com.seibel.distanthorizons.neoforge;
import com.seibel.distanthorizons.common.AbstractModInitializer;
import com.seibel.distanthorizons.common.util.ProxyUtil;
import com.seibel.distanthorizons.common.wrappers.McObjectConverter;
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.SharedApi;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.math.Mat4f;
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.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
//import io.netty.buffer.ByteBuf;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.client.multiplayer.ClientLevel;
@@ -54,10 +56,18 @@ import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import net.minecraft.client.Minecraft;
import net.neoforged.neoforge.client.event.InputEvent;
import net.neoforged.neoforge.event.TickEvent;
import net.neoforged.bus.api.SubscribeEvent;
import org.lwjgl.opengl.GL32;
#if MC_VER < MC_1_20_6
import net.neoforged.neoforge.event.TickEvent;
#else
import net.neoforged.neoforge.client.event.ClientTickEvent;
import java.util.concurrent.ThreadPoolExecutor;
#endif
/**
* This handles all events sent to the client,
* and is the starting point for most of the mod.
@@ -72,8 +82,10 @@ public class NeoforgeClientProxy implements AbstractModInitializer.IEventProxy
// private static SimpleChannel multiversePluginChannel;
// Not the cleanest way of passing this to the LOD renderer, but it'll have to do for now
public static Mat4f currentModelViewMatrix = new Mat4f();
public static Mat4f currentProjectionMatrix = new Mat4f();
private static LevelAccessor GetEventLevel(LevelEvent e) { return e.getLevel(); }
@@ -90,6 +102,7 @@ public class NeoforgeClientProxy implements AbstractModInitializer.IEventProxy
// tick events //
//=============//
#if MC_VER < MC_1_20_6
@SubscribeEvent
public void clientTickEvent(TickEvent.ClientTickEvent event)
{
@@ -98,6 +111,13 @@ public class NeoforgeClientProxy implements AbstractModInitializer.IEventProxy
ClientApi.INSTANCE.clientTickEvent();
}
}
#else
@SubscribeEvent
public void clientTickEvent(ClientTickEvent.Pre event)
{
ClientApi.INSTANCE.clientTickEvent();
}
#endif
@@ -122,7 +142,7 @@ public class NeoforgeClientProxy implements AbstractModInitializer.IEventProxy
ClientApi.INSTANCE.clientLevelLoadEvent(clientLevelWrapper);
}
@SubscribeEvent
public void clientLevelUnloadEvent(LevelEvent.Load event)
public void clientLevelUnloadEvent(LevelEvent.Unload event)
{
LOGGER.info("level unload");
@@ -146,32 +166,56 @@ public class NeoforgeClientProxy implements AbstractModInitializer.IEventProxy
@SubscribeEvent
public void rightClickBlockEvent(PlayerInteractEvent.RightClickBlock event)
{
if (SharedApi.isChunkAtBlockPosAlreadyUpdating(event.getPos().getX(), event.getPos().getZ()))
if (MC.clientConnectedToDedicatedServer())
{
return;
if (SharedApi.isChunkAtBlockPosAlreadyUpdating(event.getPos().getX(), event.getPos().getZ()))
{
return;
}
// executor to prevent locking up the render/event thread
// if the getChunk() takes longer than expected
// (which can be caused by certain mods)
ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor != null)
{
executor.execute(() ->
{
//LOGGER.trace("interact or block place event at blockPos: " + event.getPos());
LevelAccessor level = event.getLevel();
ChunkAccess chunk = level.getChunk(event.getPos());
this.onBlockChangeEvent(level, chunk);
});
}
}
//LOGGER.trace("interact or block place event at blockPos: " + event.getPos());
LevelAccessor level = event.getLevel();
ChunkAccess chunk = level.getChunk(event.getPos());
this.onBlockChangeEvent(level, chunk);
}
@SubscribeEvent
public void leftClickBlockEvent(PlayerInteractEvent.LeftClickBlock event)
{
if (SharedApi.isChunkAtBlockPosAlreadyUpdating(event.getPos().getX(), event.getPos().getZ()))
if (MC.clientConnectedToDedicatedServer())
{
return;
if (SharedApi.isChunkAtBlockPosAlreadyUpdating(event.getPos().getX(), event.getPos().getZ()))
{
return;
}
// executor to prevent locking up the render/event thread
// if the getChunk() takes longer than expected
// (which can be caused by certain mods)
ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor != null)
{
executor.execute(() ->
{
//LOGGER.trace("break or block attack at blockPos: " + event.getPos());
LevelAccessor level = event.getLevel();
ChunkAccess chunk = level.getChunk(event.getPos());
this.onBlockChangeEvent(level, chunk);
});
}
}
//LOGGER.trace("break or block attack at blockPos: " + event.getPos());
LevelAccessor level = event.getLevel();
ChunkAccess chunk = level.getChunk(event.getPos());
this.onBlockChangeEvent(level, chunk);
}
private void onBlockChangeEvent(LevelAccessor level, ChunkAccess chunk)
{
@@ -180,22 +224,6 @@ public class NeoforgeClientProxy implements AbstractModInitializer.IEventProxy
}
@SubscribeEvent
public void clientChunkLoadEvent(ChunkEvent.Load event)
{
ILevelWrapper wrappedLevel = ProxyUtil.getLevelWrapper(GetEventLevel(event));
IChunkWrapper chunk = new ChunkWrapper(event.getChunk(), GetEventLevel(event), wrappedLevel);
SharedApi.INSTANCE.chunkLoadEvent(chunk, wrappedLevel);
}
@SubscribeEvent
public void clientChunkUnloadEvent(ChunkEvent.Unload event)
{
ILevelWrapper wrappedLevel = ProxyUtil.getLevelWrapper(GetEventLevel(event));
IChunkWrapper chunk = new ChunkWrapper(event.getChunk(), GetEventLevel(event), wrappedLevel);
SharedApi.INSTANCE.chunkUnloadEvent(chunk, wrappedLevel);
}
//==============//
// key bindings //
@@ -281,6 +309,16 @@ public class NeoforgeClientProxy implements AbstractModInitializer.IEventProxy
// rendering //
//===========//
@SubscribeEvent
public void beforeLevelRenderEvent(RenderLevelStageEvent event)
{
if (event.getStage() == RenderLevelStageEvent.Stage.AFTER_SKY)
{
currentModelViewMatrix = McObjectConverter.Convert(event.getModelViewMatrix());
currentProjectionMatrix = McObjectConverter.Convert(event.getProjectionMatrix());
}
}
@SubscribeEvent
public void afterLevelRenderEvent(RenderLevelStageEvent event)
{
@@ -301,4 +339,12 @@ public class NeoforgeClientProxy implements AbstractModInitializer.IEventProxy
}
//================//
// helper methods //
//================//
private static LevelAccessor GetEventLevel(LevelEvent e) { return e.getLevel(); }
}
@@ -22,6 +22,8 @@ package com.seibel.distanthorizons.neoforge;
import com.mojang.brigadier.CommandDispatcher;
import com.seibel.distanthorizons.common.AbstractModInitializer;
import com.seibel.distanthorizons.common.wrappers.gui.GetConfigScreen;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
@@ -35,13 +37,18 @@ import net.neoforged.fml.ModLoadingContext;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.fml.event.lifecycle.FMLDedicatedServerSetupEvent;
import net.neoforged.neoforge.client.ConfigScreenHandler;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
import net.neoforged.neoforge.event.server.ServerStartingEvent;
import java.util.function.Consumer;
#if MC_VER < MC_1_20_6
import net.neoforged.neoforge.client.ConfigScreenHandler;
#else
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
#endif
/**
* Initialize and setup the Mod. <br>
* If you are looking for the real start of the mod
@@ -70,8 +77,28 @@ public class NeoforgeMain extends AbstractModInitializer
{
this.tryCreateModCompatAccessor("optifine", IOptifineAccessor.class, OptifineAccessor::new);
#if MC_VER < MC_1_20_6
ModLoadingContext.get().registerExtensionPoint(ConfigScreenHandler.ConfigScreenFactory.class,
() -> new ConfigScreenHandler.ConfigScreenFactory((client, parent) -> GetConfigScreen.getScreen(parent)));
#else
ModLoadingContext.get().registerExtensionPoint(IConfigScreenFactory.class,
// TODO fix potential null pointer
() -> (client, parent) -> GetConfigScreen.getScreen(parent));
#endif
if (Config.Client.Advanced.Logging.showModCompatibilityWarningsOnStartup.get())
{
IModChecker modChecker = SingletonInjector.INSTANCE.get(IModChecker.class);
if (modChecker.isModLoaded("alexscaves"))
{
String message =
// orange text
"\u00A76" + "Distant Horizons: Alex's Cave detected." + "\u00A7r\n" +
"You may have to change Alex's config for DH to render. ";
ClientApi.INSTANCE.showChatMessageNextFrame(message);
}
}
}
@Override
@@ -12,7 +12,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.LevelAccessor;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.TickEvent;
import net.neoforged.neoforge.event.level.ChunkEvent;
import net.neoforged.neoforge.event.level.LevelEvent;
import net.neoforged.bus.api.SubscribeEvent;
@@ -20,11 +19,17 @@ import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.server.ServerAboutToStartEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
import org.apache.logging.log4j.Logger;
import java.util.function.Supplier;
#if MC_VER < MC_1_20_6
import net.neoforged.neoforge.event.TickEvent;
#else
import net.neoforged.neoforge.event.tick.ServerTickEvent;
#endif
public class NeoforgeServerProxy implements AbstractModInitializer.IEventProxy
{
private static LevelAccessor GetEventLevel(LevelEvent e) { return e.getLevel(); }
@@ -58,7 +63,7 @@ public class NeoforgeServerProxy implements AbstractModInitializer.IEventProxy
// events //
//========//
// ServerTickEvent (at end)
#if MC_VER < MC_1_20_6
@SubscribeEvent
public void serverTickEvent(TickEvent.ServerTickEvent event)
{
@@ -67,6 +72,13 @@ public class NeoforgeServerProxy implements AbstractModInitializer.IEventProxy
this.serverApi.serverTickEvent();
}
}
#else
@SubscribeEvent
public void serverTickEvent(ServerTickEvent.Post event)
{
this.serverApi.serverTickEvent();
}
#endif
// ServerWorldLoadEvent
@SubscribeEvent
@@ -110,14 +122,6 @@ public class NeoforgeServerProxy implements AbstractModInitializer.IEventProxy
IChunkWrapper chunk = new ChunkWrapper(event.getChunk(), GetEventLevel(event), levelWrapper);
this.serverApi.serverChunkLoadEvent(chunk, levelWrapper);
}
@SubscribeEvent
public void serverChunkSaveEvent(ChunkEvent.Unload event)
{
ILevelWrapper levelWrapper = ProxyUtil.getLevelWrapper(GetEventLevel(event));
IChunkWrapper chunk = new ChunkWrapper(event.getChunk(), GetEventLevel(event), levelWrapper);
this.serverApi.serverChunkSaveEvent(chunk, levelWrapper);
}
@@ -1,75 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.neoforge.mixins.client;
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 com.seibel.distanthorizons.common.util.ILightTextureMarker;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(DynamicTexture.class)
public abstract class MixinDynamicTexture implements ILightTextureMarker
{
/** Used to prevent accidentally using other dynamic textures as a lightmap */
@Unique
private boolean isLightTexture = false;
@Shadow
#if MC_VER >= MC_1_20_4
(remap = false)
#endif
@Final
private NativeImage pixels;
@Inject(method = "upload()V", at = @At("HEAD"))
public void updateLightTexture(CallbackInfo ci)
{
// since the light map is always updated on the client render thread we should be able to access the client level at the same time
IMinecraftClientWrapper mc = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
if (!this.isLightTexture
|| mc == null
|| mc.getWrappedClientLevel() == null
)
{
return;
}
//ApiShared.LOGGER.info("Lightmap update");
IClientLevelWrapper clientLevel = mc.getWrappedClientLevel();
MinecraftRenderWrapper.INSTANCE.updateLightmap(this.pixels, clientLevel);
}
public void markLightTexture() { this.isLightTexture = true; }
}
@@ -19,10 +19,13 @@
package com.seibel.distanthorizons.neoforge.mixins.client;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
#if MC_VER < MC_1_19_4
import com.mojang.math.Matrix4f;
#else
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.neoforge.NeoforgeClientProxy;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
@@ -34,7 +37,6 @@ import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderType;
@@ -67,30 +69,6 @@ public class MixinLevelRenderer
(remap = false)
#endif
private ClientLevel level;
@Unique
private static float previousPartialTicks = 0;
// TODO: Is there any reason why this is here? Can it be deleted?
public MixinLevelRenderer()
{
throw new NullPointerException("Null cannot be cast to non-null type.");
}
#if MC_VER < MC_1_17_1
@Inject(at = @At("RETURN"), method = "renderSky(Lcom/mojang/blaze3d/vertex/PoseStack;F)V")
private void renderSky(PoseStack matrixStackIn, float partialTicks, CallbackInfo callback)
#else
@Inject(method = "renderClouds", at = @At("HEAD"), cancellable = true)
public void renderClouds(PoseStack poseStack, Matrix4f projectionMatrix, float partialTicks, double cameraX, double cameraY, double cameraZ, CallbackInfo ci)
#endif
{
// FIXME this is only called when clouds are enabled and vanilla render distance is far enough
// not having the partial ticks doesn't appear to be critical currently, but might cause weird issues down the line
// get the partial ticks since renderBlockLayer doesn't
// have access to them
previousPartialTicks = partialTicks;
}
#if MC_VER < MC_1_17_1
@@ -108,14 +86,18 @@ public class MixinLevelRenderer
method = "renderChunkLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/PoseStack;DDDLorg/joml/Matrix4f;)V",
cancellable = true)
private void renderChunkLayer(RenderType renderType, PoseStack modelViewMatrixStack, double cameraXBlockPos, double cameraYBlockPos, double cameraZBlockPos, Matrix4f projectionMatrix, CallbackInfo callback)
#else
#elif MC_VER < MC_1_20_6
@Inject(at = @At("HEAD"),
method = "Lnet/minecraft/client/renderer/LevelRenderer;renderSectionLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/PoseStack;DDDLorg/joml/Matrix4f;)V",
cancellable = true)
private void renderChunkLayer(RenderType renderType, PoseStack modelViewMatrixStack, double camX, double camY, double camZ, Matrix4f projectionMatrix, CallbackInfo callback)
#endif
#else
@Inject(at = @At("HEAD"),
method = "Lnet/minecraft/client/renderer/LevelRenderer;renderSectionLayer(Lnet/minecraft/client/renderer/RenderType;DDDLorg/joml/Matrix4f;Lorg/joml/Matrix4f;)V",
cancellable = true)
private void renderChunkLayer(RenderType renderType, double x, double y, double z, Matrix4f projectionMatrix, Matrix4f frustumMatrix, CallbackInfo callback)
#endif
{
// get MC's model view and projection matrices
#if MC_VER == MC_1_16_5
// get the matrices from the OpenGL fixed pipeline
float[] mcProjMatrixRaw = new float[16];
@@ -125,22 +107,33 @@ public class MixinLevelRenderer
Mat4f mcModelViewMatrix = McObjectConverter.Convert(matrixStackIn.last().pose());
#else
#elif MC_VER <= MC_1_20_4
// get the matrices directly from MC
Mat4f mcModelViewMatrix = McObjectConverter.Convert(modelViewMatrixStack.last().pose());
Mat4f mcProjectionMatrix = McObjectConverter.Convert(projectionMatrix);
#else
// get the matrices from neoForge's render event.
// We can't call the renderer there because we don't have access to the level that's being rendered
Mat4f mcModelViewMatrix = NeoforgeClientProxy.currentModelViewMatrix;
Mat4f mcProjectionMatrix = NeoforgeClientProxy.currentProjectionMatrix;
#endif
float frameTime;
#if MC_VER < MC_1_21_1
frameTime = Minecraft.getInstance().getFrameTime();
#else
frameTime = Minecraft.getInstance().getTimer().getRealtimeDeltaTicks();
#endif
// only render before solid blocks
if (renderType.equals(RenderType.solid()))
{
ClientApi.INSTANCE.renderLods(ClientLevelWrapper.getWrapper(level), mcModelViewMatrix, mcProjectionMatrix, Minecraft.getInstance().getFrameTime());
ClientApi.INSTANCE.renderLods(ClientLevelWrapper.getWrapper(this.level), mcModelViewMatrix, mcProjectionMatrix, frameTime);
}
else if (renderType.equals(RenderType.translucent()))
{
ClientApi.INSTANCE.renderDeferredLods(ClientLevelWrapper.getWrapper(level), mcModelViewMatrix, mcProjectionMatrix, Minecraft.getInstance().getFrameTime());
ClientApi.INSTANCE.renderDeferredLods(ClientLevelWrapper.getWrapper(this.level), mcModelViewMatrix, mcProjectionMatrix, frameTime);
}
if (Config.Client.Advanced.Debugging.lodOnlyMode.get())
@@ -155,12 +148,16 @@ public class MixinLevelRenderer
#elif MC_VER < MC_1_20_1
@Inject(at = @At(value = "TAIL", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runUpdates(IZZ)I"), method = "renderLevel")
public void callAfterRunUpdates(PoseStack poseStack, float partialTick, long finishNanoTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f projectionMatrix, CallbackInfo ci)
#else
#elif MC_VER < MC_1_20_6
@Inject(at = @At(value = "TAIL", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runLightUpdates()I"), method = "renderLevel")
private void callAfterRunUpdates(PoseStack poseStack, float partialTick, long finishNanoTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f projectionMatrix, CallbackInfo ci)
#else
@Inject(at = @At(value = "TAIL", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runLightUpdates()I"), method = "renderLevel")
private void callAfterRunUpdates(CallbackInfo ci)
#endif
{
ChunkWrapper.syncedUpdateClientLightStatus();
}
}
@@ -20,10 +20,13 @@
package com.seibel.distanthorizons.neoforge.mixins.client;
import com.seibel.distanthorizons.common.util.ILightTextureMarker;
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 net.minecraft.client.renderer.texture.DynamicTexture;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -36,13 +39,21 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public class MixinLightTexture
{
@Shadow
#if MC_VER >= MC_1_20_4
(remap = false)
#endif
@Final
private DynamicTexture lightTexture;
private NativeImage lightPixels;
@Inject(method = "<init>", at = @At("RETURN"))
public void markLightTexture(CallbackInfo ci) { ((ILightTextureMarker) this.lightTexture).markLightTexture(); }
@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);
}
}
@@ -8,6 +8,7 @@ import com.seibel.distanthorizons.core.jar.installer.GitlabGetter;
import com.seibel.distanthorizons.core.jar.installer.ModrinthGetter;
import com.seibel.distanthorizons.core.jar.updater.SelfUpdater;
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
import com.seibel.distanthorizons.coreapi.ModInfo;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.TitleScreen;
import org.spongepowered.asm.mixin.Mixin;
@@ -24,41 +25,44 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
public class MixinMinecraft
{
#if MC_VER < MC_1_20_2
#if MC_VER == MC_1_20_1
@Redirect(
method = "Lnet/minecraft/client/Minecraft;setInitialScreen(Lcom/mojang/realmsclient/client/RealmsClient;Lnet/minecraft/server/packs/resources/ReloadInstance;Lnet/minecraft/client/main/GameConfig$QuickPlayData;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setScreen(Lnet/minecraft/client/gui/screens/Screen;)V")
)
public void onOpenScreen(Minecraft instance, Screen guiScreen)
{
#else
@Redirect(
method = "<init>(Lnet/minecraft/client/main/GameConfig;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setScreen(Lnet/minecraft/client/gui/screens/Screen;)V")
)
public void onOpenScreen(Minecraft instance, Screen guiScreen)
{
#endif
if (!Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get()) // Don't do anything if the user doesn't want it
{
instance.setScreen(guiScreen); // Sets the screen back to the vanilla screen as if nothing ever happened
return;
}
// commented out due to a bug with Manifold and having nested preprocessors
// and since neoforge doesn't work for anything before MC 1.20.6 anyway it doesn't need to be included
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() == EUpdateBranch.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
}
}
#endif
//#if MC_VER < MC_1_20_2
//#if MC_VER == MC_1_20_1
//@Redirect(
// method = "Lnet/minecraft/client/Minecraft;setInitialScreen(Lcom/mojang/realmsclient/client/RealmsClient;Lnet/minecraft/server/packs/resources/ReloadInstance;Lnet/minecraft/client/main/GameConfig$QuickPlayData;)V",
// at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setScreen(Lnet/minecraft/client/gui/screens/Screen;)V")
//)
//public void onOpenScreen(Minecraft instance, Screen guiScreen)
//{
//#else
//@Redirect(
// method = "<init>(Lnet/minecraft/client/main/GameConfig;)V",
// at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setScreen(Lnet/minecraft/client/gui/screens/Screen;)V")
//)
//public void onOpenScreen(Minecraft instance, Screen guiScreen)
//{
//#endif
// if (!Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get()) // Don't do anything if the user doesn't want it
// {
// instance.setScreen(guiScreen); // Sets the screen back to the vanilla screen as if nothing ever happened
// return;
// }
//
// 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() == EUpdateBranch.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
// }
//}
//#endif
#if MC_VER >= MC_1_20_2
@Redirect(
@@ -72,11 +76,23 @@ public class MixinMinecraft
&& SelfUpdater.onStart()
)
{
runnable = () -> {
runnable = () ->
{
String versionId;
EDhApiUpdateBranch updateBranch = EDhApiUpdateBranch.convertAutoToStableOrNightly(Config.Client.Advanced.AutoUpdater.updateBranch.get());
if (updateBranch == EDhApiUpdateBranch.STABLE)
{
versionId = ModrinthGetter.getLatestIDForVersion(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion());
}
else
{
versionId = GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha");
}
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
(Config.Client.Advanced.AutoUpdater.updateBranch.get() == EDhApiUpdateBranch.STABLE ? ModrinthGetter.getLatestIDForVersion(SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion()) : GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha"))
versionId
));
};
}
@@ -23,7 +23,6 @@ import com.seibel.distanthorizons.common.wrappers.gui.GetConfigScreen;
import com.seibel.distanthorizons.common.wrappers.gui.TexturedButtonWidget;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.config.Config;
import net.minecraft.client.gui.screens.OptionsScreen;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
#if MC_VER < MC_1_19_2
@@ -31,51 +30,127 @@ import net.minecraft.network.chat.TranslatableComponent;
#endif
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
#if MC_VER >= MC_1_20_6
import net.minecraft.client.gui.layouts.LinearLayout;
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Shadow;
#endif
#if MC_VER < MC_1_21_1
import net.minecraft.client.gui.screens.OptionsScreen;
#else
import net.minecraft.client.gui.screens.options.OptionsScreen;
#endif
/**
* Adds a button to the menu to goto the config
*
* @author coolGi
* @version 12-02-2021
* @version 2024-5-20
*/
@Mixin(OptionsScreen.class)
public class MixinOptionsScreen extends Screen
{
// Get the texture for the button
private static final ResourceLocation ICON_TEXTURE = new ResourceLocation(ModInfo.ID, "textures/gui/button.png");
protected MixinOptionsScreen(Component title)
{
super(title);
}
/** Texture used for the config opening button */
@Unique
private static final ResourceLocation ICON_TEXTURE =
#if MC_VER < MC_1_21_1
new ResourceLocation(ModInfo.ID, "textures/gui/button.png");
#else
ResourceLocation.fromNamespaceAndPath(ModInfo.ID, "textures/gui/button.png");
#endif
@Inject(at = @At("HEAD"), method = "init")
@Unique
private TexturedButtonWidget optionsButton = null;
#if MC_VER >= MC_1_20_6
@Shadow
@Final
protected HeaderAndFooterLayout layout;
#endif
//==============//
// constructors //
//==============//
protected MixinOptionsScreen(Component title) { super(title); }
@Inject(at = @At("RETURN"), method = "init")
private void lodconfig$init(CallbackInfo ci)
{
if (Config.Client.optionsButton.get())
this. #if MC_VER < MC_1_17_1 addButton #else addRenderableWidget #endif
(new TexturedButtonWidget(
// Where the button is on the screen
this.width / 2 - 180, this.height / 6 - 12,
// Width and height of the button
20, 20,
// Offset
0, 0,
// Some textuary stuff
20, ICON_TEXTURE, 20, 40,
// Create the button and tell it where to go
// For now it goes to the client option by default
(buttonWidget) -> Objects.requireNonNull(minecraft).setScreen(GetConfigScreen.getScreen(this)),
// Add a title to the button
#if MC_VER < MC_1_19_2
new TranslatableComponent(ModInfo.ID + ".title")));
#else
Component.translatable(ModInfo.ID + ".title")));
#endif
{
#if MC_VER < MC_1_17_1
this.addButton(this.getOptionsButton());
#elif MC_VER < MC_1_20_6
this.addRenderableWidget(this.getOptionsButton());
#else
// add the button so it's rendered
this.addRenderableWidget(this.getOptionsButton());
// add the button to the correct location in the UI
// TODO is there a better way to do this instead of using access transformers to inject into the exact UI elements?
LinearLayout layout = (LinearLayout) this.layout.headerFrame.children.get(0).child;
// determine how wide the other option buttons are so we can put our botton to the left of them all
AtomicInteger width = new AtomicInteger(0);
layout.visitChildren(x -> { width.addAndGet(x.getWidth()); });
width.addAndGet(-10); // padding between the DH button and the FOV slider
layout.wrapped.addChild(this.getOptionsButton(), 1, 2, (settings) -> { settings.paddingLeft(width.get() * -1); });
layout.arrangeElements();
#endif
}
}
//================//
// helper methods //
//================//
@Unique
public TexturedButtonWidget getOptionsButton()
{
if (this.optionsButton == null)
{
this.optionsButton
= new TexturedButtonWidget(
// Where the button is on the screen
this.width / 2 - 180, this.height / 6 - 12,
// Width and height of the button
20, 20,
// texture UV Offset
0, 0,
// Some textuary stuff
20, ICON_TEXTURE, 20, 40,
// Create the button and tell it where to go
// For now it goes to the client option by default
(buttonWidget) -> Objects.requireNonNull(this.minecraft).setScreen(GetConfigScreen.getScreen(this)),
// Add a title to the button
#if MC_VER < MC_1_19_2
new TranslatableComponent(ModInfo.ID + ".title"));
#else
Component.translatable(ModInfo.ID + ".title"));
#endif
}
return this.optionsButton;
}
}
@@ -1,126 +0,0 @@
package com.seibel.distanthorizons.neoforge.mixins.client;
import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper;
#if FALSE
@Mixin(WorldUpgrader.class)
public class MixinWorldUpgrader {
static class FakeLevelWrapper implements IServerLevelWrapper {
private Path saveFolder;
private LevelStem stem;
private DimensionType dimension;
private DimensionTypeWrapper dimensionTypeWrapper;
public FakeLevelWrapper(LevelStorageSource.LevelStorageAccess storage, WorldGenSettings gen, ResourceKey<Level> dim) {
saveFolder = storage.getDimensionPath(dim);
stem = gen.dimensions().getOrThrow(WorldGenSettings.levelToLevelStem(dim));
dimension = stem.typeHolder().value();
dimensionTypeWrapper = DimensionTypeWrapper.getDimensionTypeWrapper(dimension);
}
@Override
public EDhApiLevelType getLevelType() {
return EDhApiLevelType.SERVER_LEVEL;
}
@Override
public IDhApiDimensionTypeWrapper getDimensionType() {
return dimensionTypeWrapper;
}
@Override
public int getBlockLight(int x, int y, int z) {
return 0;
}
@Override
public int getSkyLight(int x, int y, int z) {
return 0;
}
@Override
public boolean hasCeiling() {
return dimension.hasCeiling();
}
@Override
public boolean hasSkyLight() {
return dimension.hasSkyLight();
}
@Override
public int getHeight() {
return dimension.height();
}
@Override
public int getMinHeight() {
return dimension.minY();
}
@Override
public boolean hasChunkLoaded(int chunkX, int chunkZ) {
return false;
}
@Override
public IBlockStateWrapper getBlockState(DhBlockPos pos) {
return BlockStateWrapper.AIR;
}
@Override
public IBiomeWrapper getBiome(DhBlockPos pos) {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public Object getWrappedMcObject() {
return null;
}
@Nullable
@Override
public IClientLevelWrapper tryGetClientLevelWrapper() {
return null;
}
@Override
public File getSaveFolder() {
return saveFolder.toFile();
}
}
@Unique
private DhServerLevel dhServerLevel;
@Unique
private FakeLevelWrapper fakeLevelWrapper;
@Unique
public LocalSaveStructure saveStructure;
@Shadow @Final
private DimensionDataStorage overworldDataStorage;
@Shadow @Final
private LevelStorageSource.LevelStorageAccess levelStorage;
@Shadow @Final
private WorldGenSettings worldGenSettings;
@Inject(method = "Lnet/minecraft/util/worldupdate/WorldUpgrader;work()V",
at = @At(value = "INVOKE")
)
private void initWorldUpgrade() {
saveStructure = new LocalSaveStructure();
}
@Inject(method = "Lnet/minecraft/util/worldupdate/WorldUpgrader;work()V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/util/worldupdate/WorldUpgrader;getAllChunkPos(Lnet/minecraft/resources/ResourceKey;)Ljava/util/List;", shift = At.Shift.AFTER),
locals = LocalCapture.CAPTURE_FAILSOFT
)
private void startWorldUpgrade(CallbackInfo info, ResourceKey resourceKey) {
ResourceKey<Level> key = resourceKey;
fakeLevelWrapper = new FakeLevelWrapper(levelStorage, worldGenSettings, key);
dhServerLevel = new DhServerLevel(saveStructure, fakeLevelWrapper);
}
}
#endif
@@ -0,0 +1,35 @@
package com.seibel.distanthorizons.neoforge.mixins.server;
import com.seibel.distanthorizons.common.commonMixins.MixinChunkMapCommon;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ChunkMap.class)
public class MixinChunkMap
{
@Unique
private static final String CHUNK_SERIALIZER_WRITE
= "Lnet/minecraft/world/level/chunk/storage/ChunkSerializer;write(" +
"Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ChunkAccess;)" +
"Lnet/minecraft/nbt/CompoundTag;";
@Shadow
@Final
ServerLevel level;
// firing at INVOKE causes issues with C2ME and is probably unnecessary since we
// don't need the chunk(s) before MC has finished saving them
@Inject(method = "save", at = @At(value = "RETURN", target = CHUNK_SERIALIZER_WRITE))
private void onChunkSave(ChunkAccess chunk, CallbackInfoReturnable<Boolean> ci)
{ MixinChunkMapCommon.onChunkSave(this.level, chunk, ci); }
}
@@ -1,59 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.neoforge.mixins.server.unsafe;
import org.spongepowered.asm.mixin.Mixin;
#if MC_VER >= MC_1_18_2
import net.minecraft.util.ThreadingDetector;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.concurrent.Semaphore;
/**
* Why does this exist? But okay! (Will be probably removed when the experimental generator is done)
* FIXME: Recheck this // STILL check this
*/
@Mixin(ThreadingDetector.class)
public class MixinThreadingDetector
{
@Mutable
@Shadow
private Semaphore lock;
@Inject(method = "<init>", at = @At("RETURN"))
private void setSemaphore(CallbackInfo ci)
{
this.lock = new Semaphore(2);
}
}
#else
import net.minecraft.world.level.chunk.ChunkGenerator;
@Mixin(ChunkGenerator.class)
public class MixinThreadingDetector { }
#endif
@@ -3,10 +3,10 @@
"minVersion": "0.8",
"package": "com.seibel.distanthorizons.neoforge.mixins",
"mixins": [
"server.unsafe.MixinThreadingDetector",
"server.MixinUtilBackgroundThread",
"server.MixinChunkGenerator",
"server.MixinTFChunkGenerator"
"server.MixinTFChunkGenerator",
"server.MixinChunkMap"
],
"client": [
"client.MixinClientPacketListener",
@@ -14,7 +14,6 @@
"client.MixinFogRenderer",
"client.MixinGameRenderer",
"client.MixinLevelRenderer",
"client.MixinDynamicTexture",
"client.MixinLightTexture",
"client.MixinOptionsScreen",
"client.MixinTextureUtil"
@@ -24,13 +24,12 @@ issueTrackerURL = "${issues}"
acceptableRemoteVersions = "*"
# We may need this to make forge (lexforge) & neoforge work together
#[[mixins]]
# config = "DistantHorizons.neoforge.mixins.json"
[[mixins]]
config = "DistantHorizons.neoforge.mixins.json"
[[dependencies.distanthorizons]]
modId = "minecraft"
mandatory = true # Forge syntax
type = "required" # Neoforge syntax
type = "required"
versionRange = "${compatible_forgemc_versions}" # Where we set what version of mc it is avalible for
ordering = "NONE"
side = "BOTH"
+1 -1
View File
@@ -22,7 +22,7 @@ fabric_api_version=0.42.0+1.16
canvas_version=
fabric_incompatibility_list={ "iris": "*" }
fabric_recommend_list={ "indium": "*" }
fabric_recommend_list={}
# Fabric mod run
# 0 = Don't enable and don't run
+1 -1
View File
@@ -22,7 +22,7 @@ fabric_api_version=0.46.1+1.17
canvas_version=
fabric_incompatibility_list={ "iris": "*" }
fabric_recommend_list={ "indium": "*" }
fabric_recommend_list={}
# Fabric mod run
# 0 = Don't enable and don't run
+1 -1
View File
@@ -23,7 +23,7 @@ fabric_api_version=0.76.0+1.18.2
canvas_version=mc118:1.0.2616
fabric_incompatibility_list={ "iris": "*" }
fabric_recommend_list={ "indium": "*" }
fabric_recommend_list={}
# Fabric mod run
# 0 = Don't enable and don't run
+1 -1
View File
@@ -22,7 +22,7 @@ fabric_api_version=0.76.1+1.19.2
canvas_version=mc119-1.0.2480
fabric_incompatibility_list={ "iris": "*" }
fabric_recommend_list={ "indium": "*" }
fabric_recommend_list={}
# Fabric mod run
# 0 = Don't enable and don't run
+1 -1
View File
@@ -21,7 +21,7 @@ fabric_api_version=0.87.1+1.19.4
canvas_version=
fabric_incompatibility_list={ "iris": "*" }
fabric_recommend_list={ "indium": "*" }
fabric_recommend_list={}
# Fabric mod run
# 0 = Don't enable and don't run
+3 -3
View File
@@ -7,7 +7,7 @@ accessWidenerVersion=1_20
builds_for=fabric,forge
# Fabric loader
fabric_loader_version=0.14.24
fabric_loader_version=0.15.6
fabric_api_version=0.90.4+1.20.1
# Fabric mod versions
modmenu_version=7.2.2
@@ -20,8 +20,8 @@ fabric_api_version=0.90.4+1.20.1
immersive_portals_version=
canvas_version=
fabric_incompatibility_list={ "iris": "<=1.6.20" }
fabric_recommend_list={ "indium": "*" }
fabric_incompatibility_list={ "iris": "<=1.7.4" }
fabric_recommend_list={}
# Fabric mod run
# 0 = Don't enable and don't run
+3 -3
View File
@@ -7,7 +7,7 @@ accessWidenerVersion=1_20_2
builds_for=fabric,forge
# Fabric loader
fabric_loader_version=0.14.24
fabric_loader_version=0.15.6
fabric_api_version=0.90.4+1.20.2
# Fabric mod versions
modmenu_version=8.0.0
@@ -20,8 +20,8 @@ fabric_api_version=0.90.4+1.20.2
immersive_portals_version=
canvas_version=
fabric_incompatibility_list={ "iris": "<=1.6.20" }
fabric_recommend_list={ "indium": "*" }
fabric_incompatibility_list={ "iris": "<=1.7.4" }
fabric_recommend_list={}
# Fabric mod run
# 0 = Don't enable and don't run
+3 -3
View File
@@ -8,7 +8,7 @@ builds_for=fabric,forge
# neoforge can be added once the issue with mixins has been resolved
# Fabric loader
fabric_loader_version=0.15.1
fabric_loader_version=0.15.6
fabric_api_version=0.91.2+1.20.4
# Fabric mod versions
modmenu_version=9.0.0-pre.1
@@ -21,8 +21,8 @@ fabric_api_version=0.91.2+1.20.4
immersive_portals_version=
canvas_version=
fabric_incompatibility_list={ "iris": "<=1.6.20" }
fabric_recommend_list={ "indium": "*" }
fabric_incompatibility_list={ "iris": "<=1.7.4" }
fabric_recommend_list={}
# Fabric mod run
# 0 = Don't enable and don't run
+53
View File
@@ -0,0 +1,53 @@
# 1.20.6 version
java_version=21
minecraft_version=1.20.6
parchment_version=1.20.6:2024.05.01
compatible_minecraft_versions=["1.20.6"]
accessWidenerVersion=1_20_6
builds_for=fabric,neoforge
# forge is broken due to gradle/build script issues
# Fabric loader
fabric_loader_version=0.15.10
fabric_api_version=0.97.8+1.20.6
# Fabric mod versions
modmenu_version=10.0.0-beta.1
starlight_version_fabric=
phosphor_version_fabric=
lithium_version=
sodium_version=mc1.20.6-0.5.8
iris_version=1.7.0+1.20.6
bclib_version=
immersive_portals_version=
canvas_version=
fabric_incompatibility_list={ "iris": "<=1.7.4" }
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=50.0.19
neoforge_version=20.6.70-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
+53
View File
@@ -0,0 +1,53 @@
# 1.21.1 version
java_version=21
minecraft_version=1.21.1
parchment_version=1.20.6:2024.05.01
compatible_minecraft_versions=["1.21", "1.21.1"]
accessWidenerVersion=1_20_6
builds_for=fabric,neoforge
# forge is broken due to gradle/build script issues
# Fabric loader
fabric_loader_version=0.15.11
fabric_api_version=0.100.1+1.21
# Fabric mod versions
modmenu_version=11.0.0-beta.1
starlight_version_fabric=
phosphor_version_fabric=
lithium_version=
sodium_version=mc1.21-0.5.9
iris_version=1.7.1+1.21
bclib_version=
immersive_portals_version=
canvas_version=
fabric_incompatibility_list={ "iris": "<=1.7.4" }
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.1.6
# (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