Compare commits

...
Sign in to create a new pull request.

1137 commits

Author SHA1 Message Date
Frank
a072bb1a5a Removed unneeded 2022-07-05 17:36:00 +02:00
Frank
4e202c78ff Build Update 2022-07-05 17:35:03 +02:00
Frank
e69450692c Disabled manual POM 2022-07-05 17:19:26 +02:00
Frank
341820fb80 Made ready for sub-inclusion 2022-07-05 17:11:57 +02:00
Frank
2ac2d909cb Moved core to main 2022-07-05 17:11:49 +02:00
Frank
643052aa89 Started MultiLib build 2022-07-05 17:11:43 +02:00
Frank
a73bd23ddf [Fix] DataPack Biomes from other mods are loaded after the WorldStem was first built. Our Biomes-Sources would not recognize that change (#16) 2022-07-04 00:54:41 +02:00
Frank
3b0f609776 [Fix] Do not implicitly add keys to Biome registry (#16) 2022-07-03 23:37:35 +02:00
Frank
de94a808d0 [Feature] Flag End Biomes as ignored for picking 2022-07-03 23:29:31 +02:00
Frank
faa347c5bb Seed changes are not supported 2022-07-03 22:25:55 +02:00
Frank
bb928d5feb [Fix] RandomSource was not Thread-Safe (quiqueck/BetterEnd#29) 2022-07-03 22:21:48 +02:00
Frank
44a008bcf0 Some renaming 2022-07-03 22:15:54 +02:00
Frank
9b537683ea Changed wording 2022-07-03 22:15:12 +02:00
Frank
5bf8c4d669 [Fix] unbound biomes are valid 2022-07-03 22:14:54 +02:00
Frank
1186e962f6 [Fix] End Biomes without type are ignored (quiqueck/BetterEnd#29) 2022-07-03 21:57:41 +02:00
Frank
7d3ef6c756 Make sure default level-type in server-properties is betterx 2022-07-03 19:52:00 +02:00
Frank
e670fea21a Allow server to disable the forced preset override 2022-07-03 19:51:18 +02:00
Frank
910314a1e8 Make sure MainMixin is called early 2022-07-03 19:50:24 +02:00
Frank
f80c12f796 Removed deprecated config values 2022-07-03 19:50:05 +02:00
Frank
3be3fdd2ea No local var 2022-07-03 19:49:22 +02:00
Frank
f0fdcdba05 Removed debug logging 2022-07-03 15:08:43 +02:00
Frank
82023316c6 Added forgotten rebuild 2022-07-03 15:01:29 +02:00
Frank
c042d00c23 [Features] End Source has fallbacks if barrens/voids or center Biomes are missing 2022-07-03 14:57:19 +02:00
Frank
e0f8cb24b6 [Fix] Endless chung generation if picker is empty 2022-07-03 14:52:20 +02:00
Frank
25948c7c6b restored getBiome() name for backward compatibility 2022-07-03 13:42:15 +02:00
Frank
b3c58b42e9 [Fix] Dimension height is not updated for repaired BiomeSources (quiqueck/BetterEnd#28) 2022-07-03 13:36:24 +02:00
Frank
0eb857e293 Reformated 2022-07-03 12:55:27 +02:00
Frank
102db44595 [Change] Added old End Biome-tag 2022-07-03 12:54:42 +02:00
Frank
3e81071b96 [Change] BCLBiome is no longer a wrapper around a Biome-Instance but references the Biome by ResourceKey
[Fix] Crash with Promenade (#15)
2022-07-03 12:42:30 +02:00
Frank
89737d5f6c [Fix] End BiomeSource would contain exlcuded biomes in the possibleBiomes list 2022-07-02 20:16:48 +02:00
Frank
2bfb7a0687 [Fix] getExcludeMatching just returns Biomes that have the exact type 2022-07-02 20:16:17 +02:00
Frank
204f60174b Version Update 2022-07-02 20:07:05 +02:00
Frank
c9baae0724 Fixed JavaDoc 2022-07-02 20:06:10 +02:00
Frank
85f1d7b1e4 [Change] Moved Nether Config Settings to BiomeSource-Config 2022-07-02 20:04:31 +02:00
Frank
d6cf5a6cd9 More frequent cash dumps 2022-07-02 19:40:09 +02:00
Frank
a3aae4c016 Corrected defult values 2022-07-02 19:39:25 +02:00
Frank
600bb6f9ac [Fix] Crash when erroding 2022-07-02 19:38:45 +02:00
Frank
e35fe997c1 [Change] Change handling of End Biomes once more. (EndSource will consider Barrens and MainIsland biomes as well now) 2022-07-02 14:33:52 +02:00
Frank
e411dc10d9 [Fix] Edge Biomes are never selected when only a single biome is available 2022-07-02 14:33:00 +02:00
Frank
90d0c191f5 Version update 2022-07-01 22:29:21 +02:00
Frank
c5c61a11fd [Fix] Handling of vanilla/fabric End Biomes 2022-07-01 20:00:46 +02:00
Frank
aaba0cec25 Changed visibility 2022-07-01 16:54:02 +02:00
Frank
b1e21b097b Some Cleanup 2022-07-01 12:49:35 +02:00
Frank
9e3dbbec76 Some Cleanup 2022-07-01 12:49:22 +02:00
Frank
b045627030 Disabled loging 2022-07-01 02:23:30 +02:00
Frank
598d8f790b Find actual cover block state 2022-07-01 02:20:22 +02:00
Frank
0e0b7a5f51 [Feature] Ability to erode Template Structures 2022-07-01 01:42:03 +02:00
Frank
65e9140812 Version Update 2022-07-01 00:10:26 +02:00
Frank
2f6091f9d3 Version Update 2022-06-30 23:46:14 +02:00
Frank
f83648a099 [Change] RandomSpreadStructurePlacement salt calculation 2022-06-30 23:37:41 +02:00
Frank
ae5b3e1417 [Change] Improved Placement of Template Structure 2022-06-30 23:30:55 +02:00
Frank
37a26f25bb [Change] Basalt no longer counts as terrain 2022-06-30 23:15:26 +02:00
Frank
44a65e8bae [Change] Wall Search can end inside wall 2022-06-30 19:50:46 +02:00
Frank
e3b3ea7872 [Fix] Placing Facing Blocks 2022-06-30 19:50:24 +02:00
Frank
3f04b85565 [Feature] Parameter to change fog density 2022-06-30 18:28:16 +02:00
Frank
1de4db3cde [Change] Refactored Plant API 2022-06-30 17:51:44 +02:00
Frank
d5cf63427b [Fix] Barrel Plays closing sound early (/quiqueck/BetterEnd#20, paulevsGitch/EdenRing#41) 2022-06-30 14:51:10 +02:00
Frank
59756e6dca Better Abstraction from internal FAPI (#2, FabricMC/fabric#2369) 2022-06-30 13:53:19 +02:00
Frank
185453b0c4 Use key-based detection of End-Biomes (#11) 2022-06-30 13:52:34 +02:00
Frank
473ed9a165 [Fix] Disabled Biomes can crash BiomePicker (#7) 2022-06-30 11:46:05 +02:00
Frank
600aa50212 [Fix] Hammer in first slot gets consumed by Recipe (BetterEnd/issues#17) 2022-06-30 11:23:24 +02:00
Frank
4c54e6de31 Version Update 2022-06-30 09:53:14 +02:00
Frank
dd27373063 [Change] WorldPreset automatically sets DEFAULT to first registered Preset 2022-06-30 09:52:04 +02:00
Frank
e70823a587 [Fix] Use of non-ThreadSafe Random source (quiqueck/BetterEnd#15) 2022-06-30 09:38:20 +02:00
Frank
a6218a1da3 Latest FAPI 2022-06-30 09:19:55 +02:00
Frank
278d8c6201 [Fix] Make PresetEditorMixin a clientside Mixin (#8) 2022-06-30 08:56:54 +02:00
Frank
8850752f25 Dependency Example 2022-06-30 08:18:45 +02:00
Frank
74e465a68c Removed unneeded Mixin 2022-06-30 08:13:30 +02:00
Frank
b37399523a [Fix] Boookshelf not recognized 2022-06-30 08:04:55 +02:00
Frank
4367babec5 Marked Deprecated API 2022-06-29 00:54:56 +02:00
Frank
5d85595c5a More Feature Refactoring 2022-06-28 23:19:03 +02:00
Frank
c1d5ca7b9b Feature Builder for Random Feature Select 2022-06-27 01:04:57 +02:00
Frank
717f220af1 Mycelium Predicat 2022-06-26 11:49:42 +02:00
Frank
9d6bf741e3 Removed duplicate initialization of Tags 2022-06-25 20:57:15 +02:00
Frank
52e1db4551 Fixed Type for ITEMS TagRegistry 2022-06-25 20:54:35 +02:00
Frank
08d155c4d3 Fixed annotations for Named Tags 2022-06-25 20:54:21 +02:00
Frank
35918240f9 [Change] Disabled feature sorting (appears to conflict with other mods) 2022-06-25 20:36:24 +02:00
Frank
4dc07f6567 New event for finalizing ChunkGenerators 2022-06-25 20:35:42 +02:00
Frank
14443761d7 Fixed missing import 2022-06-25 19:15:40 +02:00
Frank
2f4f71b83a [Fix] Game hangs if BiomePicker has no Biomes 2022-06-25 19:15:08 +02:00
Frank
f1afb99b38 Disabled Debug Code 2022-06-25 19:14:38 +02:00
Frank
f007bcfa31 New Configurations for PillarFeature 2022-06-25 11:49:49 +02:00
Frank
cb5852b841 [Fix] Incorrect Offset for BasePlantBlock 2022-06-25 11:22:07 +02:00
Frank
decaf4af7a Additions for BetterNether 2022-06-25 02:02:58 +02:00
Frank
4307c11aab [Fix] Search for Vertical surfaces stays in Chunk (quiqueck/BetterNether#6) 2022-06-25 01:25:32 +02:00
Frank
eb2d25ce11 [Fix] Wrong method name for terrainAdjustments 2022-06-25 01:24:22 +02:00
Frank
f07a651279 Debug Helpers 2022-06-25 01:24:03 +02:00
Frank
6b2c997baf Updated Flooded Deltas 2022-06-24 22:23:26 +02:00
Frank
066153bbab Fixed Build for 1.19 2022-06-24 20:46:05 +02:00
quiqueck
4d6072ed36 Version Updates 2022-06-24 20:37:24 +02:00
quiqueck
cdbc6a6fcb Updated in preparation for 1.19.1 2022-06-24 20:36:56 +02:00
Frank
fcb91b25e0 Additional Accessor 2022-06-24 10:59:07 +02:00
Frank
03a94d544d [Fix] Crash when world preset is loaded before registries were ready 2022-06-24 03:10:40 +02:00
Frank
a064f2d6bb Upgrade older world presets 2022-06-24 03:04:35 +02:00
Frank
427d6dbdfd Disabled test Biomes 2022-06-24 02:15:55 +02:00
Frank
fb19ebaae6 Removed WorldPresetSettings 2022-06-24 01:56:25 +02:00
Frank
877691011f [Change] WorldPresets are configured through BiomeSources 2022-06-23 23:48:36 +02:00
Frank
2a03b46a98 Load WorldPresets from Dataset when world is created in UI 2022-06-22 13:40:24 +02:00
Frank
ec764f99e6 Tweaks to Surface Rule application 2022-06-22 01:09:58 +02:00
Frank
e1883409fa Change Surface Rules later to catch Biomes added by FAPI 2022-06-22 00:54:15 +02:00
Frank
fc280cac4c Minor Log-output change 2022-06-22 00:13:47 +02:00
Frank
524f274b18 Removed DEBUG file 2022-06-22 00:02:25 +02:00
Frank
cc36c090a1 [Changes] Cleanup of old SurfaceRule code 2022-06-22 00:00:38 +02:00
Frank
da1d4c6b40 [Feature] New Registry for Surface Rules 2022-06-21 20:41:26 +02:00
Frank
3725fbb029 Replaced deprecated method 2022-06-21 19:36:11 +02:00
Frank
c2e5096056 Moved Tag API to version directory 2022-06-21 19:26:14 +02:00
Frank
ecb0f6fb99 Smaller Changes to new API 2022-06-21 19:24:56 +02:00
Frank
70b01dc00e [fix] Make sure we can have large distances 2022-06-21 18:44:05 +02:00
Frank
057e6de3de Re-Added carelessly removed function 2022-06-21 18:43:30 +02:00
Frank
184f3a6448 [Change] Moved and refactored Tag-API 2022-06-21 18:19:10 +02:00
Frank
25fa53541f [Change] Separated all World-Load related code to a new namespace 2022-06-21 16:42:46 +02:00
Frank
67d09676c4 Changed Default 2022-06-20 02:02:55 +02:00
Frank
78c85b5b8b Void balancing 2022-06-20 01:54:06 +02:00
Frank
a651cb932e [Feature] Setting to disable void Biomes 2022-06-20 01:40:46 +02:00
Frank
def4fc3566 [Feature] Auto Detect End-Datapacks and disable custom Terrain Generation 2022-06-20 01:06:50 +02:00
Frank
c93a942eee Add vanillas Dragon-Immune-Tag 2022-06-19 23:31:41 +02:00
Frank
586485fe48 Correct handling of WorldPreset Settings from UI 2022-06-19 23:25:44 +02:00
Frank
73cd08fa69 Non default Terrain Heights for EndBiomeSource 2022-06-19 22:06:25 +02:00
Frank
35b968eb93 Removed unneeded call 2022-06-19 20:45:23 +02:00
Frank
c74b7b2d28 WorldPreset serialization and better structure for BiomeGenerator builder; 2022-06-19 19:59:29 +02:00
Frank
d63d773bb8 New Feature Types for builder 2022-06-19 01:08:15 +02:00
Frank
8a41656182 Started Magma Land Migration 2022-06-18 20:30:51 +02:00
Frank
a1d6de908c Enhanced new Feature Builders. 2022-06-18 17:42:31 +02:00
Frank
c4875b992a Some more changes to placements 2022-06-17 23:32:19 +02:00
Frank
d3b6fa7d80 Version Update 2022-06-16 15:17:59 +02:00
Frank
093f3bb21a Version Update 2022-06-16 15:14:22 +02:00
Frank
f72081620a Changed handling of vanilla/fabric end biomes 2022-06-14 14:29:50 +02:00
Frank
7c810cd3a7 [Fix] Crashes with FABI 0.56 (#4) 2022-06-14 11:58:41 +02:00
Frank
90cfcaa6c4 Latest FAPI 2022-06-14 11:36:56 +02:00
Frank
c6d7050651 Minor Changes 2022-06-12 03:12:09 +02:00
Frank
e850173d9e [Change] Removed some use of internal FAPI Biome API (#2) 2022-06-12 00:05:48 +02:00
Frank
a83117b8a9 Clean up Biomeset 2022-06-11 20:09:45 +02:00
Frank
2253913c7c More Debug output 2022-06-11 19:52:30 +02:00
Frank
f6d0cc7a38 Make sure null Biomes do not crash 2022-06-11 19:51:01 +02:00
Frank
04fe6d33d7 Fixed endless recursion :( 2022-06-10 23:09:16 +02:00
Frank
b9ee21085b [Feature] Generalized Elytra API to simplify Trinkets integration 2022-06-10 18:24:09 +02:00
Frank
104b87d874 Version Update 2022-06-10 17:26:34 +02:00
Frank
59529c6c94 [Feature] Speed modified Elytra API 2022-06-10 15:10:19 +02:00
Frank
aff1b66e19 better way to check for terrablender 2022-06-10 13:25:17 +02:00
Frank
db25e8e970 Version Updates 2022-06-10 03:52:00 +02:00
Frank
144422cfbc Version update 2022-06-10 03:40:45 +02:00
Frank
bb08368511 [Fix] Biomes from Terrablender have no features 2022-06-10 03:40:04 +02:00
Frank
e49323be93 [Fix] No Features when Terrablender is present 2022-06-10 03:23:01 +02:00
Frank
31ec9c58f2 [Fix] BiomeSource repair with Datapack Biomes does only work for Nether 2022-06-10 01:27:51 +02:00
Frank
e3c7b00758 [Feature] Ensure consisten feature sorting for Biomes from other sources. 2022-06-10 01:23:35 +02:00
Frank
c429ee3a38 [Feature] API to modify speed of DiggerItems 2022-06-09 17:17:12 +02:00
Frank
c346e1df65 Removed Dummy POI 2022-06-09 01:29:14 +02:00
Frank
8eddf9f615 New FAPI Version 2022-06-09 01:28:59 +02:00
Frank
852e5a6abc Reformated 2022-06-08 20:57:21 +02:00
Frank
079b51e3f6 Smaller Layout Change 2022-06-08 20:42:54 +02:00
Frank
722343c103 [Fix] Empty Tags are not created. This also invalidates other dependant tags 2022-06-08 20:33:54 +02:00
Frank
562a1d228a Changed internal file names 2022-06-08 19:59:59 +02:00
Frank
eb3e408613 Version Updated 2022-06-08 19:59:09 +02:00
Frank
393dbb5114 [Feature] WorldDataAPI will keep the previously saved version around. 2022-06-08 19:58:54 +02:00
Frank
47935ccdf5 [Fix] Respect force_include.end_land_biomes 2022-06-08 19:34:50 +02:00
Frank
bd46ba7fb6 [Fix] End Cave Biomes did Spawn on the Surface as regular Biomes 2022-06-08 19:32:20 +02:00
Frank
0c7f2a5b4d Refactored setBiome for 1.19 2022-06-08 19:31:46 +02:00
Frank
1e56418d26 [Fix] World Data API did not attach Mods 2022-06-08 18:53:13 +02:00
Frank
f06bf41fa4 New Attempt at poi API 2022-06-08 17:23:15 +02:00
Frank
74f4708196 [Fix] Reduced Radius for Eternal Portal Search 2022-06-08 16:41:45 +02:00
Frank
c87175159b Merge remote-tracking branch 'upstream/1.19' into 1.19 2022-06-08 14:30:12 +02:00
Frank
b731ba6598 Use POI to find EternalPortalFrame 2022-06-08 14:29:49 +02:00
Frank
8227cf2f6a Updated ParticleType API 2022-06-08 11:57:48 +02:00
Frank
73b20f88c5 BCLib Supported ParticleType handling 2022-06-08 11:34:51 +02:00
Frank
a7ba94a07c Fixed Button Collision (quiqueck/BetterNether#2) 2022-06-08 10:17:59 +02:00
Frank
4d152c596e Disabled REI as it crashes with release 1.19 2022-06-08 01:26:32 +02:00
Frank
29d99fecfe
Update README.md 2022-06-07 23:41:13 +02:00
Frank
af3fca0c41 Updated README.md 2022-06-07 23:30:28 +02:00
Frank
223d35febe Moved Featrues and Included Sculk in TagLists 2022-06-07 23:21:08 +02:00
Frank
261099babd Fixed Bartering LootTable 2022-06-07 22:20:54 +02:00
Frank
f7b4c8a618 ModMenu Version update 2022-06-07 21:26:49 +02:00
Frank
db27e14429 FAPI Version update 2022-06-07 21:24:30 +02:00
Frank
72a707f456 Updated all feature based sapling Growing 2022-06-07 21:23:02 +02:00
Frank
7285ada6d6 Updated Sapling Growing Code 2022-06-07 18:04:28 +02:00
Frank
55d243bf13 Better Sapling Growing Code 2022-06-07 17:46:13 +02:00
Frank
5ebf159965 Using public API for SemanticVersion (paulevsGitch/BCLib#146) 2022-06-07 16:47:25 +02:00
Frank
f6d5f85ec1 Refactored BCLib Package Structure 2022-06-07 16:44:14 +02:00
Frank
c658a24750 Updated Issue Template 2022-06-07 14:46:42 +02:00
Frank
37fbf7eafe
Update README.md 2022-06-07 10:26:14 +02:00
Frank
2ac472f716 UpsideDownForest 2022-06-07 02:39:12 +02:00
Frank
56e63f3ce2 Old Biomes 2022-06-06 23:34:25 +02:00
Frank
505006ed17 NetherSwampland 2022-06-06 15:56:09 +02:00
Frank
b07aec8bb6 Mor COnverted Biomes 2022-06-06 14:55:37 +02:00
Frank
e52230e7e3 Prepared EYE 2022-06-06 03:46:22 +02:00
Frank
0bcfa65f0c Allow BlockStateProviders in ScatterFeature 2022-06-06 03:35:07 +02:00
Frank
c9daf3c5d3 NetherMushroomForest 2022-06-06 02:43:06 +02:00
Frank
f0d1f1e453 Use levels registry access for dump 2022-06-06 01:02:46 +02:00
Frank
db0c89e7e7 Flooded Deltas 2022-06-06 01:00:50 +02:00
Frank
f94a8f158f Debug Commands for BCLib 2022-06-06 00:25:50 +02:00
Frank
97a65a3707 Refactoring and CrimsonPinewood 2022-06-05 17:31:03 +02:00
Frank
90921451dd Features for Crimson Glowing Woods 2022-06-04 14:05:02 +02:00
Frank
4f4ac722b1 Fixed Concurrency Issue 2022-06-04 13:55:49 +02:00
Frank
9496de1438 Updated to RC2 2022-06-04 11:39:27 +02:00
Frank
e2b7f6849a Migrated SoulPlains, MagmaLand and GravelDesert to new Feature style 2022-06-04 02:52:18 +02:00
Frank
c14928ea80 New combined Feature Strategy and Features for BoneReef 2022-06-04 01:43:25 +02:00
Frank
4a95927e1c Features for GravelDesert 2022-06-03 17:30:12 +02:00
Frank
0149b17a1a Fixed ScatterFeature 2022-06-03 16:40:17 +02:00
Frank
51bd560fa0 Merge branch '1.19' of github.com:quiqueck/BCLib into 1.19 2022-06-03 15:38:35 +02:00
Frank
51a0e7cf74 Use BlocksHelper for Terrain 2022-06-03 15:38:00 +02:00
Frank
e7486c8aa4 More Feature Updates 2022-06-03 12:37:34 +02:00
Frank
f82d8b6178 Added Warp Cap Feature 2022-06-03 11:31:56 +02:00
Frank
3a61c4cb86 Code that allows to dump registries to json 2022-06-03 10:15:13 +02:00
Frank
49edee32a8 Moved Classes 2022-06-03 01:34:49 +02:00
Frank
35ce65674b More Placement Balancing 2022-06-03 01:26:24 +02:00
Frank
7b9936af05 Some Feature related fixes 2022-06-03 00:08:26 +02:00
Frank
6adf6486ac More Features and Placement Modifiers 2022-06-02 17:34:20 +02:00
Frank
aa4133fad2 Some placement filters 2022-06-02 11:17:07 +02:00
Frank
4321017c25 Moved Classes 2022-06-02 07:55:43 +02:00
Frank
e05801277e More feature placement options 2022-05-31 19:27:23 +02:00
Frank
3512f82775 Some changes to TemplateFeatures 2022-05-31 18:12:19 +02:00
Frank
2623a817d0 Added StructureBuilder 2022-05-31 17:39:46 +02:00
Frank
359ad3c46b Fixed minor crash 2022-05-31 16:33:16 +02:00
Frank
aede1b6f2b Converted Default Structures 2022-05-31 16:24:50 +02:00
Frank
38f337cd15 Template Structures (but missaligned) 2022-05-31 13:52:08 +02:00
Frank
86a92c560b Make sure biomes are sorted 2022-05-31 09:39:28 +02:00
Frank
9ac0d78d30 Started TemplateStructure class 2022-05-31 00:10:46 +02:00
Frank
95434107ec NBT Feature Handling 2022-05-30 22:37:29 +02:00
Frank
155d3663df Allow Tags to Contain other Tags 2022-05-30 12:18:26 +02:00
Frank
5272861140 Stalagmite Placement 2022-05-29 23:25:54 +02:00
Frank
abe18ae923 Started Feature Migration in BN 2022-05-28 15:06:50 +02:00
Frank
2168787ac2 Store registryaccess Object 2022-05-27 18:20:21 +02:00
Frank
3e41a4630d Make sure BiomeAPI is initialized with the correct registry 2022-05-27 18:12:43 +02:00
Frank
5d970a27ba New bonemeal API (consumers, allows to add any plant/structure) 2022-05-27 17:13:59 +02:00
Frank
d985792cae Skip biomes that are missing in registry (paulevsGitch/BCLib#145) 2022-05-27 17:09:36 +02:00
Frank
50fe9c2342 Small post-init change/fix 2022-05-27 17:07:11 +02:00
Frank
bc565577cb Recipe sorting enhancement (paulevsGitch/BCLib#144) 2022-05-27 17:04:52 +02:00
Frank
3a514f7a78 Tag handling in BiomeBuilder 2022-05-27 17:01:23 +02:00
Frank
f70db44ffd Moved Biome classes 2022-05-27 16:34:32 +02:00
Frank
51ec0596da Added fabric Test Biome 2022-05-27 16:17:17 +02:00
Frank
54b55be51a Small Changes to Surface/Biome API 2022-05-27 16:16:43 +02:00
Frank
b8b12623bf Changes for Biomes from Fabric API 2022-05-27 12:08:19 +02:00
Frank
6c10735874 Disabled Feature Sort (handled differently by vanilla now) 2022-05-27 01:18:49 +02:00
Frank
7dd6d2de2c Moved class 2022-05-27 01:09:29 +02:00
Frank
48afc2efc1 Moved class 2022-05-27 01:09:13 +02:00
Frank
9e4931e1af Make sure all dimensions get checked for new surface rules 2022-05-27 01:06:09 +02:00
Frank
bdf30109f6 Improved SurfaceRule Handling 2022-05-27 00:42:34 +02:00
Frank
e08d85b605 Fixed SignEdit Mixin 2022-05-26 23:54:45 +02:00
Frank
3bc2018333 Moved memeber 2022-05-25 14:03:04 +02:00
Frank
a64e437c3c Code clean 2022-05-25 14:02:03 +02:00
Frank
919e3c25c5 SurfaceRule handling 2022-05-25 14:00:24 +02:00
Frank
7a891d71ee Some fixes 2022-05-25 00:00:19 +02:00
Frank
9d0a640173 Datapack Biome Handling with Custom BiomeSource 2022-05-24 23:42:25 +02:00
Frank
e10994a1e8 Test Dataset 2022-05-24 14:41:20 +02:00
Frank
f0f6d7026a Updated Comment 2022-05-23 01:27:23 +02:00
Frank
f4f2e85432 Create new Generator with settings from DataPack 2022-05-23 01:25:32 +02:00
Frank
781d6d4709 Started DataPack Handling 2022-05-23 01:01:48 +02:00
Frank
dd0c259b56 Functional WorldSetupScreen 2022-05-22 22:44:09 +02:00
Frank
faa20c608e Layout for World customization Screen 2022-05-22 18:40:50 +02:00
Frank
f45eaddde6 Prepared World customization Screen 2022-05-22 17:59:37 +02:00
Frank
7359c64fff Correct World Preset handling 2022-05-22 17:49:46 +02:00
Frank
7466048a22 Minor Update 2022-05-22 12:11:31 +02:00
Frank
0a5a608b7d Introduce BCLChunkGenerator class 2022-05-22 03:26:03 +02:00
Frank
ab0895d48c Make Sure BCLibs Default WorldPreset is applied by default for fresh Clients/Servers 2022-05-21 17:51:49 +02:00
Frank
9f8409ebe0 Sort Custom Presets to Front 2022-05-20 01:59:30 +02:00
Frank
fdc068f6bb Prepared Custom WorldPreset UI 2022-05-20 00:54:18 +02:00
Frank
375eb09bb9 Moved FlatLevelPresets to BCLib and started WorldPresets 2022-05-19 23:32:27 +02:00
Frank
e8dbbffa8e Addressed multiple ToDo's 2022-05-19 23:00:17 +02:00
Frank
c6742982df Fixes for 1.19-pre1 2022-05-19 21:11:51 +02:00
Frank
d533f93113 Added c:is_obsidian Tag for ObsidianBreaker (paulevsGitch/BetterNether#550) 2022-05-19 00:20:46 +02:00
Frank
47b537f65d Small Cleanup 2022-05-19 00:09:46 +02:00
Frank
ace65fa671 Fixed Grup 2022-05-19 00:01:23 +02:00
Frank
3ee10482ab Reorganized Imports/Packages 2022-05-18 23:56:23 +02:00
Frank
cb9459f176 Simplified TagAPI due to changes in 1.18.2 2022-05-18 22:25:38 +02:00
Frank
b84a5a1b5d Fixed Generation Bugs 2022-05-17 22:11:16 +02:00
Frank
5ff00cede0 Fixed some boot-crashes 2022-05-17 21:03:10 +02:00
Frank
558bd7d326 Cave related changes 2022-05-17 20:17:19 +02:00
Frank
a57845f96b Compile-Fixes in BetterEnd 2022-05-17 18:09:43 +02:00
Frank
b6321cec93 Add callback that allows us to modify Biome tags 2022-05-17 02:25:51 +02:00
Frank
aaa0acdd09 Keep Track of Dimension 2022-05-17 01:08:34 +02:00
Frank
45b014cd3c Changed management of actualBiome 2022-05-17 00:32:41 +02:00
Frank
38b8883b6a Re-Added seed in serialization 2022-05-13 20:52:02 +02:00
Frank
dc7a875679 Some simplification for Structure Codec handling 2022-05-13 20:51:42 +02:00
Frank
b7a74e971b Adapted StructureAPI for 1.19 2022-05-13 19:58:15 +02:00
Frank
c7117fa180 Fixed Bug in TextureAtlasMixin 2022-05-13 13:17:12 +02:00
Frank
cb7d8cf53d Changed TagAPI internals to be more flexible 2022-05-13 13:16:59 +02:00
Frank
d4c2909f17 Fixed all compile errors 2022-05-13 03:14:53 +02:00
Frank
fe4b426b5e Added Issue Template 2022-05-13 00:29:33 +02:00
Frank
374c85cb6d Changed Version 2022-05-13 00:29:18 +02:00
Frank
d07afd9b01 Fixed Startup Errors 2022-05-13 00:16:15 +02:00
Frank
05eac6f539 Fixed Compile Errors 2022-05-13 00:05:05 +02:00
Frank
4ba3a71a68 Some minor 1.19 migrations 2022-05-10 19:08:05 +02:00
Frank
50f623477f Version update 2022-05-10 18:43:40 +02:00
paulevsGitch
89440dd9d7 Small enhancements 2022-05-07 07:46:08 +03:00
paulevsGitch
b60d20196f Sign GUI rendering fix 2022-05-06 21:20:53 +03:00
paulevsGitch
0d6d24b748 Remove redundant recipes, books silk touch changes 2022-05-06 17:31:58 +03:00
paulevsGitch
470d4a6c1e Recipe search optimisation 2022-05-06 16:44:43 +03:00
Frank
6a5ba519c8 Fix for fireresistance in BaseSlab 2022-04-28 20:53:44 +02:00
Frank
819039d50d Added fireresistantce to ruby/cincinnasite armor and ruby blocks (#paulevsGitch/BetterNether/issues/531) 2022-04-28 20:25:22 +02:00
paulevsGitch
97d2e6d146 Small mixin fix 2022-04-27 13:15:11 +03:00
paulevsGitch
ab87100187 Removed most redirects, moved Enchantment Table mixin, cleanup 2022-04-27 13:07:28 +03:00
paulevsGitch
be2e206383 Updated version 2022-04-08 20:59:50 +03:00
paulevsGitch
ff7eda1287
Merge pull request #126 from paulevsGitch/1.18.2
1.18.2
2022-04-08 20:55:01 +03:00
Frank
ea999a9ba5 Fixed initial runtime crashes in BetterEnd 2022-04-08 18:17:14 +02:00
Frank
c057ca3e99 Fixed Disc Registry 2022-04-08 17:59:06 +02:00
Frank
dee3e89b3b Changed Hammer Tool handling 2022-04-08 17:32:13 +02:00
Frank
a24e03e411 Updated Fabric-API 2022-04-08 16:42:28 +02:00
Frank
a52f46a933 Generalized Version again... 2022-04-08 16:38:28 +02:00
Frank
cdb7b305d9 Possible fix for anvil crash (#116) 2022-04-08 16:38:13 +02:00
Frank
4b550b9e69 Biome, Structure and Feature fixes 2022-04-03 23:45:19 +02:00
Frank
0e1627958a More Tag Related Changes 2022-04-03 22:51:05 +02:00
paulevsGitch
1fd01a5113
Merge pull request #119 from Slarper/main
Fixed potential charset bug
2022-03-26 16:36:47 +03:00
Slarper
8f0b52a1fe
Merge branch 'paulevsGitch:main' into main 2022-03-26 11:52:34 +08:00
SpaceTeleport
0568510a88 Fixed potential charset bug again.
These may cause annoying garbled characters in the future so that I fixed them as bugs.
2022-03-26 11:51:30 +08:00
paulevsGitch
57fdcafb6f
Merge pull request #118 from Slarper/main
fixed potential decoding bug.
2022-03-26 03:45:28 +03:00
SpaceTeleport
7dc316e7e4 fixed potential charset bug. 2022-03-26 00:55:26 +08:00
Frank
ecc792ed9a Version fixes 2022-03-21 18:14:28 +01:00
Frank
c783d007b1 Fixed MultiPackResourceManagerMixin 2022-03-18 20:44:50 +01:00
Frank
12a8b0c23a Fixed TollTip Layout 2022-03-18 20:20:44 +01:00
Frank
5a30e60d31 Added Tooltips that tell players what type of ground they can place vegetation on. 2022-03-18 19:18:09 +01:00
Frank
a9c9a7359b Confirmed some fixes related to BN 2022-03-18 16:03:45 +01:00
Frank
61f9854cd5 Some mining/drop related fixes 2022-03-18 15:34:18 +01:00
Frank
a2acd50658 Some cleanup 2022-03-18 14:44:39 +01:00
Frank
5f17127ba5 New Interfaces to handle MineableTags for Blocks 2022-03-18 14:44:27 +01:00
Frank
7e981ca1d9 Fixes for SurfaceRules and Feature code cleanup 2022-03-18 01:40:17 +01:00
Frank
4f7d0939e3 Fixes BetterNether runtime crashes 2022-03-17 23:52:46 +01:00
Frank
443042c6ef Fixes for Nether Features, Structures and Commands 2022-03-17 22:05:45 +01:00
Frank
d817a69ae7 Fixes for Entities, Structures and Features 2022-03-17 21:20:15 +01:00
Frank
6bb218416f Versions and Dependencies for 1.18.2 2022-03-17 17:47:00 +01:00
Frank
03eda716fe Added BiomeTag handling 2022-03-17 17:40:28 +01:00
Frank
7cb0d5de6c Fixed BiomeSource registration error 2022-03-17 17:26:26 +01:00
Frank
e2c4f91f58 Fixed mixin errors 2022-03-17 17:19:26 +01:00
Frank
824e78abf7 Fixed compiler errors 2022-03-17 17:05:59 +01:00
Frank
6c015a9a53 More BiomeSource related fixes 2022-03-14 17:23:57 +01:00
Frank
2eccb1cb9e Fixes to BiomeSources 2022-03-14 17:00:44 +01:00
Frank
ff70a2b1a8 Changes related to Biome-Holders and Features 2022-03-14 16:42:54 +01:00
Frank
86a3480ce0 Added first test code for new StructreFeature building 2022-03-08 22:02:24 +01:00
Frank
3c746a8dc4 Collected some info about new Feature System 2022-03-08 21:27:21 +01:00
Frank
305ac05ac1 Some more Changes for 1.18.2 2022-03-08 21:26:34 +01:00
Frank
2dbbfe04d8 *WIP:* Adapted TagAPI to new TagKey's 2022-03-08 11:47:32 +01:00
Frank
48db196c7b *WIP:* Started code migration 2022-03-07 22:09:57 +01:00
Frank
557e69080b Updated loom versions for 1.18.2 2022-03-07 20:23:57 +01:00
Frank
65eb31c3e9 Version update 2022-03-04 19:40:10 +01:00
Frank
8100e59baf Make sure TagProvider Interface is processed for items as well 2022-03-04 19:38:56 +01:00
Frank
31ea19552d Make sure Mixins have unique prefix 2022-03-03 13:11:13 +01:00
paulevsGitch
74a156389e Custom fog option (#74) 2022-02-27 20:04:35 +03:00
paulevsGitch
e013f80912 Hashmap fix (#102) 2022-02-17 17:28:41 +03:00
paulevsGitch
39d5f58f0c Static fix 2022-02-16 06:12:38 +03:00
paulevsGitch
de7e7eb4ce Version change 2022-02-15 20:57:19 +03:00
paulevsGitch
b46875599d Hex chunk multithread fix 2022-02-15 20:56:12 +03:00
paulevsGitch
bed87c7d8a Multithread hexmap 2022-02-15 15:03:50 +03:00
paulevsGitch
51e787c57e Hexmap chunk managment enhancements 2022-02-14 19:30:43 +03:00
paulevsGitch
a5c3c97630 Removed static positions from SDF 2022-02-14 15:11:34 +03:00
paulevsGitch
f756673128 Merge remote-tracking branch 'origin/main' 2022-02-14 03:08:12 +03:00
paulevsGitch
1d43176853 Restored proper version 2022-02-14 03:08:03 +03:00
paulevsGitch
29ef9f0eb6
Merge pull request #100 from Q2297045667/patch-1
Create zh_cn.json
2022-02-13 09:00:38 +03:00
Missing_Love
0f26699bc0
Create zh_cn.json 2022-02-13 13:45:09 +08:00
Frank
0c24a03b18 Verbatim Name for BCLib BiomeSources 2022-02-05 14:47:09 +01:00
Frank
7939c4ec2e Make sure mixin accepts all commandline options (#93) 2022-02-05 14:28:00 +01:00
Frank
a8fd496a8b Change order of instance check 2022-02-05 14:18:42 +01:00
Frank
b2ce0c155e Version update 2022-02-05 14:16:11 +01:00
Frank
0ed7fa9dcc Fixed crash for custom Noise Generators (#85) 2022-02-04 17:29:14 +01:00
Frank
513fea6968 Changed method name to make intent clear 2022-02-04 16:32:05 +01:00
Frank
c0ce7f6e35 Make sure Dimensions without BiomeModifications still get Features 2022-02-04 16:28:41 +01:00
Frank
aef34d30da Removed redundant code 2022-02-04 14:31:07 +01:00
Frank
009028624e Don't close file systems (otherwise game will crash) 2022-01-29 17:59:01 +01:00
Frank
89516870c9 Removed Debug output 2022-01-28 17:39:30 +01:00
Frank
96820cff84 Improved Thread Safety in BlocksHelper 2022-01-28 17:31:15 +01:00
Frank
0a438ae0e4 Removed debug code 2022-01-28 16:38:09 +01:00
Frank
7ce674b1f6 Improved Version handling for mod info exchange 2022-01-28 16:28:42 +01:00
Frank
a43c885db6 Dependency Updates 2022-01-27 16:00:14 +01:00
Frank
88dc5e5746 Moved class back to correct location 2022-01-27 15:04:37 +01:00
Frank
b3fba685b7 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2022-01-27 14:54:36 +01:00
Frank
726a73bfad Make sure we disable our custom rules if there are none 2022-01-27 14:54:26 +01:00
Frank
41260267c6 Added static method for leave drops 2022-01-27 14:54:14 +01:00
paulevsGitch
d802de90f9 Liquid fog fix (#81), Nether fog config 2022-01-27 14:49:07 +03:00
paulevsGitch
ebba0dde6c Allow enchantment table mixins skip target 2022-01-27 14:05:37 +03:00
Frank
89c5cd215b Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2022-01-26 17:56:59 +01:00
Frank
baf1e0f4b1 Cleanup and Speed Improvement of SurfaceRules/Structures (paulevsGitch/BetterNether#485, paulevsGitch/BetterEnd#420) 2022-01-26 17:56:54 +01:00
paulevsGitch
23df13bde8 Small shader changes 2022-01-24 10:24:37 +03:00
Frank
01f728c282 Removed missing MixIn 2022-01-23 22:30:57 +01:00
Frank
c3e2c74283 Added missing Tags 2022-01-23 22:28:29 +01:00
Frank
14fcb93e3b Refactored Tag Constants (Pt II) 2022-01-23 21:35:48 +01:00
Frank
fffbc6acc2 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2022-01-23 21:34:45 +01:00
Frank
acfeee7b9b Refactored Tag Constants 2022-01-23 21:34:35 +01:00
paulevsGitch
d5bc2de350
Merge pull request #79 from Sushomeister/patch-2
Create uk_ua.json
2022-01-22 17:01:49 +03:00
Sushomeister
db3ed18399
Create uk_ua.json 2022-01-22 15:49:34 +02:00
paulevsGitch
0ff7799785 Shovel API 2022-01-21 18:31:58 +03:00
Frank
ba89751ea8 Added convenience Function 2022-01-21 13:48:46 +01:00
paulevsGitch
db082ef1be Updated Tag API and tag constants 2022-01-21 14:56:37 +03:00
Frank
7ab5377a03 DataFixer Screen did no show up when experimental warning was disabled 2022-01-21 10:05:33 +01:00
Frank
fe5dd3e32e Make Codec IDs local (paulevsGitch/BetterNether#503) 2022-01-20 19:37:29 +01:00
Frank
b53ed9842d Mor work on Codecs for Surface Rules (paulevsGitch/BetterNether#503) 2022-01-20 18:10:15 +01:00
Frank
44517e0749 More Codecs 2022-01-20 03:36:39 +01:00
Frank
7b789ba57a Adding Codecs to Custom SurfaceRules (paulevsGitch/BetterNether#503) 2022-01-20 03:20:01 +01:00
Frank
d78319802d
Merge pull request #73 from quiqueck/feature/TagAPITypeSafety
Type Safety for `TagAPI`
2022-01-17 22:16:15 +01:00
Frank
03635584d6 Use Datapack Loot tables for BaseOrBlocks (paulevsGitch/BetterNether#486) 2022-01-17 22:14:31 +01:00
Frank
75338586ab Merge remote-tracking branch 'upstream/main' into feature/TagAPITypeSafety 2022-01-17 21:52:57 +01:00
Frank
611fb4d50c Ensure that step-features in a BiomeSource are in Sync with our Biome Modifications (paulevsGitch/BetterNether#489) 2022-01-17 21:19:46 +01:00
Frank
5aa06315d4 Fixed sapling drops for base leaves (paulevsGitch/BetterNether#496) 2022-01-17 19:36:38 +01:00
Frank
be3876f8a6 Check Shear Tags in MatchRules (paulevsGitch/BetterNether#497) 2022-01-17 14:47:00 +01:00
Frank
05ed11978d Refactored Biome Config handling (#63) 2022-01-17 14:06:28 +01:00
Frank
b0f6dd6bff Refactored Biome Config handling (#63) 2022-01-17 14:06:22 +01:00
Frank
11985395ae Type Safety for TagAPI 2022-01-17 11:11:08 +01:00
Frank
3584432a96 Added more named vanilla Tags 2022-01-17 10:01:01 +01:00
Frank
a62e3a5980 Added convenience Methods to TagAPI 2022-01-17 09:53:36 +01:00
Frank
49a47de5f8 Change StructureFeatures in WorldData 2022-01-17 00:30:34 +01:00
paulevsGitch
0594b9f420 More enhancements 2022-01-16 16:56:08 +03:00
paulevsGitch
7b96cfee10 Merge remote-tracking branch 'origin/main' 2022-01-16 16:35:48 +03:00
paulevsGitch
be64c38293 TagAPI enhancements 2022-01-16 16:34:15 +03:00
paulevsGitch
4426f74d62
Merge pull request #68 from Sushomeister/patch-1
Update ru_ru.json
2022-01-16 16:24:00 +03:00
paulevsGitch
dfd72b9f1d Removed overwrite 2022-01-16 16:18:14 +03:00
paulevsGitch
b9307183d7 Fixed carver leaking into other biomes 2022-01-14 21:00:38 +03:00
paulevsGitch
f2fd7ab55b REI fix 2022-01-13 19:16:49 +03:00
paulevsGitch
ec61972724 Enhanced Enchantment Menu and Table mixins (#72) 2022-01-13 18:51:59 +03:00
paulevsGitch
8ebcf2352a Added recipes into named map 2022-01-13 15:50:11 +03:00
paulevsGitch
95284640e0 Added recipes into named map 2022-01-13 14:29:09 +03:00
paulevsGitch
fa3e82ed4e Version update 2022-01-13 14:18:44 +03:00
Frank
e53e14eb0e Possible fix for missing Anvil/Furnace Receipes (paulevsGitch/BetterEnd#384, paulevsGitch/BetterEnd#389) 2022-01-11 17:20:54 +01:00
Sushomeister
128aed9a14
Update ru_ru.json 2022-01-08 17:07:31 +02:00
Sushomeister
76c33e9bdd
Update ru_ru.json 2022-01-08 17:06:41 +02:00
paulevsGitch
637a5a25b2 Key check 2022-01-05 04:54:17 +03:00
Frank
11c116618a Merge branch 'main' of github.com:paulevsGitch/BCLib into main 2022-01-03 18:52:20 +01:00
paulevsGitch
013e7d4aa6 Added TODO 2022-01-03 19:48:31 +03:00
paulevsGitch
83d2a578b3 Main config restored 2022-01-03 19:47:02 +03:00
Frank
2568319493 Moved fixBiomeSource back to MainConfig 2022-01-03 17:45:30 +01:00
paulevsGitch
6b156e579a Merge remote-tracking branch 'origin/main' 2022-01-03 19:40:16 +03:00
paulevsGitch
7dd8554aff Separate options for dimensions 2022-01-03 19:40:07 +03:00
Frank
83110b74a5 Pass through Player from Network Messages 2022-01-03 16:06:45 +01:00
Frank
68ca682324 Version Update 2022-01-03 16:06:41 +01:00
paulevsGitch
384fe3a327 Biome source fix enhancements 2022-01-03 07:13:39 +03:00
paulevsGitch
03968ccda3 Removed biome hashmap 2022-01-02 07:49:50 +03:00
paulevsGitch
63946e049d Biome modification fix 2021-12-30 11:44:15 +03:00
paulevsGitch
9622fd393c Merge remote-tracking branch 'origin/main' 2021-12-30 01:27:07 +03:00
paulevsGitch
f9325397ad Version change 2021-12-30 01:26:59 +03:00
paulevsGitch
287b411f88
Merge pull request #62 from mindy15963/patch-2
Update ko_kr.json
2021-12-28 05:45:32 +03:00
E. Kim
192d78e959
Update ko_kr.json 2021-12-27 18:50:08 +09:00
paulevsGitch
9737493069 Larger start fog distance 2021-12-23 21:08:18 +03:00
paulevsGitch
6c18b1e842 Feature fixes 2021-12-23 19:48:51 +03:00
paulevsGitch
6100912627 Small ore fix 2021-12-23 19:34:08 +03:00
paulevsGitch
93d29508a8 Removed more deprecated functions, clenup 2021-12-23 19:22:48 +03:00
paulevsGitch
9a9bf2c6de Removed deprecated functions 2021-12-23 19:21:19 +03:00
paulevsGitch
663b0ef062 Ore placement fix 2021-12-23 19:18:42 +03:00
paulevsGitch
1f246b7d7d Small ore fix 2021-12-23 16:42:47 +03:00
paulevsGitch
971806922d Fixes 2021-12-23 16:15:14 +03:00
paulevsGitch
b03dc50726 Duplicate function fix 2021-12-23 16:13:02 +03:00
paulevsGitch
75a0b3d7d2 Moved erosion code into BE 2021-12-23 10:11:12 +03:00
paulevsGitch
c947af1211 Fog rendering fixes 2021-12-23 09:23:26 +03:00
paulevsGitch
3cb78dd5a5 Small fix 2021-12-23 06:56:12 +03:00
paulevsGitch
8abf92eca7 Biome setters 2021-12-23 06:55:27 +03:00
paulevsGitch
fe8b20dcba Additional builder functions 2021-12-23 06:14:15 +03:00
paulevsGitch
6d279852f0 Terrain height setter 2021-12-23 05:54:31 +03:00
paulevsGitch
717c0d7fb8 Fixed duplicate feature registration X2 2021-12-23 05:10:10 +03:00
paulevsGitch
544af449ee Fixed duplicate feature registration 2021-12-23 04:56:18 +03:00
paulevsGitch
6abf34d8bc Version change 2021-12-23 04:38:46 +03:00
paulevsGitch
8bddd64216 Moved all common features 2021-12-21 21:28:09 +03:00
paulevsGitch
2d8b7fe70a Moved vegetation functions 2021-12-21 21:02:16 +03:00
paulevsGitch
9ae9c318bd Builder count modifiers 2021-12-21 20:49:33 +03:00
paulevsGitch
1150816af2 Merge remote-tracking branch 'origin/main' 2021-12-21 18:44:24 +03:00
paulevsGitch
3cac6cad3d Feature builder prototype 2021-12-21 18:44:17 +03:00
Frank
a27806620f Version update 2021-12-21 15:56:10 +01:00
Frank
c0d186fc9f Merge branch 'main' of github.com:paulevsGitch/BCLib into main 2021-12-21 15:47:06 +01:00
Frank
eb827a58bd Fix for broken surface rules when "experimental world dialog" was disabled 2021-12-21 15:46:58 +01:00
paulevsGitch
3e203d4e47 Small fixes 2021-12-21 17:33:58 +03:00
Frank
f17d7d9d1d Reduced loader depenendecy 2021-12-21 14:21:00 +01:00
Frank
cc202f7e35 Changed dependencies 2021-12-21 11:02:34 +01:00
paulevsGitch
5e88bd3c92 Small fixes 2021-12-20 06:19:09 +03:00
paulevsGitch
7549803896 Possible filter "fix" 2021-12-20 05:42:59 +03:00
paulevsGitch
8809151a8b Feature fixes and enhancements, version change 2021-12-20 04:52:07 +03:00
Frank
572ee609d5 Prepare some common Tags 2021-12-20 00:08:43 +01:00
Frank
a8bb325bfb Add default door tag 2021-12-20 00:02:27 +01:00
Frank
388226efa6 Version update 2021-12-19 22:55:31 +01:00
Frank
e8786eb939 Added air-exposure to ore API 2021-12-18 10:54:24 +01:00
Frank
7594b029c1 javadoc 2021-12-18 03:31:21 +01:00
Frank
e6d481387d More config refinement to ensure configs are not loaded multiple times with different defaults 2021-12-18 03:20:52 +01:00
Frank
f0c77abba6 StringArray in Configs 2021-12-18 03:10:15 +01:00
Frank
61cf9711a6 Minor Refactor 2021-12-18 02:48:49 +01:00
Frank
d738e8ae75 Possible fix for missing Biome configs 2021-12-18 01:31:47 +01:00
Frank
ba7489ee79 New config option to disable the experimental warning screen on level load 2021-12-18 00:51:59 +01:00
Frank
b60df41c6c Fix for crash when biomeSource is not initialized twice (paulevsGitch/BetterNether#468) 2021-12-18 00:51:54 +01:00
Frank
1bb15de385 include ModMenu only on compiler classpath 2021-12-16 17:43:56 +01:00
Frank
72728ffac4 Spelling 2021-12-16 12:42:59 +01:00
Frank
3d45114584 Additional config options 2021-12-16 12:42:21 +01:00
Frank
192a537939 Fixed ModMenu integration (#58) 2021-12-16 11:35:32 +01:00
Frank
08e400ae20 Added ModMenu as build dependency and refactored Integration 2021-12-16 11:34:08 +01:00
paulevsGitch
0d6874085d Merge remote-tracking branch 'origin/main' 2021-12-16 10:45:24 +03:00
paulevsGitch
704112d832 Biome edge in blocks 2021-12-16 10:43:57 +03:00
Frank
3e18eb839f Datapack friendly features 2021-12-16 00:01:27 +01:00
Frank
43f6d72dda Make sure features are applied to the correct biomes 2021-12-15 20:47:57 +01:00
Frank
67d835b59b Loader update 2021-12-15 16:02:34 +01:00
Frank
abd445114f Accept squared distances 2021-12-15 12:01:07 +01:00
Frank
5a0a6d4d3f Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-12-15 11:56:20 +01:00
Frank
81f6de2ac2 Changed priority for filler 2021-12-15 11:56:09 +01:00
paulevsGitch
538003b0c7 Version change 2021-12-15 13:41:25 +03:00
paulevsGitch
294d59a88b Merge remote-tracking branch 'origin/main' 2021-12-15 13:39:54 +03:00
paulevsGitch
75b8257085 Fixed end generator 2021-12-15 13:39:45 +03:00
Frank
79bce94121 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-12-15 11:35:07 +01:00
Frank
77801986b5 Change Interface for BE 2021-12-15 11:34:29 +01:00
paulevsGitch
d8de624fd1 Recipe fixes, replaced spaces with tabs 2021-12-15 13:08:15 +03:00
Frank
5ca6a92dd0 Updated loader 2021-12-14 13:45:40 +01:00
Frank
23a36a785b Degraded missing BiomeID to a warning 2021-12-14 13:45:21 +01:00
Frank
d14e1c3952 prevent duplicates in cache 2021-12-14 11:06:34 +01:00
Frank
0298ab131f Possible solution for missing features 2021-12-14 09:25:17 +01:00
Frank
0a3be15ff3 Update to surface builder 2021-12-14 00:10:25 +01:00
Frank
7f5e27397c Some changes to surface processing 2021-12-13 21:22:47 +01:00
Frank
4c41bb1764 New Switch Int-State for SurfaceRules 2021-12-13 18:14:23 +01:00
Frank
835f4895b3 Possible fix for missing SurfaceRules in Datapack worlds 2021-12-13 16:33:18 +01:00
Frank
ff49e1325c Fixed ModMenu Integration 2021-12-13 16:10:13 +01:00
Frank
3dc1470085 missing env key does not fail automatically 2021-12-13 12:07:44 +01:00
Frank
60ca138827 Removed debug output 2021-12-12 23:41:38 +01:00
Frank
f38288b2c6 New code to put StructureFetures into datapack configurations 2021-12-12 23:25:38 +01:00
paulevsGitch
dc355679f4 Remove debug string 2021-12-11 19:45:24 +03:00
paulevsGitch
6cb75406cb Vertical biomes distribution prototype 2021-12-11 19:33:53 +03:00
Frank
330530de73 Aded optional Biome repair Fixer 2021-12-10 16:46:02 +01:00
Frank
7439286ae2 Test another jitpack fix 2021-12-10 13:09:23 +01:00
Frank
e6166918fe Version update to 1.18.1 2021-12-10 12:47:55 +01:00
Frank
358c0099dd Prevent entities from spawning if disabled 2021-12-10 00:36:18 +01:00
Frank
5be62802e8 Store additional spawn-information along with an entity 2021-12-10 00:26:09 +01:00
Frank
7292c3cf3a topMaterial handling 2021-12-09 23:00:35 +01:00
Frank
38992bbf93 Updated fabric loader 2021-12-09 22:29:39 +01:00
Frank
32280dc499 SOrt available features before BiomeSOurce is initialized 2021-12-09 22:17:25 +01:00
paulevsGitch
b6bc8acf6b Vertical biomes size config 2021-12-09 12:31:56 +03:00
paulevsGitch
84d465c0f6 Vertical biomes config 2021-12-09 12:20:18 +03:00
paulevsGitch
2f680d4c13 Small format fix 2021-12-09 12:10:31 +03:00
paulevsGitch
9bcd3e6a56 Javadocs fix 2021-12-09 12:09:16 +03:00
paulevsGitch
9d0116b271 Layer distortion 2021-12-09 12:05:44 +03:00
paulevsGitch
595063bf99 MapStack fix 2021-12-09 12:01:53 +03:00
paulevsGitch
0868d8bd2b World height apply 2021-12-09 11:45:12 +03:00
paulevsGitch
60ee68d8d1 Map stack 2021-12-09 11:37:05 +03:00
paulevsGitch
9bde4c11b9 Fix crash with multitread random 2021-12-09 10:06:51 +03:00
Frank
8b89808634 Debug Code 2021-12-09 00:17:22 +01:00
Frank
8db15979a0 Ported old noise generator for double-block biomes 2021-12-09 00:12:13 +01:00
Frank
45a24d4f8e Allow BlockState to set surface 2021-12-08 23:59:26 +01:00
Frank
21368e4e83 Supporting under materials 2021-12-08 23:23:06 +01:00
Frank
e72d1bcff2 Added some helpers for surface blocks 2021-12-08 23:18:59 +01:00
Frank
46af6f07e2 Added simple class for custom noise in SurfaceRules 2021-12-08 19:49:55 +01:00
Frank
28a2ce53fd Added Biome validation check to BCLib 2021-12-08 17:27:47 +01:00
Frank
6e18b633de AccessWideners to enable the use of ConditionSource` 2021-12-08 17:27:05 +01:00
paulevsGitch
19b0cb94fc Fix of duplicating 2021-12-08 18:25:06 +03:00
paulevsGitch
8c7a11ee04 Prevent duplicate features 2021-12-08 17:56:43 +03:00
paulevsGitch
77cc26d548 Feature sorting changes 2021-12-08 17:21:52 +03:00
paulevsGitch
f817981e91 Feature sorting optimisation 2021-12-08 16:14:25 +03:00
Frank
e48e192970 Fixed broken merge 2021-12-08 10:44:37 +01:00
Frank
cf9072cb79 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-12-08 10:41:09 +01:00
Frank
51f40633bb changed import order? 2021-12-08 10:38:41 +01:00
Frank
a56269a971 make sure feature order is initialized befor first sort 2021-12-08 10:37:42 +01:00
paulevsGitch
dc5769f92b Merge remote-tracking branch 'origin/main' 2021-12-08 11:43:23 +03:00
paulevsGitch
fb3147c6c5 Modded feature sorting 2021-12-08 11:43:15 +03:00
Frank
bbde03c961 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-12-08 09:38:16 +01:00
Frank
9e115261fb Allow biomes without any entity spawns 2021-12-08 09:37:30 +01:00
paulevsGitch
e09085aa3b Always apply builders 2021-12-08 11:30:18 +03:00
paulevsGitch
ff94bb1a97 Small order fix 2021-12-07 21:19:01 +03:00
paulevsGitch
9ebc7e906f API init 2021-12-07 21:15:40 +03:00
paulevsGitch
cb16ea86b3 Feature sorting 2021-12-07 21:13:15 +03:00
paulevsGitch
ac279c29fc Restored old version (with small fix) 2021-12-07 21:01:06 +03:00
paulevsGitch
178bf6d270 Feature lists 2021-12-07 20:47:33 +03:00
Frank
2191733f37 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-12-07 18:10:18 +01:00
Frank
38800dd38b Minor changes for BetterEnd 2021-12-07 18:10:08 +01:00
paulevsGitch
b461fe83af Version change 2021-12-07 18:23:53 +03:00
paulevsGitch
1bd7b14785 Biome config fix 2021-12-07 18:23:23 +03:00
paulevsGitch
daf9bd4c9a Cleanup & small fix 2021-12-07 18:20:28 +03:00
paulevsGitch
6d0b9cbf63 Hex map improvements & fixes 2021-12-07 18:16:10 +03:00
Frank
b6875f2c11 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-12-07 03:13:50 +01:00
Frank
aa2f0f5e8a Additional fixes (paulevs/betternether#449) 2021-12-07 03:13:37 +01:00
Frank
0f66f11628 Code to fix spawner data in structure nbt's 2021-12-07 01:14:25 +01:00
paulevsGitch
2fcfd695f9 Javadocs 2021-12-06 08:15:40 +03:00
paulevsGitch
1f8fd77a46 Biome surface provider 2021-12-06 08:13:50 +03:00
Frank
6adbe5dd04 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-12-05 11:31:18 +01:00
paulevsGitch
5e7214ff79 Version change 2021-12-05 06:50:44 +03:00
Frank
a724f81091 Fixed MainMixin to manually parse options and start the DataFixer (#55) 2021-12-05 02:11:24 +01:00
Frank
16b5dc3822 Moved WorldPresetMixin to Client Mixins 2021-12-05 02:10:00 +01:00
Frank
49bd5d8f80 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-12-04 18:41:04 +01:00
Frank
30bc61a43a Minor cleanup 2021-12-04 14:08:54 +01:00
paulevsGitch
6b7996bdf4 Fixed wrong javadoc annotation 2021-12-04 15:44:23 +03:00
paulevsGitch
6907a02141 Merge remote-tracking branch 'origin/main' 2021-12-04 15:42:40 +03:00
paulevsGitch
34e984a8b3 Jitpack possible fix 2021-12-04 15:42:33 +03:00
Frank
1841bad98f Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-12-04 13:36:52 +01:00
Frank
99f55a9b85 More variety for ore placement 2021-12-04 13:36:27 +01:00
paulevsGitch
60251a7b79 Cleanup 2021-12-04 15:32:05 +03:00
paulevsGitch
d983613872 Small changes 2021-12-04 15:30:36 +03:00
paulevsGitch
c82e2900f6 Possible biome surface fix 2021-12-04 15:26:47 +03:00
Frank
3ceb8638d5 Fix for Surface Rules in BCLBiome 2021-12-04 13:15:18 +01:00
Frank
789982a266 Reverted ore chsange in the wrong place 2021-12-04 11:28:24 +01:00
Frank
c0b461d92b readded release build path 2021-12-04 11:25:29 +01:00
Frank
0a160541e2 Fixed build env 2021-12-04 11:24:01 +01:00
Frank
58f1fd8253 Ores are minable by pickaxe 2021-12-04 11:17:17 +01:00
paulevsGitch
0d8130cca7 Fixed javadoc 2021-12-04 12:28:54 +03:00
paulevsGitch
bcee0f2239
Merge pull request #54 from paulevsGitch/1.18
1.18
2021-12-04 12:18:47 +03:00
paulevsGitch
bf1b3917f4 Steep rules & cleanup 2021-12-04 12:15:46 +03:00
paulevsGitch
9609db9f9b Ceiling rules 2021-12-04 12:00:31 +03:00
paulevsGitch
3a06c128ed Surface rule builder fixes 2021-12-04 11:52:58 +03:00
paulevsGitch
8809fa7dbc Tag provider implementation for some blocks 2021-12-04 11:22:19 +03:00
paulevsGitch
7f17e1261c Tag Provider, list to array function, surface builder fix 2021-12-04 11:16:13 +03:00
paulevsGitch
f8eb65d600 Block constructors refactoring and fixes 2021-12-04 10:32:48 +03:00
paulevsGitch
3dacd0727f Block constructors refactoring 2021-12-04 05:46:16 +03:00
paulevsGitch
d6faafd4c0 Removed deprecated code, sapling refactor 2021-12-04 05:22:27 +03:00
paulevsGitch
1af5bf2e2d Javadoc & mixin order fixes 2021-12-03 20:55:19 +03:00
paulevsGitch
1a0cb36739 More rules 2021-12-03 20:54:01 +03:00
paulevsGitch
17ded1bbcc Cleanup, fixes 2021-12-03 20:42:28 +03:00
paulevsGitch
a5e6344cdd Surface rule builder prototype 2021-12-03 20:38:49 +03:00
paulevsGitch
6118dcb2cf Small fix 2021-12-03 20:12:55 +03:00
paulevsGitch
b14cae82ff Surface builder for two blocks 2021-12-03 20:12:18 +03:00
paulevsGitch
ae6c0e9aac Surface builder for two blocks 2021-12-03 20:10:15 +03:00
paulevsGitch
c7ce0b5547 Surface rules API 2021-12-03 18:16:37 +03:00
paulevsGitch
92dae621f1 Surface rule fixes 2021-12-03 17:34:35 +03:00
Frank
699332600b Tets application of surface rules 2021-12-03 15:10:33 +01:00
Frank
d09a3a06df added back surface-Block to BiomeBuilder 2021-12-03 14:59:35 +01:00
Frank
d89b0887c1 Merge branch '1.18' of github.com-quiqueck:paulevsGitch/BCLib into 1.18 2021-12-03 14:44:22 +01:00
Frank
77eba4b33f Minor cleanup for tool breaking behavior 2021-12-03 14:44:14 +01:00
paulevsGitch
113118afbd Small changes 2021-12-03 15:55:02 +03:00
paulevsGitch
d3273f609a Crash fix 2021-12-03 15:45:19 +03:00
paulevsGitch
8e3147f176 Rule source prototype 2021-12-03 15:39:08 +03:00
paulevsGitch
04a4f77e87 Merge remote-tracking branch 'origin/1.18' into 1.18 2021-12-03 14:46:08 +03:00
paulevsGitch
dea05bce0d Biome carvers modifications 2021-12-03 14:46:02 +03:00
Frank
5154513cd4 Use CollectionUtils 2021-12-03 12:34:39 +01:00
Frank
64c3d90458 Merge branch '1.18' of github.com-quiqueck:paulevsGitch/BCLib into 1.18 2021-12-03 12:28:41 +01:00
Frank
6b5348de88 Merge branch '1.18' of github.com-quiqueck:paulevsGitch/BCLib into 1.18 2021-12-03 12:28:37 +01:00
paulevsGitch
14451494ff Biome registry init changes 2021-12-03 14:26:12 +03:00
Frank
e470610294 WIP: No longer using Fabric BiomeModification API for features/structures/spawns 2021-12-03 12:22:00 +01:00
paulevsGitch
db07cd1887 BCL Biome feature adding optimisation 2021-12-03 13:54:07 +03:00
paulevsGitch
8234a1c9dc Biome feature modification fixes 2021-12-03 13:51:36 +03:00
paulevsGitch
eaba9bf698 Collections util 2021-12-03 13:49:42 +03:00
paulevsGitch
39fe678d39 Biome feature adding 2021-12-03 13:44:58 +03:00
Frank
26fa4bb3fd Reverted changes to BiomeModification API 2021-12-03 09:48:55 +01:00
Frank
fb79201b51 Make sure biomes are not modified twice 2021-12-03 03:58:53 +01:00
Frank
298aa47e7c Filter Biomes 2021-12-03 03:16:50 +01:00
Frank
7b64221b55 Different mixin for BiomeModification call 2021-12-03 03:03:10 +01:00
Frank
f5ee249bbb Adding Carvers 2021-12-03 01:54:00 +01:00
Frank
4ac4202555 Fixed ID handling 2021-12-03 01:40:50 +01:00
Frank
c6dc52a60d override for the nether biome generator that is used by BN when older worlds are loaded 2021-12-03 00:31:53 +01:00
Frank
39255e140f New lifecycle hook that is called before the level is loaded 2021-12-03 00:30:55 +01:00
Frank
cd2b4e481e New files on WorldDataAPIdid not get an initial version tag 2021-12-02 23:34:49 +01:00
Frank
81801b3df9 build return-type is actual type of Biome 2021-12-02 22:57:31 +01:00
Frank
750c98c177 Fixed mixin signature 2021-12-02 22:56:55 +01:00
paulevsGitch
97473004a8 Biome Builder structure features 2021-12-02 12:45:35 +03:00
paulevsGitch
1c3696fd02 Prevent random desync 2021-12-02 08:31:42 +03:00
paulevsGitch
31f61f3f37 Biome configuration fix 2021-12-02 05:23:21 +03:00
paulevsGitch
6c2c943b0d Biome generator fix 2021-12-02 04:49:56 +03:00
paulevsGitch
7daf9f614c Hex biome map fixes 2021-12-02 03:34:04 +03:00
paulevsGitch
0bfefa460f Possible hexmap fixes & tests 2021-12-02 01:56:26 +03:00
paulevsGitch
59d2874c1a Custom biome class init 2021-12-02 01:10:55 +03:00
Frank
9f9849d9b0 Make sure we do not crash out 2021-12-01 19:38:57 +01:00
Frank
3801c44aab let Builder add some vanilla features 2021-12-01 18:01:51 +01:00
paulevsGitch
801f9e5a74 Possibility to change biome generator 2021-12-01 15:43:04 +03:00
paulevsGitch
72e29223a1 Small fix, cleanup 2021-12-01 14:52:21 +03:00
paulevsGitch
211d0fc751 Hexagonal biome generator 2021-12-01 14:28:27 +03:00
paulevsGitch
c7c11d0b4c More biome builder functions (generation settings, music) 2021-12-01 13:56:37 +03:00
paulevsGitch
14ab0c878b More biome builder functions (visual effects) 2021-12-01 13:16:59 +03:00
paulevsGitch
7541e39cf9 More biome builder functions (mobs, visual effects) 2021-12-01 12:54:38 +03:00
paulevsGitch
6895d705f8 Moved biome API to all other APIs 2021-12-01 12:35:39 +03:00
paulevsGitch
e1e09c4efa More biome API changes 2021-12-01 12:31:25 +03:00
paulevsGitch
548cedcffe Merge remote-tracking branch 'origin/1.18' into 1.18 2021-12-01 11:15:43 +03:00
paulevsGitch
86dd202ca4 More biome builder functions 2021-12-01 11:15:34 +03:00
Frank
6d0b776649 Merge branch 'oldBiomeGen' into 1.18 2021-12-01 08:54:04 +01:00
Frank
1365339c1e Allow usage of BCLStructureFeature to add Structures 2021-12-01 08:53:14 +01:00
Frank
9a842694dc Merge remote-tracking branch 'origin/oldBiomeGen' into 1.18 2021-12-01 08:33:16 +01:00
Frank
e7b66af02e Added missing TODOs 2021-12-01 08:31:22 +01:00
Frank
161c5ef1f3 Make sure Nether Cities are placed 2021-12-01 07:57:21 +01:00
paulevsGitch
48f4b69a98 BCL Biome cahnges, biome builder (WIP) 2021-12-01 08:37:30 +03:00
paulevsGitch
97ba6b4df1 BCL Biome terrain height (for the End terrain generator) 2021-12-01 07:51:03 +03:00
paulevsGitch
4e1ec1a148 Merge remote-tracking branch 'origin/1.18' into 1.18 2021-12-01 07:41:53 +03:00
paulevsGitch
7a073c6519 Biome API cleanup (removed reduntant functions, style changes, imports cleanup) 2021-12-01 07:41:46 +03:00
Frank
3b672af0f8 Mixin Compat Level 2021-12-01 05:19:31 +01:00
Frank
0b560b6dce Dependency and Version update 2021-12-01 05:08:57 +01:00
Frank
39dda736af Minor improvements 2021-12-01 04:58:13 +01:00
Frank
22bf4384c6 Updated gradle for Java 17 2021-12-01 01:33:33 +01:00
Frank
54f13847b5 First running version of BCLib 2021-12-01 01:33:24 +01:00
Frank
4c1e8273f6 Fixed some more compile issues 2021-12-01 01:12:20 +01:00
Frank
09c67e3e4a Removed StructureToFeatures 2021-12-01 00:15:37 +01:00
Frank
79710dead6 Removed Hydrogen Fix 2021-11-30 23:57:50 +01:00
Frank
85b11f605c Adopting Fabric Biome Modifications 2021-11-30 23:44:27 +01:00
Frank
eb287422aa Adapted BiomeAPI 2021-11-30 23:38:55 +01:00
Frank
99840b8ee2 Fixed BCLFeature 2021-11-30 22:29:58 +01:00
Frank
ddddfa1492 Fixed some mixins 2021-11-30 21:55:35 +01:00
Frank
a8108045d4 Merge branch 'main' into 1.18 2021-11-30 21:42:39 +01:00
Frank
2831882c6d Added back AccessWidener 2021-11-30 21:21:32 +01:00
Frank
53911dfe91 Backportat latest changes from master 2021-11-30 21:15:10 +01:00
paulevsGitch
301701353f Fixed recipe configs 2021-11-30 20:55:05 +03:00
paulevsGitch
8a93be6ba6 Merge branch 'main' of https://github.com/paulevsGitch/BCLib 2021-11-30 20:51:23 +03:00
paulevsGitch
4c576702ed Version change 2021-11-30 20:51:14 +03:00
Frank
af8c3ba6e8 Name change 2021-11-30 17:15:18 +01:00
Frank
c380f1d2dc Removed unneeded test 2021-11-30 16:41:36 +01:00
Frank
0d325f7637 Version and dependency update 2021-11-30 16:27:44 +01:00
Frank
7d04b3c902 Fix issue with immutable list detection 2021-11-30 16:27:22 +01:00
Frank
bfea622998 Revert "Revert "New API to hook into level loading""
This reverts commit bf8368b515.
2021-11-29 17:53:27 +01:00
Frank
bf8368b515 Revert "New API to hook into level loading"
This reverts commit a489655ddd.
2021-11-29 17:47:50 +01:00
paulevsGitch
e4ca217170 More enhancements 2021-11-29 18:50:38 +03:00
paulevsGitch
c734f83c13 BCLFeature enhancements and javadocs 2021-11-29 18:48:04 +03:00
paulevsGitch
2708eb989a Chunk feature changes 2021-11-29 18:27:57 +03:00
Frank
7f95d19bce Removed unneeded line 2021-11-29 14:45:34 +01:00
Frank
1003729208 makeOreFeature does not assuem END_STONE as HostBlock 2021-11-29 14:40:37 +01:00
Frank
c493a30361 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-11-29 14:31:53 +01:00
Frank
a489655ddd New API to hook into level loading 2021-11-29 14:31:44 +01:00
paulevsGitch
f61845f0c4 Small optimisation 2021-11-29 08:03:25 +03:00
Frank
0e31f9b22e Added DespawnableAnimal 2021-11-28 21:58:38 +01:00
paulevsGitch
cdbeede9f4 Small rule fixes 2021-11-28 19:34:42 +03:00
paulevsGitch
dde1276785 Small fix 2021-11-28 19:29:41 +03:00
paulevsGitch
6f86e33d6c Merge remote-tracking branch 'origin/main' 2021-11-28 17:11:08 +03:00
paulevsGitch
80df11526b Immutable fix 2021-11-28 17:10:59 +03:00
Frank
a50fc71e33 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-11-28 14:28:01 +01:00
Frank
94055c5454 Corrected Spelling and some Cache IDs 2021-11-28 14:27:45 +01:00
paulevsGitch
32b7ca1608 Entity spawn modification 2021-11-28 15:26:48 +03:00
paulevsGitch
41141ec251 Slab drop fix 2021-11-28 07:09:04 +03:00
paulevsGitch
99f958da37 Block spawn restrictions 2021-11-28 07:06:09 +03:00
paulevsGitch
3c606a9149 Cleanup 2021-11-28 06:39:20 +03:00
paulevsGitch
ffabd75e95 Javadoc fix 2021-11-28 06:18:40 +03:00
paulevsGitch
8439528e8e Custom spawning rule 2021-11-28 06:16:52 +03:00
paulevsGitch
e0443a7a13 Chance spawning rule 2021-11-28 06:12:05 +03:00
paulevsGitch
45216cdab5 Name fix 2021-11-28 06:03:12 +03:00
paulevsGitch
3c5d560981 Height spawn limit 2021-11-28 06:02:46 +03:00
paulevsGitch
6cbd400636 Lambda replacement 2021-11-28 05:55:45 +03:00
paulevsGitch
65d9d7f2ce Small fixes 2021-11-28 05:53:07 +03:00
paulevsGitch
7def63ee01 Spawn API enhancements 2021-11-28 05:45:53 +03:00
Frank
5d5c215f48 Version update 2021-11-27 02:11:32 +01:00
Frank
16484d18b2 Fix for height restrictions 2021-11-27 01:01:19 +01:00
Frank
53080bea06 new SpawnAPI 2021-11-26 16:22:32 +01:00
Frank
8d9a95da7a Be more tolerant against reading issues 2021-11-26 14:29:48 +01:00
paulevsGitch
fc3dc71a44 Feature array length fix 2021-11-22 17:15:33 +03:00
paulevsGitch
42aedf2525
Merge pull request #48 from mindy15963/patch-1
Create ko_kr.json
2021-11-22 12:17:13 +03:00
E. Kim
2969680b13
Create ko_kr.json 2021-11-22 13:01:11 +09:00
paulevsGitch
fd0488dd6a Biome by category adding 2021-11-20 20:32:30 +03:00
paulevsGitch
df214cdb24 Biome include lists 2021-11-20 20:24:00 +03:00
paulevsGitch
c0b7ccca72 Cleanup 2021-11-20 19:01:43 +03:00
paulevsGitch
8ea432bc19
Merge pull request #45 from davidalb97/main
Migrated BetterNether's PR BetterNether/pull/437 into BCLib
2021-11-20 18:57:13 +03:00
paulevsGitch
d8c5e84520 Version change 2021-11-20 13:58:35 +03:00
paulevsGitch
51d484f61e Small changes 2021-11-20 13:57:54 +03:00
paulevsGitch
a2dec40b0a Biome config 2021-11-20 13:41:03 +03:00
paulevsGitch
69d11c0e5a Small cleanup 2021-11-19 17:55:34 +03:00
paulevsGitch
b2ba2976dd Merge remote-tracking branch 'origin/main' 2021-11-19 17:52:54 +03:00
paulevsGitch
0e80d66097 Fix for #46 2021-11-19 17:52:41 +03:00
davidalb97
2206674c0d Migrated BetterNether's PR BetterNether/pull/437 into BCLib 2021-11-18 12:22:37 +00:00
Frank
cf8905ab1a Added ComposterAPI (paulevsGitch/BetterNether#442) 2021-11-15 14:41:46 +01:00
Frank
9183293329 Revert "Sync with current master"
This reverts commit 87858fc6d5.
2021-11-14 11:44:47 +01:00
Frank
a6e538b004 Fixed DimensionType Mixin 2021-11-14 11:38:14 +01:00
Frank
8b314577ef Minor build fixes 2021-11-13 21:06:40 +01:00
Frank
87858fc6d5 Sync with current master 2021-11-13 20:42:27 +01:00
Frank
c6a7a1d4f7 *WIP*: Merge commit 'ce4cb8974f' into 1.18 2021-11-13 19:30:01 +01:00
Frank
ce4cb8974f Fixed potential crash in AnvilMenu 2021-11-13 10:50:57 +01:00
paulevsGitch
5ad8752e00 Crafting menu fix 2021-11-13 11:33:04 +03:00
paulevsGitch
cae1d932a1 Biome feature adding API 2021-11-12 17:04:18 +03:00
paulevsGitch
e45f1ac064 Biome source options 2021-11-12 09:22:47 +03:00
paulevsGitch
cc33a9138b Biome modification API 2021-11-12 08:50:41 +03:00
Frank
60574d5b75 Fallback to uncompressed when reading player data (paulevsGitch/BetterNether#436) 2021-11-06 21:26:32 +01:00
Frank
07d8c56e18 Mark Mods that do not contain a client entrypoint as Server-Only for Sync 2021-11-06 20:59:06 +01:00
Frank
2cbfbe8047 Add config to exclude mods from syncing/info 2021-11-06 20:58:41 +01:00
Frank
8233174487 Show Mod info sorted by severity and show client-only mods as OK 2021-11-06 20:04:52 +01:00
Frank
a879a82645 Turn off ModInfo on the server by default 2021-11-06 19:41:40 +01:00
Frank
1b683b5564 Fixed crash on server 2021-11-06 19:16:20 +01:00
paulevsGitch
5247a80549 Biome changer mixin & interface 2021-11-06 15:52:26 +03:00
paulevsGitch
c61856cdba Mod blocks in registry, version change 2021-11-06 15:41:20 +03:00
Frank
473029c31f Fixed JavaDoc error 2021-11-06 11:59:13 +01:00
Frank
d04d75222e A ForcedLevelPatch is always executed against level.dat, (no matter what patchLevel is set for a world) 2021-11-06 11:57:15 +01:00
Frank
f648669c95 Removed errors created for debug purpose 2021-11-06 11:48:19 +01:00
Frank
03e8733ba0 Add UI that presents Fixer Errors to the User(paulevsGitch/BetterNether#436) 2021-11-06 11:41:17 +01:00
Frank
03bef36a45 Updated Version 2021-11-04 02:34:41 +01:00
Frank
143653e2b6 Custom parsing of relevant Mod-Info. Should make it compatible with loader 0.11.x and 0.12.x (#34, #36) 2021-11-04 02:18:33 +01:00
Frank
9a237f52f5 increased fabricloader version (#36, #34) 2021-11-04 00:53:10 +01:00
Frank
dfad74f4a0 Remove BCLib Version Warning screen when user clicks NO 2021-11-03 16:35:52 +01:00
Frank
cca21cd727 Bumped Version and reduced loader dependency (#34) 2021-11-03 16:08:58 +01:00
Frank
383346942f Updated fabric dependencies 2021-11-03 15:45:34 +01:00
Frank
b7c83923ea Using latest fabric API 2021-11-03 15:44:25 +01:00
paulevsGitch
3efa71b08a Null pointer translation fix 2021-11-02 15:25:08 +03:00
paulevsGitch
8eba063b40 Import optimisation, carver adding fix 2021-11-01 14:16:17 +03:00
Frank
2518759f7b Fixed name 2021-10-30 15:00:52 +02:00
Frank
cf31d76ee1 added c:immobile for unmovable blocks like obsidian 2021-10-30 15:00:43 +02:00
paulevsGitch
d33cb707fd Biome def sky color 2021-10-28 17:37:58 +03:00
paulevsGitch
b66c8cd5a6 Registry changes (WIP) 2021-10-26 16:12:10 +03:00
paulevsGitch
449d7cc0d0 No-abstract registry 2021-10-26 15:00:09 +03:00
Frank
0c4ac239b8 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-10-26 10:20:30 +02:00
Frank
8c535be429 Added support for custom nether-portal frames (common Tag c:nether_pframe (TagAPI.BLOCK_NETHER_PORTAL_FRAME) 2021-10-26 10:20:25 +02:00
paulevsGitch
6455cd2182 Merge remote-tracking branch 'origin/main' 2021-10-25 17:37:45 +03:00
paulevsGitch
102f80c62e Fog rendering fixes 2021-10-25 17:37:36 +03:00
Frank
460f4a8e6c Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-10-25 15:32:46 +02:00
Frank
a5bf1ea3e6 Create Ores with custom properties 2021-10-25 15:32:36 +02:00
Frank
7f7e4bfa4c Better Shears Integration 2021-10-25 15:32:17 +02:00
paulevsGitch
9256faf0ba Merge remote-tracking branch 'origin/main' 2021-10-25 16:20:15 +03:00
paulevsGitch
3888998aff Generator config fix 2021-10-25 16:20:06 +03:00
Frank
1195bcf4c3 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-10-23 19:49:14 +02:00
Frank
d47321c0f7 Prepared state fixer 2021-10-23 19:49:02 +02:00
paulevsGitch
69ae8ed33e Small change 2021-10-22 21:59:56 +03:00
Frank
f88506f654 Revert "Merge remote-tracking branch 'origin/main'"
This reverts commit ff8b366eb9, reversing
changes made to 52146286fe.
2021-10-22 15:28:37 +02:00
paulevsGitch
9b312bf4de Debug removal 2021-10-22 13:53:37 +03:00
paulevsGitch
ff8b366eb9 Merge remote-tracking branch 'origin/main' 2021-10-22 13:50:14 +03:00
paulevsGitch
52146286fe Thick fog rendering fixes 2021-10-22 13:50:07 +03:00
Frank
e37d03fa77 replaced accesswidener with simple reflection
(cherry picked from commit 18a64c267d)
2021-10-22 12:23:44 +02:00
Frank
da0d122166 Added Function that allows developers to manually fix nbts 2021-10-19 09:58:41 +02:00
Frank
bd4e61f14f Add support for hanging saplings 2021-10-18 22:53:53 +02:00
Frank
3683ebd97a Auto-Add Block/Item Tags for BaseLeavesBlock and FeatureSaplingBlock (paulevsGitch/BetterEnd#303) 2021-10-18 20:09:41 +02:00
Frank
c2452ebcc3 Added common leaves Tag (paulevsGitch/BetterEnd#303) 2021-10-18 19:52:32 +02:00
Frank
073b473840 Preparing common Tags (paulevsGitch/BetterEnd#303) 2021-10-18 19:38:31 +02:00
Frank
a52b18ba9b MultiPartBuilder.create returns an Instance not a globa Object (improves thread safety, fixes paulevsGitch/BetterEnd#316) 2021-10-18 19:26:10 +02:00
Frank
06df39445c removed unneeded import 2021-10-18 12:44:45 +02:00
Frank
c0810fd7ff add dev-configuration to build-system 2021-10-18 12:40:33 +02:00
Frank
2674940c91 Added AnvilReceipts from BE 2021-10-18 12:26:35 +02:00
Frank
57e2099650 Added IPNIgnore for compat with *Inventory Profiles Next* 2021-10-18 09:28:43 +02:00
Frank
18a64c267d replaced accesswidener with simple reflection 2021-10-12 22:04:45 +02:00
Frank
5a1bd5e31b Downgraded loom version 2021-10-12 20:08:10 +02:00
Frank
7d23a162ee Some refactoring 2021-10-12 19:20:36 +02:00
Frank
c8d1b61006 Added mixin 2021-10-12 17:45:59 +02:00
Frank
40851c38cd Changed Structure handling (Untested yet) 2021-10-12 16:27:12 +02:00
Frank
74533e2e66 fixes for 21w40a 2021-10-12 14:53:14 +02:00
Frank
cbed2868ba German translation 2021-10-07 14:22:38 +02:00
Frank
08f35ab52c Fixed compiler and Startup Errors 2021-09-25 13:18:32 +02:00
Frank
e2ab77658b Adapted to soem Fabric API changes 2021-09-25 11:36:26 +02:00
Frank
23bcbe1977 Some simple Biome related changes 2021-09-23 16:19:30 +02:00
Frank
8abcab32ec Fixed some fabric warnings 2021-09-23 15:20:03 +02:00
Frank
791db59c18 Snapshot versions 2021-09-19 16:57:08 +02:00
Frank
66e3bf483b Using Loom 0.9 2021-09-19 16:52:23 +02:00
Frank
c39c1673a9 Some german translations 2021-09-16 10:36:02 +02:00
Frank
7e2ea43817 removed debug line 2021-09-15 08:22:06 +02:00
Frank
7d9b56b6e7 Minor additions to BiomeAPI 2021-08-27 03:52:06 +02:00
Frank
329509d5c0 Removed debug code 2021-08-27 01:01:45 +02:00
Frank
9a356dbf0d Mod Warning Fixes 2021-08-26 21:28:31 +02:00
Frank
d473451295 Ignore Files from Mods that are not known on the client 2021-08-26 21:22:28 +02:00
Frank
0673a271ef ProgressScreen can not close on esc 2021-08-26 10:14:02 +02:00
Frank
1d9d752832 Added wait for Progress Stage 2021-08-25 18:02:55 +02:00
Frank
ae9463124f Latest Fabric-API 2021-08-25 17:36:21 +02:00
Frank
117ab3fa3d Fixed prefix 2021-08-25 17:31:06 +02:00
Frank
d1ef3b5807 Added progress Screen for DataFixer 2021-08-25 17:27:00 +02:00
Frank
11926ac63c Option to disable ModInfo-Screen 2021-08-25 15:12:45 +02:00
Frank
4692831007 More options for ModSyncing and Screen to display missmatching mods 2021-08-25 15:10:51 +02:00
Frank
b959e17c18 Offer Mod Info Screen 2021-08-25 14:11:48 +02:00
Frank
9d7fa9f925 Scrollable GridScreen 2021-08-25 13:47:08 +02:00
Frank
f3bdaaac8e Make sure we do not close the jar filesystem when cheking for mods (otherwise resources might fail) 2021-08-24 16:59:59 +02:00
Frank
617573ca09 BCLib Syncing fixes 2021-08-24 14:49:29 +02:00
Frank
59fcaf6ee9 Reenable BCLIb-Version warning 2021-08-24 14:37:56 +02:00
Frank
16f4451803 Inherited GridColumn width 2021-08-24 14:22:14 +02:00
Frank
939fe9ddca Added AccessWidener 2021-08-23 15:26:36 +02:00
Frank
0816fd032d Merge branch 'feature/ChunkedFileTransfer' 2021-08-23 15:24:15 +02:00
Frank
922e5c9f26 Removed testing files 2021-08-23 15:22:56 +02:00
Frank
2344ef9606 Fixed serverside crash due to Screen 2021-08-23 15:22:17 +02:00
Frank
3321d1c90e Use ProgressScreen for larger Data-Transfers 2021-08-23 15:07:02 +02:00
Frank
469e97b790 ProgressScreen 2021-08-23 15:06:34 +02:00
Frank
898668ae96 Disable mod-Sync by default 2021-08-23 10:09:44 +02:00
Frank
5c6d6a4cb0 Finished Chunk Data Transfere if Paylouad size exeeds maximum 2021-08-23 10:09:38 +02:00
Frank
63830a27d6 First (untested) version of File-Chunker 2021-08-22 15:00:29 +02:00
Frank
5e4f4d5b43 Fixed problems with DataFixer calls and Architectury 2021-08-22 12:00:21 +02:00
Frank
670928a604 Fix problem with non semantic versions 2021-08-22 11:59:43 +02:00
Frank
4a4191c80d Fixed JavaDoc Error 2021-08-21 14:33:55 +02:00
Frank
2b8ef4bcff Has-Debug should be off by default 2021-08-21 14:25:44 +02:00
Frank
4d56796244 Some minor Fixes 2021-08-21 14:25:09 +02:00
Frank
4f8840da68 Added serverside config to add additional Mods to get synced 2021-08-21 13:56:18 +02:00
Frank
ffffeb78ee Fixed issue with wron type 2021-08-21 13:54:06 +02:00
Frank
38c01b8c76 Additional Typesafety 2021-08-21 13:43:22 +02:00
Frank
edb6631768 Supporting StringArray in Configs 2021-08-21 13:43:10 +02:00
Frank
8f9ff14fac Added ConfigUI Annotation 2021-08-21 12:30:47 +02:00
Frank
0adc5024f1 Adding enabled predicate 2021-08-21 09:49:15 +02:00
Frank
b369954c05 Save configs from ModMenu-Screen 2021-08-20 16:50:23 +02:00
Frank
092e0a39e8 Auto generate a simple Config Screen 2021-08-20 14:37:40 +02:00
paulevsGitch
d31425623d Small cleanup 2021-08-20 15:28:38 +03:00
paulevsGitch
9c2b7c9188 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoFileSyncEntry.java
2021-08-20 15:26:29 +03:00
paulevsGitch
98121a30ef Small fix & cleanup 2021-08-20 15:26:13 +03:00
Frank
601b984430 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-08-20 14:15:33 +02:00
Frank
fe7e1aa28d Prepare Configs that enable us to derive a ConfigScreen 2021-08-20 14:15:21 +02:00
paulevsGitch
529565911f Fixed javadocs, biome API enhancements 2021-08-20 15:11:36 +03:00
Frank
8d6dc18ce2 Merge branch 'main' of github.com-quiqueck:paulevsGitch/BCLib 2021-08-20 13:24:56 +02:00
Frank
d88462e827 Make auto-sync options consistent with UI 2021-08-20 13:24:47 +02:00
paulevsGitch
5ed75012cd
Merge pull request #32 from glisco03/main
Always use the KubeJS workaround for recipe loading to improve mod compatibility
2021-08-20 13:02:38 +03:00
Frank
6e2a539232 Minor refactoring 2021-08-20 03:16:20 +02:00
Frank
290bed1ffe Added missing doc 2021-08-20 03:06:34 +02:00
Frank
0486b56ec3 Refactored to expose the ModMenu Integration to other Mods 2021-08-20 03:05:36 +02:00
Frank
5808bab08e ModMenu Integration 2021-08-20 01:57:27 +02:00
Frank
fa4e086220 close screen to parent 2021-08-20 01:54:20 +02:00
Frank
48c46e2105 Offer ModMenu if installed 2021-08-20 01:53:55 +02:00
Frank
7eb44ebb82 Make sure the server can load client-side mod's for syncing 2021-08-20 01:52:37 +02:00
Frank
14d3c18945 Small Mod Sync fixes 2021-08-19 20:30:43 +02:00
Frank
dfe25a9599 Sync Screen offers user-options 2021-08-19 20:12:01 +02:00
Frank
d46e375501 WIP: SyncScreen allows users to choose which content type to sync 2021-08-19 16:49:43 +02:00
Frank
698ecefba4 Fixed message 2021-08-19 16:39:37 +02:00
Frank
2d8c92d946 Better GridLayout for Screens 2021-08-19 16:39:30 +02:00
Frank
32e1b2edb4 Make config-register message clearer 2021-08-19 09:05:38 +02:00
Frank
ddaf8a0e01 Do not offer unavailable mod dependencies 2021-08-18 14:32:06 +02:00
Frank
3bb8fec4b2 Refactoring of ModUtil members 2021-08-18 14:29:15 +02:00
Frank
71ad055ea5 Implemented ModSync 2021-08-18 14:23:33 +02:00
Frank
8588191556 Adding ModFile Request 2021-08-18 10:05:11 +02:00
Frank
e9fc77ab0d Prepare to sync Mods 2021-08-18 09:57:52 +02:00
Frank
55f00c664c better naming for client/server only handlers 2021-08-17 12:56:37 +02:00
Frank
29fe59b4b9 Fix confi classes 2021-08-17 12:56:09 +02:00
Frank
123a5e2dc4 Refactored AutoSync 2021-08-17 11:57:40 +02:00
Frank
b398e47056 Make sure users have the option to not use AutoSync 2021-08-17 10:10:15 +02:00
Frank
de5e56cc04 Added API to load all ModMetadata from the 'mods' folder (classpath is not included) 2021-08-17 10:03:14 +02:00
Frank
6837c7d081 Make a non-recursive fileWalker 2021-08-17 09:53:04 +02:00
Frank
742ea56e34 Added Step to handler that allows users to stop the message from beeing sent and prepare some data. 2021-08-17 09:52:48 +02:00
Frank
132623419d moved fileWalker 2021-08-16 10:28:37 +02:00
Frank
03bef0ae88 Reject file sync in a Singleplayer World 2021-08-16 09:37:06 +02:00
Frank
0b3e4198cd Disabled testing code 2021-08-16 09:34:49 +02:00
Frank
fcf2d041cb check file-hashes before content comparison 2021-08-16 09:30:01 +02:00
Frank
31ae7a6d3a Reject folder syncs outside of game-dir 2021-08-16 09:04:53 +02:00
Frank
c7aeef43dd Reminder 2021-08-16 01:05:39 +02:00
Frank
fdbde2e0a6 Make sure we reject files that are not children of a sync-path 2021-08-16 01:01:51 +02:00
Frank
0b9d6093a0 [Ready for Testing] Folder-Syncing 2021-08-15 23:54:42 +02:00
glisco03
7f80cad6f4 Merge remote-tracking branch 'origin/main' 2021-08-15 21:37:19 +02:00
glisco03
23b372252b always load recipes later to improve mod compat 2021-08-15 21:34:22 +02:00
Frank
9cd0ef1f04 make diffContent count 2021-08-15 21:02:30 +02:00
Frank
1f239baeb9 *WIP* Prepared Folder Syncing - Filelist exchange 2021-08-15 15:06:36 +02:00
Frank
5df6de1e3a Implemented content based syncing 2021-08-15 13:55:31 +02:00
Frank Bauer
f80b55aa50 Prepared sync pipeline for content-based config sync 2021-08-15 11:30:39 +02:00
paulevsGitch
895b605523 Better Fabric biome API support 2021-08-15 01:42:51 +03:00
paulevsGitch
67be2ce342 Old biome source patcher 2021-08-14 23:06:32 +03:00
paulevsGitch
9e051d9ab9 Datapack biome source fix 2021-08-14 22:55:25 +03:00
paulevsGitch
28228b35e2 Version change, removed old API functions, javadoc fixes 2021-08-14 16:57:51 +03:00
paulevsGitch
1f0403d1b3 Merge remote-tracking branch 'origin/main' 2021-08-14 16:49:09 +03:00
paulevsGitch
410ff87c88 Small javadoc fixes 2021-08-14 16:48:55 +03:00
Frank
25bcbd6369 Using correct ModID 2021-08-14 15:45:51 +02:00
Frank
09395921ce Accept subclasses in equals 2021-08-14 15:43:48 +02:00
Frank
f28c3e0594 Config reload after write and hash-debug 2021-08-14 15:05:41 +02:00
paulevsGitch
7e36ac4159 Crash fixes 2021-08-14 01:32:25 +03:00
paulevsGitch
97ad8b44aa Small enhancements 2021-08-14 00:36:19 +03:00
paulevsGitch
015d60aee1 Generator options init fix 2021-08-14 00:18:21 +03:00
paulevsGitch
c2015848b0 More fixes 2021-08-13 22:08:18 +03:00
paulevsGitch
0acb947795 Init fixes 2021-08-13 21:59:43 +03:00
paulevsGitch
683427c312 Nether biome source (WIP) 2021-08-13 21:55:40 +03:00
paulevsGitch
34ecbb3f14 End biome source (WIP) 2021-08-13 21:23:12 +03:00
paulevsGitch
0e0944ba51 Dimensional biome API (WIP) 2021-08-13 14:53:19 +03:00
paulevsGitch
a9b8c57567 Temporal GUI disabling 2021-08-13 03:19:48 +03:00
paulevsGitch
7845f5beb3 Custom model bakery fix (#31) 2021-08-13 02:50:23 +03:00
paulevsGitch
83d5a4d2bf Temporal model loading fix (#31) 2021-08-12 03:26:53 +03:00
Frank
49e833e147 Removed unused network hooks 2021-08-11 00:04:37 +02:00
Frank
e06543dac6 More debug messages for file-sync 2021-08-11 00:01:36 +02:00
Frank
a21e7e115b Add config options to control autoSync 2021-08-10 23:39:59 +02:00
Frank
ca721368ea Make sure Mods are not registered twice 2021-08-10 23:39:34 +02:00
Frank
911ca85b03 Make sure server does not offer non-existing config files 2021-08-10 23:15:48 +02:00
paulevsGitch
0f3df9b229
Merge pull request #29 from quiqueck/main
Respect AutoSync setting for Config and add a prePatch run for level.dat
2021-08-10 23:17:01 +03:00
Frank
d479d49550 Merge branch 'main' of github.com:quiqueck/BCLib into main 2021-08-10 22:13:06 +02:00
Frank Bauer
b46f9f90d3 Introduce preChecks to the DataFixer 2021-08-10 21:50:43 +02:00
Frank Bauer
28b3669f39 Respect the auto-sync setting 2021-08-10 21:43:22 +02:00
paulevsGitch
49279480ec Version change 2021-08-10 14:44:04 +03:00
paulevsGitch
56592f3034 Javadoc fixes 2021-08-10 11:46:30 +03:00
paulevsGitch
c48c06d2f8
Merge pull request #28 from quiqueck/main
Start Hello process when player joins (not on enter)
2021-08-09 02:32:20 +03:00
Frank
54e1b486a7 Disable BCLibVersion-missmatch screen (as it is currently non functional) 2021-08-08 17:37:05 +02:00
Frank Bauer
819514a699 Start Hello process when player joins (not on enter) 2021-08-08 17:24:29 +02:00
paulevsGitch
990b024fdb
Merge pull request #27 from quiqueck/feature/data_fixer
Added Fixer for WorldDataAPI
2021-08-07 05:27:15 +03:00
Frank
862f3bb6b9 Fix ReceipBook for players 2021-08-07 04:14:35 +02:00
Frank
bc4ec0660f Make sure id-entries for the WorldDataAPI are fixed as well 2021-08-07 03:33:09 +02:00
Frank
dffda00940 Added API to fix id's on paths that can contain Lists/CompundTags 2021-08-07 02:08:17 +02:00
Frank
b232c764e0 Merge remote-tracking branch 'upstream/main' into feature/data_fixer 2021-08-07 01:52:14 +02:00
Frank
add5b65e85 Simplified Inventory handling 2021-08-07 01:51:54 +02:00
paulevsGitch
b2484cfd96
Merge pull request #26 from quiqueck/feature/networking
Auto File Sync
2021-08-07 01:38:43 +03:00
Frank
ace3156fb2 Additional Log output 2021-08-07 00:18:27 +02:00
Frank
5c11fb5424 Invalidate tokens on fail 2021-08-06 23:49:15 +02:00
Frank
5bff609fb6 Invalidate request token 2021-08-06 23:38:40 +02:00
Frank
4d3f7aa34b Api cleanup 2021-08-06 23:29:41 +02:00
Frank
3438c86de2 Refactor 2021-08-06 23:20:39 +02:00
Frank
6883287c72 Finished first draft of File syncing 2021-08-06 23:08:13 +02:00
Frank
c28a566c78 Fixed server crashes 2021-08-06 22:26:56 +02:00
Frank
d4c9695e5c Merge remote-tracking branch 'upstream/main' into feature/networking 2021-08-06 22:11:33 +02:00
Frank
391ca845cf Adding more verbose message 2021-08-06 22:11:08 +02:00
Frank
d01b7923aa Receive requested file contents on client 2021-08-06 21:27:33 +02:00
Frank Bauer
869dc762fa Send file requests 2021-08-06 19:19:49 +02:00
Frank Bauer
ae344c48ac Merge branch 'feature/networking' of github.com-quiqueck:quiqueck/BCLib into feature/networking 2021-08-06 18:12:21 +02:00
Frank
119e94520c Compare files on client/server 2021-08-06 17:40:46 +02:00
Frank
8397ef7cca Added FileHash Object 2021-08-06 09:33:11 +02:00
Frank
feee3514a9 Unpolute public API 2021-08-06 08:23:23 +02:00
Frank
9b92c6cbf2 call sendOnEnter 2021-08-05 19:09:34 +02:00
Frank
16cbba6e76 Preparing Config-Objects to automatically sync from Server to Client 2021-08-05 19:07:14 +02:00
Frank
589151af81 Call DataFixer on WorldData 2021-08-05 18:43:44 +02:00
paulevsGitch
ba38a32b86 Merge remote-tracking branch 'origin/main' 2021-08-01 17:57:31 +03:00
paulevsGitch
ceea648858 Fixed missing models with optifine 2021-08-01 17:56:09 +03:00
paulevsGitch
9541ba528b
Merge pull request #24 from RDKRACZ/main
Decreased icon file size.
2021-08-01 17:46:12 +03:00
paulevsGitch
427ae65856 Fix optifine compat 2021-08-01 17:45:50 +03:00
K0RR
5e9e30d80d
Add files via upload 2021-08-01 16:34:23 +02:00
Frank Bauer
433aa7a1c7 Merge branch 'main' into feature/networking 2021-08-01 14:24:28 +02:00
paulevsGitch
3de67d42b3 Another small fix & optimisation 2021-08-01 14:39:21 +03:00
paulevsGitch
47655f9a01 Another small fix 2021-08-01 14:17:19 +03:00
paulevsGitch
d497f97663 Small fix 2021-08-01 14:12:30 +03:00
paulevsGitch
b05f5a728f Compat with Canvas emissive shaders, fixes 2021-08-01 14:05:41 +03:00
Frank Bauer
2ae840634a Fixed message 2021-08-01 11:05:11 +02:00
paulevsGitch
bcc3b076c2 Some small alpha emission changes 2021-07-31 16:22:52 +03:00
Frank Bauer
70cfdf0dd8 Display DataExchange Dialog 2021-07-31 14:04:14 +02:00
paulevsGitch
90c8362c95 JSON cache 2021-07-31 14:58:01 +03:00
Frank Bauer
7bfe0b01cd Ability to send data when player first enters the level 2021-07-31 13:51:39 +02:00
paulevsGitch
3a5a264436 Model loading fix 2021-07-31 14:46:20 +03:00
Frank Bauer
4fc9c06204 Prepared BCLib Version warning screen 2021-07-31 11:29:13 +02:00
Frank Bauer
46a5603ff7 fixed import 2021-07-31 10:32:48 +02:00
Frank Bauer
114574bff3 Added class to help with simple GridLayouts 2021-07-31 10:32:23 +02:00
Frank Bauer
c621b0525e Hello message initiated on Server 2021-07-31 10:32:09 +02:00
Frank Bauer
165c6e5b22 Fixed crash when server initializes a new world 2021-07-31 09:19:46 +02:00
paulevsGitch
49e93e4309 Remove debug info 2021-07-31 09:22:50 +03:00
paulevsGitch
24ce3fa4e8 CTM compat fix 2021-07-31 09:22:21 +03:00
paulevsGitch
05b89607a5 Item parallel model loading 2021-07-30 21:13:05 +03:00
paulevsGitch
185da209ce Parallel model loading 2021-07-30 21:11:55 +03:00
paulevsGitch
b5ebabd824 Another terrain block small fix 2021-07-30 20:45:56 +03:00
paulevsGitch
6a1ab89e39 Terrain block small fixes 2021-07-30 20:44:34 +03:00
paulevsGitch
694b5c54c3
Merge pull request #23 from quiqueck/feature/ComplexMatStages
ComplexMaterial
2021-07-30 19:11:51 +03:00
Frank Bauer
a53c503c4e List all mod versions from server and client 2021-07-30 17:30:19 +02:00
Frank Bauer
032b4187bd Replaced string with constant value 2021-07-30 15:02:06 +02:00
Frank Bauer
0e2f876cd4 add equals and hashCode to ComplexMaterialEntry 2021-07-30 14:52:33 +02:00
Frank Bauer
37348ccb0e enable replacement of predefined types 2021-07-30 14:49:14 +02:00
Frank Bauer
2e624956aa Reenable FixerAPI 2021-07-30 13:49:12 +02:00
paulevsGitch
fdcb5ad03a Model loading changes 2021-07-30 14:41:04 +03:00
Frank Bauer
2f1a4e9518 seperated initDefault into three stages. Allows easier cosumization of wood materials 2021-07-30 10:18:31 +02:00
Frank Bauer
d33187d204 added receipeGroupPrefix otherwise everything will have the end-group 2021-07-30 10:17:53 +02:00
Frank Bauer
dca77a58ae separated different initStages 2021-07-29 12:10:55 +02:00
Frank Bauer
6b3957d04a Fixed Shulker Recipe 2021-07-29 11:56:19 +02:00
Frank Bauer
56bbc93460 Reverted wrong additions to WoodenMaterial 2021-07-29 11:29:29 +02:00
paulevsGitch
eb90792aca
Merge pull request #22 from quiqueck/feature/networking
Feature/networking
2021-07-29 12:03:45 +03:00
Frank Bauer
b74f680191 Added HelloServer handler 2021-07-29 10:57:58 +02:00
Frank Bauer
2260d547dc changes to run on non dedicated servers as well 2021-07-29 10:01:02 +02:00
Frank Bauer
7632c4e498 Send first test-message 2021-07-28 23:52:18 +02:00
Frank Bauer
8c0dfadc82 Merge branch 'main' of github.com:paulevsGitch/BCLib into feature/networking 2021-07-28 23:29:10 +02:00
Frank Bauer
30b7c8043a First network test 2021-07-28 23:28:42 +02:00
paulevsGitch
b10e505ed3
Merge pull request #21 from quiqueck/main
Initialize new worlds as fully patched
2021-07-27 08:51:55 +03:00
Frank Bauer
eda626fe10 Doc update 2021-07-26 21:36:34 +02:00
Frank Bauer
eb8c87468f Initialize new worlds as fully patched 2021-07-26 21:31:17 +02:00
paulevsGitch
8f680ac5db
Merge pull request #20 from quiqueck/main
Added Screen to confirm Patches
2021-07-26 22:00:31 +03:00
Frank Bauer
c4d7035ef2 Move method to client Environment 2021-07-26 18:52:22 +02:00
Frank Bauer
d3db623de5 Call DataFixer in Server Context 2021-07-26 18:50:15 +02:00
Frank Bauer
a247b17e7f Added Screen to confirm Patches 2021-07-26 18:36:20 +02:00
paulevsGitch
f69e839a82
Merge pull request #19 from quiqueck/main
Added Patch-Function for *level.dat *
2021-07-26 13:42:03 +03:00
Frank Bauer
c93c271bd4 Added Patch-Function for *level.dat * 2021-07-26 12:27:44 +02:00
paulevsGitch
f853ac75d9 Model transform 2021-07-24 21:30:48 +03:00
paulevsGitch
1ea7221f9f Small fix 2021-07-24 21:08:20 +03:00
paulevsGitch
a6acb67428 Multi material fix 2021-07-24 21:01:46 +03:00
paulevsGitch
d350425049 Small fix 2021-07-24 20:03:11 +03:00
paulevsGitch
4a6d618598 OBJ model offset 2021-07-24 20:02:25 +03:00
paulevsGitch
5efab22561 Culling fix & null pointer fix 2021-07-24 18:52:22 +03:00
paulevsGitch
ca9e32ae53 OBJ model enhancements and builder 2021-07-24 18:20:29 +03:00
paulevsGitch
099ecf68b6 OBJ model prototype 2021-07-24 17:05:03 +03:00
paulevsGitch
c8d9d9b252 Wooden material rename, javadoc fix, recipe entries 2021-07-24 00:59:22 +03:00
paulevsGitch
4df19c2193 Readme update 2021-07-23 22:06:37 +03:00
paulevsGitch
863562ac87 Complex material javadocs 2021-07-23 21:56:10 +03:00
paulevsGitch
0e6fa962de Material ID 2021-07-23 19:24:59 +03:00
paulevsGitch
0c73d69a93 Entites fixes 2021-07-23 18:35:13 +03:00
paulevsGitch
ec61f22682 Small fixes 2021-07-23 17:54:42 +03:00
paulevsGitch
65aae74979 Small null fix 2021-07-23 17:34:55 +03:00
paulevsGitch
806992759a Complex material init changes 2021-07-23 17:26:02 +03:00
paulevsGitch
eb18fa63af Complex material fixes 2021-07-23 15:03:16 +03:00
paulevsGitch
b661aedd60 Recipes, fixes 2021-07-23 14:24:02 +03:00
paulevsGitch
ddbdc0207d Wooden material tags 2021-07-23 14:13:36 +03:00
paulevsGitch
edf9e1d004 Material init 2021-07-23 13:41:54 +03:00
paulevsGitch
0eaaae2f99 Complex materials, wood material (WIP) 2021-07-23 13:40:37 +03:00
paulevsGitch
eb4f70b8a2
Merge pull request #18 from quiqueck/main
Datafixer update, WoodMaterial and BuildSystem improvements
2021-07-23 10:25:59 +03:00
Frank
a998c9fbe7 Merge branch 'subbuild' into main 2021-07-22 23:29:21 +02:00
paulevsGitch
8dbce734d5 Small format fixes, javadocs 2021-07-23 00:07:47 +03:00
Frank
6385480e9e Added first draft of WoodenMaterial (from BetterEnd) 2021-07-22 23:05:51 +02:00
Frank
080bd36714 Added first draft of WoodenMaterial (from BetterEnd) 2021-07-22 23:05:37 +02:00
Frank
bd77ea46b9 Merge branch 'main' into subbuild 2021-07-22 23:01:43 +02:00
Frank
3836e1f81f Merge branch 'main' of github.com:paulevsGitch/BCLib into main 2021-07-22 23:01:34 +02:00
paulevsGitch
7be63c814d Small formatting fixes 2021-07-22 23:57:32 +03:00
paulevsGitch
ceeb36fc2a Translation Helper fixes 2021-07-22 23:55:28 +03:00
paulevsGitch
551bf6865c Post init API enhancement, automatic render registry (WIP) 2021-07-22 23:38:32 +03:00
paulevsGitch
50ccbace6d Post init API 2021-07-22 23:23:14 +03:00
paulevsGitch
4e9fda44f8 Ladder block vanilla class extend 2021-07-22 23:16:58 +03:00
Frank
d259931477 Merge commit 'd179bd2ba8' into subbuild 2021-07-22 15:26:43 +02:00
Frank
5a56064e86 Moved DataFixer call 2021-07-22 15:22:14 +02:00
Frank
89d548caf9 Start fixer before the world is loaded (otherwise no changes to level.dat are possible) 2021-07-22 15:22:10 +02:00
Frank
9e42fe7294 Handle Player and Level data in Fixer 2021-07-22 15:22:07 +02:00
Frank
c64c6eca7b Fixing Entity Inventory 2021-07-22 15:21:57 +02:00
Frank Bauer
d179bd2ba8 Support fir BCLib as subproject to BN/BE 2021-07-22 08:52:46 +02:00
paulevsGitch
37b85b4cee Base plant edit 2021-07-22 07:20:09 +03:00
paulevsGitch
a6419d96e2 Less fast fog density transition, render code in separate class 2021-07-21 18:40:46 +03:00
paulevsGitch
9878854f6b Small fix for fix 2021-07-21 17:38:45 +03:00
paulevsGitch
f981527200 Feature randomization (fix MC bug) 2021-07-21 17:31:24 +03:00
paulevsGitch
a48c350aa2 Tag name changes, workbenches tag 2021-07-21 13:32:02 +03:00
paulevsGitch
51bff6b2ca Small fix 2021-07-21 13:28:27 +03:00
paulevsGitch
5fbb2e03b5 Torus SDF 2021-07-21 12:27:00 +03:00
paulevsGitch
2bfc985403 Description update 2021-07-21 02:09:57 +03:00
paulevsGitch
cdc9d85b53 Shader enhancements, disable when Optifine is installed 2021-07-20 23:55:40 +03:00
paulevsGitch
e5e948ef4f Moved TagHelper inside TagAPI 2021-07-20 04:54:41 +03:00
paulevsGitch
2e12635e66
Merge pull request #16 from quiqueck/main
Adopted WorldDataAPI for new DataFixerAPI
2021-07-20 03:43:30 +03:00
paulevsGitch
5455034da3 Small changes 2021-07-20 03:39:58 +03:00
paulevsGitch
1dc2fea7e2 Translations 2021-07-20 03:33:52 +03:00
paulevsGitch
450b7d4205 Small drop fix 2021-07-20 03:23:17 +03:00
paulevsGitch
1a4a9ef0a1 Anvil fixes 2021-07-20 02:57:43 +03:00
Frank Bauer
65b70f57ad Merge branch 'main' of github.com:paulevsGitch/BCLib 2021-07-20 00:44:49 +02:00
Frank Bauer
cfa240892f Removed original DataFixerAPI 2021-07-20 00:42:01 +02:00
Frank Bauer
dcb66ee5e0 Better Debug order 2021-07-20 00:37:16 +02:00
Frank Bauer
66851baf0f fixed bug where "WorldDataAPI" would always restore an empty Tag. 2021-07-20 00:36:59 +02:00
Frank Bauer
4ad3ff8277 Using WorldDataAPI to keep track of pacth-level 2021-07-20 00:23:49 +02:00
Frank Bauer
f595cdbbed Register BCLib in the WorldDataAPI 2021-07-20 00:22:45 +02:00
paulevsGitch
cba8637122 Anvil model fixes 2021-07-20 01:03:43 +03:00
paulevsGitch
e50ad6b6aa Imports optimised 2021-07-20 00:41:25 +03:00
Frank Bauer
fa1c58b832 Refactor 2021-07-19 23:20:08 +02:00
paulevsGitch
adadee7ef7 Version change 2021-07-20 00:19:45 +03:00
paulevsGitch
db8dc41c8f Getters interfaces to Providers rename 2021-07-20 00:16:42 +03:00
Frank Bauer
5e2961c91d renamed default config dir 2021-07-19 23:11:26 +02:00
paulevsGitch
c6afa74529 Code style fix, interface rename, custom item getter 2021-07-20 00:10:00 +03:00
Frank Bauer
5cc430de6f Fixed regex api use 2021-07-19 23:03:43 +02:00
Frank Bauer
9728dc8dc3 Use version String for DataFixer 2021-07-19 22:38:27 +02:00
paulevsGitch
179ada3296 Code style fix & version update 2021-07-19 21:41:07 +03:00
paulevsGitch
6266b30088
Merge pull request #14 from quiqueck/feature/specialBlocksRegistration
Added method to register special blocks to correct registries
2021-07-19 21:33:49 +03:00
paulevsGitch
c677787f91
Merge pull request #15 from quiqueck/feature/PatchTools
Redesigned `DataFixerAPI`-Management
2021-07-19 21:33:42 +03:00
paulevsGitch
c39eca3bd4
Merge pull request #13 from quiqueck/hashFix
Fixed hash-calculation for `ConfigKey`
2021-07-19 21:33:36 +03:00
Frank Bauer
5d2bb2c66c Call new DataFixer-API 2021-07-19 19:54:37 +02:00
Frank Bauer
51235429db Added alternative DataFixerAPI 2021-07-19 19:54:37 +02:00
Frank Bauer
102c9ec0cc Added a SessionConfig class that can generate config files inside a world folder 2021-07-19 19:54:16 +02:00
Frank Bauer
fee7cbc93a Added possibility to store configs at costum locations 2021-07-19 19:54:16 +02:00
Frank Bauer
8d5f235684 Fixed hash-calculation for ConfigKey 2021-07-19 19:54:16 +02:00
Frank Bauer
d63aa5b92e Added method to register special blocks to correct registries 2021-07-19 19:51:36 +02:00
Frank Bauer
280c6d5849 Fixed hash-calculation for ConfigKey 2021-07-19 19:24:34 +02:00
paulevsGitch
42d9f7f887 Fixed item entity lights 2021-07-19 01:38:13 +03:00
paulevsGitch
f8b88909cb Item shaders upgrade 2021-07-18 19:51:59 +03:00
paulevsGitch
5c6c4677b0 Shader update 2021-07-18 19:27:04 +03:00
paulevsGitch
5afaf5f324 Deprecated mipped shaders 2021-07-18 17:56:57 +03:00
paulevsGitch
21f33f1fef Version change 2021-07-18 17:30:24 +03:00
paulevsGitch
71935a746a Cutout emission 2021-07-18 17:29:23 +03:00
paulevsGitch
201df3746a Emissive textures support 2021-07-18 17:00:46 +03:00
paulevsGitch
940dd39e29 Test shaders 2021-07-18 14:28:57 +03:00
paulevsGitch
f10aa2776f
Merge pull request #11 from quiqueck/main
Proposal to augment default Block properties
2021-07-18 00:27:07 +03:00
Frank Bauer
f7955e60f0 Propsal for property augmentation 2021-07-17 15:12:23 +02:00
Frank Bauer
5b9eb304bc Custom Stylesheet 2021-07-17 10:27:21 +02:00
Frank Bauer
aa59cb9844 Merge branch 'main' of github.com:paulevsGitch/BCLib 2021-07-17 10:19:18 +02:00
paulevsGitch
036a94e171 Underwater plants fix 2021-07-16 15:35:01 +03:00
paulevsGitch
1609b28595 Bonemeal API update and unification, sub-biome fix 2021-07-16 15:04:42 +03:00
Frank Bauer
bc4ff96f0b JavaDoc for TagHelper 2021-07-14 13:49:38 +02:00
paulevsGitch
d8a620c589 Mining tags 2021-07-12 10:03:18 +03:00
paulevsGitch
0a2b6d9977 Mining tags 2021-07-12 10:03:13 +03:00
paulevsGitch
f4a88c277b Add value operation 2021-07-11 15:12:53 +03:00
Aleksey
8fca5aab41 Blockstates StringProperty 2021-07-11 14:32:13 +03:00
paulevsGitch
cbff862594 Fog rendering skip 2021-07-11 01:32:07 +03:00
paulevsGitch
992d755801 Post init interface 2021-07-11 01:25:11 +03:00
paulevsGitch
cb51137c87 Removed face bakery mixin 2021-07-11 00:41:29 +03:00
paulevsGitch
f1ccd563b5 Transformations 2021-07-11 00:04:37 +03:00
paulevsGitch
7d923ba04e Small fixes 2021-07-10 19:54:19 +03:00
paulevsGitch
e41b59d506 Added resource loading for JSON 2021-07-10 19:47:33 +03:00
paulevsGitch
f4df53b171 Color provider mixin 2021-07-10 16:18:05 +03:00
paulevsGitch
5a9365e2bb Fixed structure features and code style 2021-07-10 16:07:44 +03:00
paulevsGitch
d431f2555c
Merge pull request #10 from quiqueck/main
Fix for jitpack
2021-07-09 14:52:02 +03:00
Frank Bauer
370add478f Force jitpack to use Java16 2021-07-09 09:51:28 +02:00
Frank Bauer
644ea6a171 Merge remote-tracking branch 'upstream/main' 2021-07-09 09:14:51 +02:00
paulevsGitch
8d22f7a037
Merge pull request #9 from paulevsGitch/1.16.5
Merge pull request #4 from paulevsGitch/main
2021-07-07 21:07:54 +03:00
paulevsGitch
81eca6fe89
Merge pull request #8 from quiqueck/1.17.1
1.17.1
2021-07-07 20:57:53 +03:00
paulevsGitch
c8dd63136c
Merge pull request #7 from quiqueck/main
Minor fixes
2021-07-07 20:57:43 +03:00
Frank Bauer
1a049d05c4 duplicatesStrategy EXCLUDE will not copy the version number to the built jar 2021-07-07 14:00:37 +02:00
Frank Bauer
4ece50ddb1 duplicatesStrategy EXCLUDE will not copy the version number to the built jar 2021-07-07 14:00:16 +02:00
Frank Bauer
1ebb93d7ab blockStateis null if it can not be placed 2021-07-07 13:36:10 +02:00
Frank Bauer
daeca492ec Dependencies updated for 1.17.1 2021-07-07 13:35:41 +02:00
Frank Bauer
c7b49be379 TagLoader fixes 2021-07-07 13:32:23 +02:00
Frank Bauer
558b1db714 Fixed *Chest* rendering 2021-07-07 13:31:02 +02:00
Frank Bauer
abcf90e0bc Fixed *Sign* rendering 2021-07-07 13:30:42 +02:00
Frank Bauer
827018078f Register tick for *Furnace*-Blocks 2021-07-07 13:30:16 +02:00
Frank Bauer
a53114779a blockStateis null if it can not be placed 2021-07-07 13:29:17 +02:00
Frank Bauer
7dfbe7bfaf HEIGHTMAP_SQUARE has index 17 2021-07-07 13:28:52 +02:00
Frank Bauer
24e16a1850 Merge commit 'befdd2c10a' into 1.17.1 2021-07-07 13:27:01 +02:00
Aleksey
5512ece14e Tag loader fix 2021-06-25 14:38:29 +03:00
paulevsGitch
3143de77c3 Fixed feature sapling block 2021-06-24 18:08:27 +03:00
paulevsGitch
947b790138 NBT Structure fix & Surface Builder fix 2021-06-24 18:06:24 +03:00
paulevsGitch
02068df82d Fixed Structure Helper 2021-06-24 18:02:56 +03:00
Aleksey
7d79452220 Various fixes 2021-06-22 16:05:46 +03:00
Aleksey
dccb49f51c Update gradle.properties 2021-06-22 14:04:03 +03:00
Aleksey
d7b82c3a17 Update gradle-wrapper.properties 2021-06-22 13:42:26 +03:00
Aleksey
0a29192bfe Merge branch 'main' into 1.17 2021-06-22 13:42:15 +03:00
paulevsGitch
a4ff0b0721
Merge pull request #4 from paulevsGitch/main
Update 1.16.5 version
2021-06-21 01:29:15 +03:00
paulevsGitch
508ae317fe Rendering update 2021-06-20 16:29:45 +03:00
paulevsGitch
553afd872a Feature update 2021-06-20 16:25:59 +03:00
paulevsGitch
6e601179c5 Fixed biome def 2021-06-20 15:35:43 +03:00
Aleksey
849b28f7d3 Try to fix NBTStructureFeature... 2021-06-18 15:38:18 +03:00
Aleksey
cbc91f4523 Start update 2021-06-18 15:08:31 +03:00
834 changed files with 56243 additions and 18354 deletions

75
.github/ISSUE_TEMPLATE/BUG_REPORT.yml vendored Normal file
View file

@ -0,0 +1,75 @@
name: Report a Bug
description: File a bug report
title: "[Bug] "
labels: [ "bug" ]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report a Bug in BCLib!
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: markdown
attributes:
value: |
## Versions
- type: input
id: bn_version
attributes:
label: BCLib
description: What version of BCLib are you running?
placeholder: 2.x.x
validations:
required: true
- type: input
id: fabric_api_version
attributes:
label: Fabric API
description: What version of Fabric API is installed
placeholder: 0.5x.x
validations:
required: false
- type: input
id: fabric_loader_version
attributes:
label: Fabric Loader
description: What version of Fabric Loader do you use
placeholder: 0.14.x
validations:
required: false
- type: dropdown
id: mc_version
attributes:
label: Minecraft
description: What version of Minecraft is installed?
options:
- 1.19
- 1.18.2
- 1.18.1
- 1.18
- Older
validations:
required: true
- type: markdown
attributes:
value: |
## Additional Information
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: textarea
id: other_mods
attributes:
label: Other Mods
description: If you can, please supply a list of installed Mods (besides BetterNether and BCLib). This information may already be included in the log above.
render: shell

18
.github/ISSUE_TEMPLATE/SUGGEST_FROM.yml vendored Normal file
View file

@ -0,0 +1,18 @@
name: Suggest a Feature or Change
description: Have a new Idea, then suggest a Feature here.
title: "[Suggestion] "
labels: ["suggestion"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to suggest a new Feature for BCLib. We appreciate your time!
- type: textarea
id: describe
attributes:
label: Description
description: Tell us your idea
placeholder:
value:
validations:
required: true

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1 @@
blank_issues_enabled: true

1
.gitignore vendored
View file

@ -27,5 +27,6 @@ bin/
# fabric # fabric
run/ run/
run-client/
output/ output/
*.log *.log

40
BetterX_CodeFormat.xml Normal file
View file

@ -0,0 +1,40 @@
<code_scheme name="BetterX" version="173">
<option name="ENABLE_SECOND_REFORMAT" value="true" />
<JavaCodeStyleSettings>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="org.betterx" withSubpackages="true" static="false" />
<emptyLine />
<package name="com.mojang" withSubpackages="true" static="false" />
<package name="net.minecraft" withSubpackages="true" static="false" />
<emptyLine />
<package name="net.fabricmc" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<package name="java" withSubpackages="true" static="false" />
<package name="org.jetbrains" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
</JavaCodeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="BLANK_LINES_AROUND_METHOD_IN_INTERFACE" value="0" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="CALL_PARAMETERS_WRAP" value="5" />
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_PARAMETERS_WRAP" value="5" />
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_CALL_CHAIN_WRAP" value="5" />
<option name="TERNARY_OPERATION_WRAP" value="5" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_WRAP" value="5" />
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
</codeStyleSettings>
</code_scheme>

169
README.md
View file

@ -1,41 +1,146 @@
[![](https://jitpack.io/v/paulevsGitch/BCLib.svg)](https://jitpack.io/#paulevsGitch/BCLib) [![](https://jitpack.io/v/quiqueck/BCLib.svg)](https://jitpack.io/#quiqueck/BCLib)
# BCLib # BCLib
BCLib is a library mod for BetterX team mods, developed for Fabric, MC 1.16.5
## Features: BCLib is a library mod for BetterX team mods, developed for Fabric, MC 1.19
### API:
* Simple Mod Integration API;
* Structure Features API;
* World Data API;
* Bonemeal API;
* Features API;
* Biome API;
* Tag API.
### Libs:
* Spline library (simple);
* Recipe manager;
* Noise library;
* Math library;
* SDF library.
### Helpers And Utils:
* Custom surface builders;
* Translation helper;
* Weighted list;
* Block helper.
### Rendering:
* Procedural block models (from paterns or from code);
* Block render layer interface.
## Importing: ## Importing:
* Clone repo
* Edit gradle.properties if necessary You can easily include BCLib into your own mod by adding the following to your `build.gradle`:
* Run command line in folder: gradlew genSources eclipse (or Another-IDE-Name)
* Import project to IDE ```
repositories {
...
maven { url 'https://jitpack.io' }
}
```
```
dependencies {
...
modImplementation "com.github.quiqueck:BCLib:${project.bclib_version}"
}
```
You should also add a dependency to `fabirc.mod.json`. BCLib uses Semantic versioning, so adding the dependcy as follows
should respect that and ensure that your mod is not loaded with an incompatible version of BCLib:
```
"depends": {
...
"bclib": "2.0.x"
},
"breaks": {
"bclib": "<2.0.6"
}
```
In this example `2.0.6` is the BCLIb Version you are building against.
## Features:
### Rendering
* Emissive textures (with _e suffix)
* Can be applied to Solid and Transparent blocks;
* Can be changed/added with resourcepacks;
* Incompatible with Sodium and Canvas (just will be not rendered);
* Incompatible with Iris shaders (Iris without shaders works fine).
* Procedural block and item models (from paterns or from code);
* Block render interfaces.
### API:
* Simple Mod Integration API:
* Get mod inner methods, classes and objects on runtime.
* Structure Features API:
* Sructure Features with automatical registration, Helpers and math stuff.
* World Data API:
* World fixers for comfortable migration between mod versions when content was removed;
* Support for Block name changes and Tile Entities (WIP).
* Bonemeal API:
* Add custom spreadable blocks;
* Add custom plants grow with weight, biomes and other checks;
* Custom underwater plants.
* Features API:
* Features with automatical registration, Helpers and math.
* Biome API:
* Biome wrapper around MC biomes;
* Custom biome data storage;
* Custom fog density.
* Tag API:
* Pre-builded set of tags;
* Dynamical tag registration with code;
* Adding blocks and items into tags at runtime.
### Libs:
* Spline library (simple):
* Helper to create simple splines as set of points;
* Some basic operation with splines;
* Converting splines to SDF.
* Recipe manager:
* Register recipes from code with configs and ingredients check.
* Noise library:
* Voronoi noise and Open Simplex Noise.
* Math library:
* Many basic math functions that are missing in MC.
* SDF library:
* Implementation of Signed Distance Functions;
* Different SDF Operations and Primitives;
* Different materials for SDF Primitives;
* Block post-processing;
* Feature generation using SDF.
### Helpers And Utils:
* Custom surface builders.
* Translation helper:
* Generates translation template.
* Weighted list:
* A list of objects by weight;
* Weighted Tree:
* Fast approach for big weight structures;
* Block helper:
* Some useful functions to operate with blocks;
### Complex Materials
* Utility classes used for mass content generation (wooden blocks, stone blocks, etc.);
* Contains a set of defined blocks, items, recipes and tags;
* Can be modified before mods startup (will add new block type for all instances in all mods);
* All inner blocks and items are Patterned (will have auto-generated models with ability to override them with resource
packs or mod resources).
### Pre-Defined Blocks and Items:
* Most basic blocks from MC;
* Automatic item & block model generation;
### Configs:
* Custom config system based on Json;
* Hierarchical configs;
* Different entry types;
* Only-changes saves.
### Interfaces:
* BlockModelProvider:
* Allows block to return custom model and blockstate.
* ItemModelProvider:
* Allows block to return custom item model.
* CustomColorProvider:
* Make available to add block and item color provider.
* RenderLayerProvider:
* Determine block render layer (Transparent and Translucent).
* PostInitable:
* Allows block to init something after all mods are loaded.
* CustomItemProvider:
* Allows block to change its registered item (example - signs, water lilies).
## Building: ## Building:
* Clone repo * Clone repo
* Run command line in folder: gradlew build * Run command line in folder: gradlew build
* Mod .jar will be in ./build/libs * Mod .jar will be in ./build/libs

8
bclib-composit.gradle Normal file
View file

@ -0,0 +1,8 @@
plugins {
id 'idea'
id 'eclipse'
id 'fabric-loom'
id 'maven-publish'
}
apply from: "bclib.gradle"

241
bclib.gradle Normal file
View file

@ -0,0 +1,241 @@
buildscript {
dependencies {
classpath 'org.kohsuke:github-api:1.114'
}
repositories {
gradlePluginPortal()
}
}
apply from: "gradle/bclib-common.gradle"
repositories {
maven { url "https://maven.dblsaiko.net/" }
maven { url "https://maven.fabricmc.net/" }
maven { url "https://maven.shedaniel.me/" }
maven { url 'https://maven.blamejared.com' }
maven { url 'https://jitpack.io' }
maven { url 'https://maven.terraformersmc.com/releases' }
}
archivesBaseName = project.archives_base_name
version = project.mod_version
loom {
accessWidenerPath = file("src/main/resources/bclib.accesswidener")
}
moduleDependencies(project, ["together-v1"])
repositories {
maven { url 'https://maven.terraformersmc.com/releases' }
}
dependencies {
modCompileOnly "com.terraformersmc:modmenu:${project.modmenu_version}"
}
allprojects {
group = project.maven_group
apply plugin: "java-library"
apply plugin: "fabric-loom"
tasks.withType(GenerateModuleMetadata) {
enabled = false
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
it.options.release = 17
}
java {
// Must be added before the split source sets are setup.
withSourcesJar()
}
allprojects.each { p ->
loom.mods.register(p.name) {
sourceSet p.sourceSets.main
}
}
dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings loom.officialMojangMappings()
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
}
loom {
shareRemapCaches = true
}
tasks.withType(ProcessResources).configureEach {
inputs.property "version", project.version
filesMatching("fabric.mod.json") {
expand "version": project.version
}
}
tasks.withType(AbstractArchiveTask) {
preserveFileTimestamps = false
reproducibleFileOrder = true
}
jar {
from "../LICENSE"
}
}
subprojects {
javadoc.enabled = false
}
javadoc {
options {
source = "17"
encoding = "UTF-8"
charSet = "UTF-8"
memberLevel = JavadocMemberLevel.PACKAGE
addStringOption("Xdoclint:none", "-quiet")
}
allprojects.each {
if (it.name == "deprecated") return
source(it.sourceSets.main.allJava.srcDirs)
}
classpath = files(sourceSets.main.compileClasspath)
include("**")
failOnError false
}
task javadocJar(type: Jar) {
dependsOn javadoc
from javadoc.destinationDir
//Set as `fatjavadoc` to prevent an ide form trying to use this javadoc, over using the modules javadoc
archiveClassifier = "fatjavadoc"
}
build.dependsOn javadocJar
loom {}
processResources {
println "Version: ${project.mod_version}"
inputs.property "version", project.mod_version
filesMatching("fabric.mod.json") {
expand "version": project.mod_version
}
}
subprojects.each {
remapJar.dependsOn("${it.path}:remapJar")
}
dependencies {
afterEvaluate {
subprojects.each {
api project(path: "${it.path}", configuration: "namedElements")
}
}
}
remapJar {
afterEvaluate {
subprojects.each {
nestedJars.from project("${it.path}").tasks.getByName("remapJar")
}
}
}
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
it.options.release = 17
}
javadoc {
options.tags = ["reason"]
options.stylesheetFile = new File(projectDir, "javadoc.css");
}
jar {
from "LICENSE"
}
artifacts {
archives sourcesJar
archives javadocJar
}
def env = System.getenv()
import org.kohsuke.github.GHReleaseBuilder
import org.kohsuke.github.GitHub
task release(dependsOn: [remapJar, sourcesJar, javadocJar]) {
onlyIf {
env.GITHUB_TOKEN
}
doLast {
def github = GitHub.connectUsingOAuth(env.GITHUB_TOKEN as String)
def repository = github.getRepository("quiqueck/BCLib")
def releaseBuilder = new GHReleaseBuilder(repository, version as String)
releaseBuilder.name("${archivesBaseName}-${version}")
releaseBuilder.body("A changelog can be found at https://github.com/quiqueck/BCLib/commits")
releaseBuilder.commitish("main")
def ghRelease = releaseBuilder.create()
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}.jar"), "application/java-archive");
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-sources.jar"), "application/java-archive");
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-javadoc.jar"), "application/java-archive");
}
}
// configure the maven publication
publishing {
publications {
gpr(MavenPublication) {
artifactId archivesBaseName
artifact(remapJar) {
builtBy remapJar
}
artifact(sourcesJar) {
builtBy remapSourcesJar
}
}
}
// select the repositories you want to publish to
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/quiqueck/bclib")
credentials {
username = env.GITHUB_USER
password = env.GITHUB_TOKEN
}
}
}
}
configurations {
dev {
canBeResolved = false
canBeConsumed = true
}
}
artifacts {
dev jar
}

View file

@ -1,164 +1,8 @@
buildscript {
dependencies {
classpath 'org.kohsuke:github-api:1.114'
}
}
plugins { plugins {
id 'idea' id 'idea'
id 'eclipse' id 'eclipse'
id 'fabric-loom' version '0.7-SNAPSHOT' id 'fabric-loom' version "${loom_version}"
id 'maven-publish' id 'maven-publish'
} }
sourceCompatibility = JavaVersion.VERSION_1_8 apply from: "bclib.gradle"
targetCompatibility = JavaVersion.VERSION_1_8
archivesBaseName = project.archives_base_name
version = project.mod_version
group = project.maven_group
repositories {
maven { url "https://maven.dblsaiko.net/" }
maven { url "https://server.bbkr.space:8081/artifactory/libs-release/" }
maven { url "https://maven.fabricmc.net/" }
maven { url 'https://maven.blamejared.com' }
maven { url "https://maven.shedaniel.me/" }
maven { url 'https://jitpack.io' }
}
dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings minecraft.officialMojangMappings()
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
useApi "vazkii.patchouli:Patchouli:1.16.4-${project.patchouli_version}"
}
def useOptional(String dep) {
dependencies.modRuntime (dep) {
exclude group: "net.fabricmc.fabric-api"
exclude group: "net.fabricmc"
if (!dep.contains("me.shedaniel")) {
exclude group: "me.shedaniel"
}
}
dependencies.modCompileOnly (dep) {
exclude group: "net.fabricmc.fabric-api"
exclude group: "net.fabricmc"
if (!dep.contains("me.shedaniel")) {
exclude group: "me.shedaniel"
}
}
}
def useApi(String dep) {
dependencies.modApi (dep) {
exclude group: "net.fabricmc.fabric-api"
exclude group: "net.fabricmc"
if (!dep.contains("me.shedaniel")) {
exclude group: "me.shedaniel"
}
}
}
processResources {
inputs.property "version", project.version
duplicatesStrategy = 'EXCLUDE'
from(sourceSets.main.resources.srcDirs) {
include "fabric.mod.json"
expand "version": project.version
}
from(sourceSets.main.resources.srcDirs) {
exclude "fabric.mod.json"
}
}
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
javadoc {
options.tags = [ "reason" ]
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this task, sources will not be generated.
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
from sourceSets.main.allSource
}
jar {
from "LICENSE"
}
artifacts {
archives sourcesJar
archives javadocJar
}
def env = System.getenv()
import org.kohsuke.github.GHReleaseBuilder
import org.kohsuke.github.GitHub
task release(dependsOn: [remapJar, sourcesJar, javadocJar]) {
onlyIf {
env.GITHUB_TOKEN
}
doLast {
def github = GitHub.connectUsingOAuth(env.GITHUB_TOKEN as String)
def repository = github.getRepository("paulevsGitch/BCLib")
def releaseBuilder = new GHReleaseBuilder(repository, version as String)
releaseBuilder.name("${archivesBaseName}-${version}")
releaseBuilder.body("A changelog can be found at https://github.com/paulevsGitch/BCLib/commits")
releaseBuilder.commitish("main")
def ghRelease = releaseBuilder.create()
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}.jar"), "application/java-archive");
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-sources.jar"), "application/java-archive");
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-javadoc.jar"), "application/java-archive");
}
}
// configure the maven publication
publishing {
publications {
gpr(MavenPublication) {
artifactId archivesBaseName
artifact(remapJar) {
builtBy remapJar
}
artifact(sourcesJar) {
builtBy remapSourcesJar
}
}
}
// select the repositories you want to publish to
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/paulevsgitch/bclib")
credentials {
username = env.GITHUB_USER
password = env.GITHUB_TOKEN
}
}
}
}

View file

@ -1,18 +1,16 @@
# Done to increase the memory available to gradle. # Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx2G org.gradle.jvmargs=-Xmx2G
#Loom
loom_version=0.12-SNAPSHOT
# Fabric Properties # Fabric Properties
# check these on https://fabricmc.net/use # check these on https://fabricmc.net/versions.html
minecraft_version=1.16.5 minecraft_version=1.19
yarn_mappings=6 loader_version=0.14.8
loader_version=0.11.3 fabric_version=0.57.0+1.19
# Mod Properties # Mod Properties
mod_version = 0.1.45 mod_version=2.0.10
maven_group = ru.bclib together-v1-version=2.0.10
archives_base_name = bclib maven_group=org.betterx.bclib
archives_base_name=bclib
# Dependencies # Dependencies
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api modmenu_version=4.0.0
patchouli_version = 50-FABRIC
fabric_version = 0.32.9+1.16

View file

@ -0,0 +1,21 @@
def getSubprojectVersion(project) {
// Get the version from the gradle.properties file
return project.properties["${project.name}-version"]
}
def moduleDependencies(project, List<String> depNames) {
def deps = depNames.iterator().collect { project.dependencies.project(path: ":$it", configuration: 'namedElements') }
project.dependencies {
deps.each {
api it
}
}
}
ext {
getSubprojectVersion = this.&getSubprojectVersion
moduleDependencies = this.&moduleDependencies
compileOnlyDependencies = this.&compileOnlyDependencies
}

Binary file not shown.

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

269
gradlew vendored Normal file → Executable file
View file

@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright 2015 the original author or authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -17,78 +17,113 @@
# #
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
@ -105,79 +140,95 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD=$( ulimit -H -n ) ||
MAX_FD="$MAX_FD_LIMIT" warn "Could not query maximum file descriptor limit"
fi esac
ulimit -n $MAX_FD case $MAX_FD in #(
if [ $? -ne 0 ] ; then '' | soft) :;; #(
warn "Could not set maximum file descriptor limit: $MAX_FD" *)
fi ulimit -n "$MAX_FD" ||
else warn "Could not set maximum file descriptor limit to $MAX_FD"
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=`save "$@"` # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

22
gradlew.bat vendored
View file

@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if "%ERRORLEVEL%" == "0" goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -54,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -64,28 +64,14 @@ echo location of your Java installation.
goto fail goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell

903
javadoc.css Normal file
View file

@ -0,0 +1,903 @@
/*
* Javadoc style sheet
*/
@import url("resources/fonts/dejavu.css");
/*
* Styles for individual HTML elements.
*
* These are styles that are specific to individual HTML elements. Changing them affects the style of a particular
* HTML element throughout the page.
*/
body {
background-color: #ffffff;
color: #353833;
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
font-size: 14px;
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
iframe {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
overflow-y: scroll;
border: none;
}
a:link,
a:visited {
text-decoration: none;
color: #4a6782;
}
a[href]:hover,
a[href]:focus {
text-decoration: none;
color: #bb7a2a;
}
a[name] {
color: #353833;
}
pre {
font-family: "DejaVu Sans Mono", monospace;
font-size: 16px;
background-color: #fffadb;
border-radius: 5px;
}
h1 {
font-size: 20px;
}
h2 {
font-size: 18px;
}
h3 {
font-size: 16px;
}
h4 {
font-size: 13px;
}
h5 {
font-size: 12px;
}
h6 {
font-size: 11px;
}
ul {
list-style-type: disc;
}
code,
tt {
font-family: "DejaVu Sans Mono", monospace;
font-size: 14px;
padding-top: 4px;
margin-top: 8px;
line-height: 1.4em;
}
dt code {
font-family: "DejaVu Sans Mono", monospace;
font-size: 14px;
padding-top: 4px;
}
.summary-table dt code {
font-family: "DejaVu Sans Mono", monospace;
font-size: 14px;
vertical-align: top;
padding-top: 4px;
}
sup {
font-size: 8px;
}
button {
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
font-size: 14px;
}
/*
* Styles for HTML generated by javadoc.
*
* These are style classes that are used by the standard doclet to generate HTML documentation.
*/
/*
* Styles for document title and copyright.
*/
.clear {
clear: both;
height: 0;
overflow: hidden;
}
.about-language {
float: right;
padding: 0 21px 8px 8px;
font-size: 11px;
margin-top: -9px;
height: 2.9em;
}
.legal-copy {
margin-left: 0.5em;
}
.tab {
background-color: #0066ff;
color: #ffffff;
padding: 8px;
width: 5em;
font-weight: bold;
}
/*
* Styles for navigation bar.
*/
@media screen {
.flex-box {
position: fixed;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
.flex-header {
flex: 0 0 auto;
}
.flex-content {
flex: 1 1 auto;
overflow-y: auto;
}
}
.top-nav {
background-color: #4d7a97;
color: #ffffff;
float: left;
padding: 0;
width: 100%;
clear: right;
min-height: 2.8em;
padding-top: 10px;
overflow: hidden;
font-size: 12px;
}
.sub-nav {
background-color: #dee3e9;
float: left;
width: 100%;
overflow: hidden;
font-size: 12px;
}
.sub-nav div {
clear: left;
float: left;
padding: 0 0 5px 6px;
text-transform: uppercase;
}
.sub-nav .nav-list {
padding-top: 5px;
}
ul.nav-list {
display: block;
margin: 0 25px 0 0;
padding: 0;
}
ul.sub-nav-list {
float: left;
margin: 0 25px 0 0;
padding: 0;
}
ul.nav-list li {
list-style: none;
float: left;
padding: 5px 6px;
text-transform: uppercase;
}
.sub-nav .nav-list-search {
float: right;
margin: 0 0 0 0;
padding: 5px 6px;
clear: none;
}
.nav-list-search label {
position: relative;
right: -16px;
}
ul.sub-nav-list li {
list-style: none;
float: left;
padding-top: 10px;
}
.top-nav a:link,
.top-nav a:active,
.top-nav a:visited {
color: #ffffff;
text-decoration: none;
text-transform: uppercase;
}
.top-nav a:hover {
text-decoration: none;
color: #bb7a2a;
text-transform: uppercase;
}
.nav-bar-cell1-rev {
background-color: #f8981d;
color: #253441;
margin: auto 5px;
}
.skip-nav {
position: absolute;
top: auto;
left: -9999px;
overflow: hidden;
}
/*
* Hide navigation links and search box in print layout
*/
@media print {
ul.nav-list,
div.sub-nav {
display: none;
}
}
/*
* Styles for page header and footer.
*/
.title {
color: #2c4557;
margin: 10px 0;
}
.sub-title {
margin: 5px 0 0 0;
}
.header ul {
margin: 0 0 15px 0;
padding: 0;
}
.header ul li,
.footer ul li {
list-style: none;
font-size: 13px;
}
/*
* Styles for headings.
*/
body.class-declaration-page .summary h2,
body.class-declaration-page .details h2,
body.class-use-page h2,
body.module-declaration-page .block-list h2 {
font-style: italic;
padding: 0;
margin: 15px 0;
}
body.class-declaration-page .summary h3,
body.class-declaration-page .details h3,
body.class-declaration-page .summary .inherited-list h2 {
background-color: #dee3e9;
border: 1px solid #d0d9e0;
margin: 0 0 6px -8px;
padding: 7px 5px;
}
/*
* Styles for page layout containers.
*/
main {
clear: both;
padding: 10px 20px;
position: relative;
}
dl.notes > dt {
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
font-size: 12px;
font-weight: bold;
margin: 10px 0 0 0;
color: #4e4e4e;
}
dl.notes > dd {
margin: 5px 0 10px 0;
font-size: 15px;
font-family: "Roboto", "DejaVu Sans", "Helvetica Neue", Arial, Helvetica, sans-serif;
}
dl.name-value > dt {
margin-left: 1px;
font-size: 1.1em;
display: inline;
font-weight: bold;
}
dl.name-value > dd {
margin: 0 0 0 1px;
font-size: 1.1em;
display: inline;
}
/*
* Styles for lists.
*/
li.circle {
list-style: circle;
}
ul.horizontal li {
display: inline;
font-size: 0.9em;
}
div.inheritance {
margin: 0;
padding: 0;
}
div.inheritance div.inheritance {
margin-left: 2em;
}
ul.block-list,
ul.details-list,
ul.member-list,
ul.summary-list {
margin: 10px 0 10px 0;
padding: 0;
}
ul.block-list > li,
ul.details-list > li,
ul.member-list > li,
ul.summary-list > li {
list-style: none;
margin-bottom: 15px;
line-height: 1.4;
}
.summary-table dl,
.summary-table dl dt,
.summary-table dl dd {
margin-top: 0;
margin-bottom: 1px;
}
/*
* Styles for tables.
*/
.summary-table {
width: 100%;
border-spacing: 0;
border-left: 1px solid #eee;
border-right: 1px solid #eee;
border-bottom: 1px solid #eee;
}
.summary-table {
padding: 0;
}
.caption {
position: relative;
text-align: left;
background-repeat: no-repeat;
color: #253441;
font-weight: bold;
clear: none;
overflow: hidden;
padding: 0px;
padding-top: 10px;
padding-left: 1px;
margin: 0px;
white-space: pre;
}
.caption a:link,
.caption a:visited {
color: #1f389c;
}
.caption a:hover,
.caption a:active {
color: #ffffff;
}
.caption span {
white-space: nowrap;
padding-top: 5px;
padding-left: 12px;
padding-right: 12px;
padding-bottom: 7px;
display: inline-block;
float: left;
background-color: #f8981d;
border: none;
height: 16px;
}
div.table-tabs > button {
border: none;
cursor: pointer;
padding: 5px 12px 7px 12px;
font-weight: bold;
margin-right: 3px;
}
div.table-tabs > button.active-table-tab {
background: #f8981d;
color: #253441;
}
div.table-tabs > button.table-tab {
background: #4d7a97;
color: #ffffff;
}
.two-column-summary {
display: grid;
grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
}
.three-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, auto);
}
.four-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(10%, max-content) minmax(10%, max-content) minmax(10%, auto);
}
@media screen and (max-width: 600px) {
.two-column-summary {
display: grid;
grid-template-columns: 1fr;
}
}
@media screen and (max-width: 800px) {
.three-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(25%, auto);
}
.three-column-summary .col-last {
grid-column-end: span 2;
}
}
@media screen and (max-width: 1000px) {
.four-column-summary {
display: grid;
grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
}
}
.summary-table > div {
text-align: left;
padding: 8px 3px 3px 7px;
}
.col-first,
.col-second,
.col-last,
.col-constructor-name,
.col-deprecated-item-name {
vertical-align: top;
padding-right: 0;
padding-top: 8px;
padding-bottom: 3px;
}
.table-header {
background: #dee3e9;
font-weight: bold;
}
.col-first,
.col-first {
font-size: 13px;
}
.col-second,
.col-second,
.col-last,
.col-constructor-name,
.col-deprecated-item-name,
.col-last {
font-size: 13px;
}
.col-first,
.col-second,
.col-constructor-name {
vertical-align: top;
overflow: auto;
}
.col-last {
white-space: normal;
}
.col-first a:link,
.col-first a:visited,
.col-second a:link,
.col-second a:visited,
.col-first a:link,
.col-first a:visited,
.col-second a:link,
.col-second a:visited,
.col-constructor-name a:link,
.col-constructor-name a:visited,
.col-deprecated-item-name a:link,
.col-deprecated-item-name a:visited,
.constant-values-container a:link,
.constant-values-container a:visited,
.all-classes-container a:link,
.all-classes-container a:visited,
.all-packages-container a:link,
.all-packages-container a:visited {
font-weight: bold;
}
.table-sub-heading-color {
background-color: #eeeeff;
}
.even-row-color,
.even-row-color .table-header {
background-color: #ffffff;
}
.odd-row-color,
.odd-row-color .table-header {
background-color: #eeeeef;
}
/*
* Styles for contents.
*/
.deprecated-content {
margin: 0;
padding: 10px 0;
}
div.block {
font-size: 15px;
font-family: "Roboto", "DejaVu Sans", "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.col-last div {
padding-top: 0;
}
.col-last a {
padding-bottom: 3px;
}
.module-signature,
.package-signature,
.type-signature,
.member-signature {
font-family: "DejaVu Sans Mono", monospace;
font-size: 14px;
margin: 14px 0;
white-space: pre-wrap;
}
.module-signature,
.package-signature,
.type-signature {
margin-top: 0;
}
.member-signature .type-parameters-long,
.member-signature .parameters,
.member-signature .exceptions {
display: inline-block;
vertical-align: top;
white-space: pre;
}
.member-signature .type-parameters {
white-space: normal;
}
/*
* Styles for formatting effect.
*/
.source-line-no {
color: green;
padding: 0 30px 0 0;
}
h1.hidden {
visibility: hidden;
overflow: hidden;
font-size: 10px;
}
.block {
display: block;
margin: 0 10px 5px 0;
color: #474747;
}
.deprecated-label,
.descfrm-type-label,
.implementation-label,
.member-name-label,
.member-name-link,
.module-label-in-package,
.module-label-in-type,
.override-specify-label,
.package-label-in-type,
.package-hierarchy-label,
.type-name-label,
.type-name-link,
.search-tag-link {
font-weight: bold;
}
.deprecation-comment,
.help-footnote,
.interface-name {
font-style: italic;
}
.deprecation-block {
font-size: 14px;
font-family: "DejaVu Serif", Georgia, "Times New Roman", Times, serif;
border-style: solid;
border-width: thin;
border-radius: 10px;
padding: 10px;
margin-bottom: 10px;
margin-right: 10px;
display: inline-block;
}
div.block div.deprecation-comment,
div.block div.block span.emphasized-phrase,
div.block div.block span.interface-name {
font-style: normal;
}
/*
* Styles specific to HTML5 elements.
*/
main,
nav,
header,
footer,
section {
display: block;
}
/*
* Styles for javadoc search.
*/
.ui-autocomplete-category {
font-weight: bold;
font-size: 15px;
padding: 7px 0 7px 3px;
background-color: #4d7a97;
color: #ffffff;
}
.result-item {
font-size: 13px;
}
.ui-autocomplete {
max-height: 85%;
max-width: 65%;
overflow-y: scroll;
overflow-x: scroll;
white-space: nowrap;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
}
ul.ui-autocomplete {
position: fixed;
z-index: 999999;
}
ul.ui-autocomplete li {
float: left;
clear: both;
width: 100%;
}
.result-highlight {
font-weight: bold;
}
#search {
background-image: url("resources/glass.png");
background-size: 13px;
background-repeat: no-repeat;
background-position: 2px 3px;
padding-left: 20px;
position: relative;
right: -18px;
width: 400px;
}
#reset {
background-color: rgb(255, 255, 255);
background-image: url("resources/x.png");
background-position: center;
background-repeat: no-repeat;
background-size: 12px;
border: 0 none;
width: 16px;
height: 16px;
position: relative;
left: -4px;
top: -4px;
font-size: 0px;
}
.watermark {
color: #545454;
}
.search-tag-desc-result {
font-style: italic;
font-size: 11px;
}
.search-tag-holder-result {
font-style: italic;
font-size: 12px;
}
.search-tag-result:target {
background-color: yellow;
}
.module-graph span {
display: none;
position: absolute;
}
.module-graph:hover span {
display: block;
margin: -100px 0 0 100px;
z-index: 1;
}
.inherited-list {
margin: 10px 0 10px 0;
}
section.description {
line-height: 1.4;
}
.summary section[class$="-summary"],
.details section[class$="-details"],
.class-uses .detail,
.serialized-class-details {
padding: 0px 20px 5px 10px;
border: 1px solid #ededed;
background-color: #f8f8f8;
}
.inherited-list,
section[class$="-details"] .detail {
padding: 0 0 5px 8px;
background-color: #ffffff;
border: none;
}
.vertical-separator {
padding: 0 5px;
}
ul.help-section-list {
margin: 0;
}
/*
* Indicator icon for external links.
*/
main a[href*="://"]::after
{
content: "";
display: inline-block;
background-image: url('data:image/svg+xml; utf8, \
<svg xmlns="http://www.w3.org/2000/svg" width="768" height="768">\
<path d="M584 664H104V184h216V80H0v688h688V448H584zM384 0l132 \
132-240 240 120 120 240-240 132 132V0z" fill="%234a6782"/>\
</svg>');
background-size: 100% 100%;
width: 7px;
height: 7px;
margin-left: 2px;
margin-bottom: 4px;
}
main a[href*="://"]:hover::after,
main a[href*="://"]:focus::after
{
background-image: url('data:image/svg+xml; utf8, \
<svg xmlns="http://www.w3.org/2000/svg" width="768" height="768">\
<path d="M584 664H104V184h216V80H0v688h688V448H584zM384 0l132 \
132-240 240 120 120 240-240 132 132V0z" fill="%23bb7a2a"/>\
</svg>');
}
/*
* Styles for user-provided tables.
*
* borderless:
* No borders, vertical margins, styled caption.
* This style is provided for use with existing doc comments.
* In general, borderless tables should not be used for layout purposes.
*
* plain:
* Plain borders around table and cells, vertical margins, styled caption.
* Best for small tables or for complex tables for tables with cells that span
* rows and columns, when the "striped" style does not work well.
*
* striped:
* Borders around the table and vertical borders between cells, striped rows,
* vertical margins, styled caption.
* Best for tables that have a header row, and a body containing a series of simple rows.
*/
table.borderless,
table.plain,
table.striped {
margin-top: 10px;
margin-bottom: 10px;
}
table.borderless > caption,
table.plain > caption,
table.striped > caption {
font-weight: bold;
font-size: smaller;
}
table.borderless th,
table.borderless td,
table.plain th,
table.plain td,
table.striped th,
table.striped td {
padding: 2px 5px;
}
table.borderless,
table.borderless > thead > tr > th,
table.borderless > tbody > tr > th,
table.borderless > tr > th,
table.borderless > thead > tr > td,
table.borderless > tbody > tr > td,
table.borderless > tr > td {
border: none;
}
table.borderless > thead > tr,
table.borderless > tbody > tr,
table.borderless > tr {
background-color: transparent;
}
table.plain {
border-collapse: collapse;
border: 1px solid black;
}
table.plain > thead > tr,
table.plain > tbody tr,
table.plain > tr {
background-color: transparent;
}
table.plain > thead > tr > th,
table.plain > tbody > tr > th,
table.plain > tr > th,
table.plain > thead > tr > td,
table.plain > tbody > tr > td,
table.plain > tr > td {
border: 1px solid black;
}
table.striped {
border-collapse: collapse;
border: 1px solid black;
}
table.striped > thead {
background-color: #e3e3e3;
}
table.striped > thead > tr > th,
table.striped > thead > tr > td {
border: 1px solid black;
}
table.striped > tbody > tr:nth-child(even) {
background-color: #eee;
}
table.striped > tbody > tr:nth-child(odd) {
background-color: #fff;
}
table.striped > tbody > tr > th,
table.striped > tbody > tr > td {
border-left: 1px solid black;
border-right: 1px solid black;
}
table.striped > tbody > tr > th {
font-weight: normal;
}
/**
* Tweak font sizes and paddings for small screens.
*/
@media screen and (max-width: 1050px) {
#search {
width: 300px;
}
}
@media screen and (max-width: 800px) {
#search {
width: 200px;
}
.top-nav,
.bottom-nav {
font-size: 11px;
padding-top: 6px;
}
.sub-nav {
font-size: 11px;
}
.about-language {
padding-right: 16px;
}
ul.nav-list li,
.sub-nav .nav-list-search {
padding: 6px;
}
ul.sub-nav-list li {
padding-top: 5px;
}
main {
padding: 10px;
}
.summary section[class$="-summary"],
.details section[class$="-details"],
.class-uses .detail,
.serialized-class-details {
padding: 0 8px 5px 8px;
}
body {
-webkit-text-size-adjust: none;
}
}
@media screen and (max-width: 500px) {
#search {
width: 150px;
}
.top-nav,
.bottom-nav {
font-size: 10px;
}
.sub-nav {
font-size: 10px;
}
.about-language {
font-size: 10px;
padding-right: 12px;
}
}

6
jitpack.yml Normal file
View file

@ -0,0 +1,6 @@
# From https://github.com/jitpack/jitpack.io/issues/4506#issuecomment-864562270
before_install:
- source "$HOME/.sdkman/bin/sdkman-init.sh"
- sdk update
- sdk install java 17.0.1-tem
- sdk use java 17.0.1-tem

View file

@ -4,6 +4,10 @@ pluginManagement {
name = 'Fabric' name = 'Fabric'
url = 'https://maven.fabricmc.net/' url = 'https://maven.fabricmc.net/'
} }
mavenLocal()
gradlePluginPortal() gradlePluginPortal()
} }
} }
include("together-v1")

View file

@ -0,0 +1,12 @@
package org.anti_ad.mc.ipn.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// Included from "Inventory Profiles Next" (https://github.com/blackd/Inventory-Profiles)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IPNIgnore {
}

View file

@ -0,0 +1,159 @@
package org.betterx.bclib;
import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI;
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.*;
import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource;
import org.betterx.bclib.api.v2.generator.BCLibNetherBiomeSource;
import org.betterx.bclib.api.v2.generator.GeneratorOptions;
import org.betterx.bclib.api.v2.levelgen.LevelGenEvents;
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome;
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeBuilder;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import org.betterx.bclib.api.v2.levelgen.structures.TemplatePiece;
import org.betterx.bclib.api.v2.levelgen.surface.rules.Conditions;
import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates;
import org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers;
import org.betterx.bclib.commands.CommandRegistry;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.recipes.AnvilRecipe;
import org.betterx.bclib.recipes.CraftingRecipes;
import org.betterx.bclib.registry.BaseBlockEntities;
import org.betterx.bclib.registry.BaseRegistry;
import org.betterx.worlds.together.WorldsTogether;
import org.betterx.worlds.together.tag.v3.TagManager;
import org.betterx.worlds.together.util.Logger;
import org.betterx.worlds.together.world.WorldConfig;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Blocks;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import java.util.List;
public class BCLib implements ModInitializer {
public static final String MOD_ID = "bclib";
public static final Logger LOGGER = new Logger(MOD_ID);
@Override
public void onInitialize() {
LevelGenEvents.register();
BlockPredicates.ensureStaticInitialization();
BaseRegistry.register();
GeneratorOptions.init();
BaseBlockEntities.register();
BCLibEndBiomeSource.register();
BCLibNetherBiomeSource.register();
TagManager.ensureStaticallyLoaded();
CraftingRecipes.init();
WorldConfig.registerModCache(MOD_ID);
DataExchangeAPI.registerMod(MOD_ID);
AnvilRecipe.register();
Conditions.registerAll();
CommandRegistry.register();
DataExchangeAPI.registerDescriptors(List.of(
HelloClient.DESCRIPTOR,
HelloServer.DESCRIPTOR,
RequestFiles.DESCRIPTOR,
SendFiles.DESCRIPTOR,
Chunker.DESCRIPTOR
)
);
BCLibPatch.register();
TemplatePiece.ensureStaticInitialization();
PlacementModifiers.ensureStaticInitialization();
Configs.save();
WorldsTogether.FORCE_SERVER_TO_BETTERX_PRESET = Configs.SERVER_CONFIG.forceBetterXPreset();
if (false && isDevEnvironment()) {
BCLBiome theYellow = BCLBiomeBuilder
.start(makeID("the_yellow"))
.precipitation(Biome.Precipitation.NONE)
.temperature(1.0f)
.wetness(1.0f)
.fogColor(0xFFFF00)
.waterColor(0x777700)
.waterFogColor(0xFFFF00)
.skyColor(0xAAAA00)
.addNetherClimateParamater(-1, 1)
.surface(Blocks.YELLOW_CONCRETE)
.build();
BiomeAPI.registerEndLandBiome(theYellow);
BCLBiome theBlue = BCLBiomeBuilder
.start(makeID("the_blue"))
.precipitation(Biome.Precipitation.NONE)
.temperature(1.0f)
.wetness(1.0f)
.fogColor(0x0000FF)
.waterColor(0x000077)
.waterFogColor(0x0000FF)
.skyColor(0x0000AA)
.addNetherClimateParamater(-1, 1)
.surface(Blocks.LIGHT_BLUE_CONCRETE)
.build();
BiomeAPI.registerEndLandBiome(theBlue);
BCLBiome theGray = BCLBiomeBuilder
.start(makeID("the_gray"))
.precipitation(Biome.Precipitation.NONE)
.temperature(1.0f)
.wetness(1.0f)
.fogColor(0xFFFFFF)
.waterColor(0x777777)
.waterFogColor(0xFFFFFF)
.skyColor(0xAAAAAA)
.addNetherClimateParamater(-1, 1)
.surface(Blocks.GRAY_CONCRETE)
.build();
BiomeAPI.registerEndVoidBiome(theGray);
BCLBiome theOrange = BCLBiomeBuilder
.start(makeID("the_orange"))
.precipitation(Biome.Precipitation.NONE)
.temperature(1.0f)
.wetness(1.0f)
.fogColor(0xFF7700)
.waterColor(0x773300)
.waterFogColor(0xFF7700)
.skyColor(0xAA7700)
.addNetherClimateParamater(-1, 1.1f)
.surface(Blocks.ORANGE_CONCRETE)
.build();
BiomeAPI.registerNetherBiome(theOrange);
BCLBiome thePurple = BCLBiomeBuilder
.start(makeID("the_purple"))
.precipitation(Biome.Precipitation.NONE)
.temperature(1.0f)
.wetness(1.0f)
.fogColor(0xFF00FF)
.waterColor(0x770077)
.waterFogColor(0xFF00FF)
.skyColor(0xAA00AA)
.addNetherClimateParamater(-1.1f, 1)
.surface(Blocks.PURPLE_CONCRETE)
.build();
BiomeAPI.registerNetherBiome(thePurple);
}
}
public static boolean isDevEnvironment() {
return FabricLoader.getInstance().isDevelopmentEnvironment();
}
public static boolean isClient() {
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT;
}
public static ResourceLocation makeID(String path) {
return new ResourceLocation(MOD_ID, path);
}
}

View file

@ -0,0 +1,183 @@
package org.betterx.bclib;
import org.betterx.bclib.api.v2.datafixer.DataFixerAPI;
import org.betterx.bclib.api.v2.datafixer.ForcedLevelPatch;
import org.betterx.bclib.api.v2.datafixer.MigrationProfile;
import org.betterx.bclib.api.v2.generator.GeneratorOptions;
import org.betterx.bclib.api.v2.levelgen.LevelGenUtil;
import org.betterx.bclib.config.Configs;
import net.minecraft.core.RegistryAccess;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.dimension.LevelStem;
public final class BCLibPatch {
public static void register() {
// TODO separate values in config on client side (config screen)
if (Configs.MAIN_CONFIG.repairBiomes() && (GeneratorOptions.fixEndBiomeSource() || GeneratorOptions.fixNetherBiomeSource())) {
DataFixerAPI.registerPatch(BiomeSourcePatch::new);
}
}
}
final class BiomeSourcePatch extends ForcedLevelPatch {
private static final String NETHER_BIOME_SOURCE = "bclib:nether_biome_source";
private static final String END_BIOME_SOURCE = "bclib:end_biome_source";
private static final String MC_NETHER = "minecraft:the_nether";
private static final String MC_END = "minecraft:the_end";
protected BiomeSourcePatch() {
super(BCLib.MOD_ID, "1.2.1");
}
@Override
protected Boolean runLevelDatPatch(CompoundTag root, MigrationProfile profile) {
//make sure we have a working generators file before attempting to patch
LevelGenUtil.migrateGeneratorSettings();
final CompoundTag worldGenSettings = root.getCompound("Data").getCompound("WorldGenSettings");
final CompoundTag dimensions = worldGenSettings.getCompound("dimensions");
final RegistryAccess registryAccess = RegistryAccess.builtinCopy();
final RegistryOps<Tag> registryOps = RegistryOps.create(NbtOps.INSTANCE, registryAccess);
boolean result = false;
result |= checkDimension(worldGenSettings, dimensions, registryAccess, registryOps, LevelStem.NETHER);
result |= checkDimension(worldGenSettings, dimensions, registryAccess, registryOps, LevelStem.END);
System.out.println("Dimensions:" + dimensions);
return result;
// if (root != null) return false;
//
// boolean result = false;
//
// if (GeneratorOptions.fixNetherBiomeSource()) {
// if (!dimensions.contains(MC_NETHER) || !isBCLibEntry(dimensions.getCompound(MC_NETHER))) {
// CompoundTag dimRoot = new CompoundTag();
// dimRoot.put("generator", makeNetherGenerator(seed));
// dimRoot.putString("type", MC_NETHER);
// dimensions.put(MC_NETHER, dimRoot);
// result = true;
// }
// }
//
// if (GeneratorOptions.fixEndBiomeSource()) {
// if (!dimensions.contains(MC_END) || !isBCLibEntry(dimensions.getCompound(MC_END))) {
// CompoundTag dimRoot = new CompoundTag();
// dimRoot.put("generator", makeEndGenerator(seed));
// dimRoot.putString("type", MC_END);
// dimensions.put(MC_END, dimRoot);
// result = true;
// }
// }
//
// return result;
}
private boolean checkDimension(
CompoundTag worldGenSettings,
CompoundTag dimensions,
RegistryAccess registryAccess,
RegistryOps<Tag> registryOps,
ResourceKey<LevelStem> dimensionKey
) {
boolean result = false;
// final long seed = worldGenSettings.contains("seed")
// ? worldGenSettings.getLong("seed")
// : MHelper.RANDOM.nextLong();
//
// final boolean genStructures = !worldGenSettings.contains("generate_features") || worldGenSettings.getBoolean(
// "generate_features");
//
// final boolean genBonusChest = worldGenSettings.contains("bonus_chest") && worldGenSettings.getBoolean(
// "bonus_chest");
//
//
// CompoundTag dimensionTag = dimensions.getCompound(dimensionKey.location().toString());
// Optional<WorldGenSettings> oWorldGen = WorldGenSettings.CODEC
// .parse(new Dynamic<>(registryOps, worldGenSettings))
// .result();
//
// Optional<LevelStem> oLevelStem = LevelStem.CODEC
// .parse(new Dynamic<>(registryOps, dimensionTag))
// .resultOrPartial(BCLib.LOGGER::error);
//
// Optional<ChunkGenerator> netherGenerator = oLevelStem.map(l -> l.generator());
// int biomeSourceVersion = LevelGenUtil.getBiomeVersionForGenerator(netherGenerator.orElse(null));
// int targetVersion = LevelGenUtil.getBiomeVersionForCurrentWorld(dimensionKey);
// if (biomeSourceVersion != targetVersion) {
// Optional<Holder<LevelStem>> refLevelStem = LevelGenUtil.referenceStemForVersion(
// dimensionKey,
// targetVersion,
// registryAccess,
// oWorldGen.map(g -> g.seed()).orElse(seed),
// oWorldGen.map(g -> g.generateStructures()).orElse(genStructures),
// oWorldGen.map(g -> g.generateBonusChest()).orElse(genBonusChest)
// );
//
// BCLib.LOGGER.warning("The world uses the BiomeSource Version " + biomeSourceVersion + " but should have " + targetVersion + ".");
// BCLib.LOGGER.warning("Dimension: " + dimensionKey);
// BCLib.LOGGER.warning("Found: " + netherGenerator);
// BCLib.LOGGER.warning("Should: " + refLevelStem.map(l -> l.value().generator()));
//
// if (refLevelStem.isPresent()) {
// var levelStem = refLevelStem.get();
// BCLib.LOGGER.warning("Repairing level.dat in order to ensure world continuity.");
// var codec = LevelStem.CODEC.orElse(levelStem.value());
// var encodeResult = codec.encodeStart(registryOps, levelStem.value());
// if (encodeResult.result().isPresent()) {
// dimensions.put(dimensionKey.location().toString(), encodeResult.result().get());
// result = true;
// } else {
// BCLib.LOGGER.error("Unable to encode '" + dimensionKey + "' generator for level.dat.");
// }
// } else {
// BCLib.LOGGER.error("Unable to update '" + dimensionKey + "' generator in level.dat.");
// }
// }
return result;
}
private boolean isBCLibEntry(CompoundTag dimRoot) {
String type = dimRoot.getCompound("generator").getCompound("biome_source").getString("type");
if (type.isEmpty() || type.length() < 5) {
return false;
}
return type.startsWith("bclib");
}
public static CompoundTag makeNetherGenerator(long seed) {
CompoundTag generator = new CompoundTag();
generator.putString("type", "minecraft:noise");
generator.putString("settings", "minecraft:nether");
generator.putLong("seed", seed);
CompoundTag biomeSource = new CompoundTag();
biomeSource.putString("type", NETHER_BIOME_SOURCE);
biomeSource.putLong("seed", seed);
generator.put("biome_source", biomeSource);
return generator;
}
public static CompoundTag makeEndGenerator(long seed) {
CompoundTag generator = new CompoundTag();
generator.putString("type", "minecraft:noise");
generator.putString("settings", "minecraft:end");
generator.putLong("seed", seed);
CompoundTag biomeSource = new CompoundTag();
biomeSource.putString("type", END_BIOME_SOURCE);
biomeSource.putLong("seed", seed);
generator.put("biome_source", biomeSource);
return generator;
}
}

View file

@ -0,0 +1,201 @@
package org.betterx.bclib.api.v2;
import org.betterx.bclib.util.BlocksHelper;
import org.betterx.bclib.util.WeightedList;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
public class BonemealAPI {
private static final Map<ResourceLocation, Map<Block, WeightedList<BiConsumer<Level, BlockPos>>>> WATER_GRASS_BIOMES = Maps.newHashMap();
private static final Map<ResourceLocation, Map<Block, WeightedList<BiConsumer<Level, BlockPos>>>> LAND_GRASS_BIOMES = Maps.newHashMap();
private static final Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> WATER_GRASS_TYPES = Maps.newHashMap();
private static final Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> LAND_GRASS_TYPES = Maps.newHashMap();
private static final Map<Block, Block> SPREADABLE_BLOCKS = Maps.newHashMap();
private static final Set<Block> TERRAIN_TO_SPREAD = Sets.newHashSet();
private static final Set<Block> TERRAIN = Sets.newHashSet();
public static void addSpreadableBlock(Block spreadableBlock, Block surfaceForSpread) {
SPREADABLE_BLOCKS.put(spreadableBlock, surfaceForSpread);
TERRAIN_TO_SPREAD.add(surfaceForSpread);
TERRAIN.add(surfaceForSpread);
}
public static boolean isTerrain(Block block) {
return TERRAIN.contains(block);
}
public static boolean isSpreadableTerrain(Block block) {
return TERRAIN_TO_SPREAD.contains(block);
}
public static Block getSpreadable(Block block) {
return SPREADABLE_BLOCKS.get(block);
}
public static void addLandGrass(Block plant, Block... terrain) {
addLandGrass(makeConsumer(plant), terrain);
}
public static void addLandGrass(BiConsumer<Level, BlockPos> plant, Block... terrain) {
for (Block block : terrain) {
addLandGrass(plant, block, 1F);
}
}
public static void addLandGrass(ResourceLocation biome, Block plant, Block... terrain) {
addLandGrass(biome, makeConsumer(plant), terrain);
}
public static void addLandGrass(ResourceLocation biome, BiConsumer<Level, BlockPos> plant, Block... terrain) {
for (Block block : terrain) {
addLandGrass(biome, plant, block, 1F);
}
}
public static void addLandGrass(Block plant, Block terrain, float chance) {
addLandGrass(makeConsumer(plant), terrain, chance);
}
public static void addLandGrass(BiConsumer<Level, BlockPos> plant, Block terrain, float chance) {
WeightedList<BiConsumer<Level, BlockPos>> list = LAND_GRASS_TYPES.get(terrain);
if (list == null) {
list = new WeightedList<>();
LAND_GRASS_TYPES.put(terrain, list);
}
TERRAIN.add(terrain);
list.add(plant, chance);
}
public static void addLandGrass(ResourceLocation biome, Block plant, Block terrain, float chance) {
addLandGrass(biome, makeConsumer(plant), terrain, chance);
}
public static void addLandGrass(
ResourceLocation biome,
BiConsumer<Level, BlockPos> plant,
Block terrain,
float chance
) {
Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> map = LAND_GRASS_BIOMES.get(biome);
if (map == null) {
map = Maps.newHashMap();
LAND_GRASS_BIOMES.put(biome, map);
}
WeightedList<BiConsumer<Level, BlockPos>> list = map.get(terrain);
if (list == null) {
list = new WeightedList<>();
map.put(terrain, list);
}
TERRAIN.add(terrain);
list.add(plant, chance);
}
public static void addWaterGrass(Block plant, Block... terrain) {
addWaterGrass(makeConsumer(plant), terrain);
}
public static void addWaterGrass(BiConsumer<Level, BlockPos> plant, Block... terrain) {
for (Block block : terrain) {
addWaterGrass(plant, block, 1F);
}
}
public static void addWaterGrass(ResourceLocation biome, Block plant, Block... terrain) {
addWaterGrass(biome, makeConsumer(plant), terrain);
}
public static void addWaterGrass(ResourceLocation biome, BiConsumer<Level, BlockPos> plant, Block... terrain) {
for (Block block : terrain) {
addWaterGrass(biome, plant, block, 1F);
}
}
public static void addWaterGrass(Block plant, Block terrain, float chance) {
addWaterGrass(makeConsumer(plant), terrain, chance);
}
public static void addWaterGrass(BiConsumer<Level, BlockPos> plant, Block terrain, float chance) {
WeightedList<BiConsumer<Level, BlockPos>> list = WATER_GRASS_TYPES.get(terrain);
if (list == null) {
list = new WeightedList<>();
WATER_GRASS_TYPES.put(terrain, list);
}
TERRAIN.add(terrain);
list.add(plant, chance);
}
public static void addWaterGrass(ResourceLocation biome, Block plant, Block terrain, float chance) {
addWaterGrass(biome, makeConsumer(plant), terrain, chance);
}
public static void addWaterGrass(
ResourceLocation biome,
BiConsumer<Level, BlockPos> plant,
Block terrain,
float chance
) {
Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> map = WATER_GRASS_BIOMES.get(biome);
if (map == null) {
map = Maps.newHashMap();
WATER_GRASS_BIOMES.put(biome, map);
}
WeightedList<BiConsumer<Level, BlockPos>> list = map.get(terrain);
if (list == null) {
list = new WeightedList<>();
map.put(terrain, list);
}
TERRAIN.add(terrain);
list.add(plant, chance);
}
public static BiConsumer<Level, BlockPos> getLandGrass(
ResourceLocation biomeID,
Block terrain,
RandomSource random
) {
Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> map = LAND_GRASS_BIOMES.get(biomeID);
WeightedList<BiConsumer<Level, BlockPos>> list;
if (map != null) {
list = map.get(terrain);
if (list == null) {
list = LAND_GRASS_TYPES.get(terrain);
}
} else {
list = LAND_GRASS_TYPES.get(terrain);
}
return list == null ? null : list.get(random);
}
public static BiConsumer<Level, BlockPos> getWaterGrass(
ResourceLocation biomeID,
Block terrain,
RandomSource random
) {
Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> map = WATER_GRASS_BIOMES.get(biomeID);
WeightedList<BiConsumer<Level, BlockPos>> list;
if (map != null) {
list = map.get(terrain);
if (list == null) {
list = WATER_GRASS_TYPES.get(terrain);
}
} else {
list = WATER_GRASS_TYPES.get(terrain);
}
return list == null ? null : list.get(random);
}
private static BiConsumer<Level, BlockPos> makeConsumer(Block block) {
return (level, pos) -> BlocksHelper.setWithoutUpdate(level, pos, block);
}
}

View file

@ -0,0 +1,23 @@
package org.betterx.bclib.api.v2;
import org.betterx.bclib.mixin.common.ComposterBlockAccessor;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
public class ComposterAPI {
public static Block allowCompost(float chance, Block block) {
if (block != null) {
allowCompost(chance, block.asItem());
}
return block;
}
public static Item allowCompost(float chance, Item item) {
if (item != null && item != Items.AIR) {
ComposterBlockAccessor.callAdd(chance, item);
}
return item;
}
}

View file

@ -0,0 +1,35 @@
package org.betterx.bclib.api.v2;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
public class DiggerItemSpeed {
public static final List<SpeedModifier> modifiers = new LinkedList<>();
@FunctionalInterface
public interface SpeedModifier {
Optional<Float> calculateSpeed(ItemStack stack, BlockState state, float initialSpeed, float currentSpeed);
}
public static void addModifier(SpeedModifier mod) {
modifiers.add(mod);
}
public static Optional<Float> getModifiedSpeed(ItemStack stack, BlockState state, float initialSpeed) {
float currentSpeed = initialSpeed;
Optional<Float> speed = Optional.empty();
for (SpeedModifier mod : modifiers) {
Optional<Float> res = mod.calculateSpeed(stack, state, initialSpeed, currentSpeed);
if (res.isPresent()) {
currentSpeed = res.get();
speed = res;
}
}
return speed;
}
}

View file

@ -0,0 +1,145 @@
package org.betterx.bclib.api.v2;
import org.betterx.bclib.api.v2.datafixer.DataFixerAPI;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.world.level.CustomSpawner;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.ServerLevelData;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* provides some lifetime hooks for a Minecraft instance
*/
public class LifeCycleAPI {
private final static List<LevelLoadBiomesCall> onLoadLevelBiomes = new ArrayList<>(2);
private final static List<LevelLoadCall> onLoadLevel = new ArrayList<>(2);
private final static List<BeforeLevelLoadCall> beforeLoadLevel = new ArrayList<>(2);
/**
* Register a callback that is called before a level is loaded or created,
* but after the {@link org.betterx.worlds.together.world.WorldConfig} was initialized and patches from
* the {@link DataFixerAPI} were applied.
*
* @param call The callback Method
*/
public static void beforeLevelLoad(BeforeLevelLoadCall call) {
beforeLoadLevel.add(call);
}
/**
* Register a callback that is called when a new {@code ServerLevel is instantiated}.
* This callback will receive the world seed as well as it's biome registry.
*
* @param call The calbback Method
*/
public static void onLevelLoad(LevelLoadBiomesCall call) {
onLoadLevelBiomes.add(call);
}
/**
* Register a callback that is called when a new {@code ServerLevel is instantiated}.
* This callbacl will receiv all parameters that were passed to the ServerLevel's constructor
*
* @param call The calbback Method
*/
public static void onLevelLoad(LevelLoadCall call) {
onLoadLevel.add(call);
}
/**
* For internal use, You should not call this method!
*/
public static void _runBeforeLevelLoad() {
beforeLoadLevel.forEach(c -> c.beforeLoad());
}
/**
* For internal use, You should not call this method!
*
* @param minecraftServer
* @param executor
* @param levelStorageAccess
* @param serverLevelData
* @param resourceKey
* @param chunkProgressListener
* @param bl
* @param l
* @param list
* @param bl2
*/
public static void _runLevelLoad(
ServerLevel world,
MinecraftServer minecraftServer,
Executor executor,
LevelStorageSource.LevelStorageAccess levelStorageAccess,
ServerLevelData serverLevelData,
ResourceKey<Level> resourceKey,
ChunkProgressListener chunkProgressListener,
boolean bl,
long l,
List<CustomSpawner> list,
boolean bl2
) {
onLoadLevel.forEach(c -> c.onLoad(
world,
minecraftServer,
executor,
levelStorageAccess,
serverLevelData,
resourceKey,
chunkProgressListener,
bl,
l,
list,
bl2
));
final long seed = world.getSeed();
final Registry<Biome> biomeRegistry = world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
onLoadLevelBiomes.forEach(c -> c.onLoad(world, seed, biomeRegistry));
}
/**
* A callback function that is used for each new ServerLevel instance
*/
public interface BeforeLevelLoadCall {
void beforeLoad();
}
/**
* A callback function that is used for each new ServerLevel instance
*/
public interface LevelLoadBiomesCall {
void onLoad(ServerLevel world, long seed, Registry<Biome> registry);
}
/**
* A callback function that is used for each new ServerLevel instance
*/
public interface LevelLoadCall {
void onLoad(
ServerLevel world,
MinecraftServer minecraftServer,
Executor executor,
LevelStorageSource.LevelStorageAccess levelStorageAccess,
ServerLevelData serverLevelData,
ResourceKey<Level> resourceKey,
ChunkProgressListener chunkProgressListener,
boolean bl,
long l,
List<CustomSpawner> list,
boolean bl2
);
}
}

View file

@ -0,0 +1,49 @@
package org.betterx.bclib.api.v2;
import org.betterx.bclib.integration.ModIntegration;
import net.fabricmc.loader.api.FabricLoader;
import com.google.common.collect.Lists;
import java.util.List;
public class ModIntegrationAPI {
private static final List<ModIntegration> INTEGRATIONS = Lists.newArrayList();
private static final boolean HAS_CANVAS = FabricLoader.getInstance().isModLoaded("canvas");
/**
* Registers mod integration
*
* @param integration
* @return
*/
public static ModIntegration register(ModIntegration integration) {
INTEGRATIONS.add(integration);
return integration;
}
/**
* Get all registered mod integrations.
*
* @return {@link List} of {@link ModIntegration}.
*/
public static List<ModIntegration> getIntegrations() {
return INTEGRATIONS;
}
/**
* Initialize all integrations, only for internal usage.
*/
public static void registerAll() {
INTEGRATIONS.forEach(integration -> {
if (integration.modIsInstalled()) {
integration.init();
}
});
}
public static boolean hasCanvas() {
return HAS_CANVAS;
}
}

View file

@ -0,0 +1,149 @@
package org.betterx.bclib.api.v2;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.levelgen.biomes.InternalBiomeAPI;
import org.betterx.bclib.blocks.BaseBarrelBlock;
import org.betterx.bclib.blocks.BaseChestBlock;
import org.betterx.bclib.blocks.BaseFurnaceBlock;
import org.betterx.bclib.blocks.BaseSignBlock;
import org.betterx.bclib.client.render.BCLRenderLayer;
import org.betterx.bclib.client.render.BaseChestBlockEntityRenderer;
import org.betterx.bclib.client.render.BaseSignBlockEntityRenderer;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.interfaces.PostInitable;
import org.betterx.bclib.interfaces.RenderLayerProvider;
import org.betterx.bclib.interfaces.TagProvider;
import org.betterx.bclib.interfaces.tools.*;
import org.betterx.bclib.registry.BaseBlockEntities;
import org.betterx.worlds.together.tag.v3.MineableTags;
import org.betterx.worlds.together.tag.v3.TagManager;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.Registry;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.function.Consumer;
public class PostInitAPI {
private static List<Consumer<Boolean>> postInitFunctions = Lists.newArrayList();
private static List<TagKey<Block>> blockTags = Lists.newArrayList();
private static List<TagKey<Item>> itemTags = Lists.newArrayList();
/**
* Register a new function which will be called after all mods are initiated. Will be called on both client and server.
*
* @param function {@link Consumer} with {@code boolean} parameter ({@code true} for client, {@code false} for server).
*/
public static void register(Consumer<Boolean> function) {
postInitFunctions.add(function);
}
/**
* Called in proper BCLib entry points, for internal usage only.
*
* @param isClient {@code boolean}, {@code true} for client, {@code false} for server.
*/
public static void postInit(boolean isClient) {
Registry.BLOCK.forEach(block -> {
processBlockCommon(block);
if (isClient) {
processBlockClient(block);
}
});
Registry.ITEM.forEach(item -> {
processItemCommon(item);
});
if (postInitFunctions != null) {
postInitFunctions.forEach(function -> function.accept(isClient));
postInitFunctions = null;
}
blockTags = null;
itemTags = null;
InternalBiomeAPI.loadFabricAPIBiomes();
Configs.BIOMES_CONFIG.saveChanges();
}
@Environment(EnvType.CLIENT)
private static void processBlockClient(Block block) {
if (block instanceof RenderLayerProvider) {
BCLRenderLayer layer = ((RenderLayerProvider) block).getRenderLayer();
if (layer == BCLRenderLayer.CUTOUT) BlockRenderLayerMap.INSTANCE.putBlock(block, RenderType.cutout());
else if (layer == BCLRenderLayer.TRANSLUCENT)
BlockRenderLayerMap.INSTANCE.putBlock(block, RenderType.translucent());
}
if (block instanceof BaseChestBlock) {
BaseChestBlockEntityRenderer.registerRenderLayer(block);
} else if (block instanceof BaseSignBlock) {
BaseSignBlockEntityRenderer.registerRenderLayer(block);
}
}
private static void processItemCommon(Item item) {
if (item instanceof TagProvider provider) {
try {
provider.addTags(null, itemTags);
} catch (NullPointerException ex) {
BCLib.LOGGER.error(item + " probably tried to access blockTags.", ex);
}
itemTags.forEach(tag -> TagManager.ITEMS.add(tag, item));
itemTags.clear();
}
}
private static void processBlockCommon(Block block) {
if (block instanceof PostInitable) {
((PostInitable) block).postInit();
}
if (block instanceof BaseChestBlock) {
BaseBlockEntities.CHEST.registerBlock(block);
} else if (block instanceof BaseSignBlock) {
BaseBlockEntities.SIGN.registerBlock(block);
} else if (block instanceof BaseBarrelBlock) {
BaseBlockEntities.BARREL.registerBlock(block);
} else if (block instanceof BaseFurnaceBlock) {
BaseBlockEntities.FURNACE.registerBlock(block);
}
if (!(block instanceof PreventMineableAdd)) {
if (block instanceof AddMineableShears) {
TagManager.BLOCKS.add(block, MineableTags.SHEARS);
}
if (block instanceof AddMineableAxe) {
TagManager.BLOCKS.add(block, MineableTags.AXE);
}
if (block instanceof AddMineablePickaxe) {
TagManager.BLOCKS.add(block, MineableTags.PICKAXE);
}
if (block instanceof AddMineableShovel) {
TagManager.BLOCKS.add(block, MineableTags.SHOVEL);
}
if (block instanceof AddMineableHoe) {
TagManager.BLOCKS.add(block, MineableTags.HOE);
}
if (block instanceof AddMineableSword) {
TagManager.BLOCKS.add(block, MineableTags.SWORD);
}
if (block instanceof AddMineableHammer) {
TagManager.BLOCKS.add(block, MineableTags.HAMMER);
}
}
if (block instanceof TagProvider) {
((TagProvider) block).addTags(blockTags, itemTags);
blockTags.forEach(tag -> TagManager.BLOCKS.add(tag, block));
itemTags.forEach(tag -> TagManager.ITEMS.add(tag, block.asItem()));
blockTags.clear();
itemTags.clear();
}
}
}

View file

@ -0,0 +1,22 @@
package org.betterx.bclib.api.v2;
import org.betterx.bclib.mixin.common.ShovelItemAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Map;
public class ShovelAPI {
/**
* Will add left-click behaviour to shovel: when it is targeting cetrain {@link Block} it will be converting to new
* {@link BlockState} on usage. Example: grass converting to path.
*
* @param target {@link Block} that will be converted.
* @param convert {@link BlockState} to convert block into.
*/
public static void addShovelBehaviour(Block target, BlockState convert) {
Map<Block, BlockState> map = ShovelItemAccessor.bclib_getFlattenables();
map.put(target, convert);
}
}

View file

@ -0,0 +1,77 @@
package org.betterx.bclib.api.v2;
import org.betterx.worlds.together.world.WorldConfig;
import net.minecraft.nbt.CompoundTag;
import java.io.File;
/**
* @deprecated Implementation moved to {@link WorldConfig}
*/
@Deprecated(forRemoval = true)
public class WorldDataAPI {
/**
* @deprecated use {@link WorldConfig#load(File)} instead
*/
@Deprecated(forRemoval = true)
public static void load(File dataDir) {
WorldConfig.load(dataDir);
}
/**
* @deprecated use {@link WorldConfig#registerModCache(String)} instead
*/
@Deprecated(forRemoval = true)
public static void registerModCache(String modID) {
WorldConfig.registerModCache(modID);
}
/**
* @deprecated use {@link WorldConfig#getRootTag(String)} instead
*/
@Deprecated(forRemoval = true)
public static CompoundTag getRootTag(String modID) {
return WorldConfig.getRootTag(modID);
}
/**
* @deprecated use {@link WorldConfig#hasMod(String)} instead
*/
@Deprecated(forRemoval = true)
public static boolean hasMod(String modID) {
return WorldConfig.hasMod(modID);
}
/**
* @deprecated use {@link WorldConfig#getCompoundTag(String, String)} instead
*/
@Deprecated(forRemoval = true)
public static CompoundTag getCompoundTag(String modID, String path) {
return WorldConfig.getCompoundTag(modID, path);
}
/**
* @deprecated use {@link WorldConfig#saveFile(String)} instead
*/
@Deprecated(forRemoval = true)
public static void saveFile(String modID) {
WorldConfig.saveFile(modID);
}
/**
* @deprecated use {@link WorldConfig#getModVersion(String)} instead
*/
@Deprecated(forRemoval = true)
public static String getModVersion(String modID) {
return WorldConfig.getModVersion(modID);
}
/**
* @deprecated use {@link WorldConfig#getIntModVersion(String)} instead
*/
@Deprecated(forRemoval = true)
public static int getIntModVersion(String modID) {
return WorldConfig.getIntModVersion(modID);
}
}

View file

@ -0,0 +1,113 @@
package org.betterx.bclib.api.v2.dataexchange;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
public abstract class BaseDataHandler {
private final boolean originatesOnServer;
@NotNull
private final ResourceLocation identifier;
protected BaseDataHandler(ResourceLocation identifier, boolean originatesOnServer) {
this.originatesOnServer = originatesOnServer;
this.identifier = identifier;
}
final public boolean getOriginatesOnServer() {
return originatesOnServer;
}
final public ResourceLocation getIdentifier() {
return identifier;
}
@Environment(EnvType.CLIENT)
abstract void receiveFromServer(
Minecraft client,
ClientPacketListener handler,
FriendlyByteBuf buf,
PacketSender responseSender
);
private ServerPlayer lastMessageSender;
void receiveFromClient(
MinecraftServer server,
ServerPlayer player,
ServerGamePacketListenerImpl handler,
FriendlyByteBuf buf,
PacketSender responseSender
) {
lastMessageSender = player;
}
final protected boolean reply(BaseDataHandler message, MinecraftServer server) {
if (lastMessageSender == null) return false;
message.sendToClient(server, lastMessageSender);
return true;
}
abstract void sendToClient(MinecraftServer server);
abstract void sendToClient(MinecraftServer server, ServerPlayer player);
@Environment(EnvType.CLIENT)
abstract void sendToServer(Minecraft client);
protected boolean isBlocking() {
return false;
}
@Override
public String toString() {
return "BasDataHandler{" + "originatesOnServer=" + originatesOnServer + ", identifier=" + identifier + '}';
}
/**
* Write a String to a buffer (Convenience Method)
*
* @param buf The buffer to write to
* @param s The String you want to write
*/
public static void writeString(FriendlyByteBuf buf, String s) {
buf.writeByteArray(s.getBytes(StandardCharsets.UTF_8));
}
/**
* Read a string from a buffer (Convenience Method)
*
* @param buf Thea buffer to read from
* @return The received String
*/
public static String readString(FriendlyByteBuf buf) {
byte[] data = buf.readByteArray();
return new String(data, StandardCharsets.UTF_8);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BaseDataHandler)) return false;
BaseDataHandler that = (BaseDataHandler) o;
return originatesOnServer == that.originatesOnServer && identifier.equals(that.identifier);
}
@Override
public int hashCode() {
return Objects.hash(originatesOnServer, identifier);
}
}

View file

@ -0,0 +1,19 @@
package org.betterx.bclib.api.v2.dataexchange;
import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange;
import java.util.Set;
abstract class Connector {
protected final DataExchange api;
Connector(DataExchange api) {
this.api = api;
}
public abstract boolean onClient();
protected Set<DataHandlerDescriptor> getDescriptors() {
return api.getDescriptors();
}
}

View file

@ -0,0 +1,79 @@
package org.betterx.bclib.api.v2.dataexchange;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.FriendlyByteBuf;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
/**
* This is an internal class that handles a Clienetside players Connection to a Server
*/
@Environment(EnvType.CLIENT)
public class ConnectorClientside extends Connector {
private Minecraft client;
ConnectorClientside(DataExchange api) {
super(api);
this.client = null;
}
@Override
public boolean onClient() {
return true;
}
public void onPlayInit(ClientPacketListener handler, Minecraft client) {
if (this.client != null && this.client != client) {
BCLib.LOGGER.warning("Client changed!");
}
this.client = client;
for (DataHandlerDescriptor desc : getDescriptors()) {
ClientPlayNetworking.registerReceiver(desc.IDENTIFIER, (_client, _handler, _buf, _responseSender) -> {
receiveFromServer(desc, _client, _handler, _buf, _responseSender);
});
}
}
public void onPlayReady(ClientPacketListener handler, PacketSender sender, Minecraft client) {
for (DataHandlerDescriptor desc : getDescriptors()) {
if (desc.sendOnJoin) {
BaseDataHandler h = desc.JOIN_INSTANCE.get();
if (!h.getOriginatesOnServer()) {
h.sendToServer(client);
}
}
}
}
public void onPlayDisconnect(ClientPacketListener handler, Minecraft client) {
for (DataHandlerDescriptor desc : getDescriptors()) {
ClientPlayNetworking.unregisterReceiver(desc.IDENTIFIER);
}
}
void receiveFromServer(
DataHandlerDescriptor desc,
Minecraft client,
ClientPacketListener handler,
FriendlyByteBuf buf,
PacketSender responseSender
) {
BaseDataHandler h = desc.INSTANCE.get();
h.receiveFromServer(client, handler, buf, responseSender);
}
public void sendToServer(BaseDataHandler h) {
if (client == null) {
throw new RuntimeException("[internal error] Client not initialized yet!");
}
h.sendToServer(this.client);
}
}

View file

@ -0,0 +1,88 @@
package org.betterx.bclib.api.v2.dataexchange;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
/**
* This is an internal class that handles a Serverside Connection to a Client-Player
*/
public class ConnectorServerside extends Connector {
private MinecraftServer server;
ConnectorServerside(DataExchange api) {
super(api);
server = null;
}
@Override
public boolean onClient() {
return false;
}
public void onPlayInit(ServerGamePacketListenerImpl handler, MinecraftServer server) {
if (this.server != null && this.server != server) {
BCLib.LOGGER.warning("Server changed!");
}
this.server = server;
for (DataHandlerDescriptor desc : getDescriptors()) {
ServerPlayNetworking.registerReceiver(
handler,
desc.IDENTIFIER,
(_server, _player, _handler, _buf, _responseSender) -> {
receiveFromClient(
desc,
_server,
_player,
_handler,
_buf,
_responseSender
);
}
);
}
}
public void onPlayReady(ServerGamePacketListenerImpl handler, PacketSender sender, MinecraftServer server) {
for (DataHandlerDescriptor desc : getDescriptors()) {
if (desc.sendOnJoin) {
BaseDataHandler h = desc.JOIN_INSTANCE.get();
if (h.getOriginatesOnServer()) {
h.sendToClient(server, handler.player);
}
}
}
}
public void onPlayDisconnect(ServerGamePacketListenerImpl handler, MinecraftServer server) {
for (DataHandlerDescriptor desc : getDescriptors()) {
ServerPlayNetworking.unregisterReceiver(handler, desc.IDENTIFIER);
}
}
void receiveFromClient(
DataHandlerDescriptor desc,
MinecraftServer server,
ServerPlayer player,
ServerGamePacketListenerImpl handler,
FriendlyByteBuf buf,
PacketSender responseSender
) {
BaseDataHandler h = desc.INSTANCE.get();
h.receiveFromClient(server, player, handler, buf, responseSender);
}
public void sendToClient(BaseDataHandler h) {
if (server == null) {
throw new RuntimeException("[internal error] Server not initialized yet!");
}
h.sendToClient(this.server);
}
}

View file

@ -0,0 +1,217 @@
package org.betterx.bclib.api.v2.dataexchange;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange;
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSync;
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSyncID;
import org.betterx.bclib.config.Config;
import org.betterx.worlds.together.util.ModUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import com.google.common.collect.Lists;
import java.io.File;
import java.util.List;
import java.util.function.BiConsumer;
public class DataExchangeAPI extends DataExchange {
private final static List<String> MODS = Lists.newArrayList();
/**
* You should never need to create a custom instance of this Object.
*/
public DataExchangeAPI() {
super();
}
@Environment(EnvType.CLIENT)
protected ConnectorClientside clientSupplier(DataExchange api) {
return new ConnectorClientside(api);
}
protected ConnectorServerside serverSupplier(DataExchange api) {
return new ConnectorServerside(api);
}
/**
* Register a mod to participate in the DataExchange.
*
* @param modID - {@link String} modID.
*/
public static void registerMod(String modID) {
if (!MODS.contains(modID)) MODS.add(modID);
}
/**
* Register a mod dependency to participate in the DataExchange.
*
* @param modID - {@link String} modID.
*/
public static void registerModDependency(String modID) {
if (ModUtil.getModInfo(modID, false) != null && !"0.0.0".equals(ModUtil.getModVersion(modID))) {
registerMod(modID);
} else {
BCLib.LOGGER.info("Mod Dependency '" + modID + "' not found. This is probably OK.");
}
}
/**
* Returns the IDs of all registered Mods.
*
* @return List of modIDs
*/
public static List<String> registeredMods() {
return MODS;
}
/**
* Add a new Descriptor for a {@link DataHandler}.
*
* @param desc The Descriptor you want to add.
*/
public static void registerDescriptor(DataHandlerDescriptor desc) {
DataExchange api = DataExchange.getInstance();
api.getDescriptors()
.add(desc);
}
/**
* Bulk-Add a Descriptors for your {@link DataHandler}-Objects.
*
* @param desc The Descriptors you want to add.
*/
public static void registerDescriptors(List<DataHandlerDescriptor> desc) {
DataExchange api = DataExchange.getInstance();
api.getDescriptors()
.addAll(desc);
}
/**
* Sends the Handler.
* <p>
* Depending on what the result of {@link DataHandler#getOriginatesOnServer()}, the Data is sent from the server
* to the client (if {@code true}) or the other way around.
* <p>
* The method {@link DataHandler#serializeData(FriendlyByteBuf, boolean)} is called just before the data is sent. You should
* use this method to add the Data you need to the communication.
*
* @param h The Data that you want to send
*/
public static void send(BaseDataHandler h) {
if (h.getOriginatesOnServer()) {
DataExchangeAPI.getInstance().server.sendToClient(h);
} else {
DataExchangeAPI.getInstance().client.sendToServer(h);
}
}
/**
* Registers a File for automatic client syncing.
*
* @param modID The ID of the calling Mod
* @param fileName The name of the File
*/
public static void addAutoSyncFile(String modID, File fileName) {
AutoSync.addAutoSyncFileData(modID, fileName, false, SyncFileHash.NEED_TRANSFER);
}
/**
* Registers a File for automatic client syncing.
* <p>
* The file is synced of the {@link SyncFileHash} on client and server are not equal. This method will not copy the
* configs content from the client to the server.
*
* @param modID The ID of the calling Mod
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
* Details
* @param fileName The name of the File
*/
public static void addAutoSyncFile(String modID, String uniqueID, File fileName) {
AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, false, SyncFileHash.NEED_TRANSFER);
}
/**
* Registers a File for automatic client syncing.
* <p>
* The content of the file is requested for comparison. This will copy the
* entire file from the client to the server.
* <p>
* You should only use this option, if you need to compare parts of the file in order to decide
* if the File needs to be copied. Normally using the {@link SyncFileHash}
* for comparison is sufficient.
*
* @param modID The ID of the calling Mod
* @param fileName The name of the File
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
*/
public static void addAutoSyncFile(String modID, File fileName, AutoSync.NeedTransferPredicate needTransfer) {
AutoSync.addAutoSyncFileData(modID, fileName, true, needTransfer);
}
/**
* Registers a File for automatic client syncing.
* <p>
* The content of the file is requested for comparison. This will copy the
* entire file from the client to the server.
* <p>
* You should only use this option, if you need to compare parts of the file in order to decide
* if the File needs to be copied. Normally using the {@link SyncFileHash}
* for comparison is sufficient.
*
* @param modID The ID of the calling Mod
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
* Details
* @param fileName The name of the File
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
*/
public static void addAutoSyncFile(
String modID,
String uniqueID,
File fileName,
AutoSync.NeedTransferPredicate needTransfer
) {
AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, true, needTransfer);
}
/**
* Register a function that is called whenever the client receives a file from the server and replaced toe local
* file with the new content.
* <p>
* This callback is usefull if you need to reload the new content before the game is quit.
*
* @param callback A Function that receives the AutoSyncID as well as the Filename.
*/
public static void addOnWriteCallback(BiConsumer<AutoSyncID, File> callback) {
AutoSync.addOnWriteCallback(callback);
}
/**
* Returns the sync-folder for a given Mod.
* <p>
* BCLib will ensure that the contents of sync-folder on the client is the same as the one on the server.
*
* @param modID ID of the Mod
* @return The path to the sync-folder
*/
public static File getModSyncFolder(String modID) {
File fl = AutoSync.SYNC_FOLDER.localFolder.resolve(modID.replace(".", "-")
.replace(":", "-")
.replace("\\", "-")
.replace("/", "-"))
.normalize()
.toFile();
if (!fl.exists()) {
fl.mkdirs();
}
return fl;
}
static {
addOnWriteCallback(Config::reloadSyncedConfig);
}
}

View file

@ -0,0 +1,332 @@
package org.betterx.bclib.api.v2.dataexchange;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.Chunker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.world.entity.player.Player;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import java.util.Collection;
import java.util.List;
public abstract class DataHandler extends BaseDataHandler {
public abstract static class WithoutPayload extends DataHandler {
protected WithoutPayload(ResourceLocation identifier, boolean originatesOnServer) {
super(identifier, originatesOnServer);
}
@Override
protected boolean prepareData(boolean isClient) {
return true;
}
@Override
protected void serializeData(FriendlyByteBuf buf, boolean isClient) {
}
@Override
protected void deserializeIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient) {
}
}
protected DataHandler(ResourceLocation identifier, boolean originatesOnServer) {
super(identifier, originatesOnServer);
}
protected boolean prepareData(boolean isClient) {
return true;
}
abstract protected void serializeData(FriendlyByteBuf buf, boolean isClient);
abstract protected void deserializeIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient);
abstract protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient);
@Environment(EnvType.CLIENT)
@Override
void receiveFromServer(
Minecraft client,
ClientPacketListener handler,
FriendlyByteBuf buf,
PacketSender responseSender
) {
deserializeIncomingData(buf, responseSender, true);
final Runnable runner = () -> runOnGameThread(client, null, true);
if (isBlocking()) client.executeBlocking(runner);
else client.execute(runner);
}
@Override
void receiveFromClient(
MinecraftServer server,
ServerPlayer player,
ServerGamePacketListenerImpl handler,
FriendlyByteBuf buf,
PacketSender responseSender
) {
super.receiveFromClient(server, player, handler, buf, responseSender);
deserializeIncomingData(buf, responseSender, false);
final Runnable runner = () -> runOnGameThread(null, server, false);
if (isBlocking()) server.executeBlocking(runner);
else server.execute(runner);
}
@Override
void sendToClient(MinecraftServer server) {
if (prepareData(false)) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeData(buf, false);
_sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
}
}
@Override
void sendToClient(MinecraftServer server, ServerPlayer player) {
if (prepareData(false)) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeData(buf, false);
_sendToClient(getIdentifier(), server, List.of(player), buf);
}
}
public static void _sendToClient(
ResourceLocation identifier,
MinecraftServer server,
Collection<ServerPlayer> players,
FriendlyByteBuf buf
) {
if (buf.readableBytes() > Chunker.MAX_PACKET_SIZE) {
final Chunker.PacketChunkSender sender = new Chunker.PacketChunkSender(buf, identifier);
sender.sendChunks(players);
} else {
for (ServerPlayer player : players) {
ServerPlayNetworking.send(player, identifier, buf);
}
}
}
@Environment(EnvType.CLIENT)
@Override
void sendToServer(Minecraft client) {
if (prepareData(true)) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeData(buf, true);
ClientPlayNetworking.send(getIdentifier(), buf);
}
}
/**
* A Message that always originates on the Client
*/
public abstract static class FromClient extends BaseDataHandler {
public abstract static class WithoutPayload extends FromClient {
protected WithoutPayload(ResourceLocation identifier) {
super(identifier);
}
@Override
protected boolean prepareDataOnClient() {
return true;
}
@Override
protected void serializeDataOnClient(FriendlyByteBuf buf) {
}
@Override
protected void deserializeIncomingDataOnServer(
FriendlyByteBuf buf,
Player player,
PacketSender responseSender
) {
}
}
protected FromClient(ResourceLocation identifier) {
super(identifier, false);
}
@Environment(EnvType.CLIENT)
protected boolean prepareDataOnClient() {
return true;
}
@Environment(EnvType.CLIENT)
abstract protected void serializeDataOnClient(FriendlyByteBuf buf);
protected abstract void deserializeIncomingDataOnServer(
FriendlyByteBuf buf,
Player player,
PacketSender responseSender
);
protected abstract void runOnServerGameThread(MinecraftServer server, Player player);
@Environment(EnvType.CLIENT)
@Override
void receiveFromServer(
Minecraft client,
ClientPacketListener handler,
FriendlyByteBuf buf,
PacketSender responseSender
) {
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
}
@Override
void receiveFromClient(
MinecraftServer server,
ServerPlayer player,
ServerGamePacketListenerImpl handler,
FriendlyByteBuf buf,
PacketSender responseSender
) {
super.receiveFromClient(server, player, handler, buf, responseSender);
deserializeIncomingDataOnServer(buf, player, responseSender);
final Runnable runner = () -> runOnServerGameThread(server, player);
if (isBlocking()) server.executeBlocking(runner);
else server.execute(runner);
}
@Override
void sendToClient(MinecraftServer server) {
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
}
@Override
void sendToClient(MinecraftServer server, ServerPlayer player) {
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
}
@Environment(EnvType.CLIENT)
@Override
void sendToServer(Minecraft client) {
if (prepareDataOnClient()) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeDataOnClient(buf);
ClientPlayNetworking.send(getIdentifier(), buf);
}
}
}
/**
* A Message that always originates on the Server
*/
public abstract static class FromServer extends BaseDataHandler {
public abstract static class WithoutPayload extends FromServer {
protected WithoutPayload(ResourceLocation identifier) {
super(identifier);
}
@Override
protected boolean prepareDataOnServer() {
return true;
}
@Override
protected void serializeDataOnServer(FriendlyByteBuf buf) {
}
@Override
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
}
}
protected FromServer(ResourceLocation identifier) {
super(identifier, true);
}
protected boolean prepareDataOnServer() {
return true;
}
abstract protected void serializeDataOnServer(FriendlyByteBuf buf);
@Environment(EnvType.CLIENT)
abstract protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender);
@Environment(EnvType.CLIENT)
abstract protected void runOnClientGameThread(Minecraft client);
@Environment(EnvType.CLIENT)
@Override
final void receiveFromServer(
Minecraft client,
ClientPacketListener handler,
FriendlyByteBuf buf,
PacketSender responseSender
) {
deserializeIncomingDataOnClient(buf, responseSender);
final Runnable runner = () -> runOnClientGameThread(client);
if (isBlocking()) client.executeBlocking(runner);
else client.execute(runner);
}
@Override
final void receiveFromClient(
MinecraftServer server,
ServerPlayer player,
ServerGamePacketListenerImpl handler,
FriendlyByteBuf buf,
PacketSender responseSender
) {
super.receiveFromClient(server, player, handler, buf, responseSender);
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the server!");
}
public void receiveFromMemory(FriendlyByteBuf buf) {
receiveFromServer(Minecraft.getInstance(), null, buf, null);
}
@Override
final void sendToClient(MinecraftServer server) {
if (prepareDataOnServer()) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeDataOnServer(buf);
_sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
}
}
@Override
final void sendToClient(MinecraftServer server, ServerPlayer player) {
if (prepareDataOnServer()) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeDataOnServer(buf);
_sendToClient(getIdentifier(), server, List.of(player), buf);
}
}
@Environment(EnvType.CLIENT)
@Override
final void sendToServer(Minecraft client) {
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the server!");
}
}
}

View file

@ -0,0 +1,61 @@
package org.betterx.bclib.api.v2.dataexchange;
import net.minecraft.resources.ResourceLocation;
import java.util.Objects;
import java.util.function.Supplier;
import org.jetbrains.annotations.NotNull;
public class DataHandlerDescriptor {
public DataHandlerDescriptor(@NotNull ResourceLocation identifier, @NotNull Supplier<BaseDataHandler> instancer) {
this(identifier, instancer, instancer, false, false);
}
public DataHandlerDescriptor(
@NotNull ResourceLocation identifier,
@NotNull Supplier<BaseDataHandler> instancer,
boolean sendOnJoin,
boolean sendBeforeEnter
) {
this(identifier, instancer, instancer, sendOnJoin, sendBeforeEnter);
}
public DataHandlerDescriptor(
@NotNull ResourceLocation identifier,
@NotNull Supplier<BaseDataHandler> receiv_instancer,
@NotNull Supplier<BaseDataHandler> join_instancer,
boolean sendOnJoin,
boolean sendBeforeEnter
) {
this.INSTANCE = receiv_instancer;
this.JOIN_INSTANCE = join_instancer;
this.IDENTIFIER = identifier;
this.sendOnJoin = sendOnJoin;
this.sendBeforeEnter = sendBeforeEnter;
}
public final boolean sendOnJoin;
public final boolean sendBeforeEnter;
@NotNull
public final ResourceLocation IDENTIFIER;
@NotNull
public final Supplier<BaseDataHandler> INSTANCE;
@NotNull
public final Supplier<BaseDataHandler> JOIN_INSTANCE;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof ResourceLocation) {
return o.equals(IDENTIFIER);
}
if (!(o instanceof DataHandlerDescriptor that)) return false;
return IDENTIFIER.equals(that.IDENTIFIER);
}
@Override
public int hashCode() {
return Objects.hash(IDENTIFIER);
}
}

View file

@ -0,0 +1,160 @@
package org.betterx.bclib.api.v2.dataexchange;
import org.betterx.bclib.BCLib;
import net.minecraft.network.FriendlyByteBuf;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
public class FileHash {
private static final int ERR_DOES_NOT_EXIST = -10;
private static final int ERR_IO_ERROR = -20;
/**
* The md5-hash of the file
*/
@NotNull
public final byte[] md5;
/**
* The size (in bytes) of the input.
*/
public final int size;
/**
* a value that is directly calculated from defined byte positions.
*/
public final int value;
FileHash(byte[] md5, int size, int value) {
Objects.nonNull(md5);
this.md5 = md5;
this.size = size;
this.value = value;
}
static FileHash createForEmpty(int errCode) {
return new FileHash(new byte[0], 0, errCode);
}
public boolean noFile() {
return md5.length == 0;
}
/**
* Serializes the Object to a buffer
*
* @param buf The buffer to write to
*/
public void serialize(FriendlyByteBuf buf) {
buf.writeInt(size);
buf.writeInt(value);
buf.writeByteArray(md5);
}
/**
* Deserialize a Buffer to a new {@link SyncFileHash}-Object
*
* @param buf Thea buffer to read from
* @return The received String
*/
public static FileHash deserialize(FriendlyByteBuf buf) {
final int size = buf.readInt();
final int value = buf.readInt();
final byte[] md5 = buf.readByteArray();
return new FileHash(md5, size, value);
}
/**
* Convert the md5-hash to a human readable string
*
* @return The converted String
*/
public String getMd5String() {
return toHexString(md5);
}
/**
* Converts a byte-array to a hex-string representation
*
* @param bytes The source array
* @return The resulting string, or an empty String if the input was {@code null}
*/
public static String toHexString(byte[] bytes) {
if (bytes == null) return "";
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/**
* Create a new {@link FileHash}.
*
* @param file The input file
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
* identical. Will return {@code null} when an error occurs or the File does not exist
*/
public static FileHash create(File file) {
if (!file.exists()) return createForEmpty(ERR_DOES_NOT_EXIST);
final Path path = file.toPath();
int size = 0;
byte[] md5 = new byte[0];
int value = 0;
try {
byte[] data = Files.readAllBytes(path);
size = data.length;
value = size > 0 ? (data[size / 3] | (data[size / 2] << 8) | (data[size / 5] << 16)) : -1;
if (size > 20) value |= data[20] << 24;
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(data);
md5 = md.digest();
return new FileHash(md5, size, value);
} catch (IOException e) {
BCLib.LOGGER.error("Failed to read file: " + file);
return null;
} catch (NoSuchAlgorithmException e) {
BCLib.LOGGER.error("Unable to build hash for file: " + file);
}
return createForEmpty(ERR_IO_ERROR);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FileHash)) return false;
FileHash fileHash = (FileHash) o;
return size == fileHash.size && value == fileHash.value && Arrays.equals(md5, fileHash.md5);
}
@Override
public int hashCode() {
int result = Objects.hash(size, value);
result = 31 * result + Arrays.hashCode(md5);
return result;
}
@Override
public String toString() {
return String.format("%08x", size) + "-" + String.format("%08x", value) + "-" + getMd5String();
}
}

View file

@ -0,0 +1,113 @@
package org.betterx.bclib.api.v2.dataexchange;
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSync;
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSyncID;
import net.minecraft.network.FriendlyByteBuf;
import java.io.File;
import java.util.Objects;
/**
* Calculates a hash based on the contents of a File.
* <p>
* A File-Hash contains the md5-sum of the File, as well as its size and byte-values from defined positions
* <p>
* You can compare instances using {@link #equals(Object)} to determine if two files are
* identical.
*/
public class SyncFileHash extends AutoSyncID {
public final FileHash hash;
SyncFileHash(String modID, File file, byte[] md5, int size, int value) {
this(modID, file.getName(), md5, size, value);
}
SyncFileHash(String modID, String uniqueID, byte[] md5, int size, int value) {
this(modID, uniqueID, new FileHash(md5, size, value));
}
SyncFileHash(String modID, File file, FileHash hash) {
this(modID, file.getName(), hash);
}
SyncFileHash(String modID, String uniqueID, FileHash hash) {
super(modID, uniqueID);
this.hash = hash;
}
final static AutoSync.NeedTransferPredicate NEED_TRANSFER = (clientHash, serverHash, content) -> !clientHash.equals(
serverHash);
@Override
public String toString() {
return super.toString() + ": " + hash.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SyncFileHash)) return false;
if (!super.equals(o)) return false;
SyncFileHash that = (SyncFileHash) o;
return hash.equals(that.hash);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), hash);
}
/**
* Serializes the Object to a buffer
*
* @param buf The buffer to write to
*/
public void serialize(FriendlyByteBuf buf) {
hash.serialize(buf);
DataHandler.writeString(buf, modID);
DataHandler.writeString(buf, uniqueID);
}
/**
* Deserialize a Buffer to a new {@link SyncFileHash}-Object
*
* @param buf Thea buffer to read from
* @return The received String
*/
public static SyncFileHash deserialize(FriendlyByteBuf buf) {
final FileHash hash = FileHash.deserialize(buf);
final String modID = DataHandler.readString(buf);
final String uniqueID = DataHandler.readString(buf);
return new SyncFileHash(modID, uniqueID, hash);
}
/**
* Create a new {@link SyncFileHash}.
* <p>
* Will call {@link #create(String, File, String)} using the name of the File as {@code uniqueID}.
*
* @param modID ID of the calling Mod
* @param file The input file
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
* identical. Will return {@code null} when an error occurs or the File does not exist
*/
public static SyncFileHash create(String modID, File file) {
return create(modID, file, file.getName());
}
/**
* Create a new {@link SyncFileHash}.
*
* @param modID ID of the calling Mod
* @param file The input file
* @param uniqueID The unique ID that is used for this File (see {@link SyncFileHash#uniqueID} for Details.
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
* identical. Will return {@code null} when an error occurs or the File does not exist
*/
public static SyncFileHash create(String modID, File file, String uniqueID) {
return new SyncFileHash(modID, uniqueID, FileHash.create(file));
}
}

View file

@ -0,0 +1,111 @@
package org.betterx.bclib.api.v2.dataexchange.handler;
import org.betterx.bclib.api.v2.dataexchange.*;
import net.minecraft.resources.ResourceLocation;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import java.util.HashSet;
import java.util.Set;
abstract public class DataExchange {
private static DataExchangeAPI instance;
protected static DataExchangeAPI getInstance() {
if (instance == null) {
instance = new DataExchangeAPI();
}
return instance;
}
protected ConnectorServerside server;
protected ConnectorClientside client;
protected final Set<DataHandlerDescriptor> descriptors;
private final boolean didLoadSyncFolder = false;
abstract protected ConnectorClientside clientSupplier(DataExchange api);
abstract protected ConnectorServerside serverSupplier(DataExchange api);
protected DataExchange() {
descriptors = new HashSet<>();
}
public Set<DataHandlerDescriptor> getDescriptors() {
return descriptors;
}
public static DataHandlerDescriptor getDescriptor(ResourceLocation identifier) {
return getInstance().descriptors.stream().filter(d -> d.equals(identifier)).findFirst().orElse(null);
}
@Environment(EnvType.CLIENT)
protected void initClientside() {
if (client != null) return;
client = clientSupplier(this);
ClientPlayConnectionEvents.INIT.register(client::onPlayInit);
ClientPlayConnectionEvents.JOIN.register(client::onPlayReady);
ClientPlayConnectionEvents.DISCONNECT.register(client::onPlayDisconnect);
}
protected void initServerSide() {
if (server != null) return;
server = serverSupplier(this);
ServerPlayConnectionEvents.INIT.register(server::onPlayInit);
ServerPlayConnectionEvents.JOIN.register(server::onPlayReady);
ServerPlayConnectionEvents.DISCONNECT.register(server::onPlayDisconnect);
}
/**
* Initializes all datastructures that need to exist in the client component.
* <p>
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
*/
@Environment(EnvType.CLIENT)
public static void prepareClientside() {
DataExchange api = DataExchange.getInstance();
api.initClientside();
}
/**
* Initializes all datastructures that need to exist in the server component.
* <p>
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
*/
public static void prepareServerside() {
DataExchange api = DataExchange.getInstance();
api.initServerSide();
}
/**
* Automatically called before the player enters the world.
* <p>
* This is automatically called by BCLib. It will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to*
* {@code true},
*/
@Environment(EnvType.CLIENT)
public static void sendOnEnter() {
getInstance().descriptors.forEach((desc) -> {
if (desc.sendBeforeEnter) {
BaseDataHandler h = desc.JOIN_INSTANCE.get();
if (!h.getOriginatesOnServer()) {
getInstance().client.sendToServer(h);
}
}
});
}
}

View file

@ -0,0 +1,262 @@
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
import org.betterx.bclib.api.v2.dataexchange.SyncFileHash;
import org.betterx.bclib.util.Pair;
import org.betterx.bclib.util.Triple;
import org.betterx.worlds.together.util.ModUtil;
import org.betterx.worlds.together.util.ModUtil.ModInfo;
import org.betterx.worlds.together.util.PathUtil;
import net.minecraft.network.FriendlyByteBuf;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
class AutoFileSyncEntry extends AutoSyncID {
static class ForDirectFileRequest extends AutoFileSyncEntry {
final File relFile;
ForDirectFileRequest(String syncID, File relFile, File absFile) {
super(AutoSyncID.ForDirectFileRequest.MOD_ID, syncID, absFile, false, (a, b, c) -> false);
this.relFile = relFile;
}
@Override
public int serializeContent(FriendlyByteBuf buf) {
int res = super.serializeContent(buf);
DataHandler.writeString(buf, relFile.toString());
return res;
}
static AutoFileSyncEntry.ForDirectFileRequest finishDeserializeContent(String syncID, FriendlyByteBuf buf) {
final String relFile = DataHandler.readString(buf);
SyncFolderDescriptor desc = AutoSync.getSyncFolderDescriptor(syncID);
if (desc != null) {
//ensures that the file is not above the base-folder
if (desc.acceptChildElements(desc.mapAbsolute(relFile))) {
return new AutoFileSyncEntry.ForDirectFileRequest(
syncID,
new File(relFile),
desc.localFolder.resolve(relFile)
.normalize()
.toFile()
);
}
}
return null;
}
@Override
public String toString() {
return uniqueID + " - " + relFile;
}
}
static class ForModFileRequest extends AutoFileSyncEntry {
public static File getLocalPathForID(String modID, boolean matchLocalVersion) {
ModInfo mi = ModUtil.getModInfo(modID, matchLocalVersion);
if (mi != null) {
return mi.jarPath.toFile();
}
return null;
}
public final String version;
ForModFileRequest(String modID, boolean matchLocalVersion, String version) {
super(
modID,
AutoSyncID.ForModFileRequest.UNIQUE_ID,
getLocalPathForID(modID, matchLocalVersion),
false,
(a, b, c) -> false
);
if (this.fileName == null && matchLocalVersion) {
BCLib.LOGGER.error("Unknown mod '" + modID + "'.");
}
if (version == null)
this.version = ModUtil.getModVersion(modID);
else
this.version = version;
}
@Override
public int serializeContent(FriendlyByteBuf buf) {
final int res = super.serializeContent(buf);
buf.writeInt(ModUtil.convertModVersion(version));
return res;
}
static AutoFileSyncEntry.ForModFileRequest finishDeserializeContent(String modID, FriendlyByteBuf buf) {
final String version = ModUtil.convertModVersion(buf.readInt());
return new AutoFileSyncEntry.ForModFileRequest(modID, false, version);
}
@Override
public String toString() {
return "Mod " + modID + " (v" + version + ")";
}
}
public final AutoSync.NeedTransferPredicate needTransfer;
public final File fileName;
public final boolean requestContent;
private SyncFileHash hash;
AutoFileSyncEntry(
String modID,
File fileName,
boolean requestContent,
AutoSync.NeedTransferPredicate needTransfer
) {
this(modID, fileName.getName(), fileName, requestContent, needTransfer);
}
AutoFileSyncEntry(
String modID,
String uniqueID,
File fileName,
boolean requestContent,
AutoSync.NeedTransferPredicate needTransfer
) {
super(modID, uniqueID);
this.needTransfer = needTransfer;
this.fileName = fileName;
this.requestContent = requestContent;
}
public SyncFileHash getFileHash() {
if (hash == null) {
hash = SyncFileHash.create(modID, fileName, uniqueID);
}
return hash;
}
public byte[] getContent() {
if (!fileName.exists()) return new byte[0];
final Path path = fileName.toPath();
try {
return Files.readAllBytes(path);
} catch (IOException e) {
}
return new byte[0];
}
public int serializeContent(FriendlyByteBuf buf) {
DataHandler.writeString(buf, modID);
DataHandler.writeString(buf, uniqueID);
return serializeFileContent(buf);
}
public static Triple<AutoFileSyncEntry, byte[], AutoSyncID> deserializeContent(FriendlyByteBuf buf) {
final String modID = DataHandler.readString(buf);
final String uniqueID = DataHandler.readString(buf);
byte[] data = deserializeFileContent(buf);
AutoFileSyncEntry entry;
if (AutoSyncID.ForDirectFileRequest.MOD_ID.equals(modID)) {
entry = AutoFileSyncEntry.ForDirectFileRequest.finishDeserializeContent(uniqueID, buf);
} else if (AutoSyncID.ForModFileRequest.UNIQUE_ID.equals(uniqueID)) {
entry = AutoFileSyncEntry.ForModFileRequest.finishDeserializeContent(modID, buf);
} else {
entry = AutoFileSyncEntry.findMatching(modID, uniqueID);
}
return new Triple<>(entry, data, new AutoSyncID(modID, uniqueID));
}
public void serialize(FriendlyByteBuf buf) {
getFileHash().serialize(buf);
buf.writeBoolean(requestContent);
if (requestContent) {
serializeFileContent(buf);
}
}
public static AutoSync.AutoSyncTriple deserializeAndMatch(FriendlyByteBuf buf) {
Pair<SyncFileHash, byte[]> e = deserialize(buf);
AutoFileSyncEntry match = findMatching(e.first);
return new AutoSync.AutoSyncTriple(e.first, e.second, match);
}
public static Pair<SyncFileHash, byte[]> deserialize(FriendlyByteBuf buf) {
SyncFileHash hash = SyncFileHash.deserialize(buf);
boolean withContent = buf.readBoolean();
byte[] data = null;
if (withContent) {
data = deserializeFileContent(buf);
}
return new Pair(hash, data);
}
private int serializeFileContent(FriendlyByteBuf buf) {
if (!org.betterx.worlds.together.util.PathUtil.isChildOf(
org.betterx.worlds.together.util.PathUtil.GAME_FOLDER,
fileName.toPath()
)) {
BCLib.LOGGER.error(fileName + " is not within game folder " + PathUtil.GAME_FOLDER + ". Pretending it does not exist.");
buf.writeInt(0);
return 0;
}
byte[] content = getContent();
buf.writeInt(content.length);
buf.writeByteArray(content);
return content.length;
}
private static byte[] deserializeFileContent(FriendlyByteBuf buf) {
byte[] data;
int size = buf.readInt();
data = buf.readByteArray(size);
return data;
}
public static AutoFileSyncEntry findMatching(SyncFileHash hash) {
return findMatching(hash.modID, hash.uniqueID);
}
public static AutoFileSyncEntry findMatching(AutoSyncID aid) {
if (aid instanceof AutoSyncID.ForDirectFileRequest) {
AutoSyncID.ForDirectFileRequest freq = (AutoSyncID.ForDirectFileRequest) aid;
SyncFolderDescriptor desc = AutoSync.getSyncFolderDescriptor(freq.uniqueID);
if (desc != null) {
SyncFolderDescriptor.SubFile subFile = desc.getLocalSubFile(freq.relFile.toString());
if (subFile != null) {
final File absPath = desc.localFolder.resolve(subFile.relPath)
.normalize()
.toFile();
return new AutoFileSyncEntry.ForDirectFileRequest(
freq.uniqueID,
new File(subFile.relPath),
absPath
);
}
}
return null;
} else if (aid instanceof AutoSyncID.ForModFileRequest) {
AutoSyncID.ForModFileRequest mreq = (AutoSyncID.ForModFileRequest) aid;
return new AutoFileSyncEntry.ForModFileRequest(mreq.modID, true, null);
}
return findMatching(aid.modID, aid.uniqueID);
}
public static AutoFileSyncEntry findMatching(String modID, String uniqueID) {
return AutoSync.getAutoSyncFiles()
.stream()
.filter(asf -> asf.modID.equals(modID) && asf.uniqueID.equals(uniqueID))
.findFirst()
.orElse(null);
}
}

View file

@ -0,0 +1,207 @@
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI;
import org.betterx.bclib.api.v2.dataexchange.SyncFileHash;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.config.ServerConfig;
import org.betterx.worlds.together.util.PathUtil;
import net.fabricmc.loader.api.FabricLoader;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
public class AutoSync {
public static final String SYNC_CATEGORY = "auto_sync";
public final static SyncFolderDescriptor SYNC_FOLDER = new SyncFolderDescriptor(
"BCLIB-SYNC",
FabricLoader.getInstance()
.getGameDir()
.resolve("bclib-sync")
.normalize()
.toAbsolutePath(),
true
);
@FunctionalInterface
public interface NeedTransferPredicate {
boolean test(SyncFileHash clientHash, SyncFileHash serverHash, FileContentWrapper content);
}
final static class AutoSyncTriple {
public final SyncFileHash serverHash;
public final byte[] serverContent;
public final AutoFileSyncEntry localMatch;
public AutoSyncTriple(SyncFileHash serverHash, byte[] serverContent, AutoFileSyncEntry localMatch) {
this.serverHash = serverHash;
this.serverContent = serverContent;
this.localMatch = localMatch;
}
@Override
public String toString() {
return serverHash.modID + "." + serverHash.uniqueID;
}
}
// ##### File Syncing
protected final static List<BiConsumer<AutoSyncID, File>> onWriteCallbacks = new ArrayList<>(2);
/**
* Register a function that is called whenever the client receives a file from the server and replaced toe local
* file with the new content.
* <p>
* This callback is usefull if you need to reload the new content before the game is quit.
*
* @param callback A Function that receives the AutoSyncID as well as the Filename.
*/
public static void addOnWriteCallback(BiConsumer<AutoSyncID, File> callback) {
onWriteCallbacks.add(callback);
}
private static final List<AutoFileSyncEntry> autoSyncFiles = new ArrayList<>(4);
public static List<AutoFileSyncEntry> getAutoSyncFiles() {
return autoSyncFiles;
}
/**
* Registers a File for automatic client syncing.
*
* @param modID The ID of the calling Mod
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
* @param fileName The name of the File
* @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
* entire file from the client to the server.
* <p>
* You should only use this option, if you need to compare parts of the file in order to decide
* If the File needs to be copied. Normally using the {@link SyncFileHash}
* for comparison is sufficient.
*/
public static void addAutoSyncFileData(
String modID,
File fileName,
boolean requestContent,
NeedTransferPredicate needTransfer
) {
if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())) {
BCLib.LOGGER.error(fileName + " is outside of Game Folder " + PathUtil.GAME_FOLDER);
} else {
autoSyncFiles.add(new AutoFileSyncEntry(modID, fileName, requestContent, needTransfer));
}
}
/**
* Registers a File for automatic client syncing.
*
* @param modID The ID of the calling Mod
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
* Details
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
* @param fileName The name of the File
* @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
* entire file from the client to the server.
* <p>
* You should only use this option, if you need to compare parts of the file in order to decide
* If the File needs to be copied. Normally using the {@link SyncFileHash}
* for comparison is sufficient.
*/
public static void addAutoSyncFileData(
String modID,
String uniqueID,
File fileName,
boolean requestContent,
NeedTransferPredicate needTransfer
) {
if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())) {
BCLib.LOGGER.error(fileName + " is outside of Game Folder " + PathUtil.GAME_FOLDER);
} else {
autoSyncFiles.add(new AutoFileSyncEntry(modID, uniqueID, fileName, requestContent, needTransfer));
}
}
/**
* Called when {@code SendFiles} received a File on the Client and wrote it to the FileSystem.
* <p>
* This is the place where reload Code should go.
*
* @param aid The ID of the received File
* @param file The location of the FIle on the client
*/
static void didReceiveFile(AutoSyncID aid, File file) {
onWriteCallbacks.forEach(fkt -> fkt.accept(aid, file));
}
// ##### Folder Syncing
static final List<SyncFolderDescriptor> syncFolderDescriptions = Arrays.asList(SYNC_FOLDER);
private List<String> syncFolderContent;
protected List<String> getSyncFolderContent() {
if (syncFolderContent == null) {
return new ArrayList<>(0);
}
return syncFolderContent;
}
private static boolean didRegisterAdditionalMods = false;
//we call this from HelloClient on the Server to prepare transfer
protected static void loadSyncFolder() {
if (Configs.SERVER_CONFIG.isOfferingFiles()) {
syncFolderDescriptions.forEach(desc -> desc.loadCache());
}
if (!didRegisterAdditionalMods && Configs.SERVER_CONFIG.isOfferingMods()) {
didRegisterAdditionalMods = true;
List<String> modIDs = Configs.SERVER_CONFIG.get(ServerConfig.ADDITIONAL_MODS);
if (modIDs != null) {
modIDs.stream().forEach(modID -> DataExchangeAPI.registerModDependency(modID));
}
}
}
protected static SyncFolderDescriptor getSyncFolderDescriptor(String folderID) {
return syncFolderDescriptions.stream()
.filter(d -> d.equals(folderID))
.findFirst()
.orElse(null);
}
protected static Path localBasePathForFolderID(String folderID) {
final SyncFolderDescriptor desc = getSyncFolderDescriptor(folderID);
if (desc != null) {
return desc.localFolder;
} else {
BCLib.LOGGER.warning("Unknown Sync-Folder ID '" + folderID + "'");
return null;
}
}
public static void registerSyncFolder(String folderID, Path localBaseFolder, boolean removeAdditionalFiles) {
localBaseFolder = localBaseFolder.normalize();
if (PathUtil.isChildOf(PathUtil.GAME_FOLDER, localBaseFolder)) {
final SyncFolderDescriptor desc = new SyncFolderDescriptor(
folderID,
localBaseFolder,
removeAdditionalFiles
);
if (syncFolderDescriptions.contains(desc)) {
BCLib.LOGGER.warning("Tried to override Folder Sync '" + folderID + "' again.");
} else {
syncFolderDescriptions.add(desc);
}
} else {
BCLib.LOGGER.error(localBaseFolder + " (from " + folderID + ") is outside the game directory " + PathUtil.GAME_FOLDER + ". Sync is not allowed.");
}
}
}

View file

@ -0,0 +1,144 @@
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
import org.betterx.bclib.config.Config;
import org.betterx.worlds.together.util.ModUtil;
import net.minecraft.network.FriendlyByteBuf;
import java.io.File;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
public class AutoSyncID {
static class WithContentOverride extends AutoSyncID {
final FileContentWrapper contentWrapper;
final File localFile;
WithContentOverride(String modID, String uniqueID, FileContentWrapper contentWrapper, File localFile) {
super(modID, uniqueID);
this.contentWrapper = contentWrapper;
this.localFile = localFile;
}
@Override
public String toString() {
return super.toString() + " (Content override)";
}
}
static class ForDirectFileRequest extends AutoSyncID {
public final static String MOD_ID = "bclib::FILE";
final File relFile;
ForDirectFileRequest(String syncID, File relFile) {
super(ForDirectFileRequest.MOD_ID, syncID);
this.relFile = relFile;
}
@Override
void serializeData(FriendlyByteBuf buf) {
super.serializeData(buf);
DataHandler.writeString(buf, relFile.toString());
}
static ForDirectFileRequest finishDeserialize(String modID, String uniqueID, FriendlyByteBuf buf) {
final File fl = new File(DataHandler.readString(buf));
return new ForDirectFileRequest(uniqueID, fl);
}
@Override
public String toString() {
return super.uniqueID + " (" + this.relFile + ")";
}
}
static class ForModFileRequest extends AutoSyncID {
public final static String UNIQUE_ID = "bclib::MOD";
private final String version;
ForModFileRequest(String modID, String version) {
super(modID, ForModFileRequest.UNIQUE_ID);
this.version = version;
}
@Override
void serializeData(FriendlyByteBuf buf) {
super.serializeData(buf);
buf.writeInt(ModUtil.convertModVersion(version));
}
static ForModFileRequest finishDeserialize(String modID, String uniqueID, FriendlyByteBuf buf) {
final String version = ModUtil.convertModVersion(buf.readInt());
return new ForModFileRequest(modID, version);
}
@Override
public String toString() {
return super.modID + " (v" + this.version + ")";
}
}
/**
* A Unique ID for the referenced File.
* <p>
* Files with the same {@link #modID} need to have a unique IDs. Normally the filename from FileHash(String, File, byte[], int, int)
* is used to generated that ID, but you can directly specify one using FileHash(String, String, byte[], int, int).
*/
@NotNull
public final String uniqueID;
/**
* The ID of the Mod that is registering the File
*/
@NotNull
public final String modID;
public AutoSyncID(String modID, String uniqueID) {
Objects.nonNull(modID);
Objects.nonNull(uniqueID);
this.modID = modID;
this.uniqueID = uniqueID;
}
@Override
public String toString() {
return modID + "." + uniqueID;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AutoSyncID)) return false;
AutoSyncID that = (AutoSyncID) o;
return uniqueID.equals(that.uniqueID) && modID.equals(that.modID);
}
@Override
public int hashCode() {
return Objects.hash(uniqueID, modID);
}
void serializeData(FriendlyByteBuf buf) {
DataHandler.writeString(buf, modID);
DataHandler.writeString(buf, uniqueID);
}
static AutoSyncID deserializeData(FriendlyByteBuf buf) {
String modID = DataHandler.readString(buf);
String uID = DataHandler.readString(buf);
if (ForDirectFileRequest.MOD_ID.equals(modID)) {
return ForDirectFileRequest.finishDeserialize(modID, uID, buf);
} else if (ForModFileRequest.UNIQUE_ID.equals(uID)) {
return ForModFileRequest.finishDeserialize(modID, uID, buf);
} else {
return new AutoSyncID(modID, uID);
}
}
public boolean isConfigFile() {
return this.uniqueID.startsWith(Config.CONFIG_SYNC_PREFIX);
}
}

View file

@ -0,0 +1,280 @@
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.BaseDataHandler;
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
import org.betterx.bclib.api.v2.dataexchange.DataHandlerDescriptor;
import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.ProgressListener;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import java.util.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Used to seperate large data transfers into multiple smaller messages.
* <p>
* {@link DataHandler} will automatically convert larger messages into Chunks on the Server
* and assemble the original message from those chunks on the client.
*/
public class Chunker extends DataHandler.FromServer {
/**
* Responsible for assembling the original ByteBuffer created by {@link PacketChunkSender} on the
* receiving end. Automatically created from the header {@link Chunker}-Message (where the serialNo==-1)
*/
static class PacketChunkReceiver {
@NotNull
public final UUID uuid;
public final int chunkCount;
@NotNull
private final FriendlyByteBuf networkedBuf;
@Nullable
private final DataHandlerDescriptor descriptor;
private static final List<PacketChunkReceiver> active = new ArrayList<>(1);
private static PacketChunkReceiver newReceiver(@NotNull UUID uuid, int chunkCount, ResourceLocation origin) {
DataHandlerDescriptor desc = DataExchange.getDescriptor(origin);
final PacketChunkReceiver r = new PacketChunkReceiver(uuid, chunkCount, desc);
active.add(r);
return r;
}
private static PacketChunkReceiver getOrCreate(@NotNull UUID uuid, int chunkCount, ResourceLocation origin) {
return active.stream()
.filter(r -> r.uuid.equals(uuid))
.findFirst()
.orElse(newReceiver(uuid, chunkCount, origin));
}
public static PacketChunkReceiver get(@NotNull UUID uuid) {
return active.stream().filter(r -> r.uuid.equals(uuid)).findFirst().orElse(null);
}
private PacketChunkReceiver(@NotNull UUID uuid, int chunkCount, @Nullable DataHandlerDescriptor descriptor) {
this.uuid = uuid;
this.chunkCount = chunkCount;
networkedBuf = PacketByteBufs.create();
this.descriptor = descriptor;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PacketChunkReceiver)) return false;
PacketChunkReceiver that = (PacketChunkReceiver) o;
return uuid.equals(that.uuid);
}
@Override
public int hashCode() {
return Objects.hash(uuid);
}
public boolean testFinished() {
ProgressListener listener = ChunkerProgress.getProgressListener();
if (listener != null) {
listener.progressStagePercentage((100 * receivedCount) / chunkCount);
}
if (incomingBuffer == null) {
return true;
}
if (lastReadSerial >= chunkCount - 1) {
onFinish();
return true;
}
return false;
}
private void addBuffer(FriendlyByteBuf input) {
final int size = input.readableBytes();
final int cap = networkedBuf.capacity() - networkedBuf.writerIndex();
if (cap < size) {
networkedBuf.capacity(networkedBuf.writerIndex() + size);
}
input.readBytes(networkedBuf, size);
input.clear();
}
protected void onFinish() {
incomingBuffer.clear();
incomingBuffer = null;
final BaseDataHandler baseHandler = descriptor.INSTANCE.get();
if (baseHandler instanceof DataHandler.FromServer handler) {
handler.receiveFromMemory(networkedBuf);
}
}
Map<Integer, FriendlyByteBuf> incomingBuffer = new HashMap<>();
int lastReadSerial = -1;
int receivedCount = 0;
public void processReceived(FriendlyByteBuf buf, int serialNo, int size) {
receivedCount++;
if (lastReadSerial == serialNo - 1) {
addBuffer(buf);
lastReadSerial = serialNo;
} else {
//not sure if order is guaranteed by the underlying system!
boolean haveAll = true;
for (int nr = lastReadSerial + 1; nr < serialNo - 1; nr++) {
if (incomingBuffer.get(nr) == null) {
haveAll = false;
break;
}
}
if (haveAll) {
for (int nr = lastReadSerial + 1; nr < serialNo - 1; nr++) {
addBuffer(incomingBuffer.get(nr));
incomingBuffer.put(nr, null);
}
addBuffer(buf);
lastReadSerial = serialNo;
} else {
incomingBuffer.put(serialNo, buf);
}
}
}
}
/**
* Responsible for splitting an outgoing ByteBuffer into several smaller Chunks and
* send them as seperate messages to the {@link Chunker}-Channel
*/
public static class PacketChunkSender {
private final FriendlyByteBuf networkedBuf;
public final UUID uuid;
public final int chunkCount;
public final int size;
public final ResourceLocation origin;
public PacketChunkSender(FriendlyByteBuf buf, ResourceLocation origin) {
networkedBuf = buf;
size = buf.readableBytes();
chunkCount = (int) Math.ceil((double) size / MAX_PAYLOAD_SIZE);
uuid = UUID.randomUUID();
this.origin = origin;
}
public void sendChunks(Collection<ServerPlayer> players) {
BCLib.LOGGER.info("Sending Request in " + chunkCount + " Packet-Chunks");
for (int i = -1; i < chunkCount; i++) {
Chunker c = new Chunker(i, uuid, networkedBuf, chunkCount, origin);
FriendlyByteBuf buf = PacketByteBufs.create();
c.serializeDataOnServer(buf);
for (ServerPlayer player : players) {
ServerPlayNetworking.send(player, DESCRIPTOR.IDENTIFIER, buf);
}
}
}
}
//header = version + UUID + serialNo + size, see serializeDataOnServer
private static final int HEADER_SIZE = 1 + 16 + 4 + 4;
public static final int MAX_PACKET_SIZE = 1024 * 1024;
private static final int MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE;
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(
new ResourceLocation(
BCLib.MOD_ID,
"chunker"
),
Chunker::new,
false,
false
);
private int serialNo;
private UUID uuid;
private int chunkCount;
private FriendlyByteBuf networkedBuf;
private ResourceLocation origin;
protected Chunker(int serialNo, UUID uuid, FriendlyByteBuf networkedBuf, int chunkCount, ResourceLocation origin) {
super(DESCRIPTOR.IDENTIFIER);
this.serialNo = serialNo;
this.uuid = uuid;
this.networkedBuf = networkedBuf;
this.chunkCount = chunkCount;
this.origin = origin;
}
protected Chunker() {
super(DESCRIPTOR.IDENTIFIER);
}
@Override
protected void serializeDataOnServer(FriendlyByteBuf buf) {
//Sending Header. Make sure to change HEADER_SIZE if you change this!
buf.writeByte(0);
buf.writeLong(uuid.getMostSignificantBits());
buf.writeLong(uuid.getLeastSignificantBits());
buf.writeInt(serialNo);
//sending Payload
if (serialNo == -1) {
//this is our header-Chunk that transports status information
buf.writeInt(chunkCount);
writeString(buf, origin.getNamespace());
writeString(buf, origin.getPath());
} else {
//this is an actual payload chunk
buf.capacity(MAX_PACKET_SIZE);
final int size = Math.min(MAX_PAYLOAD_SIZE, networkedBuf.readableBytes());
buf.writeInt(size);
networkedBuf.readBytes(buf, size);
}
}
private PacketChunkReceiver receiver;
@Override
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
final int version = buf.readByte();
uuid = new UUID(buf.readLong(), buf.readLong());
serialNo = buf.readInt();
if (serialNo == -1) {
chunkCount = buf.readInt();
final String namespace = readString(buf);
final String path = readString(buf);
ResourceLocation ident = new ResourceLocation(namespace, path);
BCLib.LOGGER.info("Receiving " + chunkCount + " + Packet-Chunks for " + ident);
receiver = PacketChunkReceiver.getOrCreate(uuid, chunkCount, ident);
} else {
receiver = PacketChunkReceiver.get(uuid);
if (receiver != null) {
final int size = buf.readInt();
receiver.processReceived(buf, serialNo, size);
} else {
BCLib.LOGGER.error("Unknown Packet-Chunk Transfer for " + uuid);
}
}
}
@Override
protected void runOnClientGameThread(Minecraft client) {
if (receiver != null) {
receiver.testFinished();
}
}
}

View file

@ -0,0 +1,28 @@
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
import org.betterx.bclib.client.gui.screens.ProgressScreen;
import net.minecraft.util.ProgressListener;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@Environment(EnvType.CLIENT)
public class ChunkerProgress {
private static ProgressScreen progressScreen;
@Environment(EnvType.CLIENT)
public static void setProgressScreen(ProgressScreen scr) {
progressScreen = scr;
}
@Environment(EnvType.CLIENT)
public static ProgressScreen getProgressScreen() {
return progressScreen;
}
@Environment(EnvType.CLIENT)
public static ProgressListener getProgressListener() {
return progressScreen;
}
}

View file

@ -0,0 +1,75 @@
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
import org.betterx.bclib.BCLib;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class FileContentWrapper {
private byte[] rawContent;
private ByteArrayOutputStream outputStream;
FileContentWrapper(byte[] content) {
this.rawContent = content;
this.outputStream = null;
}
public byte[] getOriginalContent() {
return rawContent;
}
public byte[] getRawContent() {
if (outputStream != null) {
return outputStream.toByteArray();
}
return rawContent;
}
private void invalidateOutputStream() {
if (this.outputStream != null) {
try {
this.outputStream.close();
} catch (IOException e) {
BCLib.LOGGER.debug(e);
}
}
this.outputStream = null;
}
public void setRawContent(byte[] rawContent) {
this.rawContent = rawContent;
invalidateOutputStream();
}
public void syncWithOutputStream() {
if (outputStream != null) {
try {
outputStream.flush();
} catch (IOException e) {
BCLib.LOGGER.error(e.getMessage());
e.printStackTrace();
}
setRawContent(getRawContent());
invalidateOutputStream();
}
}
public ByteArrayInputStream getInputStream() {
if (rawContent == null) return new ByteArrayInputStream(new byte[0]);
return new ByteArrayInputStream(rawContent);
}
public ByteArrayOutputStream getOrCreateOutputStream() {
if (this.outputStream == null) {
return this.getEmptyOutputStream();
}
return this.outputStream;
}
public ByteArrayOutputStream getEmptyOutputStream() {
invalidateOutputStream();
this.outputStream = new ByteArrayOutputStream(this.rawContent.length);
return this.outputStream;
}
}

View file

@ -0,0 +1,530 @@
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI;
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
import org.betterx.bclib.api.v2.dataexchange.DataHandlerDescriptor;
import org.betterx.bclib.client.gui.screens.ModListScreen;
import org.betterx.bclib.client.gui.screens.ProgressScreen;
import org.betterx.bclib.client.gui.screens.SyncFilesScreen;
import org.betterx.bclib.client.gui.screens.WarnBCLibVersionMismatch;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.config.ServerConfig;
import org.betterx.worlds.together.util.ModUtil;
import org.betterx.worlds.together.util.ModUtil.ModInfo;
import org.betterx.worlds.together.util.PathUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.loader.api.metadata.ModEnvironment;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
* Sent from the Server to the Client.
* <p>
* For Details refer to {@link HelloServer}
*/
public class HelloClient extends DataHandler.FromServer {
public record OfferedModInfo(String version, int size, boolean canDownload) {
}
public interface IServerModMap extends Map<String, OfferedModInfo> {
}
public static class ServerModMap extends HashMap<String, OfferedModInfo> implements IServerModMap {
}
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(
new ResourceLocation(
BCLib.MOD_ID,
"hello_client"
),
HelloClient::new,
false,
false
);
public HelloClient() {
super(DESCRIPTOR.IDENTIFIER);
}
static String getBCLibVersion() {
return ModUtil.getModVersion(BCLib.MOD_ID);
}
@Override
protected boolean prepareDataOnServer() {
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
return false;
}
AutoSync.loadSyncFolder();
return true;
}
@Override
protected void serializeDataOnServer(FriendlyByteBuf buf) {
final String vbclib = getBCLibVersion();
BCLib.LOGGER.info("Sending Hello to Client. (server=" + vbclib + ")");
//write BCLibVersion (=protocol version)
buf.writeInt(ModUtil.convertModVersion(vbclib));
if (Configs.SERVER_CONFIG.isOfferingMods() || Configs.SERVER_CONFIG.isOfferingInfosForMods()) {
List<String> mods = DataExchangeAPI.registeredMods();
final List<String> inmods = mods;
if (Configs.SERVER_CONFIG.isOfferingAllMods() || Configs.SERVER_CONFIG.isOfferingInfosForMods()) {
mods = new ArrayList<>(inmods.size());
mods.addAll(inmods);
mods.addAll(ModUtil
.getMods()
.entrySet()
.stream()
.filter(entry -> entry.getValue().metadata.getEnvironment() != ModEnvironment.SERVER && !inmods.contains(
entry.getKey()))
.map(entry -> entry.getKey())
.collect(Collectors.toList())
);
}
mods = mods
.stream()
.filter(entry -> !Configs.SERVER_CONFIG.get(ServerConfig.EXCLUDED_MODS).contains(entry))
.collect(Collectors.toList());
//write Plugin Versions
buf.writeInt(mods.size());
for (String modID : mods) {
final String ver = ModUtil.getModVersion(modID);
int size = 0;
final ModInfo mi = ModUtil.getModInfo(modID);
if (mi != null) {
try {
size = (int) Files.size(mi.jarPath);
} catch (IOException e) {
BCLib.LOGGER.error("Unable to get File Size: " + e.getMessage());
}
}
writeString(buf, modID);
buf.writeInt(ModUtil.convertModVersion(ver));
buf.writeInt(size);
final boolean canDownload = size > 0 && Configs.SERVER_CONFIG.isOfferingMods() && (Configs.SERVER_CONFIG.isOfferingAllMods() || inmods.contains(
modID));
buf.writeBoolean(canDownload);
BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver + " (size: " + PathUtil.humanReadableFileSize(
size) + ", download=" + canDownload + ")");
}
} else {
BCLib.LOGGER.info("Server will not list Mods.");
buf.writeInt(0);
}
if (Configs.SERVER_CONFIG.isOfferingFiles() || Configs.SERVER_CONFIG.isOfferingConfigs()) {
//do only include files that exist on the server
final List<AutoFileSyncEntry> existingAutoSyncFiles = AutoSync.getAutoSyncFiles()
.stream()
.filter(e -> e.fileName.exists())
.filter(e -> (e.isConfigFile() && Configs.SERVER_CONFIG.isOfferingConfigs()) || (e instanceof AutoFileSyncEntry.ForDirectFileRequest && Configs.SERVER_CONFIG.isOfferingFiles()))
.collect(Collectors.toList());
//send config Data
buf.writeInt(existingAutoSyncFiles.size());
for (AutoFileSyncEntry entry : existingAutoSyncFiles) {
entry.serialize(buf);
BCLib.LOGGER.info(" - Offering " + (entry.isConfigFile() ? "Config " : "File ") + entry);
}
} else {
BCLib.LOGGER.info("Server will neither offer Files nor Configs.");
buf.writeInt(0);
}
if (Configs.SERVER_CONFIG.isOfferingFiles()) {
buf.writeInt(AutoSync.syncFolderDescriptions.size());
AutoSync.syncFolderDescriptions.forEach(desc -> {
BCLib.LOGGER.info(" - Offering Folder " + desc.localFolder + " (allowDelete=" + desc.removeAdditionalFiles + ")");
desc.serialize(buf);
});
} else {
BCLib.LOGGER.info("Server will not offer Sync Folders.");
buf.writeInt(0);
}
buf.writeBoolean(Configs.SERVER_CONFIG.isOfferingInfosForMods());
}
String bclibVersion = "0.0.0";
IServerModMap modVersion = new ServerModMap();
List<AutoSync.AutoSyncTriple> autoSyncedFiles = null;
List<SyncFolderDescriptor> autoSynFolders = null;
boolean serverPublishedModInfo = false;
@Environment(EnvType.CLIENT)
@Override
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
//read BCLibVersion (=protocol version)
bclibVersion = ModUtil.convertModVersion(buf.readInt());
//read Plugin Versions
modVersion = new ServerModMap();
int count = buf.readInt();
for (int i = 0; i < count; i++) {
final String id = readString(buf);
final String version = ModUtil.convertModVersion(buf.readInt());
final int size;
final boolean canDownload;
//since v0.4.1 we also send the size of the mod-File
size = buf.readInt();
canDownload = buf.readBoolean();
modVersion.put(id, new OfferedModInfo(version, size, canDownload));
}
//read config Data
count = buf.readInt();
autoSyncedFiles = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
//System.out.println("Deserializing ");
AutoSync.AutoSyncTriple t = AutoFileSyncEntry.deserializeAndMatch(buf);
autoSyncedFiles.add(t);
//System.out.println(t.first);
}
autoSynFolders = new ArrayList<>(1);
//since v0.4.1 we also send the sync folders
final int folderCount = buf.readInt();
for (int i = 0; i < folderCount; i++) {
SyncFolderDescriptor desc = SyncFolderDescriptor.deserialize(buf);
autoSynFolders.add(desc);
}
serverPublishedModInfo = buf.readBoolean();
}
@Environment(EnvType.CLIENT)
private void processAutoSyncFolder(
final List<AutoSyncID> filesToRequest,
final List<AutoSyncID.ForDirectFileRequest> filesToRemove
) {
if (!Configs.CLIENT_CONFIG.isAcceptingFiles()) {
return;
}
if (autoSynFolders.size() > 0) {
BCLib.LOGGER.info("Folders offered by Server:");
}
autoSynFolders.forEach(desc -> {
//desc contains the fileCache sent from the server, load the local version to get hold of the actual file cache on the client
SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(desc.folderID);
if (localDescriptor != null) {
BCLib.LOGGER.info(" - " + desc.folderID + " (" + desc.localFolder + ", allowRemove=" + desc.removeAdditionalFiles + ")");
localDescriptor.invalidateCache();
desc.relativeFilesStream()
.filter(desc::discardChildElements)
.forEach(subFile -> {
BCLib.LOGGER.warning(" * " + subFile.relPath + " (REJECTED)");
});
if (desc.removeAdditionalFiles) {
List<AutoSyncID.ForDirectFileRequest> additionalFiles = localDescriptor.relativeFilesStream()
.filter(subFile -> !desc.hasRelativeFile(
subFile))
.map(desc::mapAbsolute)
.filter(desc::acceptChildElements)
.map(absPath -> new AutoSyncID.ForDirectFileRequest(
desc.folderID,
absPath.toFile()
))
.collect(Collectors.toList());
additionalFiles.forEach(aid -> BCLib.LOGGER.info(" * " + desc.localFolder.relativize(aid.relFile.toPath()) + " (missing on server)"));
filesToRemove.addAll(additionalFiles);
}
desc.relativeFilesStream()
.filter(desc::acceptChildElements)
.forEach(subFile -> {
SyncFolderDescriptor.SubFile localSubFile = localDescriptor.getLocalSubFile(subFile.relPath);
if (localSubFile != null) {
//the file exists locally, check if the hashes match
if (!localSubFile.hash.equals(subFile.hash)) {
BCLib.LOGGER.info(" * " + subFile.relPath + " (changed)");
filesToRequest.add(new AutoSyncID.ForDirectFileRequest(
desc.folderID,
new File(subFile.relPath)
));
} else {
BCLib.LOGGER.info(" * " + subFile.relPath);
}
} else {
//the file is missing locally
BCLib.LOGGER.info(" * " + subFile.relPath + " (missing on client)");
filesToRequest.add(new AutoSyncID.ForDirectFileRequest(
desc.folderID,
new File(subFile.relPath)
));
}
});
//free some memory
localDescriptor.invalidateCache();
} else {
BCLib.LOGGER.info(" - " + desc.folderID + " (Failed to find)");
}
});
}
@Environment(EnvType.CLIENT)
private void processSingleFileSync(final List<AutoSyncID> filesToRequest) {
final boolean debugHashes = Configs.CLIENT_CONFIG.shouldPrintDebugHashes();
if (autoSyncedFiles.size() > 0) {
BCLib.LOGGER.info("Files offered by Server:");
}
//Handle single sync files
//Single files need to be registered for sync on both client and server
//There are no restrictions to the target folder, but the client decides the final
//location.
for (AutoSync.AutoSyncTriple e : autoSyncedFiles) {
String actionString = "";
FileContentWrapper contentWrapper = new FileContentWrapper(e.serverContent);
if (e.localMatch == null) {
actionString = "(unknown source -> omitting)";
//filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
} else if (e.localMatch.needTransfer.test(e.localMatch.getFileHash(), e.serverHash, contentWrapper)) {
actionString = "(prepare update)";
//we did not yet receive the new content
if (contentWrapper.getRawContent() == null) {
filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
} else {
filesToRequest.add(new AutoSyncID.WithContentOverride(
e.serverHash.modID,
e.serverHash.uniqueID,
contentWrapper,
e.localMatch.fileName
));
}
}
BCLib.LOGGER.info(" - " + e + ": " + actionString);
if (debugHashes) {
BCLib.LOGGER.info(" * " + e.serverHash + " (Server)");
BCLib.LOGGER.info(" * " + e.localMatch.getFileHash() + " (Client)");
BCLib.LOGGER.info(" * local Content " + (contentWrapper.getRawContent() == null));
}
}
}
@Environment(EnvType.CLIENT)
private void processModFileSync(final List<AutoSyncID> filesToRequest, final Set<String> mismatchingMods) {
for (Entry<String, OfferedModInfo> e : modVersion.entrySet()) {
final String localVersion = ModUtil.convertModVersion(ModUtil.convertModVersion(ModUtil.getModVersion(e.getKey())));
final OfferedModInfo serverInfo = e.getValue();
ModInfo nfo = ModUtil.getModInfo(e.getKey());
final boolean clientOnly = nfo != null && nfo.metadata.getEnvironment() == ModEnvironment.CLIENT;
final boolean requestMod = !clientOnly && !serverInfo.version.equals(localVersion) && serverInfo.size > 0 && serverInfo.canDownload;
BCLib.LOGGER.info(" - " + e.getKey() + " (client=" + localVersion + ", server=" + serverInfo.version + ", size=" + PathUtil.humanReadableFileSize(
serverInfo.size) + (requestMod ? ", requesting" : "") + (serverInfo.canDownload
? ""
: ", not offered") + (clientOnly ? ", client only" : "") + ")");
if (requestMod) {
filesToRequest.add(new AutoSyncID.ForModFileRequest(e.getKey(), serverInfo.version));
}
if (!serverInfo.version.equals(localVersion)) {
mismatchingMods.add(e.getKey());
}
}
mismatchingMods.addAll(ModListScreen.localMissing(modVersion));
mismatchingMods.addAll(ModListScreen.serverMissing(modVersion));
}
@Override
protected boolean isBlocking() {
return true;
}
@Environment(EnvType.CLIENT)
@Override
protected void runOnClientGameThread(Minecraft client) {
if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
return;
}
final String localBclibVersion = getBCLibVersion();
BCLib.LOGGER.info("Received Hello from Server. (client=" + localBclibVersion + ", server=" + bclibVersion + ")");
if (ModUtil.convertModVersion(localBclibVersion) != ModUtil.convertModVersion(bclibVersion)) {
showBCLibError(client);
return;
}
final List<AutoSyncID> filesToRequest = new ArrayList<>(2);
final List<AutoSyncID.ForDirectFileRequest> filesToRemove = new ArrayList<>(2);
final Set<String> mismatchingMods = new HashSet<>(2);
processModFileSync(filesToRequest, mismatchingMods);
processSingleFileSync(filesToRequest);
processAutoSyncFolder(filesToRequest, filesToRemove);
//Handle folder sync
//Both client and server need to know about the folder you want to sync
//Files can only get placed within that folder
if ((filesToRequest.size() > 0 || filesToRemove.size() > 0) && (Configs.CLIENT_CONFIG.isAcceptingMods() || Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles())) {
showSyncFilesScreen(client, filesToRequest, filesToRemove);
return;
} else if (serverPublishedModInfo && mismatchingMods.size() > 0 && Configs.CLIENT_CONFIG.isShowingModInfo()) {
client.setScreen(new ModListScreen(
client.screen,
Component.translatable("title.bclib.modmissmatch"),
Component.translatable("message.bclib.modmissmatch"),
CommonComponents.GUI_PROCEED,
ModUtil.getMods(),
modVersion
));
return;
}
}
@Environment(EnvType.CLIENT)
protected void showBCLibError(Minecraft client) {
BCLib.LOGGER.error("BCLib differs on client and server.");
client.setScreen(new WarnBCLibVersionMismatch((download) -> {
if (download) {
requestBCLibDownload();
this.onCloseSyncFilesScreen();
} else {
Minecraft.getInstance()
.setScreen(null);
}
}));
}
@Environment(EnvType.CLIENT)
protected void showSyncFilesScreen(
Minecraft client,
List<AutoSyncID> files,
final List<AutoSyncID.ForDirectFileRequest> filesToRemove
) {
int configFiles = 0;
int singleFiles = 0;
int folderFiles = 0;
int modFiles = 0;
for (AutoSyncID aid : files) {
if (aid.isConfigFile()) {
configFiles++;
} else if (aid instanceof AutoSyncID.ForModFileRequest) {
modFiles++;
} else if (aid instanceof AutoSyncID.ForDirectFileRequest) {
folderFiles++;
} else {
singleFiles++;
}
}
client.setScreen(new SyncFilesScreen(
modFiles,
configFiles,
singleFiles,
folderFiles,
filesToRemove.size(),
modVersion,
(downloadMods, downloadConfigs, downloadFiles, removeFiles) -> {
if (downloadMods || downloadConfigs || downloadFiles) {
BCLib.LOGGER.info("Updating local Files:");
List<AutoSyncID.WithContentOverride> localChanges = new ArrayList<>(
files.toArray().length);
List<AutoSyncID> requestFiles = new ArrayList<>(files.toArray().length);
files.forEach(aid -> {
if (aid.isConfigFile() && downloadConfigs) {
processOfferedFile(requestFiles, aid);
} else if (aid instanceof AutoSyncID.ForModFileRequest && downloadMods) {
processOfferedFile(requestFiles, aid);
} else if (downloadFiles) {
processOfferedFile(requestFiles, aid);
}
});
requestFileDownloads(requestFiles);
}
if (removeFiles) {
filesToRemove.forEach(aid -> {
BCLib.LOGGER.info(" - " + aid.relFile + " (removing)");
aid.relFile.delete();
});
}
this.onCloseSyncFilesScreen();
}
));
}
@Environment(EnvType.CLIENT)
private void onCloseSyncFilesScreen() {
Minecraft.getInstance()
.setScreen(ChunkerProgress.getProgressScreen());
}
private void processOfferedFile(List<AutoSyncID> requestFiles, AutoSyncID aid) {
if (aid instanceof AutoSyncID.WithContentOverride) {
final AutoSyncID.WithContentOverride aidc = (AutoSyncID.WithContentOverride) aid;
BCLib.LOGGER.info(" - " + aid + " (updating Content)");
SendFiles.writeSyncedFile(aid, aidc.contentWrapper.getRawContent(), aidc.localFile);
} else {
requestFiles.add(aid);
BCLib.LOGGER.info(" - " + aid + " (requesting)");
}
}
private void requestBCLibDownload() {
BCLib.LOGGER.warning("Starting download of BCLib");
requestFileDownloads(List.of(new AutoSyncID.ForModFileRequest(BCLib.MOD_ID, bclibVersion)));
}
@Environment(EnvType.CLIENT)
private void requestFileDownloads(List<AutoSyncID> files) {
BCLib.LOGGER.info("Starting download of Files:" + files.size());
final ProgressScreen progress = new ProgressScreen(
null,
Component.translatable("title.bclib.filesync.progress"),
Component.translatable("message.bclib.filesync.progress")
);
progress.progressStart(Component.translatable("message.bclib.filesync.progress.stage.empty"));
ChunkerProgress.setProgressScreen(progress);
DataExchangeAPI.send(new RequestFiles(files));
}
}

View file

@ -0,0 +1,119 @@
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI;
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
import org.betterx.bclib.api.v2.dataexchange.DataHandlerDescriptor;
import org.betterx.bclib.config.Configs;
import org.betterx.worlds.together.util.ModUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.entity.player.Player;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import java.io.File;
/**
* This message is sent once a player enters the world. It initiates a sequence of Messages that will sync files between both
* client and server.
* <table>
* <caption>Description</caption>
* <tr>
* <th>Server</th>
* <th></th>
* <th>Client</th>
* <th></th>
* </tr>
* <tr>
* <td colspan="4">Player enters World</td>
* </tr>
* <tr>
* <td></td>
* <td>&lt;--</td>
* <td>{@link HelloServer}</td>
* <td>Sends the current BLib-Version installed on the Client</td>
* </tr>
* <tr>
* <td>{@link HelloClient}</td>
* <td>--&gt;</td>
* <td></td>
* <td>Sends the current BClIb-Version, the Version of all Plugins and data for all AutpoSync-Files
* ({@link DataExchangeAPI#addAutoSyncFile(String, File)} on the Server</td>
* </tr>
* <tr>
* <td></td>
* <td>&lt;--</td>
* <td>{@link RequestFiles}</td>
* <td>Request missing or out of sync Files from the Server</td>
* </tr>
* <tr>
* <td>{@link SendFiles}</td>
* <td>--&gt;</td>
* <td></td>
* <td>Send Files from the Server to the Client</td>
* </tr>
* </table>
*/
public class HelloServer extends DataHandler.FromClient {
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(
new ResourceLocation(
BCLib.MOD_ID,
"hello_server"
),
HelloServer::new,
true,
false
);
protected String bclibVersion = "0.0.0";
public HelloServer() {
super(DESCRIPTOR.IDENTIFIER);
}
@Environment(EnvType.CLIENT)
@Override
protected boolean prepareDataOnClient() {
if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
return false;
}
return true;
}
@Environment(EnvType.CLIENT)
@Override
protected void serializeDataOnClient(FriendlyByteBuf buf) {
BCLib.LOGGER.info("Sending hello to server.");
buf.writeInt(ModUtil.convertModVersion(HelloClient.getBCLibVersion()));
}
@Override
protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, Player player, PacketSender responseSender) {
bclibVersion = ModUtil.convertModVersion(buf.readInt());
}
@Override
protected void runOnServerGameThread(MinecraftServer server, Player player) {
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
return;
}
String localBclibVersion = HelloClient.getBCLibVersion();
BCLib.LOGGER.info("Received Hello from Client. (server=" + localBclibVersion + ", client=" + bclibVersion + ")");
if (!server.isPublished()) {
BCLib.LOGGER.info("Auto-Sync is disabled for Singleplayer worlds.");
return;
}
reply(new HelloClient(), server);
}
}

View file

@ -0,0 +1,109 @@
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
import org.betterx.bclib.api.v2.dataexchange.DataHandlerDescriptor;
import org.betterx.bclib.config.Configs;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.entity.player.Player;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public class RequestFiles extends DataHandler.FromClient {
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(
new ResourceLocation(
BCLib.MOD_ID,
"request_files"
),
RequestFiles::new,
false,
false
);
static String currentToken = "";
protected List<AutoSyncID> files;
private RequestFiles() {
this(null);
}
public RequestFiles(List<AutoSyncID> files) {
super(DESCRIPTOR.IDENTIFIER);
this.files = files;
}
@Environment(EnvType.CLIENT)
@Override
protected boolean prepareDataOnClient() {
if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
return false;
}
return true;
}
@Environment(EnvType.CLIENT)
@Override
protected void serializeDataOnClient(FriendlyByteBuf buf) {
newToken();
writeString(buf, currentToken);
buf.writeInt(files.size());
for (AutoSyncID a : files) {
a.serializeData(buf);
}
}
String receivedToken = "";
@Override
protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, Player player, PacketSender responseSender) {
receivedToken = readString(buf);
int size = buf.readInt();
files = new ArrayList<>(size);
BCLib.LOGGER.info("Client requested " + size + " Files:");
for (int i = 0; i < size; i++) {
AutoSyncID asid = AutoSyncID.deserializeData(buf);
files.add(asid);
BCLib.LOGGER.info(" - " + asid);
}
}
@Override
protected void runOnServerGameThread(MinecraftServer server, Player player) {
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
return;
}
List<AutoFileSyncEntry> syncEntries = files.stream()
.map(asid -> AutoFileSyncEntry.findMatching(asid))
.filter(e -> e != null)
.collect(Collectors.toList());
reply(new SendFiles(syncEntries, receivedToken), server);
}
public static void newToken() {
currentToken = UUID.randomUUID()
.toString();
}
static {
newToken();
}
}

View file

@ -0,0 +1,225 @@
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
import org.betterx.bclib.api.v2.dataexchange.DataHandlerDescriptor;
import org.betterx.bclib.client.gui.screens.ConfirmRestartScreen;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.util.Pair;
import org.betterx.bclib.util.Triple;
import org.betterx.worlds.together.util.PathUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class SendFiles extends DataHandler.FromServer {
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(
new ResourceLocation(
BCLib.MOD_ID,
"send_files"
),
SendFiles::new,
false,
false
);
protected List<AutoFileSyncEntry> files;
private String token;
public SendFiles() {
this(null, "");
}
public SendFiles(List<AutoFileSyncEntry> files, String token) {
super(DESCRIPTOR.IDENTIFIER);
this.files = files;
this.token = token;
}
@Override
protected boolean prepareDataOnServer() {
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
return false;
}
return true;
}
@Override
protected void serializeDataOnServer(FriendlyByteBuf buf) {
List<AutoFileSyncEntry> existingFiles = files.stream()
.filter(e -> e != null && e.fileName != null && e.fileName.exists())
.collect(Collectors.toList());
/*
//this will try to send a file that was not registered or requested by the client
existingFiles.add(new AutoFileSyncEntry("none", new File("D:\\MinecraftPlugins\\BetterNether\\run\\server.properties"),true,(a, b, content) -> {
System.out.println("Got Content:" + content.length);
return true;
}));*/
/*//this will try to send a folder-file that was not registered or requested by the client
existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("test.json"), DataExchange.SYNC_FOLDER.mapAbsolute("test.json").toFile()));*/
/*//this will try to send a folder-file that was not registered or requested by the client and is outside the base-folder
existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("../breakout.json"), DataExchange.SYNC_FOLDER.mapAbsolute("../breakout.json").toFile()));*/
writeString(buf, token);
buf.writeInt(existingFiles.size());
BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:");
for (AutoFileSyncEntry entry : existingFiles) {
int length = entry.serializeContent(buf);
BCLib.LOGGER.info(" - " + entry + " (" + PathUtil.humanReadableFileSize(length) + ")");
}
}
private List<Pair<AutoFileSyncEntry, byte[]>> receivedFiles;
@Environment(EnvType.CLIENT)
@Override
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
if (Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) {
token = readString(buf);
if (!token.equals(RequestFiles.currentToken)) {
RequestFiles.newToken();
BCLib.LOGGER.error("Unrequested File Transfer!");
receivedFiles = new ArrayList<>(0);
return;
}
RequestFiles.newToken();
int size = buf.readInt();
receivedFiles = new ArrayList<>(size);
BCLib.LOGGER.info("Server sent " + size + " Files:");
for (int i = 0; i < size; i++) {
Triple<AutoFileSyncEntry, byte[], AutoSyncID> p = AutoFileSyncEntry.deserializeContent(buf);
if (p.first != null) {
final String type;
if (p.first.isConfigFile() && Configs.CLIENT_CONFIG.isAcceptingConfigs()) {
receivedFiles.add(p);
type = "Accepted Config ";
} else if (p.first instanceof AutoFileSyncEntry.ForModFileRequest && Configs.CLIENT_CONFIG.isAcceptingMods()) {
receivedFiles.add(p);
type = "Accepted Mod ";
} else if (Configs.CLIENT_CONFIG.isAcceptingFiles()) {
receivedFiles.add(p);
type = "Accepted File ";
} else {
type = "Ignoring ";
}
BCLib.LOGGER.info(" - " + type + p.first + " (" + PathUtil.humanReadableFileSize(p.second.length) + ")");
} else {
BCLib.LOGGER.error(" - Failed to receive File " + p.third + ", possibly sent from a Mod that is not installed on the client.");
}
}
}
}
@Environment(EnvType.CLIENT)
@Override
protected void runOnClientGameThread(Minecraft client) {
if (Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) {
BCLib.LOGGER.info("Writing Files:");
for (Pair<AutoFileSyncEntry, byte[]> entry : receivedFiles) {
final AutoFileSyncEntry e = entry.first;
final byte[] data = entry.second;
writeSyncedFile(e, data, e.fileName);
}
showConfirmRestart(client);
}
}
@Environment(EnvType.CLIENT)
static void writeSyncedFile(AutoSyncID e, byte[] data, File fileName) {
if (fileName != null && !PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())) {
BCLib.LOGGER.error(fileName + " is not within game folder " + PathUtil.GAME_FOLDER);
return;
}
if (!PathUtil.MOD_BAK_FOLDER.toFile().exists()) {
PathUtil.MOD_BAK_FOLDER.toFile().mkdirs();
}
Path path = fileName != null ? fileName.toPath() : null;
Path removeAfter = null;
if (e instanceof AutoFileSyncEntry.ForModFileRequest mase) {
removeAfter = path;
int count = 0;
final String prefix = "_bclib_synced_";
String name = prefix + mase.modID + "_" + mase.version.replace(".", "_") + ".jar";
do {
if (path != null) {
//move to the same directory as the existing Mod
path = path.getParent()
.resolve(name);
} else {
//move to the default mode location
path = PathUtil.MOD_FOLDER.resolve(name);
}
count++;
name = prefix + mase.modID + "_" + mase.version.replace(".", "_") + "__" + String.format(
"%03d",
count
) + ".jar";
} while (path.toFile().exists());
}
BCLib.LOGGER.info(" - Writing " + path + " (" + PathUtil.humanReadableFileSize(data.length) + ")");
try {
final File parentFile = path.getParent()
.toFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
Files.write(path, data);
if (removeAfter != null) {
final String bakFileName = removeAfter.toFile().getName();
String collisionFreeName = bakFileName;
Path targetPath;
int count = 0;
do {
targetPath = PathUtil.MOD_BAK_FOLDER.resolve(collisionFreeName);
count++;
collisionFreeName = String.format("%03d", count) + "_" + bakFileName;
} while (targetPath.toFile().exists());
BCLib.LOGGER.info(" - Moving " + removeAfter + " to " + targetPath);
removeAfter.toFile().renameTo(targetPath.toFile());
}
AutoSync.didReceiveFile(e, fileName);
} catch (IOException ioException) {
BCLib.LOGGER.error(" --> Writing " + fileName + " failed: " + ioException);
}
}
@Environment(EnvType.CLIENT)
protected void showConfirmRestart(Minecraft client) {
client.setScreen(new ConfirmRestartScreen(() -> {
Minecraft.getInstance()
.setScreen(null);
client.stop();
}));
}
}

View file

@ -0,0 +1,218 @@
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
import org.betterx.bclib.api.v2.dataexchange.FileHash;
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSyncID.ForDirectFileRequest;
import org.betterx.bclib.config.Configs;
import org.betterx.worlds.together.util.PathUtil;
import net.minecraft.network.FriendlyByteBuf;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
public class SyncFolderDescriptor {
static class SubFile {
public final String relPath;
public final FileHash hash;
SubFile(String relPath, FileHash hash) {
this.relPath = relPath;
this.hash = hash;
}
@Override
public String toString() {
return relPath;
}
public void serialize(FriendlyByteBuf buf) {
DataHandler.writeString(buf, relPath);
hash.serialize(buf);
}
public static SubFile deserialize(FriendlyByteBuf buf) {
final String relPath = DataHandler.readString(buf);
FileHash hash = FileHash.deserialize(buf);
return new SubFile(relPath, hash);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof String) return relPath.equals(o);
if (!(o instanceof SubFile)) return false;
SubFile subFile = (SubFile) o;
return relPath.equals(subFile.relPath);
}
@Override
public int hashCode() {
return relPath.hashCode();
}
}
@NotNull
public final String folderID;
public final boolean removeAdditionalFiles;
@NotNull
public final Path localFolder;
private List<SubFile> fileCache;
public SyncFolderDescriptor(String folderID, Path localFolder, boolean removeAdditionalFiles) {
this.removeAdditionalFiles = removeAdditionalFiles;
this.folderID = folderID;
this.localFolder = localFolder;
fileCache = null;
}
@Override
public String toString() {
return "SyncFolderDescriptor{" + "folderID='" + folderID + '\'' + ", removeAdditionalFiles=" + removeAdditionalFiles + ", localFolder=" + localFolder + ", files=" + (
fileCache == null
? "?"
: fileCache.size()) + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof String) {
return folderID.equals(o);
}
if (o instanceof ForDirectFileRequest) {
return folderID.equals(((ForDirectFileRequest) o).uniqueID);
}
if (!(o instanceof SyncFolderDescriptor)) return false;
SyncFolderDescriptor that = (SyncFolderDescriptor) o;
return folderID.equals(that.folderID);
}
@Override
public int hashCode() {
return folderID.hashCode();
}
public int fileCount() {
return fileCache == null ? 0 : fileCache.size();
}
public void invalidateCache() {
fileCache = null;
}
public void loadCache() {
if (fileCache == null) {
fileCache = new ArrayList<>(8);
PathUtil.fileWalker(localFolder.toFile(), p -> fileCache.add(new SubFile(
localFolder.relativize(p)
.toString(),
FileHash.create(p.toFile())
)));
/*//this tests if we can trick the system to load files that are not beneath the base-folder
if (!BCLib.isClient()) {
fileCache.add(new SubFile("../breakout.json", FileHash.create(mapAbsolute("../breakout.json").toFile())));
}*/
}
}
public void serialize(FriendlyByteBuf buf) {
final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(AutoSync.SYNC_CATEGORY, "debugHashes", false);
loadCache();
DataHandler.writeString(buf, folderID);
buf.writeBoolean(removeAdditionalFiles);
buf.writeInt(fileCache.size());
fileCache.forEach(fl -> {
BCLib.LOGGER.info(" - " + fl.relPath);
if (debugHashes) {
BCLib.LOGGER.info(" " + fl.hash);
}
fl.serialize(buf);
});
}
public static SyncFolderDescriptor deserialize(FriendlyByteBuf buf) {
final String folderID = DataHandler.readString(buf);
final boolean remAddFiles = buf.readBoolean();
final int count = buf.readInt();
SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(folderID);
final SyncFolderDescriptor desc;
if (localDescriptor != null) {
desc = new SyncFolderDescriptor(
folderID,
localDescriptor.localFolder,
localDescriptor.removeAdditionalFiles && remAddFiles
);
desc.fileCache = new ArrayList<>(count);
} else {
BCLib.LOGGER.warning(BCLib.isClient()
? "Client"
: "Server" + " does not know Sync-Folder ID '" + folderID + "'");
desc = null;
}
for (int i = 0; i < count; i++) {
SubFile relPath = SubFile.deserialize(buf);
if (desc != null) desc.fileCache.add(relPath);
}
return desc;
}
//Note: make sure loadCache was called before using this
boolean hasRelativeFile(String relFile) {
return fileCache.stream()
.filter(sf -> sf.equals(relFile))
.findFirst()
.isPresent();
}
//Note: make sure loadCache was called before using this
boolean hasRelativeFile(SubFile subFile) {
return hasRelativeFile(subFile.relPath);
}
//Note: make sure loadCache was called before using this
SubFile getLocalSubFile(String relPath) {
return fileCache.stream()
.filter(sf -> sf.relPath.equals(relPath))
.findFirst()
.orElse(null);
}
Stream<SubFile> relativeFilesStream() {
loadCache();
return fileCache.stream();
}
public Path mapAbsolute(String relPath) {
return this.localFolder.resolve(relPath)
.normalize();
}
public Path mapAbsolute(SubFile subFile) {
return this.localFolder.resolve(subFile.relPath)
.normalize();
}
public boolean acceptChildElements(Path absPath) {
return PathUtil.isChildOf(this.localFolder, absPath);
}
public boolean acceptChildElements(SubFile subFile) {
return acceptChildElements(mapAbsolute(subFile));
}
public boolean discardChildElements(SubFile subFile) {
return !acceptChildElements(subFile);
}
}

View file

@ -0,0 +1,598 @@
package org.betterx.bclib.api.v2.datafixer;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.client.gui.screens.AtomicProgressListener;
import org.betterx.bclib.client.gui.screens.ConfirmFixScreen;
import org.betterx.bclib.client.gui.screens.LevelFixErrorScreen;
import org.betterx.bclib.client.gui.screens.LevelFixErrorScreen.Listener;
import org.betterx.bclib.client.gui.screens.ProgressScreen;
import org.betterx.bclib.config.Configs;
import org.betterx.worlds.together.util.Logger;
import org.betterx.worlds.together.world.WorldConfig;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.toasts.SystemToast;
import net.minecraft.client.gui.screens.worldselection.EditWorldScreen;
import net.minecraft.nbt.*;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.storage.RegionFile;
import net.minecraft.world.level.storage.LevelResource;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import java.io.*;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.zip.ZipException;
import org.jetbrains.annotations.NotNull;
/**
* API to manage Patches that need to get applied to a world
*/
public class DataFixerAPI {
static final Logger LOGGER = new Logger("DataFixerAPI");
static class State {
public boolean didFail = false;
protected ArrayList<String> errors = new ArrayList<>();
public void addError(String s) {
errors.add(s);
}
public boolean hasError() {
return errors.size() > 0;
}
public String getErrorMessage() {
return errors.stream().reduce("", (a, b) -> a + " - " + b + "\n");
}
public String[] getErrorMessages() {
String[] res = new String[errors.size()];
return errors.toArray(res);
}
}
@FunctionalInterface
public interface Callback {
void call();
}
private static boolean wrapCall(
LevelStorageSource levelSource,
String levelID,
Function<LevelStorageAccess, Boolean> runWithLevel
) {
LevelStorageSource.LevelStorageAccess levelStorageAccess;
try {
levelStorageAccess = levelSource.createAccess(levelID);
} catch (IOException e) {
BCLib.LOGGER.warning("Failed to read level {} data", levelID, e);
SystemToast.onWorldAccessFailure(Minecraft.getInstance(), levelID);
Minecraft.getInstance().setScreen(null);
return true;
}
boolean returnValue = runWithLevel.apply(levelStorageAccess);
try {
levelStorageAccess.close();
} catch (IOException e) {
BCLib.LOGGER.warning("Failed to unlock access to level {}", levelID, e);
}
return returnValue;
}
/**
* Will apply necessary Patches to the world.
*
* @param levelSource The SourceStorage for this Minecraft instance, You can get this using
* {@code Minecraft.getInstance().getLevelSource()}
* @param levelID The ID of the Level you want to patch
* @param showUI {@code true}, if you want to present the user with a Screen that offers to backup the world
* before applying the patches
* @param onResume When this method retursn {@code true}, this function will be called when the world is ready
* @return {@code true} if the UI was displayed. The UI is only displayed if {@code showUI} was {@code true} and
* patches were enabled in the config and the Guardian did find any patches that need to be applied to the world.
*/
public static boolean fixData(
LevelStorageSource levelSource,
String levelID,
boolean showUI,
Consumer<Boolean> onResume
) {
return wrapCall(levelSource, levelID, (levelStorageAccess) -> fixData(levelStorageAccess, showUI, onResume));
}
/**
* Will apply necessary Patches to the world.
*
* @param levelStorageAccess The access class of the level you want to patch
* @param showUI {@code true}, if you want to present the user with a Screen that offers to backup the world
* before applying the patches
* @param onResume When this method retursn {@code true}, this function will be called when the world is ready
* @return {@code true} if the UI was displayed. The UI is only displayed if {@code showUI} was {@code true} and
* patches were enabled in the config and the Guardian did find any patches that need to be applied to the world.
*/
public static boolean fixData(
LevelStorageSource.LevelStorageAccess levelStorageAccess,
boolean showUI,
Consumer<Boolean> onResume
) {
File levelPath = levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile();
return fixData(levelPath, levelStorageAccess.getLevelId(), showUI, onResume);
}
/**
* Creates the patch level file for new worlds
*/
public static void initializePatchData() {
getMigrationProfile().markApplied();
WorldConfig.saveFile(BCLib.MOD_ID);
}
@Environment(EnvType.CLIENT)
private static AtomicProgressListener showProgressScreen() {
ProgressScreen ps = new ProgressScreen(
Minecraft.getInstance().screen,
Component.translatable("title.bclib.datafixer.progress"),
Component.translatable("message.bclib.datafixer.progress")
);
Minecraft.getInstance().setScreen(ps);
return ps;
}
private static boolean fixData(File dir, String levelID, boolean showUI, Consumer<Boolean> onResume) {
MigrationProfile profile = loadProfileIfNeeded(dir);
BiConsumer<Boolean, Boolean> runFixes = (createBackup, applyFixes) -> {
final AtomicProgressListener progress;
if (applyFixes) {
if (showUI) {
progress = showProgressScreen();
} else {
progress = new AtomicProgressListener() {
private long timeStamp = Util.getMillis();
private AtomicInteger counter = new AtomicInteger(0);
@Override
public void incAtomic(int maxProgress) {
int percentage = (100 * counter.incrementAndGet()) / maxProgress;
if (Util.getMillis() - this.timeStamp >= 1000L) {
this.timeStamp = Util.getMillis();
BCLib.LOGGER.info("Patching... {}%", percentage);
}
}
@Override
public void resetAtomic() {
counter = new AtomicInteger(0);
}
public void stop() {
}
public void progressStage(Component component) {
BCLib.LOGGER.info("Patcher Stage... {}%", component.getString());
}
};
}
} else {
progress = null;
}
Supplier<State> runner = () -> {
if (createBackup) {
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.waitbackup"));
EditWorldScreen.makeBackupAndShowToast(Minecraft.getInstance().getLevelSource(), levelID);
}
if (applyFixes) {
return runDataFixes(dir, profile, progress);
}
return new State();
};
if (showUI) {
Thread fixerThread = new Thread(() -> {
final State state = runner.get();
Minecraft.getInstance()
.execute(() -> {
if (profile != null && showUI) {
//something went wrong, show the user our error
if (state.didFail || state.hasError()) {
showLevelFixErrorScreen(state, (markFixed) -> {
if (markFixed) {
profile.markApplied();
}
onResume.accept(applyFixes);
});
} else {
onResume.accept(applyFixes);
}
}
});
});
fixerThread.start();
} else {
State state = runner.get();
if (state.hasError()) {
LOGGER.error("There were Errors while fixing the Level:");
LOGGER.error(state.getErrorMessage());
}
}
};
//we have some migrations
if (profile != null) {
//display the confirm UI.
if (showUI) {
showBackupWarning(levelID, runFixes);
return true;
} else {
BCLib.LOGGER.warning("Applying Fixes on Level");
runFixes.accept(false, true);
}
}
return false;
}
@Environment(EnvType.CLIENT)
private static void showLevelFixErrorScreen(State state, Listener onContinue) {
Minecraft.getInstance()
.setScreen(new LevelFixErrorScreen(
Minecraft.getInstance().screen,
state.getErrorMessages(),
onContinue
));
}
private static MigrationProfile loadProfileIfNeeded(File levelBaseDir) {
if (!Configs.MAIN_CONFIG.applyPatches()) {
LOGGER.info("World Patches are disabled");
return null;
}
MigrationProfile profile = getMigrationProfile();
profile.runPrePatches(levelBaseDir);
if (!profile.hasAnyFixes()) {
LOGGER.info("Everything up to date");
return null;
}
return profile;
}
@NotNull
private static MigrationProfile getMigrationProfile() {
final CompoundTag patchConfig = WorldConfig.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY);
MigrationProfile profile = Patch.createMigrationData(patchConfig);
return profile;
}
@Environment(EnvType.CLIENT)
static void showBackupWarning(String levelID, BiConsumer<Boolean, Boolean> whenFinished) {
Minecraft.getInstance().setScreen(new ConfirmFixScreen(null, whenFinished::accept));
}
private static State runDataFixes(File dir, MigrationProfile profile, AtomicProgressListener progress) {
State state = new State();
progress.resetAtomic();
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.reading"));
List<File> players = getAllPlayers(dir);
List<File> regions = getAllRegions(dir, null);
final int maxProgress = players.size() + regions.size() + 4;
progress.incAtomic(maxProgress);
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.players"));
players.parallelStream().forEach((file) -> {
fixPlayer(profile, state, file);
progress.incAtomic(maxProgress);
});
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.level"));
fixLevel(profile, state, dir);
progress.incAtomic(maxProgress);
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.worlddata"));
try {
profile.patchWorldData();
} catch (PatchDidiFailException e) {
state.didFail = true;
state.addError("Failed fixing worldconfig (" + e.getMessage() + ")");
BCLib.LOGGER.error(e.getMessage());
}
progress.incAtomic(maxProgress);
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.regions"));
regions.parallelStream().forEach((file) -> {
fixRegion(profile, state, file);
progress.incAtomic(maxProgress);
});
if (!state.didFail) {
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.saving"));
profile.markApplied();
WorldConfig.saveFile(BCLib.MOD_ID);
}
progress.incAtomic(maxProgress);
progress.stop();
return state;
}
private static void fixLevel(MigrationProfile profile, State state, File levelBaseDir) {
try {
LOGGER.info("Inspecting level.dat in " + levelBaseDir);
//load the level (could already contain patches applied by patchLevelDat)
CompoundTag level = profile.getLevelDat(levelBaseDir);
boolean[] changed = {profile.isLevelDatChanged()};
if (profile.getPrePatchException() != null) {
throw profile.getPrePatchException();
}
if (level.contains("Data")) {
CompoundTag dataTag = (CompoundTag) level.get("Data");
if (dataTag.contains("Player")) {
CompoundTag player = (CompoundTag) dataTag.get("Player");
fixPlayerNbt(player, changed, profile);
}
}
if (changed[0]) {
LOGGER.warning("Writing '{}'", profile.getLevelDatFile());
NbtIo.writeCompressed(level, profile.getLevelDatFile());
}
} catch (Exception e) {
BCLib.LOGGER.error("Failed fixing Level-Data.");
state.addError("Failed fixing Level-Data in level.dat (" + e.getMessage() + ")");
state.didFail = true;
e.printStackTrace();
}
}
private static void fixPlayer(MigrationProfile data, State state, File file) {
try {
LOGGER.info("Inspecting " + file);
CompoundTag player = readNbt(file);
boolean[] changed = {false};
fixPlayerNbt(player, changed, data);
if (changed[0]) {
LOGGER.warning("Writing '{}'", file);
NbtIo.writeCompressed(player, file);
}
} catch (Exception e) {
BCLib.LOGGER.error("Failed fixing Player-Data.");
state.addError("Failed fixing Player-Data in " + file.getName() + " (" + e.getMessage() + ")");
state.didFail = true;
e.printStackTrace();
}
}
private static void fixPlayerNbt(CompoundTag player, boolean[] changed, MigrationProfile data) {
//Checking Inventory
ListTag inventory = player.getList("Inventory", Tag.TAG_COMPOUND);
fixItemArrayWithID(inventory, changed, data, true);
//Checking EnderChest
ListTag enderitems = player.getList("EnderItems", Tag.TAG_COMPOUND);
fixItemArrayWithID(enderitems, changed, data, true);
//Checking ReceipBook
if (player.contains("recipeBook")) {
CompoundTag recipeBook = player.getCompound("recipeBook");
changed[0] |= fixStringIDList(recipeBook, "recipes", data);
changed[0] |= fixStringIDList(recipeBook, "toBeDisplayed", data);
}
}
static boolean fixStringIDList(CompoundTag root, String name, MigrationProfile data) {
boolean _changed = false;
if (root.contains(name)) {
ListTag items = root.getList(name, Tag.TAG_STRING);
ListTag newItems = new ListTag();
for (Tag tag : items) {
final StringTag str = (StringTag) tag;
final String replace = data.replaceStringFromIDs(str.getAsString());
if (replace != null) {
_changed = true;
newItems.add(StringTag.valueOf(replace));
} else {
newItems.add(tag);
}
}
if (_changed) {
root.put(name, newItems);
}
}
return _changed;
}
private static void fixRegion(MigrationProfile data, State state, File file) {
try {
Path path = file.toPath();
LOGGER.info("Inspecting " + path);
boolean[] changed = new boolean[1];
RegionFile region = new RegionFile(path, path.getParent(), true);
for (int x = 0; x < 32; x++) {
for (int z = 0; z < 32; z++) {
ChunkPos pos = new ChunkPos(x, z);
changed[0] = false;
if (region.hasChunk(pos) && !state.didFail) {
DataInputStream input = region.getChunkDataInputStream(pos);
CompoundTag root = NbtIo.read(input);
// if ((root.toString().contains("betternether:chest") || root.toString().contains("bclib:chest"))) {
// NbtIo.write(root, new File(file.toString() + "-" + x + "-" + z + ".nbt"));
// }
input.close();
//Checking TileEntities
ListTag tileEntities = root.getCompound("Level")
.getList("TileEntities", Tag.TAG_COMPOUND);
fixItemArrayWithID(tileEntities, changed, data, true);
//Checking Entities
ListTag entities = root.getList("Entities", Tag.TAG_COMPOUND);
fixItemArrayWithID(entities, changed, data, true);
//Checking Block Palette
ListTag sections = root.getCompound("Level")
.getList("Sections", Tag.TAG_COMPOUND);
sections.forEach((tag) -> {
ListTag palette = ((CompoundTag) tag).getList("Palette", Tag.TAG_COMPOUND);
palette.forEach((blockTag) -> {
CompoundTag blockTagCompound = ((CompoundTag) blockTag);
changed[0] |= data.replaceStringFromIDs(blockTagCompound, "Name");
});
try {
changed[0] |= data.patchBlockState(
palette,
((CompoundTag) tag).getList(
"BlockStates",
Tag.TAG_LONG
)
);
} catch (PatchDidiFailException e) {
BCLib.LOGGER.error("Failed fixing BlockState in " + pos);
state.addError("Failed fixing BlockState in " + pos + " (" + e.getMessage() + ")");
state.didFail = true;
changed[0] = false;
e.printStackTrace();
}
});
if (changed[0]) {
LOGGER.warning("Writing '{}': {}/{}", file, x, z);
// NbtIo.write(root, new File(file.toString() + "-" + x + "-" + z + "-changed.nbt"));
DataOutputStream output = region.getChunkDataOutputStream(pos);
NbtIo.write(root, output);
output.close();
}
}
}
}
region.close();
} catch (Exception e) {
BCLib.LOGGER.error("Failed fixing Region.");
state.addError("Failed fixing Region in " + file.getName() + " (" + e.getMessage() + ")");
state.didFail = true;
e.printStackTrace();
}
}
static CompoundTag patchConfTag = null;
static CompoundTag getPatchData() {
if (patchConfTag == null) {
patchConfTag = WorldConfig.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY);
}
return patchConfTag;
}
static void fixItemArrayWithID(ListTag items, boolean[] changed, MigrationProfile data, boolean recursive) {
items.forEach(inTag -> {
fixID((CompoundTag) inTag, changed, data, recursive);
});
}
static void fixID(CompoundTag inTag, boolean[] changed, MigrationProfile data, boolean recursive) {
final CompoundTag tag = inTag;
changed[0] |= data.replaceStringFromIDs(tag, "id");
if (tag.contains("Item")) {
CompoundTag item = (CompoundTag) tag.get("Item");
fixID(item, changed, data, recursive);
}
if (recursive && tag.contains("Items")) {
fixItemArrayWithID(tag.getList("Items", Tag.TAG_COMPOUND), changed, data, true);
}
if (recursive && tag.contains("Inventory")) {
ListTag inventory = tag.getList("Inventory", Tag.TAG_COMPOUND);
fixItemArrayWithID(inventory, changed, data, true);
}
if (tag.contains("tag")) {
CompoundTag entityTag = (CompoundTag) tag.get("tag");
if (entityTag.contains("BlockEntityTag")) {
CompoundTag blockEntityTag = (CompoundTag) entityTag.get("BlockEntityTag");
fixID(blockEntityTag, changed, data, recursive);
/*ListTag items = blockEntityTag.getList("Items", Tag.TAG_COMPOUND);
fixItemArrayWithID(items, changed, data, recursive);*/
}
}
}
private static List<File> getAllPlayers(File dir) {
List<File> list = new ArrayList<>();
dir = new File(dir, "playerdata");
if (!dir.exists() || !dir.isDirectory()) {
return list;
}
for (File file : dir.listFiles()) {
if (file.isFile() && file.getName().endsWith(".dat")) {
list.add(file);
}
}
return list;
}
private static List<File> getAllRegions(File dir, List<File> list) {
if (list == null) {
list = new ArrayList<>();
}
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
getAllRegions(file, list);
} else if (file.isFile() && file.getName().endsWith(".mca")) {
list.add(file);
}
}
return list;
}
/**
* register a new Patch
*
* @param patch A #Supplier that will instantiate the new Patch Object
*/
public static void registerPatch(Supplier<Patch> patch) {
Patch.getALL().add(patch.get());
}
private static CompoundTag readNbt(File file) throws IOException {
try {
return NbtIo.readCompressed(file);
} catch (ZipException | EOFException e) {
return NbtIo.read(file);
}
}
}

View file

@ -0,0 +1,57 @@
package org.betterx.bclib.api.v2.datafixer;
import org.betterx.bclib.interfaces.PatchBiFunction;
import org.betterx.bclib.interfaces.PatchFunction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
/**
* A Patch for level.dat that is always executed no matter what Patchlevel is set in a world.
*/
public abstract class ForcedLevelPatch extends Patch {
protected ForcedLevelPatch(@NotNull String modID, String version) {
super(modID, version, true);
}
@Override
public final Map<String, String> getIDReplacements() {
return new HashMap<String, String>();
}
@Override
public final PatchFunction<CompoundTag, Boolean> getWorldDataPatcher() {
return null;
}
@Override
public final PatchBiFunction<ListTag, ListTag, Boolean> getBlockStatePatcher() {
return null;
}
@Override
public final List<String> getWorldDataIDPaths() {
return null;
}
@Override
public PatchFunction<CompoundTag, Boolean> getLevelDatPatcher() {
return this::runLevelDatPatch;
}
/**
* Called with the contents of level.dat in {@code root}
*
* @param root The contents of level.dat
* @param profile The active migration profile
* @return true, if the run did change the contents of root
*/
abstract protected Boolean runLevelDatPatch(CompoundTag root, MigrationProfile profile);
}

View file

@ -0,0 +1,374 @@
package org.betterx.bclib.api.v2.datafixer;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.interfaces.PatchBiFunction;
import org.betterx.bclib.interfaces.PatchFunction;
import org.betterx.worlds.together.util.ModUtil;
import org.betterx.worlds.together.world.WorldConfig;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.Tag;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
public class MigrationProfile {
final Set<String> mods;
final Map<String, String> idReplacements;
final List<PatchFunction<CompoundTag, Boolean>> levelPatchers;
final List<PatchBiFunction<ListTag, ListTag, Boolean>> statePatchers;
final List<Patch> worldDataPatchers;
final Map<String, List<String>> worldDataIDPaths;
private final CompoundTag config;
private CompoundTag level;
private File levelBaseDir;
private boolean prePatchChangedLevelDat;
private boolean didRunPrePatch;
private Exception prePatchException;
MigrationProfile(CompoundTag config, boolean applyAll) {
this.config = config;
this.mods = Collections.unmodifiableSet(Patch.getALL()
.stream()
.map(p -> p.modID)
.collect(Collectors.toSet()));
HashMap<String, String> replacements = new HashMap<String, String>();
List<PatchFunction<CompoundTag, Boolean>> levelPatches = new LinkedList<>();
List<Patch> worldDataPatches = new LinkedList<>();
List<PatchBiFunction<ListTag, ListTag, Boolean>> statePatches = new LinkedList<>();
HashMap<String, List<String>> worldDataIDPaths = new HashMap<>();
for (String modID : mods) {
Patch.getALL()
.stream()
.filter(p -> p.modID.equals(modID))
.forEach(patch -> {
List<String> paths = patch.getWorldDataIDPaths();
if (paths != null) worldDataIDPaths.put(modID, paths);
if (applyAll || currentPatchLevel(modID) < patch.level || patch.alwaysApply) {
replacements.putAll(patch.getIDReplacements());
if (patch.getLevelDatPatcher() != null)
levelPatches.add(patch.getLevelDatPatcher());
if (patch.getWorldDataPatcher() != null)
worldDataPatches.add(patch);
if (patch.getBlockStatePatcher() != null)
statePatches.add(patch.getBlockStatePatcher());
DataFixerAPI.LOGGER.info("Applying " + patch);
} else {
DataFixerAPI.LOGGER.info("Ignoring " + patch);
}
});
}
this.worldDataIDPaths = Collections.unmodifiableMap(worldDataIDPaths);
this.idReplacements = Collections.unmodifiableMap(replacements);
this.levelPatchers = Collections.unmodifiableList(levelPatches);
this.worldDataPatchers = Collections.unmodifiableList(worldDataPatches);
this.statePatchers = Collections.unmodifiableList(statePatches);
}
/**
* This method is supposed to be used by developers to apply id-patches to custom nbt structures. It is only
* available in Developer-Mode
*/
public static void fixCustomFolder(File dir) {
if (!BCLib.isDevEnvironment()) return;
MigrationProfile profile = Patch.createMigrationData();
List<File> nbts = getAllNbts(dir, null);
nbts.parallelStream().forEach((file) -> {
DataFixerAPI.LOGGER.info("Loading NBT " + file);
try {
CompoundTag root = NbtIo.readCompressed(file);
boolean[] changed = {false};
int spawnerIdx = -1;
if (root.contains("palette")) {
ListTag items = root.getList("palette", Tag.TAG_COMPOUND);
for (int idx = 0; idx < items.size(); idx++) {
final CompoundTag tag = (CompoundTag) items.get(idx);
if (tag.contains("Name") && tag.getString("Name").equals("minecraft:spawner"))
spawnerIdx = idx;
if (tag.contains("Name") && (tag.getString("Name").equals("minecraft:") || tag.getString("Name")
.equals(""))) {
System.out.println("Empty Name");
}
if (tag.contains("id") && (tag.getString("id").equals("minecraft:") || tag.getString("id")
.equals(""))) {
System.out.println("Empty ID");
}
changed[0] |= profile.replaceStringFromIDs(tag, "Name");
}
}
if (spawnerIdx >= 0 && root.contains("blocks")) {
ListTag items = root.getList("blocks", Tag.TAG_COMPOUND);
for (int idx = 0; idx < items.size(); idx++) {
final CompoundTag blockTag = (CompoundTag) items.get(idx);
if (blockTag.contains("state") && blockTag.getInt("state") == spawnerIdx && blockTag.contains(
"nbt")) {
CompoundTag nbt = blockTag.getCompound("nbt");
if (nbt.contains("SpawnData")) {
final CompoundTag entity = nbt.getCompound("SpawnData");
if (!entity.contains("entity")) {
CompoundTag data = new CompoundTag();
data.put("entity", entity);
nbt.put("SpawnData", data);
changed[0] = true;
}
}
if (nbt.contains("SpawnPotentials")) {
ListTag pots = nbt.getList("SpawnPotentials", Tag.TAG_COMPOUND);
for (Tag potItemIn : pots) {
final CompoundTag potItem = (CompoundTag) potItemIn;
if (potItem.contains("Weight")) {
int weight = potItem.getInt("Weight");
potItem.putInt("weight", weight);
potItem.remove("Weight");
changed[0] = true;
}
if (potItem.contains("Entity")) {
CompoundTag entity = potItem.getCompound("Entity");
CompoundTag data = new CompoundTag();
data.put("entity", entity);
potItem.put("data", data);
potItem.remove("Entity");
changed[0] = true;
}
}
}
}
}
}
if (changed[0]) {
DataFixerAPI.LOGGER.info("Writing NBT " + file);
NbtIo.writeCompressed(root, file);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
private static List<File> getAllNbts(File dir, List<File> list) {
if (list == null) {
list = new ArrayList<>();
}
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
getAllNbts(file, list);
} else if (file.isFile() && file.getName().endsWith(".nbt")) {
list.add(file);
}
}
return list;
}
final public CompoundTag getLevelDat(File levelBaseDir) {
if (level == null || this.levelBaseDir == null || !this.levelBaseDir.equals(levelBaseDir)) {
runPrePatches(levelBaseDir);
}
return level;
}
final public boolean isLevelDatChanged() {
return prePatchChangedLevelDat;
}
final public File getLevelDatFile() {
return new File(levelBaseDir, "level.dat");
}
final public Exception getPrePatchException() {
return prePatchException;
}
final public void runPrePatches(File levelBaseDir) {
if (didRunPrePatch) {
BCLib.LOGGER.warning("Already did run PrePatches for " + this.levelBaseDir + ".");
}
BCLib.LOGGER.info("Running Pre Patchers on " + levelBaseDir);
this.levelBaseDir = levelBaseDir;
this.level = null;
this.prePatchException = null;
didRunPrePatch = true;
this.prePatchChangedLevelDat = runPreLevelPatches(getLevelDatFile());
}
private boolean runPreLevelPatches(File levelDat) {
try {
level = NbtIo.readCompressed(levelDat);
boolean changed = patchLevelDat(level);
return changed;
} catch (IOException | PatchDidiFailException e) {
prePatchException = e;
return false;
}
}
final public void markApplied() {
for (String modID : mods) {
DataFixerAPI.LOGGER.info(
"Updating Patch-Level for '{}' from {} to {}",
modID,
ModUtil.convertModVersion(currentPatchLevel(modID)),
ModUtil.convertModVersion(Patch.maxPatchLevel(modID))
);
if (config != null)
config.putString(modID, Patch.maxPatchVersion(modID));
}
}
public String currentPatchVersion(@NotNull String modID) {
if (config == null || !config.contains(modID)) return "0.0.0";
return config.getString(modID);
}
public int currentPatchLevel(@NotNull String modID) {
return ModUtil.convertModVersion(currentPatchVersion(modID));
}
public boolean hasAnyFixes() {
boolean hasLevelDatPatches;
if (didRunPrePatch != false) {
hasLevelDatPatches = prePatchChangedLevelDat;
} else {
hasLevelDatPatches = levelPatchers.size() > 0;
}
return idReplacements.size() > 0 || hasLevelDatPatches || worldDataPatchers.size() > 0;
}
public String replaceStringFromIDs(@NotNull String val) {
final String replace = idReplacements.get(val);
return replace;
}
public boolean replaceStringFromIDs(@NotNull CompoundTag tag, @NotNull String key) {
if (!tag.contains(key)) return false;
final String val = tag.getString(key);
final String replace = idReplacements.get(val);
if (replace != null) {
DataFixerAPI.LOGGER.warning("Replacing ID '{}' with '{}'.", val, replace);
tag.putString(key, replace);
return true;
}
return false;
}
private boolean replaceIDatPath(@NotNull ListTag list, @NotNull String[] parts, int level) {
boolean[] changed = {false};
if (level == parts.length - 1) {
DataFixerAPI.fixItemArrayWithID(list, changed, this, true);
} else {
list.forEach(inTag -> changed[0] |= replaceIDatPath((CompoundTag) inTag, parts, level + 1));
}
return changed[0];
}
private boolean replaceIDatPath(@NotNull CompoundTag tag, @NotNull String[] parts, int level) {
boolean changed = false;
for (int i = level; i < parts.length - 1; i++) {
final String part = parts[i];
if (tag.contains(part)) {
final byte type = tag.getTagType(part);
if (type == Tag.TAG_LIST) {
ListTag list = tag.getList(part, Tag.TAG_COMPOUND);
return replaceIDatPath(list, parts, i);
} else if (type == Tag.TAG_COMPOUND) {
tag = tag.getCompound(part);
}
} else {
return false;
}
}
if (tag != null && parts.length > 0) {
final String key = parts[parts.length - 1];
final byte type = tag.getTagType(key);
if (type == Tag.TAG_LIST) {
final ListTag list = tag.getList(key, Tag.TAG_COMPOUND);
final boolean[] _changed = {false};
if (list.size() == 0) {
_changed[0] = DataFixerAPI.fixStringIDList(tag, key, this);
} else {
DataFixerAPI.fixItemArrayWithID(list, _changed, this, true);
}
return _changed[0];
} else if (type == Tag.TAG_STRING) {
return replaceStringFromIDs(tag, key);
} else if (type == Tag.TAG_COMPOUND) {
final CompoundTag cTag = tag.getCompound(key);
boolean[] _changed = {false};
DataFixerAPI.fixID(cTag, _changed, this, true);
return _changed[0];
}
}
return false;
}
public boolean replaceIDatPath(@NotNull CompoundTag root, @NotNull String path) {
String[] parts = path.split("\\.");
return replaceIDatPath(root, parts, 0);
}
public boolean patchLevelDat(@NotNull CompoundTag level) throws PatchDidiFailException {
boolean changed = false;
for (PatchFunction<CompoundTag, Boolean> f : levelPatchers) {
changed |= f.apply(level, this);
}
return changed;
}
public void patchWorldData() throws PatchDidiFailException {
for (Patch patch : worldDataPatchers) {
CompoundTag root = WorldConfig.getRootTag(patch.modID);
boolean changed = patch.getWorldDataPatcher().apply(root, this);
if (changed) {
WorldConfig.saveFile(patch.modID);
}
}
for (Map.Entry<String, List<String>> entry : worldDataIDPaths.entrySet()) {
CompoundTag root = WorldConfig.getRootTag(entry.getKey());
boolean[] changed = {false};
entry.getValue().forEach(path -> {
changed[0] |= replaceIDatPath(root, path);
});
if (changed[0]) {
WorldConfig.saveFile(entry.getKey());
}
}
}
public boolean patchBlockState(ListTag palette, ListTag states) throws PatchDidiFailException {
boolean changed = false;
for (PatchBiFunction<ListTag, ListTag, Boolean> f : statePatchers) {
changed |= f.apply(palette, states, this);
}
return changed;
}
}

View file

@ -0,0 +1,237 @@
package org.betterx.bclib.api.v2.datafixer;
import org.betterx.bclib.interfaces.PatchBiFunction;
import org.betterx.bclib.interfaces.PatchFunction;
import org.betterx.worlds.together.util.ModUtil;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
public abstract class Patch {
private static final List<Patch> ALL = new ArrayList<>(10);
/**
* The Patch-Level derived from {@link #version}
*/
public final int level;
/**
* The Patch-Version string
*/
public final String version;
/**
* The Mod-ID that registered this Patch
*/
@NotNull
public final String modID;
/**
* This Mod is tested for each level start
*/
public final boolean alwaysApply;
static List<Patch> getALL() {
return ALL;
}
/**
* Returns the highest Patch-Version that is available for the given mod. If no patches were
* registerd for the mod, this will return 0.0.0
*
* @param modID The ID of the mod you want to query
* @return The highest Patch-Version that was found
*/
public static String maxPatchVersion(@NotNull String modID) {
return ALL.stream().filter(p -> p.modID.equals(modID)).map(p -> p.version).reduce((p, c) -> c).orElse("0.0.0");
}
/**
* Returns the highest patch-level that is available for the given mod. If no patches were
* registerd for the mod, this will return 0
*
* @param modID The ID of the mod you want to query
* @return The highest Patch-Level that was found
*/
public static int maxPatchLevel(@NotNull String modID) {
return ALL.stream().filter(p -> p.modID.equals(modID)).mapToInt(p -> p.level).max().orElse(0);
}
/**
* Called by inheriting classes.
* <p>
* Performs some sanity checks on the values and might throw a #RuntimeException if any
* inconsistencies are found.
*
* @param modID The ID of the Mod you want to register a patch for. This should be your
* ModID only. The ModID can not be {@code null} or an empty String.
* @param version The mod-version that introduces the patch. This needs Semantic-Version String
* like x.x.x. Developers are responsible for registering their patches in the correct
* order (with increasing versions). You are not allowed to register a new
* Patch with a version lower or equal than
* {@link Patch#maxPatchVersion(String)}
*/
protected Patch(@NotNull String modID, String version) {
this(modID, version, false);
}
/**
* Internal Constructor used to create patches that can allways run (no matter what patchlevel a level has)
*
* @param modID The ID of the Mod
* @param version The mod-version that introduces the patch. When {@Code runAllways} is set, this version will
* determine the patchlevel that is written to the level
* @param alwaysApply When true, this patch is always active, no matter the patchlevel of the world.
* This should be used sparingly and just for patches that apply to level.dat (as they only take
* effect when changes are detected). Use {@link ForcedLevelPatch} to instatiate.
*/
Patch(@NotNull String modID, String version, boolean alwaysApply) {
//Patchlevels need to be unique and registered in ascending order
if (modID == null || modID.isEmpty()) {
throw new RuntimeException("[INTERNAL ERROR] Patches need a valid modID!");
}
if (version == null || version.isEmpty()) {
throw new RuntimeException("Invalid Mod-Version");
}
this.version = version;
this.alwaysApply = alwaysApply;
this.level = ModUtil.convertModVersion(version);
if (!ALL.stream().filter(p -> p.modID.equals(modID)).noneMatch(p -> p.level >= this.level) || this.level <= 0) {
throw new RuntimeException(
"[INTERNAL ERROR] Patch-levels need to be created in ascending order beginning with 1.");
}
this.modID = modID;
}
@Override
public String toString() {
return "Patch{" + modID + ':' + version + ':' + level + '}';
}
/**
* Return block data fixes. Fixes will be applied on world load if current patch-level for
* the linked mod is lower than the {@link #level}.
* <p>
* The default implementation of this method returns an empty map.
*
* @return The returned Map should contain the replacements. All occurences of the
* {@code KeySet} are replaced with the associated value.
*/
public Map<String, String> getIDReplacements() {
return new HashMap<String, String>();
}
/**
* Return a {@link PatchFunction} that is called with the content of <i>level.dat</i>.
* <p>
* The function needs to return {@code true}, if changes were made to the data.
* If an error occurs, the method should throw a {@link PatchDidiFailException}
* <p>
* The default implementation of this method returns null.
*
* @return {@code true} if changes were applied and we need to save the data
*/
public PatchFunction<CompoundTag, Boolean> getLevelDatPatcher() {
return null;
}
/**
* Return a {@link PatchFunction} that is called with the content from the
* {@link org.betterx.worlds.together.world.WorldConfig} for this Mod.
* The function needs to return {@code true}, if changes were made to the data.
* If an error occurs, the method should throw a {@link PatchDidiFailException}
* <p>
* The default implementation of this method returns null.
*
* @return {@code true} if changes were applied and we need to save the data
*/
public PatchFunction<CompoundTag, Boolean> getWorldDataPatcher() {
return null;
}
/**
* Return a {@link PatchBiFunction} that is called with pallette and blockstate of
* each chunk in every region. This method is called AFTER all ID replacements
* from {@link #getIDReplacements()} were applied to the pallete.
* <p>
* The first parameter is the palette and the second is the blockstate.
* <p>
* The function needs to return {@code true}, if changes were made to the data.
* If an error occurs, the method should throw a {@link PatchDidiFailException}
* <p>
* The default implementation of this method returns null.
*
* @return {@code true} if changes were applied and we need to save the data
*/
public PatchBiFunction<ListTag, ListTag, Boolean> getBlockStatePatcher() {
return null;
}
/**
* Generates ready to use data for all currently registered patches. The list of
* patches is selected by the current patch-level of the world.
* <p>
* A {@link #Patch} with a given {@link #level} is only included if the patch-level of the
* world is less
*
* @param config The current patch-level configuration*
* @return a new {@link MigrationProfile} Object.
*/
static MigrationProfile createMigrationData(CompoundTag config) {
return new MigrationProfile(config, false);
}
/**
* This method is supposed to be used by developers to apply id-patches to custom nbt structures. It is only
* available in Developer-Mode
*/
static MigrationProfile createMigrationData() {
return new MigrationProfile(null, true);
}
/**
* Returns a list of paths where your mod stores IDs in your {@link org.betterx.worlds.together.world.WorldConfig}-File.
* <p>
* {@link DataFixerAPI} will use information from the latest patch that returns a non-null-result. This list is used
* to automatically fix changed IDs from all active patches (see {@link Patch#getIDReplacements()}
* <p>
* The end of the path can either be a {@link net.minecraft.nbt.StringTag}, a {@link net.minecraft.nbt.ListTag} or
* a {@link CompoundTag}. If the Path contains a non-leaf {@link net.minecraft.nbt.ListTag}, all members of that
* list will be processed. For example:
* <pre>
* - global +
* | - key (String)
* | - items (List) +
* | - { id (String) }
* | - { id (String) }
* </pre>
* The path <b>global.items.id</b> will fix all <i>id</i>-entries in the <i>items</i>-list, while the path
* <b>global.key</b> will only fix the <i>key</i>-entry.
* <p>
* if the leaf-entry (= the last part of the path, which would be <i>items</i> in <b>global.items</b>) is a
* {@link CompoundTag}, the system will fix any <i>id</i> entry. If the {@link CompoundTag} contains an <i>item</i>
* or <i>tag.BlockEntityTag</i> entry, the system will recursivley continue with those. If an <i>items</i>
* or <i>inventory</i>-{@link net.minecraft.nbt.ListTag} was found, the system will continue recursivley with
* every item of that list.
* <p>
* if the leaf-entry is a {@link net.minecraft.nbt.ListTag}, it is handle the same as a child <i>items</i> entry
* of a {@link CompoundTag}.
*
* @return {@code null} if nothing changes or a list of Paths in your {@link org.betterx.worlds.together.world.WorldConfig}-File.
* Paths are dot-seperated (see {@link org.betterx.worlds.together.world.WorldConfig#getCompoundTag(String, String)}).
*/
public List<String> getWorldDataIDPaths() {
return null;
}
}

View file

@ -0,0 +1,11 @@
package org.betterx.bclib.api.v2.datafixer;
public class PatchDidiFailException extends Exception {
public PatchDidiFailException() {
super();
}
public PatchDidiFailException(Exception e) {
super(e);
}
}

View file

@ -0,0 +1,124 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import org.betterx.worlds.together.biomesource.BiomeSourceFromRegistry;
import org.betterx.worlds.together.biomesource.MergeableBiomeSource;
import org.betterx.worlds.together.world.BiomeSourceWithNoiseRelatedSettings;
import org.betterx.worlds.together.world.BiomeSourceWithSeed;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import com.google.common.collect.Sets;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
public abstract class BCLBiomeSource extends BiomeSource implements BiomeSourceWithSeed, MergeableBiomeSource<BCLBiomeSource>, BiomeSourceWithNoiseRelatedSettings, BiomeSourceFromRegistry {
protected final Registry<Biome> biomeRegistry;
protected long currentSeed;
protected int maxHeight;
private static List<Holder<Biome>> preInit(Registry<Biome> biomeRegistry, List<Holder<Biome>> biomes) {
biomes = biomes.stream().sorted(Comparator.comparing(holder -> holder.unwrapKey()
.get()
.location()
.toString()))
.toList();
biomes.forEach(biome -> BiomeAPI.sortBiomeFeatures(biome));
return biomes;
}
protected BCLBiomeSource(
Registry<Biome> biomeRegistry,
List<Holder<Biome>> list,
long seed
) {
super(preInit(biomeRegistry, list));
this.biomeRegistry = biomeRegistry;
this.currentSeed = seed;
}
final public void setSeed(long seed) {
if (seed != currentSeed) {
System.out.println(this + " set Seed: " + seed);
this.currentSeed = seed;
initMap(seed);
}
}
/**
* Set world height
*
* @param maxHeight height of the World.
*/
final public void setMaxHeight(int maxHeight) {
if (this.maxHeight != maxHeight) {
System.out.println(this + " set Max Height: " + maxHeight);
this.maxHeight = maxHeight;
onHeightChange(maxHeight);
}
}
protected final void initMap(long seed) {
System.out.println(this + " updates Map");
onInitMap(seed);
}
protected abstract void onInitMap(long newSeed);
protected abstract void onHeightChange(int newHeight);
public BCLBiomeSource createCopyForDatapack(Set<Holder<Biome>> datapackBiomes) {
Set<Holder<Biome>> mutableSet = Sets.newHashSet();
mutableSet.addAll(datapackBiomes);
return cloneForDatapack(mutableSet);
}
protected abstract BCLBiomeSource cloneForDatapack(Set<Holder<Biome>> datapackBiomes);
public interface ValidBiomePredicate {
boolean isValid(Holder<Biome> biome, ResourceLocation location);
}
protected static List<Holder<Biome>> getBiomes(
Registry<Biome> biomeRegistry,
List<String> exclude,
List<String> include,
BCLibNetherBiomeSource.ValidBiomePredicate test
) {
return biomeRegistry.stream()
.filter(biome -> biomeRegistry.getResourceKey(biome).isPresent())
.map(biome -> biomeRegistry.getOrCreateHolderOrThrow(biomeRegistry.getResourceKey(biome)
.get()))
.filter(biome -> {
ResourceLocation location = biome.unwrapKey().orElseThrow().location();
final String strLocation = location.toString();
if (exclude.contains(strLocation)) return false;
if (include.contains(strLocation)) return true;
return test.isValid(biome, location);
})
.toList();
}
@Override
public BCLBiomeSource mergeWithBiomeSource(BiomeSource inputBiomeSource) {
final Set<Holder<Biome>> datapackBiomes = inputBiomeSource.possibleBiomes();
return this.createCopyForDatapack(datapackBiomes);
}
public void onLoadGeneratorSettings(NoiseGeneratorSettings generator) {
this.setMaxHeight(generator.noiseSettings().height());
}
public Registry<Biome> getBiomeRegistry() {
return biomeRegistry;
}
}

View file

@ -0,0 +1,227 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.levelgen.LevelGenUtil;
import org.betterx.bclib.interfaces.NoiseGeneratorSettingsProvider;
import org.betterx.bclib.mixin.common.ChunkGeneratorAccessor;
import org.betterx.worlds.together.WorldsTogether;
import org.betterx.worlds.together.biomesource.MergeableBiomeSource;
import org.betterx.worlds.together.chunkgenerator.EnforceableChunkGenerator;
import org.betterx.worlds.together.chunkgenerator.InjectableSurfaceRules;
import org.betterx.worlds.together.chunkgenerator.RestorableBiomeSource;
import org.betterx.worlds.together.world.BiomeSourceWithNoiseRelatedSettings;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeGenerationSettings;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.FeatureSorter;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.WorldGenSettings;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.synth.NormalNoise;
import com.google.common.base.Suppliers;
import java.util.List;
import java.util.function.Function;
public class BCLChunkGenerator extends NoiseBasedChunkGenerator implements RestorableBiomeSource<BCLChunkGenerator>, InjectableSurfaceRules<BCLChunkGenerator>, EnforceableChunkGenerator<BCLChunkGenerator> {
public static final Codec<BCLChunkGenerator> CODEC = RecordCodecBuilder
.create((RecordCodecBuilder.Instance<BCLChunkGenerator> builderInstance) -> {
final RecordCodecBuilder<BCLChunkGenerator, Registry<NormalNoise.NoiseParameters>> noiseGetter = RegistryOps
.retrieveRegistry(
Registry.NOISE_REGISTRY)
.forGetter(
BCLChunkGenerator::getNoises);
RecordCodecBuilder<BCLChunkGenerator, BiomeSource> biomeSourceCodec = BiomeSource.CODEC
.fieldOf("biome_source")
.forGetter((BCLChunkGenerator generator) -> generator.biomeSource);
RecordCodecBuilder<BCLChunkGenerator, Holder<NoiseGeneratorSettings>> settingsCodec = NoiseGeneratorSettings.CODEC
.fieldOf("settings")
.forGetter((BCLChunkGenerator generator) -> generator.settings);
return NoiseBasedChunkGenerator
.commonCodec(builderInstance)
.and(builderInstance.group(noiseGetter, biomeSourceCodec, settingsCodec))
.apply(builderInstance, builderInstance.stable(BCLChunkGenerator::new));
});
public final BiomeSource initialBiomeSource;
public BCLChunkGenerator(
Registry<StructureSet> registry,
Registry<NormalNoise.NoiseParameters> registry2,
BiomeSource biomeSource,
Holder<NoiseGeneratorSettings> holder
) {
super(registry, registry2, biomeSource, holder);
initialBiomeSource = biomeSource;
if (biomeSource instanceof BiomeSourceWithNoiseRelatedSettings bcl) {
bcl.onLoadGeneratorSettings(holder.value());
}
if (WorldsTogether.RUNS_TERRABLENDER) {
BCLib.LOGGER.info("Make sure features are loaded from terrablender for " + biomeSource);
//terrablender is invalidating the feature initialization
//we redo it at this point, otherwise we will get blank biomes
rebuildFeaturesPerStep(biomeSource);
}
System.out.println("Chunk Generator: " + this + " (biomeSource: " + biomeSource + ")");
}
private void rebuildFeaturesPerStep(BiomeSource biomeSource) {
if (this instanceof ChunkGeneratorAccessor acc) {
Function<Holder<Biome>, BiomeGenerationSettings> function = (Holder<Biome> hh) -> hh.value()
.getGenerationSettings();
acc.bcl_setFeaturesPerStep(Suppliers.memoize(() -> FeatureSorter.buildFeaturesPerStep(
List.copyOf(biomeSource.possibleBiomes()),
(hh) -> function.apply(hh).features(),
true
)));
}
}
/**
* Other Mods like TerraBlender might inject new BiomeSources. We und that change after the world setup did run.
*
* @param dimensionKey The Dimension where this ChunkGenerator is used from
*/
@Override
public void restoreInitialBiomeSource(ResourceKey<LevelStem> dimensionKey) {
if (initialBiomeSource != getBiomeSource()) {
if (this instanceof ChunkGeneratorAccessor acc) {
if (initialBiomeSource instanceof MergeableBiomeSource bs) {
acc.bcl_setBiomeSource(bs.mergeWithBiomeSource(getBiomeSource()));
}
rebuildFeaturesPerStep(getBiomeSource());
}
}
}
@Override
protected Codec<? extends ChunkGenerator> codec() {
return CODEC;
}
private Registry<NormalNoise.NoiseParameters> getNoises() {
if (this instanceof NoiseGeneratorSettingsProvider p) {
return p.bclib_getNoises();
}
return null;
}
@Override
public String toString() {
return "BCLib - Chunk Generator (" + Integer.toHexString(hashCode()) + ")";
}
// This method is injected by Terrablender.
// We make sure terrablender does not rewrite the feature-set for our ChunkGenerator by overwriting the
// Mixin-Method with an empty implementation
public void appendFeaturesPerStep() {
}
public static RandomState createRandomState(ServerLevel level, ChunkGenerator generator) {
if (generator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
return RandomState.create(
noiseBasedChunkGenerator.generatorSettings().value(),
level.registryAccess().registryOrThrow(Registry.NOISE_REGISTRY),
level.getSeed()
);
} else {
return RandomState.create(level.registryAccess(), NoiseGeneratorSettings.OVERWORLD, level.getSeed());
}
}
@Override
public WorldGenSettings enforceGeneratorInWorldGenSettings(
RegistryAccess access,
ResourceKey<LevelStem> dimensionKey,
ResourceKey<DimensionType> dimensionTypeKey,
ChunkGenerator loadedChunkGenerator,
WorldGenSettings settings
) {
BCLib.LOGGER.info("Enforcing Correct Generator for " + dimensionKey.location().toString() + ".");
ChunkGenerator referenceGenerator = this;
if (loadedChunkGenerator instanceof org.betterx.bclib.interfaces.ChunkGeneratorAccessor generator) {
if (loadedChunkGenerator instanceof NoiseGeneratorSettingsProvider noiseProvider) {
if (referenceGenerator instanceof NoiseGeneratorSettingsProvider referenceProvider) {
final BiomeSource bs;
if (referenceGenerator.getBiomeSource() instanceof MergeableBiomeSource mbs) {
bs = mbs.mergeWithBiomeSource(loadedChunkGenerator.getBiomeSource());
} else {
bs = referenceGenerator.getBiomeSource();
}
referenceGenerator = new BCLChunkGenerator(
generator.bclib_getStructureSetsRegistry(),
noiseProvider.bclib_getNoises(),
bs,
buildGeneratorSettings(
referenceProvider.bclib_getNoiseGeneratorSettingHolders(),
noiseProvider.bclib_getNoiseGeneratorSettingHolders(),
bs
)
);
}
}
}
return LevelGenUtil.replaceGenerator(
dimensionKey,
dimensionTypeKey,
access,
settings,
referenceGenerator
);
}
private static Holder<NoiseGeneratorSettings> buildGeneratorSettings(
Holder<NoiseGeneratorSettings> reference,
Holder<NoiseGeneratorSettings> settings,
BiomeSource biomeSource
) {
return settings;
// NoiseGeneratorSettings old = settings.value();
// NoiseGeneratorSettings noise = new NoiseGeneratorSettings(
// old.noiseSettings(),
// old.defaultBlock(),
// old.defaultFluid(),
// old.noiseRouter(),
// SurfaceRuleRegistry.mergeSurfaceRulesFromBiomes(old.surfaceRule(), biomeSource),
// //SurfaceRuleUtil.addRulesForBiomeSource(old.surfaceRule(), biomeSource),
// old.spawnTarget(),
// old.seaLevel(),
// old.disableMobGeneration(),
// old.aquifersEnabled(),
// old.oreVeinsEnabled(),
// old.useLegacyRandomSource()
// );
//
//
// return Holder.direct(noise);
}
}

View file

@ -0,0 +1,362 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig;
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.worlds.together.biomesource.BiomeSourceWithConfig;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.Holder;
import net.minecraft.core.QuartPos;
import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BiomeTags;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.levelgen.DensityFunction;
import java.awt.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import org.jetbrains.annotations.NotNull;
public class BCLibEndBiomeSource extends BCLBiomeSource implements BiomeSourceWithConfig<BCLibEndBiomeSource, BCLEndBiomeSourceConfig> {
public static Codec<BCLibEndBiomeSource> CODEC
= RecordCodecBuilder.create((instance) -> instance.group(
RegistryOps
.retrieveRegistry(Registry.BIOME_REGISTRY)
.forGetter((theEndBiomeSource) -> theEndBiomeSource.biomeRegistry),
Codec
.LONG
.fieldOf("seed")
.stable()
.forGetter(source -> source.currentSeed),
BCLEndBiomeSourceConfig
.CODEC
.fieldOf("config")
.orElse(BCLEndBiomeSourceConfig.DEFAULT)
.forGetter(o -> o.config)
)
.apply(
instance,
instance.stable(BCLibEndBiomeSource::new)
)
);
private final Point pos;
private final BiFunction<Point, Integer, Boolean> endLandFunction;
private BiomeMap mapLand;
private BiomeMap mapVoid;
private BiomeMap mapCenter;
private BiomeMap mapBarrens;
private final BiomePicker endLandBiomePicker;
private BiomePicker endVoidBiomePicker;
private BiomePicker endCenterBiomePicker;
private BiomePicker endBarrensBiomePicker;
private BCLEndBiomeSourceConfig config;
public BCLibEndBiomeSource(Registry<Biome> biomeRegistry, long seed, BCLEndBiomeSourceConfig config) {
this(biomeRegistry, seed, config, true);
}
public BCLibEndBiomeSource(Registry<Biome> biomeRegistry, BCLEndBiomeSourceConfig config) {
this(biomeRegistry, 0, config, false);
}
private BCLibEndBiomeSource(
Registry<Biome> biomeRegistry,
long seed,
BCLEndBiomeSourceConfig config,
boolean initMaps
) {
this(biomeRegistry, getBiomes(biomeRegistry), seed, config, initMaps);
}
private BCLibEndBiomeSource(
Registry<Biome> biomeRegistry,
List<Holder<Biome>> list,
long seed,
BCLEndBiomeSourceConfig config,
boolean initMaps
) {
super(biomeRegistry, list, seed);
this.config = config;
var includeMap = Configs.BIOMES_CONFIG.getBiomeIncludeMap();
var excludeList = Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.END);
endLandBiomePicker = new BiomePicker(biomeRegistry);
endVoidBiomePicker = new BiomePicker(biomeRegistry);
endCenterBiomePicker = new BiomePicker(biomeRegistry);
endBarrensBiomePicker = new BiomePicker(biomeRegistry);
Map<BiomeAPI.BiomeType, BiomePicker> pickerMap = new HashMap<>();
pickerMap.put(BiomeAPI.BiomeType.END_LAND, endLandBiomePicker);
pickerMap.put(BiomeAPI.BiomeType.END_VOID, endVoidBiomePicker);
pickerMap.put(BiomeAPI.BiomeType.END_CENTER, endCenterBiomePicker);
pickerMap.put(BiomeAPI.BiomeType.END_BARRENS, endBarrensBiomePicker);
this.possibleBiomes().forEach(biome -> {
ResourceKey<Biome> key = biome.unwrapKey().orElseThrow();
ResourceLocation biomeID = key.location();
String biomeStr = biomeID.toString();
//exclude everything that was listed
if (excludeList != null && excludeList.contains(biomeStr)) return;
if (!biome.isBound()) {
BCLib.LOGGER.warning("Biome " + biomeStr + " is requested but not yet bound.");
return;
}
final BCLBiome bclBiome;
if (!BiomeAPI.hasBiome(biomeID)) {
bclBiome = new BCLBiome(biomeID, biome.value());
} else {
bclBiome = BiomeAPI.getBiome(biomeID);
}
if (bclBiome != null || bclBiome != BiomeAPI.EMPTY_BIOME) {
if (bclBiome.getParentBiome() == null) {
//ignore small islands when void biomes are disabled
if (!config.withVoidBiomes) {
if (biomeID.equals(Biomes.SMALL_END_ISLANDS.location())) {
return;
}
}
//force include biomes
boolean didForceAdd = false;
for (var entry : pickerMap.entrySet()) {
var includeList = includeMap == null ? null : includeMap.get(entry.getKey());
if (includeList != null && includeList.contains(biomeStr)) {
entry.getValue().addBiome(bclBiome);
didForceAdd = true;
}
}
if (!didForceAdd) {
if (BiomeAPI.wasRegisteredAs(biomeID, BiomeAPI.BiomeType.END_IGNORE)) {
//we should not add this biome anywhere, so just ignore it
} else if (BiomeAPI.wasRegisteredAs(biomeID, BiomeAPI.BiomeType.END_CENTER)
|| TheEndBiomesHelper.canGenerateAsMainIslandBiome(key)) {
endCenterBiomePicker.addBiome(bclBiome);
} else if (BiomeAPI.wasRegisteredAs(biomeID, BiomeAPI.BiomeType.END_LAND)
|| TheEndBiomesHelper.canGenerateAsHighlandsBiome(key)) {
if (!config.withVoidBiomes) endVoidBiomePicker.addBiome(bclBiome);
endLandBiomePicker.addBiome(bclBiome);
} else if (BiomeAPI.wasRegisteredAs(biomeID, BiomeAPI.BiomeType.END_BARRENS)
|| TheEndBiomesHelper.canGenerateAsEndBarrens(key)) {
endBarrensBiomePicker.addBiome(bclBiome);
} else if (BiomeAPI.wasRegisteredAs(biomeID, BiomeAPI.BiomeType.END_VOID)
|| TheEndBiomesHelper.canGenerateAsSmallIslandsBiome(key)) {
endVoidBiomePicker.addBiome(bclBiome);
} else {
BCLib.LOGGER.info("Found End Biome " + biomeStr + " that was not registers with fabric or bclib. Assuming end-land Biome...");
endLandBiomePicker.addBiome(bclBiome);
}
}
}
}
});
endLandBiomePicker.rebuild();
endVoidBiomePicker.rebuild();
endBarrensBiomePicker.rebuild();
endCenterBiomePicker.rebuild();
if (endVoidBiomePicker.isEmpty()) {
BCLib.LOGGER.info("No Void Biomes found. Disabling by using barrens");
endVoidBiomePicker = endBarrensBiomePicker;
}
if (endBarrensBiomePicker.isEmpty()) {
BCLib.LOGGER.info("No Barrens Biomes found. Disabling by using land Biomes");
endBarrensBiomePicker = endLandBiomePicker;
endVoidBiomePicker = endLandBiomePicker;
}
if (endCenterBiomePicker.isEmpty()) {
BCLib.LOGGER.warning("No Center Island Biomes found. Forcing use of vanilla center.");
endCenterBiomePicker.addBiome(BiomeAPI.THE_END);
endCenterBiomePicker.rebuild();
if (endCenterBiomePicker.isEmpty()) {
BCLib.LOGGER.error("Unable to force vanilla central Island. Falling back to land Biomes...");
endCenterBiomePicker = endLandBiomePicker;
}
}
this.endLandFunction = GeneratorOptions.getEndLandFunction();
this.pos = new Point();
if (initMaps) {
initMap(seed);
}
}
protected BCLBiomeSource cloneForDatapack(Set<Holder<Biome>> datapackBiomes) {
datapackBiomes.addAll(getBclBiomes(this.biomeRegistry));
return new BCLibEndBiomeSource(
this.biomeRegistry,
datapackBiomes.stream().toList(),
this.currentSeed,
this.config,
true
);
}
private static List<Holder<Biome>> getBclBiomes(Registry<Biome> biomeRegistry) {
return getBiomes(
biomeRegistry,
Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.END),
Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.END),
BCLibEndBiomeSource::isValidNonVanillaEndBiome
);
}
private static List<Holder<Biome>> getBiomes(Registry<Biome> biomeRegistry) {
return getBiomes(
biomeRegistry,
Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.END),
Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.END),
BCLibEndBiomeSource::isValidEndBiome
);
}
private static boolean isValidEndBiome(Holder<Biome> biome, ResourceLocation location) {
return biome.is(BiomeTags.IS_END) ||
BiomeAPI.wasRegisteredAsEndBiome(location) ||
TheEndBiomesHelper.canGenerateInEnd(biome.unwrapKey().orElse(null));
}
private static boolean isValidNonVanillaEndBiome(Holder<Biome> biome, ResourceLocation location) {
return biome.is(BiomeTags.IS_END) ||
BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_LAND) ||
BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_VOID) ||
BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_CENTER) ||
BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_BARRENS) ||
TheEndBiomesHelper.canGenerateInEnd(biome.unwrapKey().orElse(null));
}
public static void register() {
Registry.register(Registry.BIOME_SOURCE, BCLib.makeID("end_biome_source"), CODEC);
}
@Override
protected void onInitMap(long seed) {
this.mapLand = config.mapVersion.mapBuilder.apply(
seed,
config.landBiomesSize,
endLandBiomePicker
);
this.mapVoid = config.mapVersion.mapBuilder.apply(
seed,
config.voidBiomesSize,
endVoidBiomePicker
);
this.mapCenter = config.mapVersion.mapBuilder.apply(
seed,
config.centerBiomesSize,
endCenterBiomePicker
);
this.mapBarrens = config.mapVersion.mapBuilder.apply(
seed,
config.barrensBiomesSize,
endBarrensBiomePicker
);
}
@Override
protected void onHeightChange(int newHeight) {
}
@Override
public Holder<Biome> getNoiseBiome(int biomeX, int biomeY, int biomeZ, Climate.@NotNull Sampler sampler) {
if (mapLand == null || mapVoid == null || mapCenter == null || mapBarrens == null)
return this.possibleBiomes().stream().findFirst().orElseThrow();
int posX = QuartPos.toBlock(biomeX);
int posY = QuartPos.toBlock(biomeY);
int posZ = QuartPos.toBlock(biomeZ);
long dist = Math.abs(posX) + Math.abs(posZ) > (long) config.innerVoidRadiusSquared
? ((long) config.innerVoidRadiusSquared + 1)
: (long) posX * (long) posX + (long) posZ * (long) posZ;
if ((biomeX & 63) == 0 || (biomeZ & 63) == 0) {
mapLand.clearCache();
mapVoid.clearCache();
mapCenter.clearCache();
mapVoid.clearCache();
}
if (config.generatorVersion == BCLEndBiomeSourceConfig.EndBiomeGeneratorType.VANILLA || endLandFunction == null) {
if (dist <= (long) config.innerVoidRadiusSquared) {
return mapCenter.getBiome(posX, biomeY << 2, posZ).biome;
}
int x = (SectionPos.blockToSectionCoord(posX) * 2 + 1) * 8;
int z = (SectionPos.blockToSectionCoord(posZ) * 2 + 1) * 8;
double d = sampler.erosion().compute(new DensityFunction.SinglePointContext(x, posY, z));
if (d > 0.25) {
return mapLand.getBiome(posX, biomeY << 2, posZ).biome; //highlands
} else if (d >= -0.0625) {
return mapLand.getBiome(posX, biomeY << 2, posZ).biome; //midlands
} else {
return d < -0.21875
? mapVoid.getBiome(posX, biomeY << 2, posZ).biome //small islands
: (config.withVoidBiomes ? mapBarrens : mapLand).getBiome(
posX,
biomeY << 2,
posZ
).biome; //barrens
}
} else {
pos.setLocation(biomeX, biomeZ);
if (endLandFunction.apply(pos, maxHeight)) {
return (dist <= (long) config.innerVoidRadiusSquared ? mapCenter : mapLand)
.getBiome(posX, biomeY << 2, posZ).biome;
} else {
return (dist <= (long) config.innerVoidRadiusSquared ? mapBarrens : mapVoid)
.getBiome(posX, biomeY << 2, posZ).biome;
}
}
}
@Override
protected Codec<? extends BiomeSource> codec() {
return CODEC;
}
@Override
public String toString() {
return "BCLib - The End BiomeSource (" + Integer.toHexString(hashCode()) + ", config=" + config + ", seed=" + currentSeed + ", height=" + maxHeight + ", customLand=" + (endLandFunction != null) + ", biomes=" + possibleBiomes().size() + ")";
}
@Override
public BCLEndBiomeSourceConfig getTogetherConfig() {
return config;
}
@Override
public void setTogetherConfig(BCLEndBiomeSourceConfig newConfig) {
this.config = newConfig;
this.initMap(currentSeed);
}
}

View file

@ -0,0 +1,216 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.generator.config.BCLNetherBiomeSourceConfig;
import org.betterx.bclib.api.v2.generator.map.MapStack;
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.util.TriFunction;
import org.betterx.worlds.together.biomesource.BiomeSourceWithConfig;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BiomeTags;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.Climate;
import net.fabricmc.fabric.api.biome.v1.NetherBiomes;
import java.util.List;
import java.util.Set;
public class BCLibNetherBiomeSource extends BCLBiomeSource implements BiomeSourceWithConfig<BCLibNetherBiomeSource, BCLNetherBiomeSourceConfig> {
public static final Codec<BCLibNetherBiomeSource> CODEC = RecordCodecBuilder
.create(instance -> instance
.group(
RegistryOps
.retrieveRegistry(Registry.BIOME_REGISTRY)
.forGetter(source -> source.biomeRegistry),
Codec
.LONG
.fieldOf("seed")
.stable()
.forGetter(source -> {
return source.currentSeed;
}),
BCLNetherBiomeSourceConfig
.CODEC
.fieldOf("config")
.orElse(BCLNetherBiomeSourceConfig.DEFAULT)
.forGetter(o -> o.config)
)
.apply(instance, instance.stable(BCLibNetherBiomeSource::new))
);
private BiomeMap biomeMap;
private final BiomePicker biomePicker;
private BCLNetherBiomeSourceConfig config;
public BCLibNetherBiomeSource(Registry<Biome> biomeRegistry, BCLNetherBiomeSourceConfig config) {
this(biomeRegistry, 0, config, false);
}
public BCLibNetherBiomeSource(Registry<Biome> biomeRegistry, long seed, BCLNetherBiomeSourceConfig config) {
this(biomeRegistry, seed, config, true);
}
private BCLibNetherBiomeSource(
Registry<Biome> biomeRegistry,
long seed,
BCLNetherBiomeSourceConfig config,
boolean initMaps
) {
this(biomeRegistry, getBiomes(biomeRegistry), seed, config, initMaps);
}
private BCLibNetherBiomeSource(
Registry<Biome> biomeRegistry,
List<Holder<Biome>> list,
long seed,
BCLNetherBiomeSourceConfig config,
boolean initMaps
) {
super(biomeRegistry, list, seed);
this.config = config;
biomePicker = new BiomePicker(biomeRegistry);
this.possibleBiomes().forEach(biome -> {
ResourceLocation biomeID = biome.unwrapKey().orElseThrow().location();
if (!biome.isBound()) {
BCLib.LOGGER.warning("Biome " + biomeID.toString() + " is requested but not yet bound.");
return;
}
if (!BiomeAPI.hasBiome(biomeID)) {
BCLBiome bclBiome = new BCLBiome(biomeID, biome.value());
biomePicker.addBiome(bclBiome);
} else {
BCLBiome bclBiome = BiomeAPI.getBiome(biomeID);
if (bclBiome != BiomeAPI.EMPTY_BIOME) {
if (bclBiome.getParentBiome() == null) {
biomePicker.addBiome(bclBiome);
}
}
}
});
biomePicker.rebuild();
if (initMaps) {
initMap(seed);
}
}
protected BCLBiomeSource cloneForDatapack(Set<Holder<Biome>> datapackBiomes) {
datapackBiomes.addAll(getBclBiomes(this.biomeRegistry));
return new BCLibNetherBiomeSource(
this.biomeRegistry,
datapackBiomes.stream().toList(),
this.currentSeed,
config,
true
);
}
private static List<Holder<Biome>> getBclBiomes(Registry<Biome> biomeRegistry) {
List<String> include = Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.NETHER);
List<String> exclude = Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.NETHER);
return getBiomes(biomeRegistry, exclude, include, BCLibNetherBiomeSource::isValidNonVanillaNetherBiome);
}
private static List<Holder<Biome>> getBiomes(Registry<Biome> biomeRegistry) {
List<String> include = Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.NETHER);
List<String> exclude = Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.NETHER);
return getBiomes(biomeRegistry, exclude, include, BCLibNetherBiomeSource::isValidNetherBiome);
}
private static boolean isValidNetherBiome(Holder<Biome> biome, ResourceLocation location) {
return NetherBiomes.canGenerateInNether(biome.unwrapKey().get()) ||
biome.is(BiomeTags.IS_NETHER) ||
BiomeAPI.wasRegisteredAsNetherBiome(location);
}
private static boolean isValidNonVanillaNetherBiome(Holder<Biome> biome, ResourceLocation location) {
return (
!"minecraft".equals(location.getNamespace()) &&
NetherBiomes.canGenerateInNether(biome.unwrapKey().get())) ||
BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_NETHER);
}
public static <T> void debug(Object el, Registry<T> reg) {
System.out.println("Unknown " + el + " in " + reg);
}
public static void register() {
Registry.register(Registry.BIOME_SOURCE, BCLib.makeID("nether_biome_source"), CODEC);
}
@Override
public Holder<Biome> getNoiseBiome(int biomeX, int biomeY, int biomeZ, Climate.Sampler var4) {
if (biomeMap == null)
return this.possibleBiomes().stream().findFirst().get();
if ((biomeX & 63) == 0 && (biomeZ & 63) == 0) {
biomeMap.clearCache();
}
BiomePicker.ActualBiome bb = biomeMap.getBiome(biomeX << 2, biomeY << 2, biomeZ << 2);
return bb.biome;
}
@Override
protected Codec<? extends BiomeSource> codec() {
return CODEC;
}
@Override
protected void onInitMap(long seed) {
TriFunction<Long, Integer, BiomePicker, BiomeMap> mapConstructor = config.mapVersion.mapBuilder;
if (maxHeight > config.biomeSizeVertical * 1.5 && config.useVerticalBiomes) {
this.biomeMap = new MapStack(
seed,
config.biomeSize,
biomePicker,
config.biomeSizeVertical,
maxHeight,
mapConstructor
);
} else {
this.biomeMap = mapConstructor.apply(
seed,
config.biomeSize,
biomePicker
);
}
}
@Override
protected void onHeightChange(int newHeight) {
initMap(currentSeed);
}
@Override
public String toString() {
return "BCLib - Nether BiomeSource (" + Integer.toHexString(hashCode()) + ", config=" + config + ", seed=" + currentSeed + ", height=" + maxHeight + ", biomes=" + possibleBiomes().size() + ")";
}
@Override
public BCLNetherBiomeSourceConfig getTogetherConfig() {
return config;
}
@Override
public void setTogetherConfig(BCLNetherBiomeSourceConfig newConfig) {
this.config = newConfig;
initMap(currentSeed);
}
}

View file

@ -0,0 +1,164 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import org.betterx.bclib.util.WeighTree;
import org.betterx.bclib.util.WeightedList;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import com.google.common.collect.Lists;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class BiomePicker {
private final Map<BCLBiome, ActualBiome> all = new HashMap<>();
public final Registry<Biome> biomeRegistry;
private final List<ActualBiome> biomes = Lists.newArrayList();
private final List<String> allowedBiomes;
public final ActualBiome fallbackBiome;
private WeighTree<ActualBiome> tree;
public BiomePicker(Registry<Biome> biomeRegistry) {
this(biomeRegistry, null);
}
public BiomePicker(Registry<Biome> biomeRegistry, List<Holder<Biome>> allowedBiomes) {
this.biomeRegistry = biomeRegistry;
this.allowedBiomes = allowedBiomes != null ? allowedBiomes
.stream()
.map(h -> h.unwrapKey())
.filter(o -> o.isPresent())
.map(o -> o.get().location().toString()).toList() : null;
this.fallbackBiome = create(BiomeAPI.EMPTY_BIOME);
}
private boolean isAllowed(BCLBiome b) {
if (allowedBiomes == null) return true;
return allowedBiomes.contains(b.getID().toString());
}
private ActualBiome create(BCLBiome bclBiome) {
ActualBiome e = all.get(bclBiome);
if (e != null) return e;
return new ActualBiome(bclBiome);
}
public void addBiome(BCLBiome biome) {
biomes.add(create(biome));
}
public ActualBiome getBiome(WorldgenRandom random) {
return biomes.isEmpty() ? fallbackBiome : tree.get(random);
}
public boolean isEmpty() {
return biomes.isEmpty();
}
public void rebuild() {
WeightedList<ActualBiome> list = new WeightedList<>();
biomes.forEach(biome -> {
if (biome.isValid)
list.add(biome, biome.bclBiome.getGenChance());
});
//only a single biome, we need to add the edges as well
if (list.size() == 1) {
ActualBiome biome = list.get(0);
if (biome.getEdge() != null) {
float defaultBiomeSize = 128;
float edgeSize = (biome.bclBiome.getEdgeSize() * list.getWeight(0)) / defaultBiomeSize;
list.add(biome.getEdge(), edgeSize);
}
}
//no Biome, make sure we add at least one, otherwise bad things will happen
if (list.isEmpty()) {
list.add(create(BiomeAPI.EMPTY_BIOME), 1);
}
tree = new WeighTree<>(list);
}
public class ActualBiome {
public final BCLBiome bclBiome;
public final Holder<Biome> biome;
public final ResourceKey<Biome> key;
private final WeightedList<ActualBiome> subbiomes = new WeightedList<>();
private final ActualBiome edge;
private final ActualBiome parent;
public final boolean isValid;
private ActualBiome(BCLBiome bclBiome) {
all.put(bclBiome, this);
this.bclBiome = bclBiome;
this.key = biomeRegistry.getResourceKey(biomeRegistry.get(bclBiome.getID())).orElse(null);
this.biome = key != null ? biomeRegistry.getOrCreateHolderOrThrow(key) : null;
this.isValid = key != null && biome != null && biome.isBound();
bclBiome.forEachSubBiome((b, w) -> {
if (isAllowed(b))
subbiomes.add(create(b), w);
});
if (bclBiome.getEdge() != null && isAllowed(bclBiome.getEdge())) {
edge = create(bclBiome.getEdge());
} else {
edge = null;
}
parent = bclBiome.getParentBiome() != null ? create(bclBiome.getParentBiome()) : null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ActualBiome entry = (ActualBiome) o;
return bclBiome.equals(entry.bclBiome);
}
@Override
public int hashCode() {
return Objects.hash(bclBiome);
}
public ActualBiome getSubBiome(WorldgenRandom random) {
return subbiomes.get(random);
}
public ActualBiome getEdge() {
return edge;
}
public ActualBiome getParentBiome() {
return parent;
}
public boolean isSame(ActualBiome e) {
return bclBiome.isSame(e.bclBiome);
}
@Override
public String toString() {
return "ActualBiome{" +
"key=" + key.location() +
", subbiomes=" + subbiomes.size() +
", edge=" + (edge != null ? edge.key.location() : "null") +
", parent=" + (parent != null ? parent.key.location() : "null") +
", isValid=" + isValid +
'}';
}
}
}

View file

@ -0,0 +1,5 @@
package org.betterx.bclib.api.v2.generator;
public enum BiomeType {
LAND, VOID
}

View file

@ -0,0 +1,103 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.config.Configs;
import java.awt.*;
import java.util.function.BiFunction;
import java.util.function.Function;
public class GeneratorOptions {
private static BiFunction<Point, Integer, Boolean> endLandFunction;
private static boolean fixEndBiomeSource = true;
private static boolean fixNetherBiomeSource = true;
public static void init() {
fixEndBiomeSource = Configs.GENERATOR_CONFIG.getBoolean("options.biomeSource", "fixEndBiomeSource", true);
fixNetherBiomeSource = Configs.GENERATOR_CONFIG.getBoolean("options.biomeSource", "fixNetherBiomeSource", true);
}
@Deprecated(forRemoval = true)
public static int getBiomeSizeNether() {
return 256;
}
@Deprecated(forRemoval = true)
public static int getVerticalBiomeSizeNether() {
return 86;
}
@Deprecated(forRemoval = true)
public static int getBiomeSizeEndLand() {
return 256;
}
@Deprecated(forRemoval = true)
public static int getBiomeSizeEndVoid() {
return 256;
}
/**
* @param endLandFunction
* @deprecated use {@link #setEndLandFunction(BiFunction)} instead
*/
@Deprecated(forRemoval = true)
public static void setEndLandFunction(Function<Point, Boolean> endLandFunction) {
GeneratorOptions.endLandFunction = (p, h) -> endLandFunction.apply(p);
}
public static void setEndLandFunction(BiFunction<Point, Integer, Boolean> endLandFunction) {
GeneratorOptions.endLandFunction = endLandFunction;
}
public static BiFunction<Point, Integer, Boolean> getEndLandFunction() {
return endLandFunction;
}
@Deprecated(forRemoval = true)
public static long getFarEndBiomes() {
return 1000000;
}
/**
* Set distance of far End biomes generation, in blocks
*
* @param distance
*/
@Deprecated(forRemoval = true)
public static void setFarEndBiomes(int distance) {
}
/**
* Set distance of far End biomes generation, in blocks^2
*
* @param distanceSqr the distance squared
*/
@Deprecated(forRemoval = true)
public static void setFarEndBiomesSqr(long distanceSqr) {
}
@Deprecated(forRemoval = true)
public static boolean customNetherBiomeSource() {
return true;
}
@Deprecated(forRemoval = true)
public static boolean customEndBiomeSource() {
return true;
}
@Deprecated(forRemoval = true)
public static boolean useVerticalBiomes() {
return true;
}
public static boolean fixEndBiomeSource() {
return fixEndBiomeSource;
}
public static boolean fixNetherBiomeSource() {
return fixNetherBiomeSource;
}
}

View file

@ -0,0 +1,92 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
/**
* Helper class until FAPI integrates <a href="https://github.com/FabricMC/fabric/pull/2369">this PR</a>
*/
public class TheEndBiomesHelper {
@ApiStatus.Internal
private static Map<BiomeAPI.BiomeType, Set<ResourceKey<Biome>>> END_BIOMES = new HashMap<>();
@ApiStatus.Internal
public static void add(BiomeAPI.BiomeType type, ResourceKey<Biome> biome) {
if (biome == null) return;
END_BIOMES.computeIfAbsent(type, t -> new HashSet<>()).add(biome);
}
private static boolean has(BiomeAPI.BiomeType type, ResourceKey<Biome> biome) {
if (biome == null) return false;
Set<ResourceKey<Biome>> set = END_BIOMES.get(type);
if (set == null) return false;
return set.contains(biome);
}
/**
* Returns true if the given biome was added as a main end Biome in the end, considering the Vanilla end biomes,
* and any biomes added to the End by mods.
*
* @param biome The biome to search for
*/
public static boolean canGenerateAsMainIslandBiome(ResourceKey<Biome> biome) {
return has(BiomeAPI.BiomeType.END_CENTER, biome);
}
/**
* Returns true if the given biome was added as a small end islands Biome in the end, considering the Vanilla end biomes,
* and any biomes added to the End by mods.
*
* @param biome The biome to search for
*/
public static boolean canGenerateAsSmallIslandsBiome(ResourceKey<Biome> biome) {
return has(BiomeAPI.BiomeType.END_VOID, biome);
}
/**
* Returns true if the given biome was added as a Highland Biome in the end, considering the Vanilla end biomes,
* and any biomes added to the End by mods.
*
* @param biome The biome to search for
*/
public static boolean canGenerateAsHighlandsBiome(ResourceKey<Biome> biome) {
return has(BiomeAPI.BiomeType.END_LAND, biome);
}
/**
* Returns true if the given biome was added as midland biome in the end, considering the Vanilla end biomes,
* and any biomes added to the End as midland biome by mods.
*
* @param biome The biome to search for
*/
public static boolean canGenerateAsEndMidlands(ResourceKey<Biome> biome) {
return false;
}
/**
* Returns true if the given biome was added as barrens biome in the end, considering the Vanilla end biomes,
* and any biomes added to the End as barrens biome by mods.
*
* @param biome The biome to search for
*/
public static boolean canGenerateAsEndBarrens(ResourceKey<Biome> biome) {
return has(BiomeAPI.BiomeType.END_BARRENS, biome);
}
public static boolean canGenerateInEnd(ResourceKey<Biome> biome) {
return canGenerateAsHighlandsBiome(biome)
|| canGenerateAsEndBarrens(biome)
|| canGenerateAsEndMidlands(biome)
|| canGenerateAsSmallIslandsBiome(biome)
|| canGenerateAsMainIslandBiome(biome);
}
}

View file

@ -0,0 +1,215 @@
package org.betterx.bclib.api.v2.generator.config;
import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.api.v2.generator.map.hex.HexBiomeMap;
import org.betterx.bclib.api.v2.generator.map.square.SquareBiomeMap;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.util.TriFunction;
import org.betterx.worlds.together.biomesource.config.BiomeSourceConfig;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.util.Mth;
import net.minecraft.util.StringRepresentable;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
public class BCLEndBiomeSourceConfig implements BiomeSourceConfig<BCLibEndBiomeSource> {
public static final BCLEndBiomeSourceConfig VANILLA = new BCLEndBiomeSourceConfig(
EndBiomeMapType.VANILLA,
EndBiomeGeneratorType.VANILLA,
true,
4096,
128,
128,
128,
128
);
public static final BCLEndBiomeSourceConfig MINECRAFT_17 = new BCLEndBiomeSourceConfig(
EndBiomeMapType.SQUARE,
EndBiomeGeneratorType.PAULEVS,
true,
VANILLA.innerVoidRadiusSquared * 16 * 16,
256,
256,
256,
256
);
public static final BCLEndBiomeSourceConfig MINECRAFT_18 = new BCLEndBiomeSourceConfig(
EndBiomeMapType.HEX,
EndBiomeGeneratorType.PAULEVS,
true,
MINECRAFT_17.innerVoidRadiusSquared,
MINECRAFT_17.centerBiomesSize,
MINECRAFT_17.voidBiomesSize,
MINECRAFT_17.landBiomesSize,
MINECRAFT_17.barrensBiomesSize
);
public static final BCLEndBiomeSourceConfig DEFAULT = MINECRAFT_18;
public static final Codec<BCLEndBiomeSourceConfig> CODEC = RecordCodecBuilder.create(instance -> instance
.group(
EndBiomeMapType.CODEC
.fieldOf("map_type")
.orElse(DEFAULT.mapVersion)
.forGetter(o -> o.mapVersion),
EndBiomeGeneratorType.CODEC
.fieldOf("generator_version")
.orElse(DEFAULT.generatorVersion)
.forGetter(o -> o.generatorVersion),
Codec.BOOL
.fieldOf("with_void_biomes")
.orElse(DEFAULT.withVoidBiomes)
.forGetter(o -> o.withVoidBiomes),
Codec.INT
.fieldOf("inner_void_radius_squared")
.orElse(DEFAULT.innerVoidRadiusSquared)
.forGetter(o -> o.innerVoidRadiusSquared),
Codec.INT
.fieldOf("center_biomes_size")
.orElse(DEFAULT.centerBiomesSize)
.forGetter(o -> o.centerBiomesSize),
Codec.INT
.fieldOf("void_biomes_size")
.orElse(DEFAULT.voidBiomesSize)
.forGetter(o -> o.voidBiomesSize),
Codec.INT
.fieldOf("land_biomes_size")
.orElse(DEFAULT.landBiomesSize)
.forGetter(o -> o.landBiomesSize),
Codec.INT
.fieldOf("barrens_biomes_size")
.orElse(DEFAULT.barrensBiomesSize)
.forGetter(o -> o.barrensBiomesSize)
)
.apply(instance, BCLEndBiomeSourceConfig::new));
public BCLEndBiomeSourceConfig(
@NotNull EndBiomeMapType mapVersion,
@NotNull EndBiomeGeneratorType generatorVersion,
boolean withVoidBiomes,
int innerVoidRadiusSquared,
int centerBiomesSize,
int voidBiomesSize,
int landBiomesSize,
int barrensBiomesSize
) {
this.mapVersion = mapVersion;
this.generatorVersion = generatorVersion;
this.withVoidBiomes = withVoidBiomes;
this.innerVoidRadiusSquared = innerVoidRadiusSquared;
this.barrensBiomesSize = Mth.clamp(barrensBiomesSize, 1, 8192);
this.voidBiomesSize = Mth.clamp(voidBiomesSize, 1, 8192);
this.centerBiomesSize = Mth.clamp(centerBiomesSize, 1, 8192);
this.landBiomesSize = Mth.clamp(landBiomesSize, 1, 8192);
}
public enum EndBiomeMapType implements StringRepresentable {
VANILLA("vanilla", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker)),
SQUARE("square", (seed, biomeSize, picker) -> new SquareBiomeMap(seed, biomeSize, picker)),
HEX("hex", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker));
public static final Codec<EndBiomeMapType> CODEC = StringRepresentable.fromEnum(EndBiomeMapType::values);
public final String name;
public final @NotNull TriFunction<Long, Integer, BiomePicker, BiomeMap> mapBuilder;
EndBiomeMapType(String name, @NotNull TriFunction<Long, Integer, BiomePicker, BiomeMap> mapBuilder) {
this.name = name;
this.mapBuilder = mapBuilder;
}
@Override
public String getSerializedName() {
return name;
}
@Override
public String toString() {
return name;
}
}
public enum EndBiomeGeneratorType implements StringRepresentable {
VANILLA("vanilla"),
PAULEVS("paulevs");
public static final Codec<EndBiomeGeneratorType> CODEC = StringRepresentable.fromEnum(EndBiomeGeneratorType::values);
public final String name;
EndBiomeGeneratorType(String name) {
this.name = name;
}
@Override
public String getSerializedName() {
return name;
}
@Override
public String toString() {
return name;
}
}
public final @NotNull EndBiomeMapType mapVersion;
public final @NotNull EndBiomeGeneratorType generatorVersion;
public final boolean withVoidBiomes;
public final int innerVoidRadiusSquared;
public final int voidBiomesSize;
public final int centerBiomesSize;
public final int landBiomesSize;
public final int barrensBiomesSize;
@Override
public String toString() {
return "BCLEndBiomeSourceConfig{" +
"mapVersion=" + mapVersion +
", generatorVersion=" + generatorVersion +
", withVoidBiomes=" + withVoidBiomes +
", innerVoidRadiusSquared=" + innerVoidRadiusSquared +
", voidBiomesSize=" + voidBiomesSize +
", centerBiomesSize=" + centerBiomesSize +
", landBiomesSize=" + landBiomesSize +
", barrensBiomesSize=" + barrensBiomesSize +
'}';
}
@Override
public boolean couldSetWithoutRepair(BiomeSourceConfig<?> input) {
if (input instanceof BCLEndBiomeSourceConfig cfg) {
return withVoidBiomes == cfg.withVoidBiomes && mapVersion == cfg.mapVersion;
}
return false;
}
@Override
public boolean sameConfig(BiomeSourceConfig<?> input) {
return this.equals(input);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BCLEndBiomeSourceConfig that = (BCLEndBiomeSourceConfig) o;
return withVoidBiomes == that.withVoidBiomes && innerVoidRadiusSquared == that.innerVoidRadiusSquared && voidBiomesSize == that.voidBiomesSize && centerBiomesSize == that.centerBiomesSize && landBiomesSize == that.landBiomesSize && barrensBiomesSize == that.barrensBiomesSize && mapVersion == that.mapVersion && generatorVersion == that.generatorVersion;
}
@Override
public int hashCode() {
return Objects.hash(
mapVersion,
generatorVersion,
withVoidBiomes,
innerVoidRadiusSquared,
voidBiomesSize,
centerBiomesSize,
landBiomesSize,
barrensBiomesSize
);
}
}

View file

@ -0,0 +1,130 @@
package org.betterx.bclib.api.v2.generator.config;
import org.betterx.bclib.api.v2.generator.BCLibNetherBiomeSource;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.api.v2.generator.map.hex.HexBiomeMap;
import org.betterx.bclib.api.v2.generator.map.square.SquareBiomeMap;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.util.TriFunction;
import org.betterx.worlds.together.biomesource.config.BiomeSourceConfig;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.util.Mth;
import net.minecraft.util.StringRepresentable;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
public class BCLNetherBiomeSourceConfig implements BiomeSourceConfig<BCLibNetherBiomeSource> {
public static final BCLNetherBiomeSourceConfig VANILLA = new BCLNetherBiomeSourceConfig(
NetherBiomeMapType.VANILLA,
256,
86,
false
);
public static final BCLNetherBiomeSourceConfig MINECRAFT_17 = new BCLNetherBiomeSourceConfig(
NetherBiomeMapType.SQUARE,
256,
86,
true
);
public static final BCLNetherBiomeSourceConfig MINECRAFT_18 = new BCLNetherBiomeSourceConfig(
NetherBiomeMapType.HEX,
MINECRAFT_17.biomeSize,
MINECRAFT_17.biomeSizeVertical,
MINECRAFT_17.useVerticalBiomes
);
public static final BCLNetherBiomeSourceConfig DEFAULT = MINECRAFT_18;
public static final Codec<BCLNetherBiomeSourceConfig> CODEC = RecordCodecBuilder.create(instance -> instance
.group(
BCLNetherBiomeSourceConfig.NetherBiomeMapType.CODEC
.fieldOf("map_type")
.orElse(DEFAULT.mapVersion)
.forGetter(o -> o.mapVersion),
Codec.INT.fieldOf("biome_size").orElse(DEFAULT.biomeSize).forGetter(o -> o.biomeSize),
Codec.INT.fieldOf("biome_size_vertical")
.orElse(DEFAULT.biomeSizeVertical)
.forGetter(o -> o.biomeSizeVertical),
Codec.BOOL.fieldOf("use_vertical_biomes")
.orElse(DEFAULT.useVerticalBiomes)
.forGetter(o -> o.useVerticalBiomes)
)
.apply(instance, BCLNetherBiomeSourceConfig::new));
public final @NotNull NetherBiomeMapType mapVersion;
public final int biomeSize;
public final int biomeSizeVertical;
public final boolean useVerticalBiomes;
public BCLNetherBiomeSourceConfig(
@NotNull NetherBiomeMapType mapVersion,
int biomeSize,
int biomeSizeVertical,
boolean useVerticalBiomes
) {
this.mapVersion = mapVersion;
this.biomeSize = Mth.clamp(biomeSize, 1, 8192);
this.biomeSizeVertical = Mth.clamp(biomeSizeVertical, 1, 8192);
this.useVerticalBiomes = useVerticalBiomes;
}
@Override
public String toString() {
return "BCLibNetherBiomeSourceConfig{" +
"mapVersion=" + mapVersion +
'}';
}
@Override
public boolean couldSetWithoutRepair(BiomeSourceConfig<?> input) {
if (input instanceof BCLNetherBiomeSourceConfig cfg) {
return mapVersion == cfg.mapVersion;
}
return false;
}
@Override
public boolean sameConfig(BiomeSourceConfig<?> input) {
return this.equals(input);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BCLNetherBiomeSourceConfig)) return false;
BCLNetherBiomeSourceConfig that = (BCLNetherBiomeSourceConfig) o;
return mapVersion == that.mapVersion;
}
@Override
public int hashCode() {
return Objects.hash(mapVersion);
}
public enum NetherBiomeMapType implements StringRepresentable {
VANILLA("vanilla", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker)),
SQUARE("square", (seed, biomeSize, picker) -> new SquareBiomeMap(seed, biomeSize, picker)),
HEX("hex", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker));
public static final Codec<NetherBiomeMapType> CODEC = StringRepresentable.fromEnum(NetherBiomeMapType::values);
public final String name;
public final TriFunction<Long, Integer, BiomePicker, BiomeMap> mapBuilder;
NetherBiomeMapType(String name, TriFunction<Long, Integer, BiomePicker, BiomeMap> mapBuilder) {
this.name = name;
this.mapBuilder = mapBuilder;
}
@Override
public String getSerializedName() {
return name;
}
@Override
public String toString() {
return name;
}
}
}

View file

@ -0,0 +1,113 @@
package org.betterx.bclib.api.v2.generator.map;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.interfaces.BiomeChunk;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.interfaces.TriConsumer;
import org.betterx.bclib.noise.OpenSimplexNoise;
import org.betterx.bclib.util.TriFunction;
import net.minecraft.util.Mth;
import java.util.Random;
public class MapStack implements BiomeMap {
private final OpenSimplexNoise noise;
private final BiomeMap[] maps;
private final double layerDistortion;
private final int worldHeight;
private final int minValue;
private final int maxValue;
private final int maxIndex;
public MapStack(
long seed,
int size,
BiomePicker picker,
int mapHeight,
int worldHeight,
TriFunction<Long, Integer, BiomePicker, BiomeMap> mapConstructor
) {
final int mapCount = Mth.ceil((float) worldHeight / mapHeight);
this.maxIndex = mapCount - 1;
this.worldHeight = worldHeight;
this.layerDistortion = mapHeight * 0.1;
minValue = Mth.floor(mapHeight * 0.5F + 0.5F);
maxValue = Mth.floor(worldHeight - mapHeight * 0.5F + 0.5F);
maps = new BiomeMap[mapCount];
Random random = new Random(seed);
for (int i = 0; i < mapCount; i++) {
maps[i] = mapConstructor.apply(random.nextLong(), size, picker);
maps[i].setChunkProcessor(this::onChunkCreation);
}
noise = new OpenSimplexNoise(random.nextInt());
}
@Override
public void clearCache() {
for (BiomeMap map : maps) {
map.clearCache();
}
}
@Override
public void setChunkProcessor(TriConsumer<Integer, Integer, Integer> processor) {
}
@Override
public BiomeChunk getChunk(int cx, int cz, boolean update) {
return null;
}
@Override
public BiomePicker.ActualBiome getBiome(double x, double y, double z) {
int mapIndex;
if (y < minValue) {
mapIndex = 0;
} else if (y > maxValue) {
mapIndex = maxIndex;
} else {
mapIndex = Mth.floor((y + noise.eval(
x * 0.03,
z * 0.03
) * layerDistortion) / worldHeight * maxIndex + 0.5F);
mapIndex = Mth.clamp(mapIndex, 0, maxIndex);
}
return maps[mapIndex].getBiome(x, y, z);
}
private void onChunkCreation(int cx, int cz, int side) {
BiomePicker.ActualBiome[][] biomeMap = new BiomePicker.ActualBiome[side][side];
BiomeChunk[] chunks = new BiomeChunk[maps.length];
boolean isNoEmpty = false;
for (int i = 0; i < maps.length; i++) {
chunks[i] = maps[i].getChunk(cx, cz, false);
for (int x = 0; x < side; x++) {
for (int z = 0; z < side; z++) {
if (biomeMap[x][z] == null) {
BiomePicker.ActualBiome biome = chunks[i].getBiome(x, z);
if (biome.bclBiome.isVertical()) {
biomeMap[x][z] = biome;
isNoEmpty = true;
}
}
}
}
}
if (isNoEmpty) {
for (int i = 0; i < maps.length; i++) {
for (int x = 0; x < side; x++) {
for (int z = 0; z < side; z++) {
if (biomeMap[x][z] != null) {
chunks[i].setBiome(x, z, biomeMap[x][z]);
}
}
}
}
}
}
}

View file

@ -0,0 +1,160 @@
package org.betterx.bclib.api.v2.generator.map.hex;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.interfaces.BiomeChunk;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import java.util.Arrays;
public class HexBiomeChunk implements BiomeChunk {
private static final short SIDE = 32;
private static final byte SIDE_PRE = 4;
private static final short SIZE = SIDE * SIDE;
private static final short MAX_SIDE = SIZE - SIDE;
private static final byte SCALE_PRE = SIDE / SIDE_PRE;
private static final byte SIZE_PRE = SIDE_PRE * SIDE_PRE;
private static final byte SIDE_MASK = SIDE - 1;
private static final byte SIDE_PRE_MASK = SIDE_PRE - 1;
private static final byte SIDE_OFFSET = (byte) Math.round(Math.log(SIDE) / Math.log(2));
private static final byte SIDE_PRE_OFFSET = (byte) Math.round(Math.log(SIDE_PRE) / Math.log(2));
private static final short[][] NEIGHBOURS;
private final BiomePicker.ActualBiome[] biomes = new BiomePicker.ActualBiome[SIZE];
public HexBiomeChunk(WorldgenRandom random, BiomePicker picker) {
BiomePicker.ActualBiome[][] buffers = new BiomePicker.ActualBiome[2][SIZE];
for (BiomePicker.ActualBiome[] buffer : buffers) {
Arrays.fill(buffer, null);
}
for (byte index = 0; index < SIZE_PRE; index++) {
byte px = (byte) (index >> SIDE_PRE_OFFSET);
byte pz = (byte) (index & SIDE_PRE_MASK);
px = (byte) (px * SCALE_PRE + random.nextInt(SCALE_PRE));
pz = (byte) (pz * SCALE_PRE + random.nextInt(SCALE_PRE));
circle(buffers[0], getIndex(px, pz), picker.getBiome(random), null);
}
boolean hasEmptyCells = true;
byte bufferIndex = 0;
while (hasEmptyCells) {
BiomePicker.ActualBiome[] inBuffer = buffers[bufferIndex];
bufferIndex = (byte) ((bufferIndex + 1) & 1);
BiomePicker.ActualBiome[] outBuffer = buffers[bufferIndex];
hasEmptyCells = false;
for (short index = SIDE; index < MAX_SIDE; index++) {
byte z = (byte) (index & SIDE_MASK);
if (z == 0 || z == SIDE_MASK) {
continue;
}
if (inBuffer[index] != null) {
outBuffer[index] = inBuffer[index];
short[] neighbours = getNeighbours(index & SIDE_MASK);
short indexSide = (short) (index + neighbours[random.nextInt(6)]);
if (indexSide >= 0 && indexSide < SIZE && outBuffer[indexSide] == null) {
outBuffer[indexSide] = inBuffer[index];
}
} else {
hasEmptyCells = true;
}
}
}
BiomePicker.ActualBiome[] outBuffer = buffers[bufferIndex];
byte preN = (byte) (SIDE_MASK - 2);
for (byte index = 0; index < SIDE; index++) {
outBuffer[getIndex(index, (byte) 0)] = outBuffer[getIndex(index, (byte) 2)];
outBuffer[getIndex((byte) 0, index)] = outBuffer[getIndex((byte) 2, index)];
outBuffer[getIndex(index, SIDE_MASK)] = outBuffer[getIndex(index, preN)];
outBuffer[getIndex(SIDE_MASK, index)] = outBuffer[getIndex(preN, index)];
}
for (short index = 0; index < SIZE; index++) {
if (outBuffer[index] == null) {
outBuffer[index] = picker.getBiome(random);
} else if (random.nextInt(4) == 0) {
circle(outBuffer, index, outBuffer[index].getSubBiome(random), outBuffer[index]);
}
}
System.arraycopy(outBuffer, 0, this.biomes, 0, SIZE);
}
private void circle(
BiomePicker.ActualBiome[] buffer,
short center,
BiomePicker.ActualBiome biome,
BiomePicker.ActualBiome mask
) {
if (buffer[center] == mask) {
buffer[center] = biome;
}
short[] neighbours = getNeighbours(center & SIDE_MASK);
for (short i : neighbours) {
short index = (short) (center + i);
if (index >= 0 && index < SIZE && buffer[index] == mask) {
buffer[index] = biome;
}
}
}
private static byte wrap(int value) {
return (byte) (value & SIDE_MASK);
}
private short getIndex(byte x, byte z) {
return (short) ((short) x << SIDE_OFFSET | z);
}
@Override
public BiomePicker.ActualBiome getBiome(int x, int z) {
return biomes[getIndex(wrap(x), wrap(z))];
}
@Override
public void setBiome(int x, int z, BiomePicker.ActualBiome biome) {
biomes[getIndex(wrap(x), wrap(z))] = biome;
}
@Override
public int getSide() {
return SIDE;
}
public static int scaleCoordinate(int value) {
return value >> SIDE_OFFSET;
}
public static boolean isBorder(int value) {
return wrap(value) == SIDE_MASK;
}
private short[] getNeighbours(int z) {
return NEIGHBOURS[z & 1];
}
public static float scaleMap(float size) {
return size / (SIDE >> 2);
}
static {
NEIGHBOURS = new short[2][6];
NEIGHBOURS[0][0] = 1;
NEIGHBOURS[0][1] = -1;
NEIGHBOURS[0][2] = SIDE;
NEIGHBOURS[0][3] = -SIDE;
NEIGHBOURS[0][4] = SIDE + 1;
NEIGHBOURS[0][5] = SIDE - 1;
NEIGHBOURS[1][0] = 1;
NEIGHBOURS[1][1] = -1;
NEIGHBOURS[1][2] = SIDE;
NEIGHBOURS[1][3] = -SIDE;
NEIGHBOURS[1][4] = -SIDE + 1;
NEIGHBOURS[1][5] = -SIDE - 1;
}
}

View file

@ -0,0 +1,187 @@
package org.betterx.bclib.api.v2.generator.map.hex;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.interfaces.BiomeChunk;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.interfaces.TriConsumer;
import org.betterx.bclib.noise.OpenSimplexNoise;
import org.betterx.bclib.util.MHelper;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.Random;
public class HexBiomeMap implements BiomeMap {
private static final float RAD_INNER = (float) Math.sqrt(3.0) * 0.5F;
private static final float COEF = 0.25F * (float) Math.sqrt(3.0);
private static final float COEF_HALF = COEF * 0.5F;
private static final float SIN = (float) Math.sin(0.4);
private static final float COS = (float) Math.cos(0.4);
private static final float[] EDGE_CIRCLE_X;
private static final float[] EDGE_CIRCLE_Z;
private final Map<ChunkPos, HexBiomeChunk> chunks = Maps.newConcurrentMap();
private final BiomePicker picker;
private final OpenSimplexNoise[] noises = new OpenSimplexNoise[2];
private TriConsumer<Integer, Integer, Integer> processor;
private final byte noiseIterations;
private final float scale;
private final int seed;
public HexBiomeMap(long seed, int size, BiomePicker picker) {
this.picker = picker;
this.scale = HexBiomeChunk.scaleMap(size);
Random random = new Random(seed);
noises[0] = new OpenSimplexNoise(random.nextInt());
noises[1] = new OpenSimplexNoise(random.nextInt());
noiseIterations = (byte) Math.min(Math.ceil(Math.log(scale) / Math.log(2)), 5);
this.seed = random.nextInt();
}
@Override
public void clearCache() {
if (chunks.size() > 127) {
chunks.clear();
}
}
@Override
public BiomePicker.ActualBiome getBiome(double x, double y, double z) {
BiomePicker.ActualBiome biome = getRawBiome(x, z);
BiomePicker.ActualBiome edge = biome.getEdge();
int size = biome.bclBiome.getEdgeSize();
if (edge == null && biome.getParentBiome() != null) {
edge = biome.getParentBiome().getEdge();
size = biome.getParentBiome().bclBiome.getEdgeSize();
}
if (edge == null) {
return biome;
}
for (byte i = 0; i < 8; i++) {
if (!getRawBiome(x + size * EDGE_CIRCLE_X[i], z + size * EDGE_CIRCLE_Z[i]).isSame(biome)) {
return edge;
}
}
return biome;
}
@Override
public BiomeChunk getChunk(final int cx, final int cz, final boolean update) {
final ChunkPos pos = new ChunkPos(cx, cz);
HexBiomeChunk chunk = chunks.get(pos);
if (chunk == null) {
WorldgenRandom random = new WorldgenRandom(RandomSource.create(MHelper.getSeed(seed, cx, cz)));
chunk = new HexBiomeChunk(random, picker);
if (update && processor != null) {
processor.accept(cx, cz, chunk.getSide());
}
chunks.put(pos, chunk);
}
return chunk;
}
@Override
public void setChunkProcessor(TriConsumer<Integer, Integer, Integer> processor) {
this.processor = processor;
}
private BiomePicker.ActualBiome getRawBiome(double x, double z) {
double px = x / scale * RAD_INNER;
double pz = z / scale;
double dx = rotateX(px, pz);
double dz = rotateZ(px, pz);
px = dx;
pz = dz;
dx = getNoise(px, pz, (byte) 0) * 0.2F;
dz = getNoise(pz, px, (byte) 1) * 0.2F;
px += dx;
pz += dz;
int cellZ = (int) Math.floor(pz);
boolean offset = (cellZ & 1) == 1;
if (offset) {
px += 0.5;
}
int cellX = (int) Math.floor(px);
float pointX = (float) (px - cellX - 0.5);
float pointZ = (float) (pz - cellZ - 0.5);
if (Math.abs(pointZ) < 0.3333F) {
return getChunkBiome(cellX, cellZ);
}
if (insideHexagon(0, 0, 1.1555F, pointZ * RAD_INNER, pointX)) {
return getChunkBiome(cellX, cellZ);
}
cellX = pointX < 0 ? (offset ? cellX - 1 : cellX) : (offset ? cellX : cellX + 1);
cellZ = pointZ < 0 ? cellZ - 1 : cellZ + 1;
return getChunkBiome(cellX, cellZ);
}
private BiomePicker.ActualBiome getChunkBiome(int x, int z) {
int cx = HexBiomeChunk.scaleCoordinate(x);
int cz = HexBiomeChunk.scaleCoordinate(z);
if (((z >> 2) & 1) == 0 && HexBiomeChunk.isBorder(x)) {
x = 0;
cx += 1;
} else if (((x >> 2) & 1) == 0 && HexBiomeChunk.isBorder(z)) {
z = 0;
cz += 1;
}
return getChunk(cx, cz, true).getBiome(x, z);
}
private boolean insideHexagon(float centerX, float centerZ, float radius, float x, float z) {
double dx = Math.abs(x - centerX) / radius;
double dy = Math.abs(z - centerZ) / radius;
return (dy <= COEF) && (COEF * dx + 0.25F * dy <= COEF_HALF);
}
private double getNoise(double x, double z, byte state) {
double result = 0;
for (byte i = 1; i <= noiseIterations; i++) {
OpenSimplexNoise noise = noises[state];
state = (byte) ((state + 1) & 1);
result += noise.eval(x * i, z * i) / i;
}
return result;
}
private double rotateX(double x, double z) {
return x * COS - z * SIN;
}
private double rotateZ(double x, double z) {
return x * SIN + z * COS;
}
static {
EDGE_CIRCLE_X = new float[8];
EDGE_CIRCLE_Z = new float[8];
for (byte i = 0; i < 8; i++) {
float angle = i / 4F * (float) Math.PI;
EDGE_CIRCLE_X[i] = (float) Math.sin(angle);
EDGE_CIRCLE_Z[i] = (float) Math.cos(angle);
}
}
}

View file

@ -0,0 +1,66 @@
package org.betterx.bclib.api.v2.generator.map.square;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.interfaces.BiomeChunk;
import net.minecraft.world.level.levelgen.WorldgenRandom;
public class SquareBiomeChunk implements BiomeChunk {
private static final int BIT_OFFSET = 4;
protected static final int WIDTH = 1 << BIT_OFFSET;
private static final int SM_WIDTH = WIDTH >> 1;
private static final int SM_BIT_OFFSET = BIT_OFFSET >> 1;
private static final int MASK_OFFSET = SM_WIDTH - 1;
protected static final int MASK_WIDTH = WIDTH - 1;
private static final int SM_CAPACITY = SM_WIDTH * SM_WIDTH;
private static final int CAPACITY = WIDTH * WIDTH;
private final BiomePicker.ActualBiome[] biomes;
public SquareBiomeChunk(WorldgenRandom random, BiomePicker picker) {
BiomePicker.ActualBiome[] PreBio = new BiomePicker.ActualBiome[SM_CAPACITY];
biomes = new BiomePicker.ActualBiome[CAPACITY];
for (int x = 0; x < SM_WIDTH; x++) {
int offset = x << SM_BIT_OFFSET;
for (int z = 0; z < SM_WIDTH; z++) {
PreBio[offset | z] = picker.getBiome(random);
}
}
for (int x = 0; x < WIDTH; x++) {
int offset = x << BIT_OFFSET;
for (int z = 0; z < WIDTH; z++) {
biomes[offset | z] = PreBio[getSmIndex(offsetXZ(x, random), offsetXZ(z, random))].getSubBiome(random);
}
}
}
@Override
public BiomePicker.ActualBiome getBiome(int x, int z) {
return biomes[getIndex(x & MASK_WIDTH, z & MASK_WIDTH)];
}
@Override
public void setBiome(int x, int z, BiomePicker.ActualBiome biome) {
biomes[getIndex(x & MASK_WIDTH, z & MASK_WIDTH)] = biome;
}
@Override
public int getSide() {
return WIDTH;
}
private int offsetXZ(int x, WorldgenRandom random) {
return ((x + random.nextInt(2)) >> 1) & MASK_OFFSET;
}
private int getIndex(int x, int z) {
return x << BIT_OFFSET | z;
}
private int getSmIndex(int x, int z) {
return x << SM_BIT_OFFSET | z;
}
}

View file

@ -0,0 +1,143 @@
package org.betterx.bclib.api.v2.generator.map.square;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.interfaces.BiomeChunk;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.interfaces.TriConsumer;
import org.betterx.bclib.noise.OpenSimplexNoise;
import org.betterx.bclib.util.MHelper;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import com.google.common.collect.Maps;
import java.util.Map;
public class SquareBiomeMap implements BiomeMap {
private final Map<ChunkPos, SquareBiomeChunk> maps = Maps.newHashMap();
private final OpenSimplexNoise noiseX;
private final OpenSimplexNoise noiseZ;
private final WorldgenRandom random;
private final BiomePicker picker;
private final int sizeXZ;
private final int depth;
private final int size;
private TriConsumer<Integer, Integer, Integer> processor;
public SquareBiomeMap(long seed, int size, BiomePicker picker) {
random = new WorldgenRandom(new LegacyRandomSource(seed));
noiseX = new OpenSimplexNoise(random.nextLong());
noiseZ = new OpenSimplexNoise(random.nextLong());
this.sizeXZ = size;
depth = (int) Math.ceil(Math.log(size) / Math.log(2)) - 2;
this.size = 1 << depth;
this.picker = picker;
}
@Override
public void clearCache() {
if (maps.size() > 32) {
maps.clear();
}
}
@Override
public BiomePicker.ActualBiome getBiome(double x, double y, double z) {
BiomePicker.ActualBiome biome = getRawBiome(x, z);
if (biome.getEdge() != null || (biome.getParentBiome() != null && biome.getParentBiome().getEdge() != null)) {
BiomePicker.ActualBiome search = biome;
if (biome.getParentBiome() != null) {
search = biome.getParentBiome();
}
int size = search.bclBiome.getEdgeSize();
boolean edge = !search.isSame(getRawBiome(x + size, z));
edge = edge || !search.isSame(getRawBiome(x - size, z));
edge = edge || !search.isSame(getRawBiome(x, z + size));
edge = edge || !search.isSame(getRawBiome(x, z - size));
edge = edge || !search.isSame(getRawBiome(x - 1, z - 1));
edge = edge || !search.isSame(getRawBiome(x - 1, z + 1));
edge = edge || !search.isSame(getRawBiome(x + 1, z - 1));
edge = edge || !search.isSame(getRawBiome(x + 1, z + 1));
if (edge) {
biome = search.getEdge();
}
}
return biome;
}
@Override
public void setChunkProcessor(TriConsumer<Integer, Integer, Integer> processor) {
this.processor = processor;
}
@Override
public BiomeChunk getChunk(int cx, int cz, boolean update) {
ChunkPos cpos = new ChunkPos(cx, cz);
SquareBiomeChunk chunk = maps.get(cpos);
if (chunk == null) {
synchronized (random) {
random.setLargeFeatureWithSalt(0, cpos.x, cpos.z, 0);
chunk = new SquareBiomeChunk(random, picker);
}
maps.put(cpos, chunk);
if (update && processor != null) {
processor.accept(cx, cz, chunk.getSide());
}
}
return chunk;
}
private BiomePicker.ActualBiome getRawBiome(double bx, double bz) {
double x = bx * size / sizeXZ;
double z = bz * size / sizeXZ;
double px = bx * 0.2;
double pz = bz * 0.2;
for (int i = 0; i < depth; i++) {
double nx = (x + noiseX.eval(px, pz)) / 2F;
double nz = (z + noiseZ.eval(px, pz)) / 2F;
x = nx;
z = nz;
px = px / 2 + i;
pz = pz / 2 + i;
}
int ix = MHelper.floor(x);
int iz = MHelper.floor(z);
if ((ix & SquareBiomeChunk.MASK_WIDTH) == SquareBiomeChunk.MASK_WIDTH) {
x += (iz / 2) & 1;
}
if ((iz & SquareBiomeChunk.MASK_WIDTH) == SquareBiomeChunk.MASK_WIDTH) {
z += (ix / 2) & 1;
}
ChunkPos cpos = new ChunkPos(
MHelper.floor(x / SquareBiomeChunk.WIDTH),
MHelper.floor(z / SquareBiomeChunk.WIDTH)
);
SquareBiomeChunk chunk = maps.get(cpos);
if (chunk == null) {
synchronized (random) {
random.setLargeFeatureWithSalt(0, cpos.x, cpos.z, 0);
chunk = new SquareBiomeChunk(random, picker);
}
maps.put(cpos, chunk);
}
return chunk.getBiome(MHelper.floor(x), MHelper.floor(z));
}
}

View file

@ -0,0 +1,161 @@
package org.betterx.bclib.api.v2.levelgen;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.LifeCycleAPI;
import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI;
import org.betterx.bclib.api.v2.datafixer.DataFixerAPI;
import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource;
import org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig;
import org.betterx.bclib.api.v2.levelgen.biomes.InternalBiomeAPI;
import org.betterx.bclib.api.v2.tag.TagAPI;
import org.betterx.bclib.registry.PresetsRegistry;
import org.betterx.worlds.together.tag.v3.TagManager;
import org.betterx.worlds.together.world.WorldConfig;
import org.betterx.worlds.together.world.event.WorldEvents;
import org.betterx.worlds.together.worldPreset.TogetherWorldPreset;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagLoader;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.WorldGenSettings;
import net.minecraft.world.level.levelgen.presets.WorldPreset;
import net.minecraft.world.level.storage.LevelStorageSource;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
public class LevelGenEvents {
public static void setupWorld() {
InternalBiomeAPI.prepareNewLevel();
DataExchangeAPI.prepareServerside();
}
public static void register() {
WorldEvents.BEFORE_WORLD_LOAD.on(LevelGenEvents::prepareWorld);
WorldEvents.BEFORE_SERVER_WORLD_LOAD.on(LevelGenEvents::prepareServerWorld);
WorldEvents.ON_WORLD_LOAD.on(LevelGenEvents::onWorldLoad);
WorldEvents.WORLD_REGISTRY_READY.on(LevelGenEvents::onRegistryReady);
WorldEvents.ON_FINALIZE_LEVEL_STEM.on(LevelGenEvents::finalizeStem);
WorldEvents.PATCH_WORLD.on(LevelGenEvents::patchExistingWorld);
WorldEvents.ADAPT_WORLD_PRESET.on(LevelGenEvents::adaptWorldPresetSettings);
WorldEvents.BEFORE_ADDING_TAGS.on(LevelGenEvents::appplyTags);
}
private static void appplyTags(
String directory,
Map<ResourceLocation, List<TagLoader.EntryWithSource>> tagsMap
) {
//make sure we include Tags registered by the deprecated API
TagAPI.apply(directory, tagsMap);
if (directory.equals(TagManager.BIOMES.directory)) {
InternalBiomeAPI._runBiomeTagAdders();
}
}
private static boolean patchExistingWorld(
LevelStorageSource.LevelStorageAccess storageAccess,
Consumer<Boolean> allDone
) {
return DataFixerAPI.fixData(storageAccess, true, allDone);
}
private static Optional<Holder<WorldPreset>> adaptWorldPresetSettings(
Optional<Holder<WorldPreset>> currentPreset,
WorldGenSettings worldGenSettings
) {
LevelStem endStem = worldGenSettings.dimensions().get(LevelStem.END);
//We probably loaded a Datapack for the End
if (!(endStem.generator().getBiomeSource() instanceof BCLibEndBiomeSource)) {
if (currentPreset.isPresent()) {
if (currentPreset.get().value() instanceof TogetherWorldPreset worldPreset) {
ResourceKey worldPresetKey = currentPreset.get().unwrapKey().orElse(null);
//user did not configure/change the Preset!
if (PresetsRegistry.BCL_WORLD.equals(worldPresetKey)
|| PresetsRegistry.BCL_WORLD_17.equals(worldPresetKey)) {
BCLib.LOGGER.info("Detected Datapack for END.");
LevelStem configuredEndStem = worldPreset.getDimension(LevelStem.END);
if (configuredEndStem.generator().getBiomeSource() instanceof BCLibEndBiomeSource endSource) {
BCLib.LOGGER.info("Changing Default WorldPreset Settings for Datapack use.");
BCLEndBiomeSourceConfig inputConfig = endSource.getTogetherConfig();
endSource.setTogetherConfig(new BCLEndBiomeSourceConfig(
inputConfig.mapVersion,
BCLEndBiomeSourceConfig.EndBiomeGeneratorType.VANILLA,
false,
inputConfig.innerVoidRadiusSquared,
inputConfig.centerBiomesSize,
inputConfig.voidBiomesSize,
inputConfig.landBiomesSize,
inputConfig.barrensBiomesSize
));
}
}
}
}
}
return currentPreset;
}
private static void onRegistryReady(RegistryAccess a) {
InternalBiomeAPI.initRegistry(a);
}
private static void prepareWorld(
LevelStorageSource.LevelStorageAccess storageAccess,
Map<ResourceKey<LevelStem>, ChunkGenerator> dimensions,
boolean isNewWorld
) {
setupWorld();
if (isNewWorld) {
WorldConfig.saveFile(BCLib.MOD_ID);
DataFixerAPI.initializePatchData();
} else {
LevelGenUtil.migrateGeneratorSettings();
}
}
private static void prepareServerWorld(
LevelStorageSource.LevelStorageAccess storageAccess,
Map<ResourceKey<LevelStem>, ChunkGenerator> dimensions,
boolean isNewWorld
) {
setupWorld();
if (isNewWorld) {
WorldConfig.saveFile(BCLib.MOD_ID);
DataFixerAPI.initializePatchData();
} else {
LevelGenUtil.migrateGeneratorSettings();
DataFixerAPI.fixData(storageAccess, false, (didFix) -> {/* not called when showUI==false */});
}
}
private static void onWorldLoad() {
LifeCycleAPI._runBeforeLevelLoad();
}
private static void finalizeStem(
WorldGenSettings settings,
ResourceKey<LevelStem> dimension,
LevelStem levelStem
) {
InternalBiomeAPI.applyModifications(levelStem.generator().getBiomeSource(), dimension);
}
}

View file

@ -0,0 +1,206 @@
package org.betterx.bclib.api.v2.levelgen;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.generator.BCLChunkGenerator;
import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource;
import org.betterx.bclib.api.v2.generator.BCLibNetherBiomeSource;
import org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig;
import org.betterx.bclib.api.v2.generator.config.BCLNetherBiomeSourceConfig;
import org.betterx.bclib.registry.PresetsRegistry;
import org.betterx.worlds.together.levelgen.WorldGenUtil;
import org.betterx.worlds.together.util.ModUtil;
import org.betterx.worlds.together.world.WorldConfig;
import org.betterx.worlds.together.worldPreset.TogetherWorldPreset;
import com.mojang.serialization.Lifecycle;
import net.minecraft.core.Holder;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.chunk.ChunkGenerator;
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.levelgen.presets.WorldPreset;
import net.minecraft.world.level.levelgen.presets.WorldPresets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
public class LevelGenUtil {
private static final String TAG_VERSION = "version";
private static final String TAG_BN_GEN_VERSION = "generator_version";
@NotNull
public static LevelStem getBCLNetherLevelStem(WorldGenUtil.Context context, BCLNetherBiomeSourceConfig config) {
BCLibNetherBiomeSource netherSource = new BCLibNetherBiomeSource(context.biomes, config);
return new LevelStem(
context.dimension,
new BCLChunkGenerator(
context.structureSets,
context.noiseParameters,
netherSource,
context.generatorSettings
)
);
}
public static LevelStem getBCLEndLevelStem(WorldGenUtil.Context context, BCLEndBiomeSourceConfig config) {
BCLibEndBiomeSource endSource = new BCLibEndBiomeSource(context.biomes, config);
return new LevelStem(
context.dimension,
new BCLChunkGenerator(
context.structureSets,
context.noiseParameters,
endSource,
context.generatorSettings
)
);
}
public static WorldGenSettings replaceGenerator(
ResourceKey<LevelStem> dimensionKey,
ResourceKey<DimensionType> dimensionTypeKey,
RegistryAccess registryAccess,
WorldGenSettings worldGenSettings,
ChunkGenerator generator
) {
Registry<DimensionType> dimensionTypeRegistry = registryAccess.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY);
Registry<LevelStem> newDimensions = withDimension(
dimensionKey,
dimensionTypeKey,
dimensionTypeRegistry,
worldGenSettings.dimensions(),
generator
);
return new WorldGenSettings(
worldGenSettings.seed(),
worldGenSettings.generateStructures(),
worldGenSettings.generateBonusChest(),
newDimensions
);
}
public static Registry<LevelStem> withDimension(
ResourceKey<LevelStem> dimensionKey,
ResourceKey<DimensionType> dimensionTypeKey,
Registry<DimensionType> dimensionTypeRegistry,
Registry<LevelStem> inputDimensions,
ChunkGenerator generator
) {
LevelStem levelStem = inputDimensions.get(dimensionKey);
Holder<DimensionType> dimensionType = levelStem == null
? dimensionTypeRegistry.getOrCreateHolderOrThrow(dimensionTypeKey)
: levelStem.typeHolder();
return withDimension(dimensionKey, inputDimensions, new LevelStem(dimensionType, generator));
}
public static Registry<LevelStem> withDimension(
ResourceKey<LevelStem> dimensionKey,
Registry<LevelStem> inputDimensions,
LevelStem levelStem
) {
MappedRegistry<LevelStem> writableRegistry = new MappedRegistry<>(
Registry.LEVEL_STEM_REGISTRY,
Lifecycle.experimental(),
null
);
writableRegistry.register(
dimensionKey,
levelStem,
Lifecycle.stable()
);
for (Map.Entry<ResourceKey<LevelStem>, LevelStem> entry : inputDimensions.entrySet()) {
ResourceKey<LevelStem> resourceKey = entry.getKey();
if (resourceKey == dimensionKey) continue;
writableRegistry.register(
resourceKey,
entry.getValue(),
inputDimensions.lifecycle(entry.getValue())
);
}
return writableRegistry;
}
public static void migrateGeneratorSettings() {
final CompoundTag settingsNbt = WorldGenUtil.getPresetsNbt();
if (settingsNbt.size() == 0) {
CompoundTag oldGen = WorldGenUtil.getGeneratorNbt();
if (oldGen != null) {
if (oldGen.contains("type")) {
BCLib.LOGGER.info("Found World with beta generator Settings.");
if ("bclib:bcl_world_preset_settings".equals(oldGen.getString("type"))) {
int netherVersion = 18;
int endVersion = 18;
if (oldGen.contains("minecraft:the_nether"))
netherVersion = oldGen.getInt("minecraft:the_nether");
if (oldGen.contains("minecraft:the_end"))
endVersion = oldGen.getInt("minecraft:the_end");
if (netherVersion == 18) netherVersion = 0;
else if (netherVersion == 17) netherVersion = 1;
else netherVersion = 2;
if (endVersion == 18) endVersion = 0;
else if (endVersion == 17) endVersion = 1;
else endVersion = 2;
var presets = List.of(
TogetherWorldPreset.getDimensionsMap(PresetsRegistry.BCL_WORLD),
TogetherWorldPreset.getDimensionsMap(PresetsRegistry.BCL_WORLD_17),
TogetherWorldPreset.getDimensionsMap(WorldPresets.NORMAL)
);
Map<ResourceKey<LevelStem>, ChunkGenerator> dimensions = new HashMap<>();
dimensions.put(LevelStem.OVERWORLD, presets.get(0).get(LevelStem.OVERWORLD));
dimensions.put(LevelStem.NETHER, presets.get(netherVersion).get(LevelStem.NETHER));
dimensions.put(LevelStem.END, presets.get(endVersion).get(LevelStem.END));
TogetherWorldPreset.writeWorldPresetSettingsDirect(dimensions);
}
return;
}
}
BCLib.LOGGER.info("Found World without generator Settings. Setting up data...");
ResourceKey<WorldPreset> biomeSourceVersion = PresetsRegistry.BCL_WORLD;
final CompoundTag bclRoot = WorldConfig.getRootTag(BCLib.MOD_ID);
String bclVersion = "0.0.0";
if (bclRoot.contains(TAG_VERSION)) {
bclVersion = bclRoot.getString(TAG_VERSION);
}
boolean isPre18 = !ModUtil.isLargerOrEqualVersion(bclVersion, "1.0.0");
if (isPre18) {
BCLib.LOGGER.info("World was create pre 1.18!");
biomeSourceVersion = PresetsRegistry.BCL_WORLD_17;
}
if (WorldConfig.hasMod("betternether")) {
BCLib.LOGGER.info("Found Data from BetterNether, using for migration.");
final CompoundTag bnRoot = WorldConfig.getRootTag("betternether");
biomeSourceVersion = "1.17".equals(bnRoot.getString(TAG_BN_GEN_VERSION))
? PresetsRegistry.BCL_WORLD_17
: PresetsRegistry.BCL_WORLD;
}
Registry<LevelStem> dimensions = TogetherWorldPreset.getDimensions(biomeSourceVersion);
if (dimensions != null) {
BCLib.LOGGER.info("Set world to BiomeSource Version " + biomeSourceVersion);
TogetherWorldPreset.writeWorldPresetSettings(dimensions);
} else {
BCLib.LOGGER.error("Failed to set world to BiomeSource Version " + biomeSourceVersion);
}
}
}
}

View file

@ -0,0 +1,363 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
import org.betterx.bclib.util.WeightedList;
import net.minecraft.core.Registry;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
public class BCLBiome extends BCLBiomeSettings {
private final Set<TagKey<Biome>> biomeTags = Sets.newHashSet();
private final WeightedList<BCLBiome> subbiomes = new WeightedList<>();
private final Map<String, Object> customData = Maps.newHashMap();
private final ResourceLocation biomeID;
private final ResourceKey<Biome> biomeKey;
final Biome biomeToRegister;
private final List<Climate.ParameterPoint> parameterPoints = Lists.newArrayList();
private BCLBiome biomeParent;
/**
* Create wrapper for existing biome using its {@link ResourceLocation} identifier.
*
* @param biomeKey {@link ResourceKey} for the {@link Biome}.
*/
protected BCLBiome(ResourceKey<Biome> biomeKey) {
this(biomeKey.location());
}
/**
* Create wrapper for existing biome using its {@link ResourceLocation} identifier.
*
* @param biomeID {@link ResourceLocation} biome ID.
*/
protected BCLBiome(ResourceLocation biomeID) {
this(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID), null);
}
/**
* Create wrapper for existing biome using biome instance from {@link BuiltinRegistries}.
*
* @param biomeToRegister {@link Biome} to wrap.
*/
@Deprecated(forRemoval = true)
protected BCLBiome(Biome biomeToRegister) {
this(biomeToRegister, null);
}
/**
* Create wrapper for existing biome using biome instance from {@link BuiltinRegistries}.
*
* @param biomeToRegister {@link Biome} to wrap.
* @param settings The Settings for this Biome or {@code null} if you want to apply default settings
*/
@Deprecated(forRemoval = true)
protected BCLBiome(Biome biomeToRegister, VanillaBiomeSettings settings) {
this(BiomeAPI.getBiomeID(biomeToRegister), biomeToRegister, settings);
}
/**
* Create wrapper for existing biome using biome instance from {@link BuiltinRegistries}.
*
* @param biomeToRegister {@link Biome} to wrap.
* @param biomeID Teh ResoureLocation for this Biome
*/
@Deprecated(forRemoval = true)
public BCLBiome(ResourceLocation biomeID, Biome biomeToRegister) {
this(biomeID, biomeToRegister, null);
}
/**
* Create a new Biome
*
* @param biomeID {@link ResourceLocation} biome ID.
* @param biomeToRegister {@link Biome} to wrap.
* @param defaults The Settings for this Biome or null if you want to apply the defaults
*/
protected BCLBiome(ResourceLocation biomeID, Biome biomeToRegister, BCLBiomeSettings defaults) {
this(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID), biomeToRegister, defaults);
}
/**
* Create a new Biome
*
* @param biomeKey {@link ResourceKey<Biome>} of the wrapped Biome
* @param defaults The Settings for this Biome or null if you want to apply the defaults
*/
protected BCLBiome(ResourceKey<Biome> biomeKey, BCLBiomeSettings defaults) {
this(biomeKey, null, defaults);
}
/**
* Create a new Biome
*
* @param biomeKey {@link ResourceKey<Biome>} of the wrapped Biome
* @param biomeToRegister The biome you want to use when this instance gets registered through the {@link BiomeAPI}
* @param defaults The Settings for this Biome or null if you want to apply the defaults
*/
protected BCLBiome(ResourceKey<Biome> biomeKey, Biome biomeToRegister, BCLBiomeSettings defaults) {
this.biomeToRegister = biomeToRegister;
this.subbiomes.add(this, 1.0F);
this.biomeID = biomeKey.location();
this.biomeKey = biomeKey;
if (defaults != null) {
defaults.applyWithDefaults(this);
}
}
/**
* Get current biome edge.
*
* @return {@link BCLBiome} edge.
*/
@Nullable
public BCLBiome getEdge() {
return edge;
}
/**
* Set biome edge for this biome instance.
*
* @param edge {@link BCLBiome} as the edge biome.
* @return same {@link BCLBiome}.
*/
BCLBiome setEdge(BCLBiome edge) {
this.edge = edge;
edge.biomeParent = this;
return this;
}
/**
* Set biome edge for this biome instance. If there is already an edge, the
* biome is added as subBiome to the current edge-biome
*
* @param edge The new edge
* @return same {@link BCLBiome}.
*/
public BCLBiome addEdge(BCLBiome edge) {
if (this.edge != null) {
this.edge.addSubBiome(edge);
} else {
this.setEdge(edge);
}
return this;
}
/**
* Adds sub-biome into this biome instance. Biome chance will be interpreted as a sub-biome generation chance.
* Biome itself has chance 1.0 compared to all its sub-biomes.
*
* @param biome {@link Random} to be added.
* @return same {@link BCLBiome}.
*/
public BCLBiome addSubBiome(BCLBiome biome) {
biome.biomeParent = this;
subbiomes.add(biome, biome.getGenChance());
return this;
}
/**
* Checks if specified biome is a sub-biome of this one.
*
* @param biome {@link Random}.
* @return true if this instance contains specified biome as a sub-biome.
*/
public boolean containsSubBiome(BCLBiome biome) {
return subbiomes.contains(biome);
}
/**
* Getter for a random sub-biome from all existing sub-biomes. Will return biome itself if there are no sub-biomes.
*
* @param random {@link Random}.
* @return {@link BCLBiome}.
*/
public BCLBiome getSubBiome(WorldgenRandom random) {
return subbiomes.get(random);
}
public void forEachSubBiome(BiConsumer<BCLBiome, Float> consumer) {
for (int i = 0; i < subbiomes.size(); i++)
consumer.accept(subbiomes.get(i), subbiomes.getWeight(i));
}
/**
* Getter for parent {@link BCLBiome} or null if there are no parent biome.
*
* @return {@link BCLBiome} or null.
*/
@Nullable
public BCLBiome getParentBiome() {
return this.biomeParent;
}
/**
* Compares biome instances (directly) and their parents. Used in custom world generator.
*
* @param biome {@link BCLBiome}
* @return true if biome or its parent is same.
*/
public boolean isSame(BCLBiome biome) {
return biome == this || (biome.biomeParent != null && biome.biomeParent == this);
}
/**
* Getter for biome identifier.
*
* @return {@link ResourceLocation}
*/
public ResourceLocation getID() {
return biomeID;
}
/**
* Getter for biome from buil-in registry. For datapack biomes will be same as actual biome.
*
* @return {@link Biome}.
*/
@Deprecated(forRemoval = true)
public Biome getBiome() {
if (biomeToRegister != null) return biomeToRegister;
return BiomeAPI.getFromBuiltinRegistry(biomeKey).value();
}
/**
* Getter for biomeKey
*
* @return {@link ResourceKey<Biome>}.
*/
public ResourceKey<Biome> getBiomeKey() {
return biomeKey;
}
/**
* For internal use from BiomeAPI only
*/
void afterRegistration() {
}
/**
* Getter for custom data. Will get custom data object or null if object doesn't exists.
*
* @param name {@link String} name of data object.
* @return object value or null.
*/
@Nullable
@SuppressWarnings("unchecked")
@Deprecated(forRemoval = true)
public <T> T getCustomData(String name) {
return (T) customData.get(name);
}
/**
* Getter for custom data. Will get custom data object or default value if object doesn't exists.
*
* @param name {@link String} name of data object.
* @param defaultValue object default value.
* @return object value or default value.
*/
@SuppressWarnings("unchecked")
@Deprecated(forRemoval = true)
public <T> T getCustomData(String name, T defaultValue) {
return (T) customData.getOrDefault(name, defaultValue);
}
/**
* Adds custom data object to this biome instance.
*
* @param name {@link String} name of data object.
* @param obj any data to add.
* @return same {@link BCLBiome}.
*/
public BCLBiome addCustomData(String name, Object obj) {
customData.put(name, obj);
return this;
}
/**
* Adds custom data object to this biome instance.
*
* @param data a {@link Map} with custom data.
* @return same {@link BCLBiome}.
*/
public BCLBiome addCustomData(Map<String, Object> data) {
customData.putAll(data);
return this;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
BCLBiome biome = (BCLBiome) obj;
return biome != null && biomeID.equals(biome.biomeID);
}
@Override
public int hashCode() {
return biomeID.hashCode();
}
@Override
public String toString() {
return biomeID.toString();
}
/**
* Adds structures to this biome. For internal use only.
* Used inside {@link BCLBiomeBuilder}.
*/
void addClimateParameters(List<Climate.ParameterPoint> params) {
this.parameterPoints.addAll(params);
}
public void forEachClimateParameter(Consumer<Climate.ParameterPoint> consumer) {
this.parameterPoints.forEach(consumer);
}
/**
* Returns the group used in the config Files for this biome
* <p>
* Example: {@code Configs.BIOMES_CONFIG.getFloat(configGroup(), "generation_chance", 1.0);}
*
* @return The group name
*/
public String configGroup() {
return biomeID.getNamespace() + "." + biomeID.getPath();
}
private final boolean didLoadConfig = false;
public boolean isEdgeBiome() {
if (getParentBiome() == null) return false;
return getParentBiome().edge == this;
}
boolean allowFabricRegistration() {
return !isEdgeBiome();
}
}

View file

@ -0,0 +1,858 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
import org.betterx.bclib.api.v2.levelgen.features.BCLFeature;
import org.betterx.bclib.api.v2.levelgen.structures.BCLStructure;
import org.betterx.bclib.api.v2.levelgen.surface.SurfaceRuleBuilder;
import org.betterx.bclib.entity.BCLEntityWrapper;
import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor;
import org.betterx.bclib.util.CollectionsUtil;
import org.betterx.bclib.util.ColorUtil;
import org.betterx.bclib.util.Pair;
import org.betterx.bclib.util.TriFunction;
import org.betterx.worlds.together.surfaceRules.SurfaceRuleRegistry;
import org.betterx.worlds.together.tag.v3.TagManager;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.data.worldgen.BiomeDefaultFeatures;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.Music;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.level.biome.*;
import net.minecraft.world.level.biome.Biome.BiomeBuilder;
import net.minecraft.world.level.biome.Biome.Precipitation;
import net.minecraft.world.level.biome.MobSpawnSettings.SpawnerData;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.Noises;
import net.minecraft.world.level.levelgen.SurfaceRules;
import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
public class BCLBiomeBuilder {
@FunctionalInterface
public interface BiomeSupplier<T> extends TriFunction<ResourceLocation, Biome, BCLBiomeSettings, T> {
}
private static final BCLBiomeBuilder INSTANCE = new BCLBiomeBuilder();
private static final SurfaceRules.ConditionSource SURFACE_NOISE = SurfaceRules.noiseCondition(
Noises.SOUL_SAND_LAYER,
-0.012
);
private final List<Pair<GenerationStep.Carving, Holder<? extends ConfiguredWorldCarver<?>>>> carvers = new ArrayList<>(
1);
private BiomeGenerationSettings.Builder generationSettings;
private BiomeSpecialEffects.Builder effectsBuilder;
private MobSpawnSettings.Builder spawnSettings;
private SurfaceRules.RuleSource surfaceRule;
private Precipitation precipitation;
private ResourceLocation biomeID;
private final Set<TagKey<Biome>> tags = Sets.newHashSet();
private final List<Climate.ParameterPoint> parameters = Lists.newArrayList();
private float temperature;
private float fogDensity;
private float genChance;
private float downfall;
private float height;
private int edgeSize;
private BCLBiome edge;
private boolean vertical;
private BiomeAPI.BiomeType biomeType;
/**
* Starts new biome building process.
*
* @param biomeID {@link ResourceLocation} biome identifier.
* @return prepared {@link BCLBiomeBuilder} instance.
*/
public static BCLBiomeBuilder start(ResourceLocation biomeID) {
INSTANCE.biomeID = biomeID;
INSTANCE.precipitation = Precipitation.NONE;
INSTANCE.generationSettings = null;
INSTANCE.effectsBuilder = null;
INSTANCE.spawnSettings = null;
INSTANCE.temperature = 1.0F;
INSTANCE.fogDensity = 1.0F;
INSTANCE.edgeSize = 0;
INSTANCE.downfall = 1.0F;
INSTANCE.genChance = 1.0F;
INSTANCE.height = 0.1F;
INSTANCE.vertical = false;
INSTANCE.edge = null;
INSTANCE.carvers.clear();
INSTANCE.parameters.clear();
INSTANCE.tags.clear();
INSTANCE.biomeType = null;
return INSTANCE;
}
public BCLBiomeBuilder addNetherClimateParamater(float temperature, float humidity) {
parameters.add(Climate.parameters(temperature, humidity, 0, 0, 0, 0, 0));
return this;
}
/**
* Set the type for this Biome. If the type was set, the Biome can be registered.
*
* @param type selected Type
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder type(BiomeAPI.BiomeType type) {
this.biomeType = type;
return this;
}
/**
* Set biome {@link Precipitation}. Affect biome visual effects (rain, snow, none).
*
* @param precipitation {@link Precipitation}
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder precipitation(Precipitation precipitation) {
this.precipitation = precipitation;
return this;
}
/**
* Set biome temperature, affect plant color, biome generation and ice formation.
*
* @param temperature biome temperature.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder temperature(float temperature) {
this.temperature = temperature;
return this;
}
/**
* Set biome wetness (same as downfall). Affect plant color and biome generation.
*
* @param wetness biome wetness (downfall).
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder wetness(float wetness) {
this.downfall = wetness;
return this;
}
/**
* Adds mob spawning to biome.
*
* @param entityType {@link EntityType} mob type.
* @param weight spawn weight.
* @param minGroupCount minimum mobs in group.
* @param maxGroupCount maximum mobs in group.
* @return same {@link BCLBiomeBuilder} instance.
*/
public <M extends Mob> BCLBiomeBuilder spawn(
EntityType<M> entityType,
int weight,
int minGroupCount,
int maxGroupCount
) {
getSpawns().addSpawn(
entityType.getCategory(),
new SpawnerData(entityType, weight, minGroupCount, maxGroupCount)
);
return this;
}
/**
* Adds mob spawning to biome.
*
* @param wrapper {@link BCLEntityWrapper} mob type.
* @param weight spawn weight.
* @param minGroupCount minimum mobs in group.
* @param maxGroupCount maximum mobs in group.
* @return same {@link BCLBiomeBuilder} instance.
*/
public <M extends Mob> BCLBiomeBuilder spawn(
BCLEntityWrapper<M> wrapper,
int weight,
int minGroupCount,
int maxGroupCount
) {
if (wrapper.canSpawn()) {
return spawn(wrapper.type(), weight, minGroupCount, maxGroupCount);
}
return this;
}
/**
* Adds ambient particles to thr biome.
*
* @param particle {@link ParticleOptions} particles (or {@link net.minecraft.core.particles.ParticleType}).
* @param probability particle spawn probability, should have low value (example: 0.01F).
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder particles(ParticleOptions particle, float probability) {
getEffects().ambientParticle(new AmbientParticleSettings(particle, probability));
return this;
}
/**
* Sets sky color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder skyColor(int color) {
getEffects().skyColor(color);
return this;
}
/**
* Sets sky color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder skyColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return skyColor(ColorUtil.color(red, green, blue));
}
/**
* Sets fog color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder fogColor(int color) {
getEffects().fogColor(color);
return this;
}
/**
* Sets fog color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder fogColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return fogColor(ColorUtil.color(red, green, blue));
}
/**
* Sets fog density for the biome.
*
* @param density fog density as a float, default value is 1.0F.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder fogDensity(float density) {
this.fogDensity = density;
return this;
}
/**
* Sets generation chance for this biome.
*
* @param genChance
* @return same {@link BCLBiomeBuilder}.
*/
public BCLBiomeBuilder genChance(float genChance) {
this.genChance = genChance;
return this;
}
/**
* Sets edge size for this biome.
*
* @param edgeSize size of the Edge (in Blocks)
* @return same {@link BCLBiomeBuilder}.
*/
public BCLBiomeBuilder edgeSize(int edgeSize) {
this.edgeSize = edgeSize;
return this;
}
/**
* Sets edge-Biome for this biome.
*
* @param edge The Edge Biome
* @return same {@link BCLBiomeBuilder}.
*/
public BCLBiomeBuilder edge(BCLBiome edge) {
this.edge = edge;
return this;
}
/**
* Sets edge-Biome for this biome.
*
* @param edge The Edge Biome
* @param edgeSize size of the Edge (in Blocks)
* @return same {@link BCLBiomeBuilder}.
*/
public BCLBiomeBuilder edge(BCLBiome edge, int edgeSize) {
this.edge(edge);
this.edgeSize(edgeSize);
return this;
}
/**
* Sets water color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterColor(int color) {
getEffects().waterColor(color);
return this;
}
/**
* Sets water color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return waterColor(ColorUtil.color(red, green, blue));
}
/**
* Sets underwater fog color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterFogColor(int color) {
getEffects().waterFogColor(color);
return this;
}
/**
* Sets underwater fog color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterFogColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return waterFogColor(ColorUtil.color(red, green, blue));
}
/**
* Sets water and underwater fig color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterAndFogColor(int color) {
return waterColor(color).waterFogColor(color);
}
/**
* Sets water and underwater fig color for the biome. Color is in ARGB int format.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterAndFogColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return waterAndFogColor(ColorUtil.color(red, green, blue));
}
/**
* Sets grass color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder grassColor(int color) {
getEffects().grassColorOverride(color);
return this;
}
/**
* Sets grass color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder grassColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return grassColor(ColorUtil.color(red, green, blue));
}
/**
* Sets leaves and plants color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder foliageColor(int color) {
getEffects().foliageColorOverride(color);
return this;
}
/**
* Sets leaves and plants color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder foliageColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return foliageColor(ColorUtil.color(red, green, blue));
}
/**
* Sets grass, leaves and all plants color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder plantsColor(int color) {
return grassColor(color).foliageColor(color);
}
/**
* Sets grass, leaves and all plants color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder plantsColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return plantsColor(ColorUtil.color(red, green, blue));
}
/**
* Sets biome music, used for biomes in the Nether and End.
*
* @param music {@link Music} to use.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder music(Music music) {
getEffects().backgroundMusic(music);
return this;
}
/**
* Sets biome music, used for biomes in the Nether and End.
*
* @param music {@link SoundEvent} to use.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder music(SoundEvent music) {
return music(new Music(music, 600, 2400, true));
}
/**
* Sets biome ambient loop sound. Can be used for biome environment.
*
* @param loopSound {@link SoundEvent} to use as a loop.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder loop(SoundEvent loopSound) {
getEffects().ambientLoopSound(loopSound);
return this;
}
/**
* Sets biome mood sound. Can be used for biome environment.
*
* @param mood {@link SoundEvent} to use as a mood.
* @param tickDelay delay between sound events in ticks.
* @param blockSearchExtent block search radius (for area available for sound).
* @param soundPositionOffset offset in sound.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder mood(SoundEvent mood, int tickDelay, int blockSearchExtent, float soundPositionOffset) {
getEffects().ambientMoodSound(new AmbientMoodSettings(mood, tickDelay, blockSearchExtent, soundPositionOffset));
return this;
}
/**
* Sets biome mood sound. Can be used for biome environment.
*
* @param mood {@link SoundEvent} to use as a mood.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder mood(SoundEvent mood) {
return mood(mood, 6000, 8, 2.0F);
}
/**
* Sets biome additionsl ambient sounds.
*
* @param additions {@link SoundEvent} to use.
* @param intensity sound intensity. Default is 0.0111F.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder additions(SoundEvent additions, float intensity) {
getEffects().ambientAdditionsSound(new AmbientAdditionsSettings(additions, intensity));
return this;
}
/**
* Sets biome additionsl ambient sounds.
*
* @param additions {@link SoundEvent} to use.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder additions(SoundEvent additions) {
return additions(additions, 0.0111F);
}
/**
* Adds new feature to the biome.
*
* @param decoration {@link Decoration} feature step.
* @param feature {@link PlacedFeature}.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder feature(Decoration decoration, Holder<PlacedFeature> feature) {
getGeneration().addFeature(decoration, feature);
return this;
}
/**
* Adds vanilla Mushrooms.
*
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder defaultMushrooms() {
return feature(BiomeDefaultFeatures::addDefaultMushrooms);
}
/**
* Adds vanilla Nether Ores.
*
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder netherDefaultOres() {
return feature(BiomeDefaultFeatures::addNetherDefaultOres);
}
/**
* Will add features into biome, used for vanilla feature adding functions.
*
* @param featureAdd {@link Consumer} with {@link BiomeGenerationSettings.Builder}.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder feature(Consumer<BiomeGenerationSettings.Builder> featureAdd) {
featureAdd.accept(getGeneration());
return this;
}
/**
* Adds new feature to the biome.
*
* @param feature {@link BCLFeature}.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder feature(BCLFeature feature) {
return feature(feature.getDecoration(), feature.getPlacedFeature());
}
/**
* Adds new feature to the biome.
*
* @param feature {@link BCLFeature}.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder feature(org.betterx.bclib.api.v3.levelgen.features.BCLFeature feature) {
return feature(feature.decoration, feature.placedFeature);
}
/**
* Adds new structure feature into the biome.
*
* @param structureTag {@link TagKey} to add.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder structure(TagKey<Biome> structureTag) {
tags.add(structureTag);
return this;
}
/**
* Adds new structure feature into thr biome. Will add building biome into the structure list.
*
* @param structure {@link BCLStructure} to add.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder structure(BCLStructure structure) {
structure.addInternalBiome(biomeID);
return structure(structure.biomeTag);
}
/**
* Adds new world carver into the biome.
*
* @param carver {@link ConfiguredWorldCarver} to add.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder carver(GenerationStep.Carving step, Holder<? extends ConfiguredWorldCarver<?>> carver) {
final ResourceLocation immutableID = biomeID;
var oKey = carver.unwrapKey();
if (oKey.isPresent()) {
BiomeModifications.addCarver(
ctx -> ctx.getBiomeKey().location().equals(immutableID),
step,
(ResourceKey<ConfiguredWorldCarver<?>>) oKey.get()
);
}
//carvers.add(new Pair<>(step, carver));
return this;
}
/**
* Adds new world surface rule for the given block
*
* @param surfaceBlock {@link Block} to use.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder surface(Block surfaceBlock) {
return surface(surfaceBlock.defaultBlockState());
}
/**
* Adds new world surface rule for the given block
*
* @param surfaceBlock {@link BlockState} to use.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder surface(BlockState surfaceBlock) {
return surface(SurfaceRuleBuilder.start().surface(surfaceBlock).build());
}
/**
* Adds blocks to the biome surface and below it (with specified depth).
*
* @param surfaceBlock {@link Block} that will cover biome.
* @param subterrainBlock {@link Block} below it with specified depth.
* @param depth thickness of bottom block layer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder surface(Block surfaceBlock, Block subterrainBlock, int depth) {
return surface(SurfaceRuleBuilder
.start()
.surface(surfaceBlock.defaultBlockState())
.subsurface(subterrainBlock.defaultBlockState(), depth)
.build());
}
/**
* Adds surface rule to this biome.
*
* @param newSurfaceRule {link SurfaceRules.RuleSource} surface rule.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder surface(SurfaceRules.RuleSource newSurfaceRule) {
this.surfaceRule = newSurfaceRule;
return this;
}
public BCLBiomeBuilder tag(TagKey<Biome>... tag) {
for (TagKey<Biome> t : tag) {
tags.add(t);
}
return this;
}
/**
* Set terrain height for the biome. Can be used in custom generators, doesn't change vanilla biome distribution or generation.
*
* @param height a relative float terrain height value.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder terrainHeight(float height) {
this.height = height;
return this;
}
/**
* Make this a vertical Biome
*
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder vertical() {
this.vertical = vertical;
return this;
}
private static BiomeGenerationSettings fixGenerationSettings(BiomeGenerationSettings settings) {
//Fabric Biome Modification API can not handle an empty carver map, thus we will create one with
//an empty HolderSet for every possible step:
//https://github.com/FabricMC/fabric/issues/2079
//TODO: Remove, once fabric gets fixed
if (settings instanceof BiomeGenerationSettingsAccessor acc) {
Map<GenerationStep.Carving, HolderSet<ConfiguredWorldCarver<?>>> carvers = CollectionsUtil.getMutable(acc.bclib_getCarvers());
for (GenerationStep.Carving step : GenerationStep.Carving.values()) {
carvers.computeIfAbsent(step, __ -> HolderSet.direct(Lists.newArrayList()));
}
acc.bclib_setCarvers(carvers);
}
return settings;
}
/**
* Get or create {@link BiomeSpecialEffects.Builder} for biome visual effects.
* For internal usage only.
* For internal usage only.
*
* @return new or same {@link BiomeSpecialEffects.Builder} instance.
*/
private BiomeSpecialEffects.Builder getEffects() {
if (effectsBuilder == null) {
effectsBuilder = new BiomeSpecialEffects.Builder();
}
return effectsBuilder;
}
/**
* Get or create {@link MobSpawnSettings.Builder} for biome mob spawning.
* For internal usage only.
*
* @return new or same {@link MobSpawnSettings.Builder} instance.
*/
private MobSpawnSettings.Builder getSpawns() {
if (spawnSettings == null) {
spawnSettings = new MobSpawnSettings.Builder();
}
return spawnSettings;
}
/**
* Get or create {@link BiomeGenerationSettings.Builder} for biome features and generation.
* For internal usage only.
*
* @return new or same {@link BiomeGenerationSettings.Builder} instance.
*/
private BiomeGenerationSettings.Builder getGeneration() {
if (generationSettings == null) {
generationSettings = new BiomeGenerationSettings.Builder();
}
return generationSettings;
}
/**
* Finalize biome creation.
*
* @return created {@link BCLBiome} instance.
*/
public BCLBiome build() {
return build((BiomeSupplier<BCLBiome>) BCLBiome::new);
}
/**
* Finalize biome creation.
*
* @param biomeConstructor {@link BiFunction} biome constructor.
* @return created {@link BCLBiome} instance.
* @deprecated Replaced with {@link #build(BiomeSupplier)}
*/
@Deprecated(forRemoval = true)
public <T extends BCLBiome> T build(BiFunction<ResourceLocation, Biome, T> biomeConstructor) {
return build((id, biome, settings) -> biomeConstructor.apply(id, biome));
}
/**
* Finalize biome creation.
*
* @param biomeConstructor {@link BiomeSupplier} biome constructor.
* @return created {@link BCLBiome} instance.
*/
public <T extends BCLBiome> T build(BiomeSupplier<T> biomeConstructor) {
BiomeBuilder builder = new BiomeBuilder()
.precipitation(precipitation)
.temperature(temperature)
.downfall(downfall);
builder.mobSpawnSettings(getSpawns().build());
builder.specialEffects(getEffects().build());
builder.generationSettings(fixGenerationSettings(getGeneration().build()));
BCLBiomeSettings settings = BCLBiomeSettings.createBCL()
.setTerrainHeight(height)
.setFogDensity(fogDensity)
.setGenChance(genChance)
.setEdgeSize(edgeSize)
.setEdge(edge)
.setVertical(vertical)
.build();
final Biome biome = builder.build();
final T res = biomeConstructor.apply(biomeID, biome, settings);
tags.forEach(tagKey -> TagManager.BIOMES.add(tagKey, res.getBiomeKey()));
//res.addBiomeTags(tags);
//res.setSurface(surfaceRule);
SurfaceRuleRegistry.registerRule(biomeID, surfaceRule, biomeID);
res.addClimateParameters(parameters);
//carvers.forEach(cfg -> BiomeAPI.addBiomeCarver(biome, cfg.second, cfg.first));
return res;
}
}

View file

@ -0,0 +1,202 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.config.Configs;
import net.minecraft.world.level.biome.Biome;
public class BCLBiomeSettings {
public static Builder createBCL() {
return new Builder();
}
public static class Builder extends CommonBuilder<BCLBiomeSettings, Builder> {
public Builder() {
super(new BCLBiomeSettings());
}
}
public static class CommonBuilder<T extends BCLBiomeSettings, R extends CommonBuilder> {
private final T storage;
CommonBuilder(T storage) {
this.storage = storage;
}
public T build() {
return storage;
}
/**
* Set gen chance for this biome, default value is 1.0.
*
* @param genChance chance of this biome to be generated.
* @return same {@link BCLBiomeSettings}.
*/
public R setGenChance(float genChance) {
storage.genChance = genChance;
return (R) this;
}
/**
* Setter for terrain height, can be used in custom terrain generator.
*
* @param terrainHeight a relative float terrain height value.
* @return same {@link Builder}.
*/
public R setTerrainHeight(float terrainHeight) {
storage.terrainHeight = terrainHeight;
return (R) this;
}
/**
* Set biome vertical distribution (for tall Nether only).
*
* @return same {@link Builder}.
*/
public R setVertical() {
return setVertical(true);
}
/**
* Set biome vertical distribution (for tall Nether only).
*
* @param vertical {@code boolean} value.
* @return same {@link Builder}.
*/
public R setVertical(boolean vertical) {
storage.vertical = vertical;
return (R) this;
}
/**
* Set edges size for this biome. Size is in blocks.
*
* @param size as a float value.
* @return same {@link Builder}.
*/
public R setEdgeSize(int size) {
storage.edgeSize = size;
return (R) this;
}
/**
* Set edges:biome for this biome.
*
* @param edge The {@link Biome}.
* @return same {@link Builder}.
*/
public R setEdge(BCLBiome edge) {
storage.edge = edge;
return (R) this;
}
/**
* Sets fog density for this biome.
*
* @param fogDensity
* @return same {@link Builder}.
*/
public R setFogDensity(float fogDensity) {
storage.fogDensity = fogDensity;
return (R) this;
}
}
protected BCLBiomeSettings() {
this.terrainHeight = 0.1F;
this.fogDensity = 1.0F;
this.genChance = 1.0F;
this.edgeSize = 0;
this.vertical = false;
this.edge = null;
}
float terrainHeight;
float fogDensity;
float genChance;
int edgeSize;
boolean vertical;
BCLBiome edge;
/**
* Getter for biome generation chance, used in {@link BiomePicker} and in custom generators.
*
* @return biome generation chance as float.
*/
public float getGenChance() {
return this.genChance;
}
/**
* Checks if biome is vertical, for tall Nether only (or for custom generators).
*
* @return is biome vertical or not.
*/
public boolean isVertical() {
return vertical;
}
/**
* Getter for terrain height, can be used in custom terrain generator.
*
* @return terrain height.
*/
public float getTerrainHeight() {
return terrainHeight;
}
/**
* Getter for fog density, used in custom for renderer.
*
* @return fog density as a float.
*/
public float getFogDensity() {
return fogDensity;
}
/**
* Getter for biome edge size.
*
* @return edge size in blocks.
*/
public int getEdgeSize() {
return edgeSize;
}
/**
* Getter for edge-biome.
*
* @return The assigned edge biome.
*/
public BCLBiome getEdge() {
return edge;
}
/**
* Load values from Config and apply to the passed Biome. The Default values for the loaded settings
* are derifed from the values store in this object
*
* @param biome {@link BCLBiome} to assign values to
*/
public void applyWithDefaults(BCLBiome biome) {
final String group = biome.configGroup();
biome.genChance = Configs.BIOMES_CONFIG.getFloat(group, "generation_chance", this.genChance);
if (edge != null) {
biome.edgeSize = Configs.BIOMES_CONFIG.getInt(group, "edge_size", this.edgeSize);
if (edgeSize > 0) {
biome.setEdge(edge);
}
}
if (!(this instanceof VanillaBiomeSettings)) {
biome.fogDensity = Configs.BIOMES_CONFIG.getFloat(group, "fog_density", this.fogDensity);
biome.vertical = Configs.BIOMES_CONFIG.getBoolean(group, "vertical", this.vertical);
biome.terrainHeight = Configs.BIOMES_CONFIG.getFloat(group, "terrain_height", this.terrainHeight);
}
Configs.BIOMES_CONFIG.saveChanges();
}
}

View file

@ -0,0 +1,965 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.levelgen.features.BCLFeature;
import org.betterx.bclib.interfaces.SurfaceMaterialProvider;
import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor;
import org.betterx.bclib.mixin.common.MobSpawnSettingsAccessor;
import org.betterx.bclib.util.CollectionsUtil;
import org.betterx.worlds.together.tag.v3.CommonBiomeTags;
import org.betterx.worlds.together.tag.v3.TagManager;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Registry;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BiomeTags;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.biome.MobSpawnSettings.SpawnerData;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.PalettedContainerRO;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.biome.v1.NetherBiomes;
import net.fabricmc.fabric.api.biome.v1.TheEndBiomes;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
public class BiomeAPI {
public static class BiomeType {
public static final BiomeType NONE = new BiomeType("NONE");
public static final BiomeType OVERWORLD = new BiomeType("OVERWORLD");
public static final BiomeType NETHER = new BiomeType("NETHER");
public static final BiomeType BCL_NETHER = new BiomeType("BCL_NETHER", NETHER);
public static final BiomeType END = new BiomeType("END");
public static final BiomeType END_IGNORE = new BiomeType("END_IGNORE", END);
public static final BiomeType END_LAND = new BiomeType("END_LAND", END);
public static final BiomeType END_VOID = new BiomeType("END_VOID", END);
public static final BiomeType END_CENTER = new BiomeType("END_CENTER", END);
public static final BiomeType END_BARRENS = new BiomeType("END_BARRENS", END);
public static final BiomeType BCL_END_LAND = new BiomeType("BCL_END_LAND", END_LAND);
public static final BiomeType BCL_END_VOID = new BiomeType("BCL_END_VOID", END_VOID);
public static final BiomeType BCL_END_CENTER = new BiomeType("BCL_END_CENTER", END_CENTER);
public static final BiomeType BCL_END_BARRENS = new BiomeType("BCL_END_BARRENS", END_BARRENS);
static final Map<ResourceLocation, BiomeType> BIOME_TYPE_MAP = Maps.newHashMap();
public final BiomeType parentOrNull;
private final String name;
public BiomeType(String name) {
this(name, null);
}
public BiomeType(String name, BiomeType parentOrNull) {
this.parentOrNull = parentOrNull;
this.name = name;
}
public boolean is(BiomeType d) {
if (d == this) return true;
if (parentOrNull != null) return parentOrNull.is(d);
return false;
}
public String getName() {
return name;
}
@Override
public String toString() {
String str = name;
if (parentOrNull != null) str += " -> " + parentOrNull;
return str;
}
}
/**
* Empty biome used as default value if requested biome doesn't exist or linked. Shouldn't be registered anywhere to prevent bugs.
* Have {@code Biomes.THE_VOID} as the reference biome.
*/
public static final BCLBiome EMPTY_BIOME = new BCLBiome(Biomes.THE_VOID.location());
private static final Map<ResourceLocation, BCLBiome> ID_MAP = Maps.newHashMap();
public static final BCLBiome NETHER_WASTES_BIOME = InternalBiomeAPI.wrapNativeBiome(
Biomes.NETHER_WASTES,
InternalBiomeAPI.OTHER_NETHER
);
public static final BCLBiome CRIMSON_FOREST_BIOME = InternalBiomeAPI.wrapNativeBiome(
Biomes.CRIMSON_FOREST,
InternalBiomeAPI.OTHER_NETHER
);
public static final BCLBiome WARPED_FOREST_BIOME = InternalBiomeAPI.wrapNativeBiome(
Biomes.WARPED_FOREST,
InternalBiomeAPI.OTHER_NETHER
);
public static final BCLBiome SOUL_SAND_VALLEY_BIOME = InternalBiomeAPI.wrapNativeBiome(
Biomes.SOUL_SAND_VALLEY,
InternalBiomeAPI.OTHER_NETHER
);
public static final BCLBiome BASALT_DELTAS_BIOME = InternalBiomeAPI.wrapNativeBiome(
Biomes.BASALT_DELTAS,
InternalBiomeAPI.OTHER_NETHER
);
public static final BCLBiome THE_END = InternalBiomeAPI.wrapNativeBiome(
Biomes.THE_END,
0.5F,
InternalBiomeAPI.OTHER_END_CENTER
);
public static final BCLBiome END_MIDLANDS = InternalBiomeAPI.wrapNativeBiome(
Biomes.END_MIDLANDS,
0.5F,
InternalBiomeAPI.OTHER_END_LAND
);
public static final BCLBiome END_HIGHLANDS = InternalBiomeAPI.wrapNativeBiome(
Biomes.END_HIGHLANDS,
END_MIDLANDS,
8,
0.5F,
InternalBiomeAPI.OTHER_END_LAND
);
public static final BCLBiome END_BARRENS = InternalBiomeAPI.wrapNativeBiome(
Biomes.END_BARRENS,
InternalBiomeAPI.OTHER_END_BARRENS
);
public static final BCLBiome SMALL_END_ISLANDS = InternalBiomeAPI.wrapNativeBiome(
Biomes.SMALL_END_ISLANDS,
InternalBiomeAPI.OTHER_END_VOID
);
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
*
* @param bclbiome {@link BCLBiome}
* @param dim The Dimension fo rthis Biome
* @return {@link BCLBiome}
*/
public static BCLBiome registerBiome(BCLBiome bclbiome, BiomeType dim) {
return registerBiome(bclbiome, dim, BuiltinRegistries.BIOME);
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
*
* @param bclbiome {@link BCLBiome}
* @param dim The Dimension fo rthis Biome
* @return {@link BCLBiome}
*/
static BCLBiome registerBiome(BCLBiome bclbiome, BiomeType dim, Registry<Biome> registryOrNull) {
if (registryOrNull != null
&& bclbiome.biomeToRegister != null
&& registryOrNull.get(bclbiome.getID()) == null) {
Registry.register(registryOrNull, bclbiome.getBiomeKey(), bclbiome.biomeToRegister);
}
ID_MAP.put(bclbiome.getID(), bclbiome);
BiomeType.BIOME_TYPE_MAP.put(bclbiome.getID(), dim);
if (dim != null && dim.is(BiomeType.NETHER)) {
TagManager.BIOMES.add(BiomeTags.IS_NETHER, bclbiome.getBiomeKey());
TagManager.BIOMES.add(CommonBiomeTags.IN_NETHER, bclbiome.getBiomeKey());
} else if (dim != null && dim.is(BiomeType.END)) {
TagManager.BIOMES.add(BiomeTags.IS_END, bclbiome.getBiomeKey());
TagManager.BIOMES.add(CommonBiomeTags.IN_END, bclbiome.getBiomeKey());
}
bclbiome.afterRegistration();
return bclbiome;
}
public static BCLBiome registerSubBiome(BCLBiome parent, BCLBiome subBiome) {
return registerSubBiome(
parent,
subBiome,
BiomeType.BIOME_TYPE_MAP.getOrDefault(parent.getID(), BiomeType.NONE)
);
}
public static BCLBiome registerSubBiome(BCLBiome parent, Biome subBiome, float genChance) {
return registerSubBiome(
parent,
subBiome,
genChance,
BiomeType.BIOME_TYPE_MAP.getOrDefault(parent.getID(), BiomeType.NONE)
);
}
public static BCLBiome registerSubBiome(BCLBiome parent, BCLBiome subBiome, BiomeType dim) {
registerBiome(subBiome, dim);
parent.addSubBiome(subBiome);
return subBiome;
}
public static BCLBiome registerSubBiome(BCLBiome parent, Biome biome, float genChance, BiomeType dim) {
BCLBiome subBiome = new BCLBiome(biome, VanillaBiomeSettings.createVanilla().setGenChance(genChance).build());
return registerSubBiome(parent, subBiome, dim);
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerEndLandBiome(BCLBiome biome) {
registerBiome(biome, BiomeType.BCL_END_LAND);
float weight = biome.getGenChance();
ResourceKey<Biome> key = biome.getBiomeKey();
if (biome.allowFabricRegistration()) {
if (biome.isEdgeBiome()) {
ResourceKey<Biome> parentKey = biome.getParentBiome().getBiomeKey();
TheEndBiomes.addMidlandsBiome(parentKey, key, weight);
} else {
TheEndBiomes.addHighlandsBiome(key, weight);
}
}
return biome;
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a void biome (will generate only in the End void - between islands).
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerEndVoidBiome(BCLBiome biome) {
registerBiome(biome, BiomeType.BCL_END_VOID);
float weight = biome.getGenChance();
ResourceKey<Biome> key = biome.getBiomeKey();
if (biome.allowFabricRegistration()) {
TheEndBiomes.addSmallIslandsBiome(key, weight);
}
return biome;
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a center island
* biome (will generate only on the center island).
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerEndCenterBiome(BCLBiome biome) {
registerBiome(biome, BiomeType.BCL_END_CENTER);
float weight = biome.getGenChance();
ResourceKey<Biome> key = biome.getBiomeKey();
if (biome.allowFabricRegistration()) {
TheEndBiomes.addMainIslandBiome(key, weight);
}
return biome;
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a barrens island
* biome (will generate on the edge of midland biomes on the larger islands).
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerEndBarrensBiome(BCLBiome highlandBiome, BCLBiome biome) {
registerBiome(biome, BiomeType.BCL_END_BARRENS);
float weight = biome.getGenChance();
ResourceKey<Biome> key = biome.getBiomeKey();
if (biome.allowFabricRegistration()) {
ResourceKey<Biome> parentKey = highlandBiome.getBiomeKey();
TheEndBiomes.addBarrensBiome(parentKey, key, weight);
}
return biome;
}
public static BCLBiome registerEndBiome(Holder<Biome> biome) {
BCLBiome bclBiome = new BCLBiome(biome.value(), null);
registerBiome(bclBiome, BiomeType.END);
return bclBiome;
}
public static BCLBiome registerCenterBiome(Holder<Biome> biome) {
BCLBiome bclBiome = new BCLBiome(biome.value(), null);
registerBiome(bclBiome, BiomeType.END_CENTER);
return bclBiome;
}
/**
* Get {@link BCLBiome} from {@link Biome} instance on server. Used to convert world biomes to BCLBiomes.
*
* @param biome - {@link Holder<Biome>} from world.
* @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
*/
@Deprecated(forRemoval = true)
public static BCLBiome getFromBiome(Holder<Biome> biome) {
if (InternalBiomeAPI.biomeRegistry == null) {
return EMPTY_BIOME;
}
return ID_MAP.getOrDefault(biome.unwrapKey().orElseThrow().location(), EMPTY_BIOME);
}
/**
* Get {@link BCLBiome} from biome on client. Used in fog rendering.
*
* @param biome - {@link Biome} from client world.
* @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
*/
@Environment(EnvType.CLIENT)
public static BCLBiome getRenderBiome(Biome biome) {
BCLBiome endBiome = InternalBiomeAPI.CLIENT.get(biome);
if (endBiome == null) {
Minecraft minecraft = Minecraft.getInstance();
ResourceLocation id = minecraft.level.registryAccess()
.registryOrThrow(Registry.BIOME_REGISTRY)
.getKey(biome);
endBiome = id == null ? EMPTY_BIOME : ID_MAP.getOrDefault(id, EMPTY_BIOME);
InternalBiomeAPI.CLIENT.put(biome, endBiome);
}
return endBiome;
}
/**
* Get biome {@link ResourceKey} from given {@link Biome}.
*
* @param biome - {@link Biome} from server world.
* @return biome {@link ResourceKey} or {@code null}.
*/
@Nullable
public static ResourceKey getBiomeKey(Biome biome) {
if (InternalBiomeAPI.biomeRegistry != null) {
Optional<ResourceKey<Biome>> key = InternalBiomeAPI.biomeRegistry.getResourceKey(biome);
if (key.isPresent()) return key.get();
}
return BuiltinRegistries.BIOME
.getResourceKey(biome)
.orElseGet(null);
}
/**
* Get biome {@link ResourceLocation} from given {@link Biome}.
*
* @param biome - {@link Biome} from server world.
* @return biome {@link ResourceLocation}.
*/
public static ResourceLocation getBiomeID(Biome biome) {
ResourceLocation id = null;
if (InternalBiomeAPI.biomeRegistry != null) {
id = InternalBiomeAPI.biomeRegistry.getKey(biome);
}
if (id == null) {
id = BuiltinRegistries.BIOME.getKey(biome);
}
if (id == null) {
BCLib.LOGGER.error("Unable to get ID for " + biome + ". Falling back to empty Biome...");
id = EMPTY_BIOME.getID();
}
return id;
}
/**
* Get biome {@link ResourceLocation} from given {@link Biome}.
*
* @param biome - {@link Holder<Biome>} from server world.
* @return biome {@link ResourceLocation}.
*/
public static ResourceLocation getBiomeID(Holder<Biome> biome) {
var oKey = biome.unwrapKey();
if (oKey.isPresent()) {
return oKey.get().location();
}
return null;
}
public static ResourceKey getBiomeKey(Holder<Biome> biome) {
return biome.unwrapKey().orElse(null);
}
public static ResourceKey getBiomeKeyOrThrow(Holder<Biome> biome) {
return biome.unwrapKey().orElseThrow();
}
public static Holder<Biome> getBiomeHolder(BCLBiome biome) {
return getBiomeHolder(biome.getBiomeKey());
}
public static Holder<Biome> getBiomeHolder(Biome biome) {
Optional<ResourceKey<Biome>> key = Optional.empty();
if (InternalBiomeAPI.biomeRegistry != null) {
key = InternalBiomeAPI.biomeRegistry.getResourceKey(biome);
} else {
key = BuiltinRegistries.BIOME.getResourceKey(biome);
}
return getBiomeHolder(key.orElseThrow());
}
public static Holder<Biome> getBiomeHolder(ResourceKey<Biome> biomeKey) {
if (InternalBiomeAPI.biomeRegistry != null) {
return InternalBiomeAPI.biomeRegistry.getOrCreateHolderOrThrow(biomeKey);
}
return BuiltinRegistries.BIOME.getOrCreateHolderOrThrow(biomeKey);
}
public static Holder<Biome> getBiomeHolder(ResourceLocation biome) {
return getBiomeHolder(ResourceKey.create(Registry.BIOME_REGISTRY, biome));
}
/**
* Get {@link BCLBiome} from given {@link ResourceLocation}.
*
* @param biomeID - biome {@link ResourceLocation}.
* @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
*/
public static BCLBiome getBiome(ResourceLocation biomeID) {
return ID_MAP.getOrDefault(biomeID, EMPTY_BIOME);
}
/**
* Get {@link BCLBiome} from given {@link Biome}.
*
* @param biome - biome {@link Biome}.
* @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
*/
public static BCLBiome getBiome(Biome biome) {
return getBiome(BiomeAPI.getBiomeID(biome));
}
/**
* Get {@link BCLBiome} from given {@link Biome}.
*
* @param biome - biome {@link Biome}.
* @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
*/
public static BCLBiome getBiome(Holder<Biome> biome) {
return getBiome(BiomeAPI.getBiomeID(biome));
}
/**
* Check if biome with {@link ResourceLocation} exists in API registry.
*
* @param biomeID - biome {@link ResourceLocation}.
* @return {@code true} if biome exists in API registry and {@code false} if not.
*/
public static boolean hasBiome(ResourceLocation biomeID) {
return ID_MAP.containsKey(biomeID);
}
public static Holder<Biome> getFromRegistry(ResourceLocation biomeID) {
if (InternalBiomeAPI.biomeRegistry != null)
return InternalBiomeAPI.biomeRegistry.getHolder(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID))
.orElseThrow();
return getFromBuiltinRegistry(biomeID);
}
@Nullable
public static Holder<Biome> getFromRegistry(ResourceKey<Biome> key) {
if (InternalBiomeAPI.biomeRegistry != null)
return InternalBiomeAPI.biomeRegistry.getHolder(key).orElseThrow();
return getFromBuiltinRegistry(key);
}
@Nullable
public static Holder<Biome> getFromBuiltinRegistry(ResourceLocation biomeID) {
return BuiltinRegistries.BIOME.getHolder(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID)).orElse(null);
}
@Nullable
public static Holder<Biome> getFromBuiltinRegistry(ResourceKey<Biome> key) {
return BuiltinRegistries.BIOME.getHolder(key).orElse(null);
}
@Deprecated(forRemoval = true)
public static boolean registryContains(ResourceKey<Biome> key) {
if (InternalBiomeAPI.biomeRegistry != null)
return InternalBiomeAPI.biomeRegistry.containsKey(key);
return builtinRegistryContains(key);
}
@Nullable
@Deprecated(forRemoval = true)
public static boolean builtinRegistryContains(ResourceKey<Biome> key) {
return BuiltinRegistries.BIOME.containsKey(key);
}
public static boolean isDatapackBiome(ResourceLocation biomeID) {
return getFromBuiltinRegistry(biomeID) == null;
}
public static boolean wasRegisteredAs(ResourceLocation biomeID, BiomeType dim) {
if (BiomeType.BIOME_TYPE_MAP.containsKey(biomeID) && BiomeType.BIOME_TYPE_MAP.get(biomeID).is(dim)) return true;
BCLBiome biome = getBiome(biomeID);
if (biome != null && biome != BiomeAPI.EMPTY_BIOME && biome.getParentBiome() != null) {
return wasRegisteredAs(biome.getParentBiome().getID(), dim);
}
return false;
}
public static boolean wasRegisteredAsNetherBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.NETHER);
}
public static boolean wasRegisteredAsEndBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.END);
}
public static boolean wasRegisteredAsEndLandBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.END_LAND);
}
public static boolean wasRegisteredAsEndVoidBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.END_VOID);
}
public static boolean wasRegisteredAsEndCenterBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.END_CENTER);
}
public static boolean wasRegisteredAsEndBarrensBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.END_BARRENS);
}
/**
* Registers new biome modification for specified dimension. Will work both for mod and datapack biomes.
*
* @param dimensionID {@link ResourceLocation} dimension ID, example: Level.OVERWORLD or "minecraft:overworld".
* @param modification {@link BiConsumer} with {@link ResourceKey} biome ID and {@link Biome} parameters.
*/
public static void registerBiomeModification(
ResourceKey<LevelStem> dimensionID,
BiConsumer<ResourceLocation, Holder<Biome>> modification
) {
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = InternalBiomeAPI.MODIFICATIONS.computeIfAbsent(
dimensionID,
k -> Lists.newArrayList()
);
modifications.add(modification);
}
/**
* Registers new biome modification for the Overworld. Will work both for mod and datapack biomes.
*
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerOverworldBiomeModification(BiConsumer<ResourceLocation, Holder<Biome>> modification) {
registerBiomeModification(LevelStem.OVERWORLD, modification);
}
/**
* Registers new biome modification for the Nether. Will work both for mod and datapack biomes.
*
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerNetherBiomeModification(BiConsumer<ResourceLocation, Holder<Biome>> modification) {
registerBiomeModification(LevelStem.NETHER, modification);
}
/**
* Registers new biome modification for the End. Will work both for mod and datapack biomes.
*
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerEndBiomeModification(BiConsumer<ResourceLocation, Holder<Biome>> modification) {
registerBiomeModification(LevelStem.END, modification);
}
/**
* Registers new biome modification for specified dimension that is executed when all
* BiomeTags are finalized by the game during level load. Will work both for mod and
* datapack biomes.
*
* @param dimensionID {@link ResourceLocation} dimension ID, example: Level.OVERWORLD or "minecraft:overworld".
* @param modification {@link BiConsumer} with {@link ResourceKey} biome ID and {@link Biome} parameters.
*/
public static void onFinishingBiomeTags(
ResourceKey dimensionID,
BiConsumer<ResourceLocation, Holder<Biome>> modification
) {
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = InternalBiomeAPI.TAG_ADDERS.computeIfAbsent(
dimensionID,
k -> Lists.newArrayList()
);
modifications.add(modification);
}
/**
* Registers new biome modification for the Nether dimension that is executed when all
* BiomeTags are finalized by the game during level load. Will work both for mod and
* datapack biomes.
*
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void onFinishingNetherBiomeTags(BiConsumer<ResourceLocation, Holder<Biome>> modification) {
onFinishingBiomeTags(Level.NETHER, modification);
}
/**
* Registers new biome modification for the End that is executed when all
* BiomeTags are finalized by the game during level load. Will work both for mod and
* datapack biomes.
*
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void onFinishingEndBiomeTags(BiConsumer<ResourceLocation, Holder<Biome>> modification) {
onFinishingBiomeTags(Level.END, modification);
}
/**
* Create a unique sort order for all Features of the Biome. This method is automatically called for each Biome
* after all biome Modifications were executed.
*
* @param biome The {@link Biome} to sort the features for
*/
public static void sortBiomeFeatures(Holder<Biome> biome) {
sortBiomeFeatures(biome.value());
}
static void sortBiomeFeatures(Biome biome) {
// BiomeGenerationSettings settings = biome.getGenerationSettings();
// BiomeGenerationSettingsAccessor accessor = (BiomeGenerationSettingsAccessor) settings;
// List<HolderSet<PlacedFeature>> featureList = CollectionsUtil.getMutable(accessor.bclib_getFeatures());
// final int size = featureList.size();
// for (int i = 0; i < size; i++) {
// List<Holder<PlacedFeature>> features = getFeaturesListCopy(featureList, i);
// sortFeatures(features);
// featureList.set(i, HolderSet.direct(features));
// }
// accessor.bclib_setFeatures(featureList);
}
/**
* Adds new features to existing biome.
*
* @param biome {@link Biome} to add features in.
* @param feature {@link ConfiguredFeature} to add.
*/
public static void addBiomeFeature(Holder<Biome> biome, BCLFeature feature) {
addBiomeFeature(biome, feature.getDecoration(), feature.getPlacedFeature());
}
/**
* Adds new features to existing biome.
*
* @param biome {@link Biome} to add features in.
* @param feature {@link ConfiguredFeature} to add.
*/
public static void addBiomeFeature(
Holder<Biome> biome,
org.betterx.bclib.api.v3.levelgen.features.BCLFeature feature
) {
addBiomeFeature(biome, feature.getDecoration(), feature.getPlacedFeature());
}
/**
* Adds new features to existing biome.
*
* @param biome {@link Biome} to add features in.
* @param step a {@link Decoration} step for the feature.
* @param featureList {@link ConfiguredFeature} to add.
*/
public static void addBiomeFeature(Holder<Biome> biome, Decoration step, Holder<PlacedFeature>... featureList) {
addBiomeFeature(biome, step, List.of(featureList));
}
/**
* Adds new features to existing biome.
*
* @param biome {@link Biome} to add features in.
* @param step a {@link Decoration} step for the feature.
* @param additionalFeatures List of {@link ConfiguredFeature} to add.
*/
private static void addBiomeFeature(
Holder<Biome> biome,
Decoration step,
List<Holder<PlacedFeature>> additionalFeatures
) {
BiomeGenerationSettingsAccessor accessor = (BiomeGenerationSettingsAccessor) biome.value()
.getGenerationSettings();
List<HolderSet<PlacedFeature>> allFeatures = CollectionsUtil.getMutable(accessor.bclib_getFeatures());
List<Holder<PlacedFeature>> features = getFeaturesListCopy(allFeatures, step);
for (var feature : additionalFeatures) {
if (!features.contains(feature))
features.add(feature);
}
allFeatures.set(step.ordinal(), HolderSet.direct(features));
final Supplier<List<ConfiguredFeature<?, ?>>> flowerFeatures = Suppliers.memoize(() -> allFeatures.stream()
.flatMap(
HolderSet::stream)
.map(Holder::value)
.flatMap(
PlacedFeature::getFeatures)
.filter(configuredFeature -> configuredFeature.feature() == Feature.FLOWER)
.collect(
ImmutableList.toImmutableList()));
final Supplier<Set<PlacedFeature>> featureSet = Suppliers.memoize(() -> allFeatures.stream()
.flatMap(HolderSet::stream)
.map(Holder::value)
.collect(Collectors.toSet()));
accessor.bclib_setFeatures(allFeatures);
accessor.bclib_setFeatureSet(featureSet);
accessor.bclib_setFlowerFeatures(flowerFeatures);
}
/**
* Adds mob spawning to specified biome.
*
* @param biome {@link Biome} to add mob spawning.
* @param entityType {@link EntityType} mob type.
* @param weight spawn weight.
* @param minGroupCount minimum mobs in group.
* @param maxGroupCount maximum mobs in group.
*/
public static <M extends Mob> void addBiomeMobSpawn(
Holder<Biome> biome,
EntityType<M> entityType,
int weight,
int minGroupCount,
int maxGroupCount
) {
final MobCategory category = entityType.getCategory();
MobSpawnSettingsAccessor accessor = (MobSpawnSettingsAccessor) biome.value().getMobSettings();
Map<MobCategory, WeightedRandomList<SpawnerData>> spawners = CollectionsUtil.getMutable(accessor.bcl_getSpawners());
List<SpawnerData> mobs = spawners.containsKey(category)
? CollectionsUtil.getMutable(spawners.get(category)
.unwrap())
: Lists.newArrayList();
mobs.add(new SpawnerData(entityType, weight, minGroupCount, maxGroupCount));
spawners.put(category, WeightedRandomList.create(mobs));
accessor.bcl_setSpawners(spawners);
}
public static Optional<BlockState> findTopMaterial(WorldGenLevel world, BlockPos pos) {
return findTopMaterial(getBiome(world.getBiome(pos)));
}
public static Optional<BlockState> findTopMaterial(Holder<Biome> biome) {
return findTopMaterial(getBiome(biome.value()));
}
public static Optional<BlockState> findTopMaterial(Biome biome) {
return findTopMaterial(getBiome(biome));
}
public static Optional<BlockState> findTopMaterial(BCLBiome biome) {
if (biome instanceof SurfaceMaterialProvider smp) {
return Optional.of(smp.getTopMaterial());
}
return Optional.empty();
}
public static Optional<BlockState> findUnderMaterial(Holder<Biome> biome) {
return findUnderMaterial(getBiome(biome.value()));
}
public static Optional<BlockState> findUnderMaterial(BCLBiome biome) {
if (biome instanceof SurfaceMaterialProvider smp) {
return Optional.of(smp.getUnderMaterial());
}
return Optional.empty();
}
/**
* Set biome in chunk at specified position.
*
* @param chunk {@link ChunkAccess} chunk to set biome in.
* @param pos {@link BlockPos} biome position.
* @param biome {@link Holder<Biome>} instance. Should be biome from world.
*/
public static void setBiome(ChunkAccess chunk, BlockPos pos, Holder<Biome> biome) {
int sectionY = (pos.getY() - chunk.getMinBuildHeight()) >> 4;
PalettedContainerRO<Holder<Biome>> biomes = chunk.getSection(sectionY).getBiomes();
if (biomes instanceof PalettedContainer<Holder<Biome>> palette) {
palette.set((pos.getX() & 15) >> 2, (pos.getY() & 15) >> 2, (pos.getZ() & 15) >> 2, biome);
} else {
BCLib.LOGGER.warning("Unable to change Biome at " + pos);
}
}
/**
* Set biome in world at specified position.
*
* @param level {@link LevelAccessor} world to set biome in.
* @param pos {@link BlockPos} biome position.
* @param biome {@link Holder<Biome>} instance. Should be biome from world.
*/
public static void setBiome(LevelAccessor level, BlockPos pos, Holder<Biome> biome) {
ChunkAccess chunk = level.getChunk(pos);
setBiome(chunk, pos, biome);
}
private static void sortFeatures(List<Holder<PlacedFeature>> features) {
// InternalBiomeAPI.initFeatureOrder();
//
// Set<Holder<PlacedFeature>> featuresWithoutDuplicates = Sets.newHashSet();
// features.forEach(holder -> featuresWithoutDuplicates.add(holder));
//
// if (featuresWithoutDuplicates.size() != features.size()) {
// features.clear();
// featuresWithoutDuplicates.forEach(feature -> features.add(feature));
// }
//
// features.forEach(feature -> {
// InternalBiomeAPI.FEATURE_ORDER.computeIfAbsent(
// feature,
// f -> InternalBiomeAPI.FEATURE_ORDER_ID.getAndIncrement()
// );
// });
//
// features.sort((f1, f2) -> {
// int v1 = InternalBiomeAPI.FEATURE_ORDER.getOrDefault(f1, 70000);
// int v2 = InternalBiomeAPI.FEATURE_ORDER.getOrDefault(f2, 70000);
// return Integer.compare(v1, v2);
// });
}
private static List<Holder<PlacedFeature>> getFeaturesListCopy(
List<HolderSet<PlacedFeature>> features,
Decoration step
) {
return getFeaturesListCopy(features, step.ordinal());
}
private static List<Holder<PlacedFeature>> getFeaturesListCopy(List<HolderSet<PlacedFeature>> features, int index) {
while (features.size() <= index) {
features.add(HolderSet.direct(Lists.newArrayList()));
}
return features.get(index).stream().collect(Collectors.toList());
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib Nether Biome Generator and into Fabric Biome API.
*
* @param bclBiome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerNetherBiome(BCLBiome bclBiome) {
registerBiome(bclBiome, BiomeType.BCL_NETHER);
ResourceKey<Biome> key = bclBiome.getBiomeKey();
if (bclBiome.allowFabricRegistration()) {
bclBiome.forEachClimateParameter(p -> NetherBiomes.addNetherBiome(key, p));
}
return bclBiome;
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib Nether Biome Generator and into Fabric Biome API.
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
@Deprecated(forRemoval = true)
public static BCLBiome registerNetherBiome(Biome biome) {
return InternalBiomeAPI.wrapNativeBiome(biome, -1, InternalBiomeAPI.OTHER_NETHER);
}
@Deprecated(forRemoval = true)
public static BCLBiome registerEndLandBiome(Holder<Biome> biome) {
return InternalBiomeAPI.wrapNativeBiome(biome.unwrapKey().orElseThrow(), InternalBiomeAPI.OTHER_END_LAND);
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a void biome (will generate only in the End void - between islands).
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
@Deprecated(forRemoval = true)
public static BCLBiome registerEndVoidBiome(Holder<Biome> biome) {
return InternalBiomeAPI.wrapNativeBiome(biome.unwrapKey().orElseThrow(), InternalBiomeAPI.OTHER_END_VOID);
}
/**
* Register {@link BCLBiome} wrapper for {@link Biome}.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biome {@link BCLBiome};
* @param genChance float generation chance.
* @return {@link BCLBiome}
*/
@Deprecated(forRemoval = true)
public static BCLBiome registerEndLandBiome(Holder<Biome> biome, float genChance) {
return InternalBiomeAPI.wrapNativeBiome(
biome.unwrapKey().orElseThrow(),
genChance,
InternalBiomeAPI.OTHER_END_LAND
);
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a void biome (will generate only in the End void - between islands).
*
* @param biome {@link BCLBiome}.
* @param genChance float generation chance.
* @return {@link BCLBiome}
*/
@Deprecated(forRemoval = true)
public static BCLBiome registerEndVoidBiome(Holder<Biome> biome, float genChance) {
return InternalBiomeAPI.wrapNativeBiome(
biome.unwrapKey().orElseThrow(),
genChance,
InternalBiomeAPI.OTHER_END_VOID
);
}
}

View file

@ -0,0 +1,383 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.interfaces.BiomeSourceAccessor;
import org.betterx.bclib.interfaces.NoiseGeneratorSettingsProvider;
import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor;
import org.betterx.worlds.together.surfaceRules.SurfaceRuleProvider;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.mutable.MutableInt;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public class InternalBiomeAPI {
public static final BiomeAPI.BiomeType OTHER_NETHER = new BiomeAPI.BiomeType(
"OTHER_NETHER",
BiomeAPI.BiomeType.NETHER
);
public static final BiomeAPI.BiomeType OTHER_END_LAND = new BiomeAPI.BiomeType(
"OTHER_END_LAND",
BiomeAPI.BiomeType.END_LAND
);
public static final BiomeAPI.BiomeType OTHER_END_VOID = new BiomeAPI.BiomeType(
"OTHER_END_VOID",
BiomeAPI.BiomeType.END_VOID
);
public static final BiomeAPI.BiomeType OTHER_END_CENTER = new BiomeAPI.BiomeType(
"OTHER_END_CENTER",
BiomeAPI.BiomeType.END_CENTER
);
public static final BiomeAPI.BiomeType OTHER_END_BARRENS = new BiomeAPI.BiomeType(
"OTHER_END_BARRENS",
BiomeAPI.BiomeType.END_BARRENS
);
static final Map<Biome, BCLBiome> CLIENT = Maps.newHashMap();
static final Map<Holder<PlacedFeature>, Integer> FEATURE_ORDER = Maps.newHashMap();
static final MutableInt FEATURE_ORDER_ID = new MutableInt(0);
static final Map<ResourceKey<LevelStem>, List<BiConsumer<ResourceLocation, Holder<Biome>>>> MODIFICATIONS = Maps.newHashMap();
static final Map<ResourceKey, List<BiConsumer<ResourceLocation, Holder<Biome>>>> TAG_ADDERS = Maps.newHashMap();
static Registry<Biome> biomeRegistry;
static RegistryAccess registryAccess;
static void initFeatureOrder() {
if (!FEATURE_ORDER.isEmpty()) {
return;
}
BuiltinRegistries.BIOME
.entrySet()
.stream()
.filter(entry -> entry
.getKey()
.location()
.getNamespace()
.equals("minecraft"))
.map(Map.Entry::getValue)
.map(biome -> (BiomeGenerationSettingsAccessor) biome.getGenerationSettings())
.map(BiomeGenerationSettingsAccessor::bclib_getFeatures)
.forEach(stepFeatureSuppliers -> stepFeatureSuppliers.forEach(step -> step.forEach(feature -> {
FEATURE_ORDER.computeIfAbsent(feature, f -> FEATURE_ORDER_ID.getAndIncrement());
})));
}
public static RegistryAccess worldRegistryAccess() {
return registryAccess;
}
/**
* Initialize registry for current server.
*
* @param access - The new, active {@link RegistryAccess} for the current session.
*/
public static void initRegistry(RegistryAccess access) {
if (access != registryAccess) {
registryAccess = access;
Registry<Biome> biomeRegistry = access.registry(Registry.BIOME_REGISTRY).orElse(null);
if (biomeRegistry != InternalBiomeAPI.biomeRegistry) {
InternalBiomeAPI.biomeRegistry = biomeRegistry;
CLIENT.clear();
BIOMES_TO_SORT.forEach(id -> {
Biome b = biomeRegistry.get(id);
if (b != null) {
BCLib.LOGGER.info("Found non fabric/bclib Biome: " + id + "(" + b + ")");
BiomeAPI.sortBiomeFeatures(b);
} else {
BCLib.LOGGER.info("Unknown Biome: " + id);
}
});
}
}
}
/**
* For internal use only.
* <p>
* This method gets called before a world is loaded/created to flush cashes we build.
*/
public static void prepareNewLevel() {
BIOMES_TO_SORT.clear();
}
/**
* Load biomes from Fabric API. For internal usage only.
*/
public static void loadFabricAPIBiomes() {
// FabricBiomesData.NETHER_BIOMES.forEach((key) -> {
// if (!BiomeAPI.hasBiome(key.location())) {
// Optional<Holder<Biome>> optional = BuiltinRegistries.BIOME.getHolder(key);
// if (optional.isPresent()) {
// BiomeAPI.registerNetherBiome(optional.get().value());
// }
// }
// });
//
// FabricBiomesData.END_LAND_BIOMES.forEach((key, weight) -> {
// if (!BiomeAPI.hasBiome(key.location())) {
// Optional<Holder<Biome>> optional = BuiltinRegistries.BIOME.getHolder(key);
// if (optional.isPresent()) {
// BiomeAPI.registerEndLandBiome(optional.get(), weight);
// }
// }
// });
//
// FabricBiomesData.END_VOID_BIOMES.forEach((key, weight) -> {
// if (!BiomeAPI.hasBiome(key.location())) {
// Optional<Holder<Biome>> optional = BuiltinRegistries.BIOME.getHolder(key);
// if (optional.isPresent()) {
// BiomeAPI.registerEndVoidBiome(optional.get(), weight);
// }
// }
// });
}
/**
* For internal use only
*/
public static void _runBiomeTagAdders() {
for (var mod : TAG_ADDERS.entrySet()) {
Stream<ResourceLocation> s = null;
if (mod.getKey() == Level.NETHER) s = BiomeAPI.BiomeType.BIOME_TYPE_MAP.entrySet()
.stream()
.filter(e -> e.getValue()
.is(BiomeAPI.BiomeType.NETHER))
.map(e -> e.getKey());
else if (mod.getKey() == Level.END) s = BiomeAPI.BiomeType.BIOME_TYPE_MAP.entrySet()
.stream()
.filter(e -> e.getValue().is(
BiomeAPI.BiomeType.END))
.map(e -> e.getKey());
if (s != null) {
s.forEach(id -> {
Holder<Biome> biomeHolder = BiomeAPI.getFromRegistry(id);
if (biomeHolder != null && biomeHolder.isBound()) {
mod.getValue().forEach(c -> c.accept(id, biomeHolder));
} else {
BCLib.LOGGER.info("No Holder for " + id);
}
});
}
}
}
@Deprecated(forRemoval = true)
public static void applyModificationsDeprecated(ServerLevel level) {
//TODO: Now Disabled, because we fix the settings when everything gets loaded
if (level != null) return;
NoiseGeneratorSettings noiseGeneratorSettings = null;
final ChunkGenerator chunkGenerator = level.getChunkSource().getGenerator();
final BiomeSource source = chunkGenerator.getBiomeSource();
final Set<Holder<Biome>> biomes = source.possibleBiomes();
if (chunkGenerator instanceof NoiseGeneratorSettingsProvider gen)
noiseGeneratorSettings = gen.bclib_getNoiseGeneratorSettings();
// Datapacks (like Amplified Nether)will change the GeneratorSettings upon load, so we will
// only use the default Setting for Nether/End if we were unable to find a settings object
if (noiseGeneratorSettings == null) {
if (level.dimension() == Level.NETHER) {
noiseGeneratorSettings = BuiltinRegistries.NOISE_GENERATOR_SETTINGS.get(NoiseGeneratorSettings.NETHER);
} else if (level.dimension() == Level.END) {
noiseGeneratorSettings = BuiltinRegistries.NOISE_GENERATOR_SETTINGS.get(NoiseGeneratorSettings.END);
}
}
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = MODIFICATIONS.get(level
.dimensionTypeRegistration()
.unwrapKey()
.orElseThrow());
for (Holder<Biome> biomeHolder : biomes) {
if (biomeHolder.isBound()) {
applyModificationsAndUpdateFeatures(modifications, biomeHolder);
}
}
if (noiseGeneratorSettings != null) {
final SurfaceRuleProvider provider = SurfaceRuleProvider.class.cast(noiseGeneratorSettings);
// Multiple Biomes can use the same generator. So we need to keep track of all Biomes that are
// Provided by all the BiomeSources that use the same generator.
// This happens for example when using the MiningDimensions, which reuses the generator for the
// Nethering Dimension
//MODIFIED_SURFACE_PROVIDERS.add(provider);
//provider.bclib_addBiomeSource(source);
} else {
BCLib.LOGGER.warning("No generator for " + source);
}
((BiomeSourceAccessor) source).bclRebuildFeatures();
}
public static void applyModifications(BiomeSource source, ResourceKey<LevelStem> dimension) {
BCLib.LOGGER.info("Apply Modifications for " + dimension.location() + " BiomeSource " + source);
/*if (dimension.location().equals(LevelStem.NETHER)){
if (source instanceof BCLBiomeSource s) {
NetherBiomes.useLegacyGeneration = s.biomeSourceVersion==BCLBiomeSource.BIOME_SOURCE_VERSION_SQUARE;
}
}*/
final Set<Holder<Biome>> biomes = source.possibleBiomes();
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = MODIFICATIONS.get(dimension);
for (Holder<Biome> biomeHolder : biomes) {
if (biomeHolder.isBound()) {
applyModificationsAndUpdateFeatures(modifications, biomeHolder);
}
}
}
private static void applyModificationsAndUpdateFeatures(
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications,
Holder<Biome> biome
) {
ResourceLocation biomeID = BiomeAPI.getBiomeID(biome);
if (modifications != null) {
modifications.forEach(consumer -> {
consumer.accept(biomeID, biome);
});
}
BiomeAPI.sortBiomeFeatures(biome);
}
private static final Set<ResourceLocation> BIOMES_TO_SORT = Sets.newHashSet();
/**
* Register {@link BCLBiome} wrapper for {@link Biome}.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biomeKey The source biome to wrap
* @return {@link BCLBiome}
*/
public static BCLBiome wrapNativeBiome(ResourceKey<Biome> biomeKey, BiomeAPI.BiomeType type) {
return wrapNativeBiome(biomeKey, -1, type);
}
/**
* Register {@link BCLBiome} wrapper for {@link Biome}.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biomeKey The source biome to wrap
* @param genChance generation chance. If &lt;0 the default genChance is used
* @return {@link BCLBiome}
*/
public static BCLBiome wrapNativeBiome(ResourceKey<Biome> biomeKey, float genChance, BiomeAPI.BiomeType type) {
return wrapNativeBiome(
biomeKey,
genChance < 0 ? null : VanillaBiomeSettings.createVanilla().setGenChance(genChance).build(),
type
);
}
public static BCLBiome wrapNativeBiome(
ResourceKey<Biome> biomeKey,
BCLBiome edgeBiome,
int edgeBiomeSize,
float genChance,
BiomeAPI.BiomeType type
) {
VanillaBiomeSettings.Builder settings = VanillaBiomeSettings.createVanilla();
if (genChance >= 0) settings.setGenChance(genChance);
settings.setEdge(edgeBiome);
settings.setEdgeSize(edgeBiomeSize);
return wrapNativeBiome(biomeKey, settings.build(), type);
}
/**
* Register {@link BCLBiome} wrapper for {@link Biome}.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biomeKey The source biome to wrap
* @param setings the {@link VanillaBiomeSettings} to use
* @return {@link BCLBiome}
*/
private static BCLBiome wrapNativeBiome(
ResourceKey<Biome> biomeKey,
VanillaBiomeSettings setings,
BiomeAPI.BiomeType type
) {
BCLBiome bclBiome = BiomeAPI.getBiome(biomeKey.location());
if (bclBiome == BiomeAPI.EMPTY_BIOME) {
bclBiome = new BCLBiome(biomeKey, setings);
}
BiomeAPI.registerBiome(bclBiome, type);
return bclBiome;
}
/**
* Register {@link BCLBiome} wrapper for {@link Biome}.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biome The source biome to wrap
* @param genChance generation chance.
* @return {@link BCLBiome}
*/
@Deprecated(forRemoval = true)
static BCLBiome wrapNativeBiome(Biome biome, float genChance, BiomeAPI.BiomeType type) {
BCLBiome bclBiome = BiomeAPI.getBiome(biome);
if (bclBiome == BiomeAPI.EMPTY_BIOME) {
bclBiome = new BCLBiome(
biome,
genChance < 0 ? null : VanillaBiomeSettings.createVanilla().setGenChance(genChance).build()
);
}
BiomeAPI.registerBiome(bclBiome, type, null);
return bclBiome;
}
static {
DynamicRegistrySetupCallback.EVENT.register(registryManager -> {
Optional<? extends Registry<Biome>> oBiomeRegistry = registryManager.registry(Registry.BIOME_REGISTRY);
RegistryEntryAddedCallback
.event(oBiomeRegistry.get())
.register((rawId, id, biome) -> {
BCLBiome b = BiomeAPI.getBiome(id);
if (!"minecraft".equals(id.getNamespace()) && (b == null || b == BiomeAPI.EMPTY_BIOME)) {
//BCLib.LOGGER.info(" #### " + rawId + ", " + biome + ", " + id);
BIOMES_TO_SORT.add(id);
}
});
});
}
public static boolean registryContainsBound(ResourceKey<Biome> key) {
Registry<Biome> reg = biomeRegistry;
if (reg == null) reg = BuiltinRegistries.BIOME;
if (reg.containsKey(key)) {
return reg.getOrCreateHolderOrThrow(key).isBound();
}
return false;
}
}

View file

@ -0,0 +1,14 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
public class VanillaBiomeSettings extends BCLBiomeSettings {
public static class Builder extends BCLBiomeSettings.CommonBuilder<VanillaBiomeSettings, VanillaBiomeSettings.Builder> {
public Builder() {
super(new VanillaBiomeSettings());
}
}
public static Builder createVanilla() {
return new Builder();
}
}

View file

@ -0,0 +1,166 @@
package org.betterx.bclib.api.v2.levelgen.features;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration;
import net.minecraft.world.level.levelgen.placement.PlacementModifier;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockMatchTest;
@Deprecated(forRemoval = true)
public class BCLCommonFeatures {
/**
* Will create a basic plant feature.
*
* @param id {@link ResourceLocation} feature ID.
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
* @param density iterations per chunk.
* @return new BCLFeature instance.
*/
public static BCLFeature makeVegetationFeature(
ResourceLocation id,
Feature<NoneFeatureConfiguration> feature,
int density
) {
return makeVegetationFeature(id, feature, density, false);
}
/**
* Will create a basic plant feature.
*
* @param id {@link ResourceLocation} feature ID.
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
* @param density iterations per chunk.
* @param allHeight if {@code true} will generate plant on all layers, if {@code false} - only on surface.
* @return new BCLFeature instance.
*/
public static BCLFeature makeVegetationFeature(
ResourceLocation id,
Feature<NoneFeatureConfiguration> feature,
int density,
boolean allHeight
) {
if (allHeight) {
return BCLFeatureBuilder
.start(id, feature)
.countLayers(density)
.squarePlacement()
.onlyInBiome()
.buildAndRegister();
} else {
return BCLFeatureBuilder
.start(id, feature)
.countMax(density)
.squarePlacement()
.heightmap()
.onlyInBiome()
.buildAndRegister();
}
}
/**
* Will create feature which will be generated once in each chunk.
*
* @param id {@link ResourceLocation} feature ID.
* @param decoration {@link Decoration} feature step.
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
* @return new BCLFeature instance.
*/
public static BCLFeature makeChunkFeature(
ResourceLocation id,
Decoration decoration,
Feature<NoneFeatureConfiguration> feature
) {
return BCLFeatureBuilder.start(id, feature).decoration(decoration).count(1).onlyInBiome().buildAndRegister();
}
/**
* Will create feature with chanced decoration, chance for feature to generate per chunk is 1 / chance.
*
* @param id {@link ResourceLocation} feature ID.
* @param decoration {@link Decoration} feature step.
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
* @param chance chance for feature to be generated in.
* @return new BCLFeature instance.
*/
public static BCLFeature makeChancedFeature(
ResourceLocation id,
Decoration decoration,
Feature<NoneFeatureConfiguration> feature,
int chance
) {
return BCLFeatureBuilder.start(id, feature)
.decoration(decoration)
.onceEvery(chance)
.squarePlacement()
.onlyInBiome()
.buildAndRegister();
}
/**
* Will create feature with specified generation iterations per chunk.
*
* @param id {@link ResourceLocation} feature ID.
* @param decoration {@link Decoration} feature step.
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
* @param count iterations steps.
* @return new BCLFeature instance.
*/
public static BCLFeature makeCountFeature(
ResourceLocation id,
Decoration decoration,
Feature<NoneFeatureConfiguration> feature,
int count
) {
return BCLFeatureBuilder.start(id, feature)
.decoration(decoration)
.count(count)
.squarePlacement()
.onlyInBiome()
.buildAndRegister();
}
/**
* Will create a basic ore feature.
*
* @param id {@link ResourceLocation} feature ID.
* @param blockOre {@link Decoration} feature step.
* @param hostBlock {@link Block} to generate feature in.
* @param veins iterations per chunk.
* @param veinSize size of ore vein.
* @param airDiscardChance chance that this orge gets discarded when it is exposed to air
* @param placement {@link net.minecraft.world.level.levelgen.placement.PlacementModifier} for the ore distribution,
* for example {@code PlacementUtils.FULL_RANGE}, {@code PlacementUtils.RANGE_10_10}
* @param rare when true, this is placed as a rare resource
* @return new BCLFeature instance.
*/
public static BCLFeature makeOreFeature(
ResourceLocation id,
Block blockOre,
Block hostBlock,
int veins,
int veinSize,
float airDiscardChance,
PlacementModifier placement,
boolean rare
) {
BCLFeatureBuilder builder = BCLFeatureBuilder.start(id, Feature.ORE).decoration(Decoration.UNDERGROUND_ORES);
if (rare) {
builder.onceEvery(veins);
} else {
builder.count(veins);
}
builder.modifier(placement).squarePlacement().onlyInBiome();
return builder.buildAndRegister(new OreConfiguration(
new BlockMatchTest(hostBlock),
blockOre.defaultBlockState(),
veinSize,
airDiscardChance
));
}
}

View file

@ -0,0 +1,290 @@
package org.betterx.bclib.api.v2.levelgen.features;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.levelgen.features.config.ScatterFeatureConfig;
import org.betterx.bclib.api.v2.levelgen.features.features.ScatterFeature;
import org.betterx.bclib.api.v2.levelgen.features.features.WeightedRandomSelectorFeature;
import org.betterx.bclib.api.v3.levelgen.features.BCLConfigureFeature;
import org.betterx.bclib.api.v3.levelgen.features.BCLFeatureBuilder;
import org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature;
import org.betterx.bclib.api.v3.levelgen.features.config.ConditionFeatureConfig;
import org.betterx.bclib.api.v3.levelgen.features.config.PlaceFacingBlockConfig;
import org.betterx.bclib.api.v3.levelgen.features.config.SequenceFeatureConfig;
import org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.data.worldgen.features.FeatureUtils;
import net.minecraft.data.worldgen.placement.PlacementUtils;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.RandomFeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.RandomPatchConfiguration;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.minecraft.world.level.levelgen.placement.PlacementModifier;
import java.util.Map;
import java.util.Optional;
/**
* @param <F>
* @param <FC>
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature} instead
*/
@Deprecated(forRemoval = true)
public class BCLFeature<F extends Feature<FC>, FC extends FeatureConfiguration> {
/**
* @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#PLACE_BLOCK}
*/
@Deprecated(forRemoval = true)
public static final Feature<PlaceFacingBlockConfig> PLACE_BLOCK = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.PLACE_BLOCK;
@Deprecated(forRemoval = true)
public static final Feature<ScatterFeatureConfig.OnSolid> SCATTER_ON_SOLID = register(
BCLib.makeID("scatter_on_solid"),
new ScatterFeature<>(ScatterFeatureConfig.OnSolid.CODEC)
);
@Deprecated(forRemoval = true)
public static final Feature<ScatterFeatureConfig.ExtendTop> SCATTER_EXTEND_TOP = register(
BCLib.makeID("scatter_extend_top"),
new ScatterFeature<>(ScatterFeatureConfig.ExtendTop.CODEC)
);
@Deprecated(forRemoval = true)
public static final Feature<ScatterFeatureConfig.ExtendBottom> SCATTER_EXTEND_BOTTOM = register(
BCLib.makeID("scatter_extend_bottom"),
new ScatterFeature<>(ScatterFeatureConfig.ExtendBottom.CODEC)
);
@Deprecated(forRemoval = true)
public static final Feature<RandomFeatureConfiguration> RANDOM_SELECTOR = register(
BCLib.makeID("random_select"),
new WeightedRandomSelectorFeature()
);
/**
* @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#TEMPLATE}
*/
@Deprecated(forRemoval = true)
public static final Feature<TemplateFeatureConfig> TEMPLATE = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.TEMPLATE;
/**
* @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#MARK_POSTPROCESSING}
*/
@Deprecated(forRemoval = true)
public static final Feature<NoneFeatureConfiguration> MARK_POSTPROCESSING = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.MARK_POSTPROCESSING;
/**
* @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#SEQUENCE}
*/
@Deprecated(forRemoval = true)
public static final Feature<SequenceFeatureConfig> SEQUENCE = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.SEQUENCE;
/**
* @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#CONDITION}
*/
@Deprecated(forRemoval = true)
public static final Feature<ConditionFeatureConfig> CONDITION = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.CONDITION;
public final ResourceLocation id;
org.betterx.bclib.api.v3.levelgen.features.BCLFeature<F, FC> proxy;
@Deprecated(forRemoval = true)
public BCLFeature(
ResourceLocation id,
F feature,
Decoration featureStep,
FC configuration,
PlacementModifier[] modifiers
) {
this(id, feature, featureStep, configuration, buildPlacedFeature(id, feature, configuration, modifiers));
}
private static <E> boolean containsObj(Registry<E> registry, E obj) {
Optional<Map.Entry<ResourceKey<E>, E>> optional = registry
.entrySet()
.stream()
.filter(entry -> entry.getValue() == obj)
.findAny();
return optional.isPresent();
}
private static <F extends Feature<FC>, FC extends FeatureConfiguration> org.betterx.bclib.api.v3.levelgen.features.BCLFeature<F, FC> build(
ResourceLocation id,
F feature,
Decoration featureStep,
FC configuration,
Holder<PlacedFeature> placedFeature
) {
BCLConfigureFeature<F, FC> cfg = BCLFeatureBuilder.start(id, feature)
.configuration(configuration)
.build();
if (!BuiltinRegistries.PLACED_FEATURE.containsKey(id)) {
Registry.register(BuiltinRegistries.PLACED_FEATURE, id, placedFeature.value());
}
if (!Registry.FEATURE.containsKey(id) && !containsObj(Registry.FEATURE, feature)) {
Registry.register(Registry.FEATURE, id, feature);
}
return new org.betterx.bclib.api.v3.levelgen.features.BCLFeature<>(cfg, placedFeature, featureStep);
}
@Deprecated(forRemoval = true)
public BCLFeature(
ResourceLocation id,
F feature,
Decoration featureStep,
FC configuration,
Holder<PlacedFeature> placedFeature
) {
this(build(id, feature, featureStep, configuration, placedFeature));
}
@Deprecated(forRemoval = true)
public BCLFeature(org.betterx.bclib.api.v3.levelgen.features.BCLFeature proxy) {
this.proxy = proxy;
this.id = proxy.configuredFeature.id;
}
private static <FC extends FeatureConfiguration, F extends Feature<FC>> Holder<PlacedFeature> buildPlacedFeature(
ResourceLocation id,
F feature,
FC configuration,
PlacementModifier[] modifiers
) {
Holder<ConfiguredFeature<?, ?>> configuredFeature;
if (!BuiltinRegistries.CONFIGURED_FEATURE.containsKey(id)) {
configuredFeature = (Holder<ConfiguredFeature<?, ?>>) (Object) FeatureUtils.register(
id.toString(),
feature,
configuration
);
} else {
configuredFeature = BuiltinRegistries.CONFIGURED_FEATURE
.getHolder(ResourceKey.create(
BuiltinRegistries.CONFIGURED_FEATURE.key(),
id
))
.orElseThrow();
}
if (!BuiltinRegistries.PLACED_FEATURE.containsKey(id)) {
return PlacementUtils.register(id.toString(), configuredFeature, modifiers);
} else {
return BuiltinRegistries.PLACED_FEATURE.getHolder(ResourceKey.create(
BuiltinRegistries.PLACED_FEATURE.key(),
id
)).orElseThrow();
}
}
/**
* @param string
* @param feature
* @param <C>
* @param <F>
* @return
* @deprecated Use {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#register(ResourceLocation, Feature)} instead
*/
@Deprecated(forRemoval = true)
public static <C extends FeatureConfiguration, F extends Feature<C>> F register(
ResourceLocation string,
F feature
) {
return org.betterx.bclib.api.v3.levelgen.features.BCLFeature.register(string, feature);
}
/**
* Get raw feature.
*
* @return {@link Feature}.
*/
public F getFeature() {
return proxy.getFeature();
}
public BCLConfigureFeature<F, FC> getConfFeature() {
return proxy.configuredFeature;
}
/**
* Get configured feature.
*
* @return {@link PlacedFeature}.
*/
public Holder<PlacedFeature> getPlacedFeature() {
return proxy.getPlacedFeature();
}
/**
* Get feature decoration step.
*
* @return {@link Decoration}.
*/
public Decoration getDecoration() {
return proxy.getDecoration();
}
public FC getConfiguration() {
return proxy.getConfiguration();
}
public boolean place(ServerLevel level, BlockPos pos, RandomSource random) {
return place(this.getFeature(), this.getConfiguration(), level, pos, random);
}
private static boolean placeUnbound(
Feature<?> feature,
FeatureConfiguration config,
ServerLevel level,
BlockPos pos,
RandomSource random
) {
if (config instanceof RandomPatchConfiguration rnd) {
var configured = rnd.feature().value().feature().value();
feature = configured.feature();
config = configured.config();
}
if (feature instanceof UserGrowableFeature growable) {
return growable.grow(level, pos, random, config);
}
FeaturePlaceContext context = new FeaturePlaceContext(
Optional.empty(),
level,
level.getChunkSource().getGenerator(),
random,
pos,
config
);
return feature.place(context);
}
public static boolean place(
Feature<NoneFeatureConfiguration> feature,
ServerLevel level,
BlockPos pos,
RandomSource random
) {
return placeUnbound(feature, FeatureConfiguration.NONE, level, pos, random);
}
public static <FC extends FeatureConfiguration> boolean place(
Feature<FC> feature,
FC config,
ServerLevel level,
BlockPos pos,
RandomSource random
) {
return placeUnbound(feature, config, level, pos, random);
}
}

View file

@ -0,0 +1,402 @@
package org.betterx.bclib.api.v2.levelgen.features;
import org.betterx.bclib.api.v2.levelgen.features.placement.IsEmptyAboveSampledFilter;
import org.betterx.bclib.api.v2.levelgen.features.placement.MinEmptyFilter;
import org.betterx.bclib.api.v2.levelgen.features.placement.Stencil;
import org.betterx.bclib.api.v3.levelgen.features.placement.*;
import org.betterx.worlds.together.tag.v3.CommonBlockTags;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.data.worldgen.placement.PlacementUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.valueproviders.IntProvider;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.SimpleBlockFeature;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.SimpleBlockConfiguration;
import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider;
import net.minecraft.world.level.levelgen.placement.*;
import net.minecraft.world.level.material.Material;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* @param <FC>
* @param <F>
* @deprecated please use {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeatureBuilder} instead
*/
@Deprecated(forRemoval = true)
public class BCLFeatureBuilder<FC extends FeatureConfiguration, F extends Feature<FC>> {
private final List<PlacementModifier> modifications = new ArrayList<>(5);
private final ResourceLocation featureID;
private Decoration decoration = Decoration.VEGETAL_DECORATION;
private final F feature;
private BlockStateProvider provider;
private BCLFeatureBuilder(ResourceLocation featureID, F feature) {
this.featureID = featureID;
this.feature = feature;
}
/**
* Starts a new {@link BCLFeature} builder.
*
* @param featureID {@link ResourceLocation} feature identifier.
* @param feature {@link Feature} to construct.
* @return {@link BCLFeatureBuilder} instance.
*/
public static BCLFeatureBuilder start(ResourceLocation featureID, Feature<?> feature) {
return new BCLFeatureBuilder(featureID, feature);
}
public static BCLFeatureBuilder<SimpleBlockConfiguration, SimpleBlockFeature> start(
ResourceLocation featureID,
Block block
) {
return start(featureID, BlockStateProvider.simple(block));
}
public static BCLFeatureBuilder<SimpleBlockConfiguration, SimpleBlockFeature> start(
ResourceLocation featureID,
BlockState state
) {
return start(featureID, BlockStateProvider.simple(state));
}
public static BCLFeatureBuilder<SimpleBlockConfiguration, SimpleBlockFeature> start(
ResourceLocation featureID,
BlockStateProvider provider
) {
BCLFeatureBuilder<SimpleBlockConfiguration, SimpleBlockFeature> builder = new BCLFeatureBuilder(
featureID,
Feature.SIMPLE_BLOCK
);
builder.provider = provider;
return builder;
}
/**
* Set generation step for the feature. Default is {@code VEGETAL_DECORATION}.
*
* @param decoration {@link Decoration} step.
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder decoration(Decoration decoration) {
this.decoration = decoration;
return this;
}
/**
* Add feature placement modifier. Used as a condition for feature how to generate.
*
* @param modifier {@link PlacementModifier}.
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder modifier(PlacementModifier modifier) {
modifications.add(modifier);
return this;
}
public BCLFeatureBuilder modifier(List<PlacementModifier> modifiers) {
modifications.addAll(modifiers);
return this;
}
/**
* Generate feature in certain iterations (per chunk).
*
* @param count how many times feature will be generated in chunk.
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder count(int count) {
return modifier(CountPlacement.of(count));
}
/**
* Generate feature in certain iterations (per chunk). Count can be between 0 and max value.
*
* @param count maximum amount of iterations per chunk.
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder countMax(int count) {
return modifier(CountPlacement.of(UniformInt.of(0, count)));
}
public BCLFeatureBuilder countRange(int min, int max) {
return modifier(CountPlacement.of(UniformInt.of(min, max)));
}
/**
* Generate feature in certain iterations (per chunk).
* Feature will be generated on all layers (example - Nether plants).
*
* @param count how many times feature will be generated in chunk layers.
* @return same {@link BCLFeatureBuilder} instance.
*/
@SuppressWarnings("deprecation")
public BCLFeatureBuilder countLayers(int count) {
return modifier(CountOnEveryLayerPlacement.of(count));
}
/**
* Generate feature in certain iterations (per chunk). Count can be between 0 and max value.
* Feature will be generated on all layers (example - Nether plants).
*
* @param count maximum amount of iterations per chunk layers.
* @return same {@link BCLFeatureBuilder} instance.
*/
@SuppressWarnings("deprecation")
public BCLFeatureBuilder countLayersMax(int count) {
return modifier(CountOnEveryLayerPlacement.of(UniformInt.of(0, count)));
}
/**
* Will place feature once every n-th attempts (in average).
*
* @param n amount of attempts.
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder onceEvery(int n) {
return modifier(RarityFilter.onAverageOnceEvery(n));
}
/**
* Restricts feature generation only to biome where feature was added.
*
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder onlyInBiome() {
return modifier(BiomeFilter.biome());
}
public BCLFeatureBuilder squarePlacement() {
return modifier(InSquarePlacement.spread());
}
public BCLFeatureBuilder stencil() {
return modifier(Stencil.all());
}
public BCLFeatureBuilder all() {
return modifier(All.simple());
}
public BCLFeatureBuilder stencilOneIn4() {
return modifier(Stencil.oneIn4());
}
/**
* Select random height that is 10 above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight10FromFloorCeil() {
return modifier(PlacementUtils.RANGE_10_10);
}
/**
* Select random height that is 4 above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight4FromFloorCeil() {
return modifier(PlacementUtils.RANGE_4_4);
}
/**
* Select random height that is 8 above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight8FromFloorCeil() {
return modifier(PlacementUtils.RANGE_8_8);
}
/**
* Select random height that is above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight() {
return modifier(PlacementUtils.FULL_RANGE);
}
public BCLFeatureBuilder isEmptyAbove4() {
return modifier(IsEmptyAboveSampledFilter.emptyAbove4());
}
public BCLFeatureBuilder isEmptyAbove2() {
return modifier(IsEmptyAboveSampledFilter.emptyAbove2());
}
public BCLFeatureBuilder isEmptyAbove() {
return modifier(IsEmptyAboveSampledFilter.emptyAbove());
}
public BCLFeatureBuilder isEmptyBelow4() {
return modifier(IsEmptyAboveSampledFilter.emptyBelow4());
}
public BCLFeatureBuilder isEmptyBelow2() {
return modifier(IsEmptyAboveSampledFilter.emptyBelow2());
}
public BCLFeatureBuilder isEmptyBelow() {
return modifier(IsEmptyAboveSampledFilter.emptyBelow());
}
public BCLFeatureBuilder isEmptyAbove(int d1, int d2) {
return modifier(new IsEmptyAboveSampledFilter(d1, d2));
}
public BCLFeatureBuilder onEveryLayer() {
return modifier(OnEveryLayer.simple());
}
public BCLFeatureBuilder underEveryLayer() {
return modifier(UnderEveryLayer.simple());
}
public BCLFeatureBuilder spreadHorizontal(IntProvider p) {
return modifier(RandomOffsetPlacement.horizontal(p));
}
public BCLFeatureBuilder spreadVertical(IntProvider p) {
return modifier(RandomOffsetPlacement.horizontal(p));
}
public BCLFeatureBuilder spread(IntProvider horizontal, IntProvider vertical) {
return modifier(RandomOffsetPlacement.of(horizontal, vertical));
}
public BCLFeatureBuilder offset(Direction dir) {
return modifier(Offset.inDirection(dir));
}
public BCLFeatureBuilder offset(Vec3i dir) {
return modifier(new Offset(dir));
}
/**
* Cast a downward ray with max {@code distance} length to find the next solid Block.
*
* @param distance The maximum search Distance
* @return The instance it was called on
* @see #findSolidSurface(Direction, int) for Details
*/
public BCLFeatureBuilder findSolidFloor(int distance) {
return modifier(FindSolidInDirection.down(distance));
}
public BCLFeatureBuilder noiseBasedCount(float noiseLevel, int belowNoiseCount, int aboveNoiseCount) {
return modifier(NoiseThresholdCountPlacement.of(noiseLevel, belowNoiseCount, aboveNoiseCount));
}
public BCLFeatureBuilder extendDown(int min, int max) {
return modifier(new Extend(Direction.DOWN, UniformInt.of(min, max)));
}
public BCLFeatureBuilder inBasinOf(BlockPredicate... predicates) {
return modifier(new IsBasin(BlockPredicate.anyOf(predicates)));
}
public BCLFeatureBuilder inOpenBasinOf(BlockPredicate... predicates) {
return modifier(IsBasin.openTop(BlockPredicate.anyOf(predicates)));
}
public BCLFeatureBuilder is(BlockPredicate... predicates) {
return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.empty()));
}
public BCLFeatureBuilder isAbove(BlockPredicate... predicates) {
return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.of(Direction.DOWN.getNormal())));
}
public BCLFeatureBuilder isUnder(BlockPredicate... predicates) {
return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.of(Direction.UP.getNormal())));
}
public BCLFeatureBuilder findSolidCeil(int distance) {
return modifier(FindSolidInDirection.up(distance));
}
public BCLFeatureBuilder hasMinimumDownwardSpace() {
return modifier(MinEmptyFilter.down());
}
public BCLFeatureBuilder hasMinimumUpwardSpace() {
return modifier(MinEmptyFilter.up());
}
/**
* Cast a ray with max {@code distance} length to find the next solid Block. The ray will travel through replaceable
* Blocks (see {@link Material#isReplaceable()}) and will be accepted if it hits a block with the
* {@link CommonBlockTags#TERRAIN}-tag
*
* @param dir The direction the ray is cast
* @param distance The maximum search Distance
* @return The instance it was called on
* @see #findSolidSurface(Direction, int) for Details
*/
public BCLFeatureBuilder findSolidSurface(Direction dir, int distance) {
return modifier(new FindSolidInDirection(dir, distance, 0));
}
public BCLFeatureBuilder findSolidSurface(List<Direction> dir, int distance, boolean randomSelect) {
return modifier(new FindSolidInDirection(dir, distance, randomSelect, 0));
}
public BCLFeatureBuilder heightmap() {
return modifier(PlacementUtils.HEIGHTMAP);
}
public BCLFeatureBuilder heightmapTopSolid() {
return modifier(PlacementUtils.HEIGHTMAP_TOP_SOLID);
}
public BCLFeatureBuilder heightmapWorldSurface() {
return modifier(PlacementUtils.HEIGHTMAP_WORLD_SURFACE);
}
/**
* Builds a new {@link BCLFeature} instance. Features will be registered during this process.
*
* @param configuration any {@link FeatureConfiguration} for provided {@link Feature}.
* @return created {@link BCLFeature} instance.
*/
public BCLFeature buildAndRegister(FC configuration) {
PlacementModifier[] modifiers = modifications.toArray(new PlacementModifier[modifications.size()]);
return new BCLFeature(featureID, feature, decoration, configuration, modifiers);
}
/**
* Builds a new {@link BCLFeature} instance with {@code NONE} {@link FeatureConfiguration}.
* Features will be registered during this process.
*
* @return created {@link BCLFeature} instance.
*/
public BCLFeature buildAndRegister() {
if (this.feature == Feature.SIMPLE_BLOCK && provider != null)
return buildAndRegister((FC) new SimpleBlockConfiguration(provider));
return buildAndRegister((FC) FeatureConfiguration.NONE);
}
@Deprecated(forRemoval = true)
public BCLFeature build(FC configuration) {
return buildAndRegister(configuration);
}
@Deprecated(forRemoval = true)
public BCLFeature build() {
return buildAndRegister();
}
}

View file

@ -0,0 +1,255 @@
package org.betterx.bclib.api.v2.levelgen.features;
@Deprecated(forRemoval = true)
public class FastFeatures {
// @Deprecated(forRemoval = true)
// public static RandomPatchConfiguration grassPatch(BlockStateProvider stateProvider, int tries) {
// return FeatureUtils.simpleRandomPatchConfiguration(
// tries,
// PlacementUtils.onlyWhenEmpty(Feature.SIMPLE_BLOCK, new SimpleBlockConfiguration(stateProvider))
// );
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature<ScatterFeature<ScatterFeatureConfig.OnSolid>, ScatterFeatureConfig.OnSolid> vine(
// ResourceLocation location,
// boolean onFloor,
// boolean sparse,
// ScatterFeatureConfig.Builder builder
// ) {
// return scatter(location, onFloor, sparse, builder,
// org.betterx.bclib.api.v3.levelgen.features.BCLFeature.SCATTER_ON_SOLID
// );
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature scatter(
// ResourceLocation location,
// boolean onFloor,
// boolean sparse,
// ScatterFeatureConfig.Builder builder,
// Feature scatterFeature
// ) {
// BCLFeatureBuilder fBuilder = BCLFeatureBuilder.start(location, scatterFeature);
// if (onFloor) {
// fBuilder.findSolidFloor(3).isEmptyAbove2();
// builder.onFloor();
// } else {
// fBuilder.findSolidCeil(3).isEmptyBelow2();
// builder.onCeil();
// }
// if (sparse) {
// fBuilder.onceEvery(3);
// }
//
// return fBuilder
// .is(BlockPredicate.ONLY_IN_AIR_PREDICATE)
// .buildAndRegister(builder.build());
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature patch(ResourceLocation location, Block block) {
// return patch(location, block, 96, 7, 3);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patch(ResourceLocation location, Block block, int attempts, int xzSpread, int ySpread) {
// return patch(
// location,
// attempts,
// xzSpread,
// ySpread,
// Feature.SIMPLE_BLOCK,
// new SimpleBlockConfiguration(BlockStateProvider.simple(block))
// );
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patch(ResourceLocation location, BlockStateProvider provider, int attempts, int xzSpread, int ySpread) {
// return patch(
// location,
// attempts,
// xzSpread,
// ySpread,
// Feature.SIMPLE_BLOCK,
// new SimpleBlockConfiguration(provider)
// );
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature patchWitRandomInt(ResourceLocation location, Block block, IntegerProperty prop) {
// return patchWitRandomInt(location, block, prop, 96, 7, 3);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patchWitRandomInt(
// ResourceLocation location,
// Block block,
// IntegerProperty prop,
// int attempts,
// int xzSpread,
// int ySpread
// ) {
// return patch(
// location,
// attempts,
// xzSpread,
// ySpread,
// simple(location, ySpread, false, block.defaultBlockState(), prop)
// );
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// simple(
// ResourceLocation location,
// int searchDist,
// boolean rare,
// Feature<NoneFeatureConfiguration> feature
// ) {
// return simple(location, searchDist, rare, feature, NoneFeatureConfiguration.NONE);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// single(ResourceLocation location, Block block) {
// return single(location, BlockStateProvider.simple(block));
//
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// single(ResourceLocation location, BlockStateProvider provider) {
// return BCLFeatureBuilder
// .start(location, provider)
// .buildAndRegister();
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// simple(ResourceLocation location, Feature<NoneFeatureConfiguration> feature) {
// return BCLFeatureBuilder
// .start(location, feature)
// .buildAndRegister();
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// simple(
// ResourceLocation location,
// int searchDist,
// boolean rare,
// BlockState baseState,
// IntegerProperty property
// ) {
// int min = Integer.MAX_VALUE;
// int max = Integer.MIN_VALUE;
//
// for (Integer i : property.getPossibleValues()) {
// if (i < min) min = i;
// if (i > max) max = i;
// }
//
// return simple(
// location,
// searchDist,
// rare,
// Feature.SIMPLE_BLOCK,
// new SimpleBlockConfiguration(new RandomizedIntStateProvider(
// BlockStateProvider.simple(baseState),
// property,
// UniformInt.of(min, max)
// ))
// );
// }
//
// @Deprecated(forRemoval = true)
//
// public static <FC extends FeatureConfiguration> BCLFeature<Feature<FC>, FC>
// simple(
// ResourceLocation location,
// int searchDist,
// boolean rare,
// Feature<FC> feature,
// FC config
// ) {
// BCLFeatureBuilder builder = BCLFeatureBuilder
// .start(location, feature)
// .findSolidFloor(Math.min(12, searchDist))
// .is(BlockPredicate.ONLY_IN_AIR_PREDICATE);
// if (rare) {
// builder.onceEvery(4);
// }
// return builder.buildAndRegister(config);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patch(ResourceLocation location, Feature<NoneFeatureConfiguration> feature) {
// return patch(location, 96, 7, 3, feature, FeatureConfiguration.NONE);
// }
//
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patch(
// ResourceLocation location,
// int attempts,
// int xzSpread,
// int ySpread,
// Feature<NoneFeatureConfiguration> feature
// ) {
// return patch(location, attempts, xzSpread, ySpread, feature, FeatureConfiguration.NONE);
// }
//
// @Deprecated(forRemoval = true)
// public static <FC extends FeatureConfiguration> BCLFeature
// patch(
// ResourceLocation location,
// int attempts,
// int xzSpread,
// int ySpread,
// Feature<FC> feature,
// FC config
// ) {
// final BCLFeature SINGLE = simple(location, ySpread, false, feature, config);
// return patch(location, attempts, xzSpread, ySpread, SINGLE);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// wallPatch(
// ResourceLocation location,
// Block block,
// int attempts,
// int xzSpread,
// int ySpread
// ) {
// final BCLFeature SINGLE = simple(location, ySpread, false,
// org.betterx.bclib.api.v3.levelgen.features.BCLFeature.PLACE_BLOCK,
// new PlaceFacingBlockConfig(block, PlaceFacingBlockConfig.HORIZONTAL)
// );
// return patch(location, attempts, xzSpread, ySpread, SINGLE);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patch(
// ResourceLocation location,
// int attempts,
// int xzSpread,
// int ySpread,
// BCLFeature single
// ) {
// ResourceLocation patchLocation = new ResourceLocation(location.getNamespace(), location.getPath() + "_patch");
//
// return BCLFeatureBuilder
// .start(patchLocation, Feature.RANDOM_PATCH)
// .buildAndRegister(new RandomPatchConfiguration(attempts, xzSpread, ySpread, single.getPlacedFeature()));
// }
//
}

View file

@ -0,0 +1,11 @@
package org.betterx.bclib.api.v2.levelgen.features;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
/**
* @param <FC>
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature} instead
*/
@Deprecated(forRemoval = true)
public interface UserGrowableFeature<FC extends FeatureConfiguration> extends org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature<FC> {
}

View file

@ -0,0 +1,37 @@
package org.betterx.bclib.api.v2.levelgen.features.blockpredicates;
import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates;
import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.IsFullShape;
import com.mojang.serialization.Codec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate;
import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicateType;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates} instead
*/
@Deprecated(forRemoval = true)
public class Types {
/**
* @deprecated Please use {@link BlockPredicates#FULL_SHAPE} instead
*/
@Deprecated(forRemoval = true)
public static final BlockPredicateType<IsFullShape> FULL_SHAPE = BlockPredicates.FULL_SHAPE;
/**
* @param location
* @param codec
* @param <P>
* @return
* @deprecated Please use {@link BlockPredicates#register(ResourceLocation, Codec)} instead
*/
@Deprecated(forRemoval = true)
public static <P extends BlockPredicate> BlockPredicateType<P> register(ResourceLocation location, Codec<P> codec) {
return BlockPredicates.register(location, codec);
}
public static void ensureStaticInitialization() {
}
}

View file

@ -0,0 +1,50 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import org.betterx.bclib.api.v2.levelgen.features.BCLFeature;
import net.minecraft.core.Holder;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.minecraft.world.level.levelgen.placement.PlacementFilter;
import net.minecraft.world.level.levelgen.placement.PlacementModifier;
import java.util.Optional;
import org.jetbrains.annotations.NotNull;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.ConditionFeatureConfig} instead
*/
@Deprecated(forRemoval = true)
public class ConditionFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.ConditionFeatureConfig {
public ConditionFeatureConfig(@NotNull PlacementFilter filter, @NotNull BCLFeature okFeature) {
super(filter, okFeature);
}
public ConditionFeatureConfig(
@NotNull PlacementFilter filter,
@NotNull BCLFeature okFeature,
@NotNull BCLFeature failFeature
) {
super(filter, okFeature, failFeature);
}
public ConditionFeatureConfig(@NotNull PlacementFilter filter, @NotNull Holder<PlacedFeature> okFeature) {
super(filter, okFeature);
}
public ConditionFeatureConfig(
@NotNull PlacementFilter filter,
@NotNull Holder<PlacedFeature> okFeature,
@NotNull Holder<PlacedFeature> failFeature
) {
super(filter, okFeature, failFeature);
}
protected ConditionFeatureConfig(
@NotNull PlacementModifier filter,
@NotNull Holder<PlacedFeature> okFeature,
@NotNull Optional<Holder<PlacedFeature>> failFeature
) {
super(filter, okFeature, failFeature);
}
}

View file

@ -0,0 +1,32 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import net.minecraft.util.random.SimpleWeightedRandomList;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider;
import java.util.List;
@Deprecated(forRemoval = true)
public abstract class PlaceBlockFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.PlaceBlockFeatureConfig {
public PlaceBlockFeatureConfig(Block block) {
super(block);
}
public PlaceBlockFeatureConfig(BlockState state) {
super(state);
}
public PlaceBlockFeatureConfig(List<BlockState> states) {
super(states);
}
public PlaceBlockFeatureConfig(SimpleWeightedRandomList<BlockState> blocks) {
super(blocks);
}
public PlaceBlockFeatureConfig(BlockStateProvider blocks) {
super(blocks);
}
}

View file

@ -0,0 +1,37 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import net.minecraft.core.Direction;
import net.minecraft.util.random.SimpleWeightedRandomList;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider;
import java.util.List;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.PlaceFacingBlockConfig} instead
*/
@Deprecated(forRemoval = true)
public class PlaceFacingBlockConfig extends org.betterx.bclib.api.v3.levelgen.features.config.PlaceFacingBlockConfig {
public PlaceFacingBlockConfig(Block block, List<Direction> dir) {
super(block, dir);
}
public PlaceFacingBlockConfig(BlockState state, List<Direction> dir) {
super(state, dir);
}
public PlaceFacingBlockConfig(List<BlockState> states, List<Direction> dir) {
super(states, dir);
}
public PlaceFacingBlockConfig(SimpleWeightedRandomList<BlockState> blocks, List<Direction> dir) {
super(blocks, dir);
}
public PlaceFacingBlockConfig(BlockStateProvider blocks, List<Direction> dir) {
super(blocks, dir);
}
}

View file

@ -0,0 +1,548 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.blocks.BlockProperties;
import org.betterx.bclib.util.BlocksHelper;
import com.mojang.datafixers.util.Function15;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.util.valueproviders.ConstantInt;
import net.minecraft.util.valueproviders.IntProvider;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider;
import java.util.Optional;
public abstract class ScatterFeatureConfig implements FeatureConfiguration {
public interface Instancer<T extends ScatterFeatureConfig> extends Function15<BlockStateProvider, Optional<BlockStateProvider>, Optional<BlockStateProvider>, Optional<BlockState>, Float, Float, Float, Float, Integer, Integer, Float, Float, Float, Boolean, IntProvider, T> {
}
public final BlockStateProvider clusterBlock;
public final BlockStateProvider tipBlock;
public final BlockStateProvider bottomBlock;
public final Optional<BlockState> baseState;
public final float baseReplaceChance;
public final float chanceOfDirectionalSpread;
public final float chanceOfSpreadRadius2;
public final float chanceOfSpreadRadius3;
public final int minHeight;
public final int maxHeight;
public final float maxSpread;
public final float sizeVariation;
public final float floorChance;
public final IntProvider spreadCount;
public final boolean growWhileFree;
public ScatterFeatureConfig(
BlockStateProvider clusterBlock,
Optional<BlockStateProvider> tipBlock,
Optional<BlockStateProvider> bottomBlock,
Optional<BlockState> baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3,
int minHeight,
int maxHeight,
float maxSpread,
float sizeVariation,
float floorChance,
boolean growWhileFree,
IntProvider spreadCount
) {
this.clusterBlock = clusterBlock;
this.tipBlock = tipBlock.orElse(clusterBlock);
this.bottomBlock = bottomBlock.orElse(clusterBlock);
this.baseState = baseState;
this.baseReplaceChance = baseReplaceChance;
this.chanceOfDirectionalSpread = chanceOfDirectionalSpread;
this.chanceOfSpreadRadius2 = chanceOfSpreadRadius2;
this.chanceOfSpreadRadius3 = chanceOfSpreadRadius3;
this.minHeight = minHeight;
this.maxHeight = maxHeight;
this.maxSpread = maxSpread;
this.sizeVariation = sizeVariation;
this.floorChance = floorChance;
this.growWhileFree = growWhileFree;
this.spreadCount = spreadCount;
}
public boolean isFloor(RandomSource random) {
return random.nextFloat() < floorChance;
}
public abstract boolean isValidBase(BlockState state);
public abstract BlockState createBlock(int height, int maxHeight, RandomSource random, BlockPos pos);
public static <T extends ScatterFeatureConfig> Codec<T> buildCodec(Instancer<T> instancer) {
return RecordCodecBuilder.create((instance) -> instance
.group(
BlockStateProvider.CODEC
.fieldOf("cluster_block")
.forGetter((T cfg) -> cfg.clusterBlock),
BlockStateProvider.CODEC
.optionalFieldOf("tip_block")
.orElse(Optional.empty())
.forGetter((T cfg) -> cfg.tipBlock == cfg.clusterBlock
? Optional.empty()
: Optional.of(cfg.tipBlock)),
BlockStateProvider.CODEC
.optionalFieldOf("bottom_block")
.orElse(Optional.empty())
.forGetter((T cfg) -> cfg.bottomBlock == cfg.clusterBlock
? Optional.empty()
: Optional.of(cfg.bottomBlock)),
BlockState.CODEC
.optionalFieldOf("base_state")
.forGetter((T cfg) -> cfg.baseState),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("baseReplaceChance")
.orElse(1.0F)
.forGetter((T cfg) -> cfg.baseReplaceChance),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("chance_of_directional_spread")
.orElse(0.7F)
.forGetter((T cfg) -> cfg.chanceOfDirectionalSpread),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("chance_of_spread_radius2")
.orElse(0.5F)
.forGetter((T cfg) -> cfg.chanceOfSpreadRadius2),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("chance_of_spread_radius3")
.orElse(0.5F)
.forGetter((T cfg) -> cfg.chanceOfSpreadRadius3),
Codec
.intRange(1, 64)
.fieldOf("min_height")
.orElse(2)
.forGetter((T cfg) -> cfg.minHeight),
Codec
.intRange(1, 64)
.fieldOf("max_height")
.orElse(7)
.forGetter((T cfg) -> cfg.maxHeight),
Codec
.floatRange(0, 16)
.fieldOf("max_spread")
.orElse(2f)
.forGetter((T cfg) -> cfg.maxSpread),
Codec
.floatRange(0, 1)
.fieldOf("size_variation")
.orElse(0.7f)
.forGetter((T cfg) -> cfg.sizeVariation),
Codec
.floatRange(0, 1)
.fieldOf("floor_chance")
.orElse(0.5f)
.forGetter((T cfg) -> cfg.floorChance),
Codec
.BOOL
.fieldOf("grow_while_empty")
.orElse(false)
.forGetter((T cfg) -> cfg.growWhileFree),
IntProvider.codec(0, 64)
.fieldOf("length")
.orElse(UniformInt.of(0, 3))
.forGetter(cfg -> cfg.spreadCount)
)
.apply(instance, instancer)
);
}
public static class Builder<T extends ScatterFeatureConfig> {
private BlockStateProvider clusterBlock;
private BlockStateProvider tipBlock;
private BlockStateProvider bottomBlock;
private Optional<BlockState> baseState = Optional.empty();
private float baseReplaceChance = 0;
private float chanceOfDirectionalSpread = 0;
private float chanceOfSpreadRadius2 = 0;
private float chanceOfSpreadRadius3 = 0;
private int minHeight = 2;
private int maxHeight = 12;
private float maxSpread = 0;
private float sizeVariation = 0;
private float floorChance = 0.5f;
private boolean growWhileFree = false;
public IntProvider spreadCount = ConstantInt.of(0);
private final Instancer<T> instancer;
public Builder(Instancer<T> instancer) {
this.instancer = instancer;
}
public static <T extends ScatterFeatureConfig> Builder<T> start(Instancer<T> instancer) {
return new Builder<>(instancer);
}
public Builder<T> block(Block b) {
return block(b.defaultBlockState());
}
public Builder<T> singleBlock(Block b) {
return block(b.defaultBlockState()).heightRange(1, 1).spread(0, 0, ConstantInt.of(0));
}
public Builder<T> block(BlockState s) {
this.clusterBlock = BlockStateProvider.simple(s);
if (tipBlock == null) tipBlock = BlockStateProvider.simple(s);
if (bottomBlock == null) bottomBlock = BlockStateProvider.simple(s);
return this;
}
public Builder<T> tipBlock(BlockState s) {
tipBlock = BlockStateProvider.simple(s);
return this;
}
public Builder<T> bottomBlock(BlockState s) {
bottomBlock = BlockStateProvider.simple(s);
return this;
}
public Builder<T> tripleShape(Block s) {
return tripleShape(s.defaultBlockState());
}
public Builder<T> tripleShape(BlockState s) {
block(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.MIDDLE));
tipBlock(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.TOP));
bottomBlock(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.BOTTOM));
return this;
}
public Builder<T> tripleShapeCeil(Block s) {
return tripleShapeCeil(s.defaultBlockState());
}
public Builder<T> tripleShapeCeil(BlockState s) {
block(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.MIDDLE));
tipBlock(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.BOTTOM));
bottomBlock(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.TOP));
return this;
}
public Builder<T> block(BlockStateProvider s) {
this.clusterBlock = s;
if (tipBlock == null) tipBlock = s;
if (bottomBlock == null) bottomBlock = s;
return this;
}
public Builder<T> tipBlock(BlockStateProvider s) {
tipBlock = s;
return this;
}
public Builder<T> bottomBlock(BlockStateProvider s) {
bottomBlock = s;
return this;
}
public Builder<T> heightRange(int min, int max) {
minHeight = min;
maxHeight = max;
return this;
}
public Builder<T> growWhileFree() {
growWhileFree = true;
return this;
}
public Builder<T> minHeight(int h) {
minHeight = h;
return this;
}
public Builder<T> maxHeight(int h) {
maxHeight = h;
return this;
}
public Builder<T> generateBaseBlock(BlockState baseState) {
return generateBaseBlock(baseState, 1, 0, 0, 0);
}
public Builder<T> generateBaseBlock(BlockState baseState, float baseReplaceChance) {
return generateBaseBlock(baseState, baseReplaceChance, 0, 0, 0);
}
public Builder<T> generateBaseBlock(
BlockState baseState,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3
) {
return generateBaseBlock(
baseState,
1,
chanceOfDirectionalSpread,
chanceOfSpreadRadius2,
chanceOfSpreadRadius3
);
}
public Builder<T> generateBaseBlock(
BlockState baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3
) {
if (this.baseState.isPresent() && this.baseReplaceChance == 0) {
BCLib.LOGGER.error("Base generation was already selected.");
}
this.baseState = Optional.of(baseState);
this.baseReplaceChance = baseReplaceChance;
this.chanceOfDirectionalSpread = chanceOfDirectionalSpread;
this.chanceOfSpreadRadius2 = chanceOfSpreadRadius2;
this.chanceOfSpreadRadius3 = chanceOfSpreadRadius3;
return this;
}
public Builder<T> noSpread() {
return spread(0, 0, ConstantInt.of(0));
}
public Builder<T> spread(float maxSpread, float sizeVariation) {
return spread(maxSpread, sizeVariation, ConstantInt.of((int) Math.min(16, 4 * maxSpread * maxSpread)));
}
public Builder<T> spread(float maxSpread, float sizeVariation, IntProvider spreadCount) {
this.spreadCount = spreadCount; //
this.maxSpread = maxSpread;
this.sizeVariation = sizeVariation;
return this;
}
public Builder<T> floorChance(float chance) {
this.floorChance = chance;
return this;
}
public Builder<T> onFloor() {
this.floorChance = 1;
return this;
}
public Builder<T> onCeil() {
this.floorChance = 0;
return this;
}
public T build() {
return instancer.apply(
this.clusterBlock,
Optional.of(this.tipBlock),
Optional.of(this.bottomBlock),
this.baseState,
this.baseReplaceChance,
this.chanceOfDirectionalSpread,
this.chanceOfSpreadRadius2,
this.chanceOfSpreadRadius3,
this.minHeight,
this.maxHeight,
this.maxSpread,
this.sizeVariation,
this.floorChance,
this.growWhileFree,
this.spreadCount
);
}
}
public static Builder<OnSolid> startOnSolid() {
return Builder.start(OnSolid::new);
}
public static class OnSolid extends ScatterFeatureConfig {
public static final Codec<OnSolid> CODEC = buildCodec(OnSolid::new);
protected OnSolid(
BlockStateProvider clusterBlock,
Optional<BlockStateProvider> tipBlock,
Optional<BlockStateProvider> bottomBlock,
Optional<BlockState> baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3,
int minHeight,
int maxHeight,
float maxSpread,
float sizeVariation,
float floorChance,
boolean growWhileFree,
IntProvider spreadCount
) {
super(
clusterBlock,
tipBlock,
bottomBlock,
baseState,
baseReplaceChance,
chanceOfDirectionalSpread,
chanceOfSpreadRadius2,
chanceOfSpreadRadius3,
minHeight,
maxHeight,
maxSpread,
sizeVariation,
floorChance,
growWhileFree,
spreadCount
);
}
@Override
public boolean isValidBase(BlockState state) {
return BlocksHelper.isTerrain(state)
|| baseState.map(s -> state.is(s.getBlock())).orElse(false);
}
@Override
public BlockState createBlock(int height, int maxHeight, RandomSource random, BlockPos pos) {
if (height == 0) return this.bottomBlock.getState(random, pos);
return height == maxHeight
? this.tipBlock.getState(random, pos)
: this.clusterBlock.getState(random, pos);
}
}
public static Builder<ExtendTop> startExtendTop() {
return Builder.start(ExtendTop::new);
}
public static class ExtendTop extends ScatterFeatureConfig {
public static final Codec<ExtendTop> CODEC = buildCodec(ExtendTop::new);
protected ExtendTop(
BlockStateProvider clusterBlock,
Optional<BlockStateProvider> tipBlock,
Optional<BlockStateProvider> bottomBlock,
Optional<BlockState> baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3,
int minHeight,
int maxHeight,
float maxSpread,
float sizeVariation,
float floorChance,
boolean growWhileFree,
IntProvider spreadCount
) {
super(
clusterBlock,
tipBlock,
bottomBlock,
baseState,
baseReplaceChance,
chanceOfDirectionalSpread,
chanceOfSpreadRadius2,
chanceOfSpreadRadius3,
minHeight,
maxHeight,
maxSpread,
sizeVariation,
floorChance,
growWhileFree,
spreadCount
);
}
@Override
public boolean isValidBase(BlockState state) {
return BlocksHelper.isTerrain(state)
|| baseState.map(s -> state.is(s.getBlock())).orElse(false);
}
@Override
public BlockState createBlock(int height, int maxHeight, RandomSource random, BlockPos pos) {
if (height == 0) return this.bottomBlock.getState(random, pos);
if (height == 1) return this.clusterBlock.getState(random, pos);
return this.tipBlock.getState(random, pos);
}
}
public static Builder<ExtendBottom> startExtendBottom() {
return Builder.start(ExtendBottom::new);
}
public static class ExtendBottom extends ScatterFeatureConfig {
public static final Codec<ExtendBottom> CODEC = buildCodec(ExtendBottom::new);
protected ExtendBottom(
BlockStateProvider clusterBlock,
Optional<BlockStateProvider> tipBlock,
Optional<BlockStateProvider> bottomBlock,
Optional<BlockState> baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3,
int minHeight,
int maxHeight,
float maxSpread,
float sizeVariation,
float floorChance,
boolean growWhileFree,
IntProvider spreadCount
) {
super(
clusterBlock,
tipBlock,
bottomBlock,
baseState,
baseReplaceChance,
chanceOfDirectionalSpread,
chanceOfSpreadRadius2,
chanceOfSpreadRadius3,
minHeight,
maxHeight,
maxSpread,
sizeVariation,
floorChance,
growWhileFree,
spreadCount
);
}
@Override
public boolean isValidBase(BlockState state) {
return BlocksHelper.isTerrain(state)
|| baseState.map(s -> state.is(s.getBlock())).orElse(false);
}
@Override
public BlockState createBlock(int height, int maxHeight, RandomSource random, BlockPos pos) {
if (height == maxHeight) return this.tipBlock.getState(random, pos);
if (height == maxHeight - 1) return this.clusterBlock.getState(random, pos);
return this.bottomBlock.getState(random, pos);
}
}
}

View file

@ -0,0 +1,17 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import net.minecraft.core.Holder;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import java.util.List;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.SequenceFeatureConfig instead}
*/
@Deprecated(forRemoval = true)
public class SequenceFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.SequenceFeatureConfig {
public SequenceFeatureConfig(List<Holder<PlacedFeature>> features) {
super(features);
}
}

View file

@ -0,0 +1,23 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import org.betterx.bclib.api.v2.levelgen.structures.StructurePlacementType;
import org.betterx.bclib.api.v2.levelgen.structures.StructureWorldNBT;
import net.minecraft.resources.ResourceLocation;
import java.util.List;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig} instead
*/
@Deprecated(forRemoval = true)
public class TemplateFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig {
public TemplateFeatureConfig(ResourceLocation location, int offsetY, StructurePlacementType type) {
super(location, offsetY, type);
}
public TemplateFeatureConfig(List<StructureWorldNBT> structures) {
super(structures);
}
}

View file

@ -0,0 +1,8 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.ConditionFeature} instead.
*/
@Deprecated(forRemoval = true)
public class ConditionFeature extends org.betterx.bclib.api.v3.levelgen.features.features.ConditionFeature {
}

View file

@ -0,0 +1,45 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import org.betterx.bclib.util.BlocksHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap.Types;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
public abstract class DefaultFeature extends Feature<NoneFeatureConfiguration> {
public static final BlockState AIR = Blocks.AIR.defaultBlockState();
public static final BlockState WATER = Blocks.WATER.defaultBlockState();
public DefaultFeature() {
super(NoneFeatureConfiguration.CODEC);
}
public static int getYOnSurface(WorldGenLevel world, int x, int z) {
return world.getHeight(Types.WORLD_SURFACE, x, z);
}
public static int getYOnSurfaceWG(WorldGenLevel world, int x, int z) {
return world.getHeight(Types.WORLD_SURFACE_WG, x, z);
}
public static BlockPos getPosOnSurface(WorldGenLevel world, BlockPos pos) {
return world.getHeightmapPos(Types.WORLD_SURFACE, pos);
}
public static BlockPos getPosOnSurfaceWG(WorldGenLevel world, BlockPos pos) {
return world.getHeightmapPos(Types.WORLD_SURFACE_WG, pos);
}
public static BlockPos getPosOnSurfaceRaycast(WorldGenLevel world, BlockPos pos) {
return getPosOnSurfaceRaycast(world, pos, 256);
}
public static BlockPos getPosOnSurfaceRaycast(WorldGenLevel world, BlockPos pos, int dist) {
int h = BlocksHelper.downRay(world, pos, dist);
return pos.below(h);
}
}

View file

@ -0,0 +1,8 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.MarkPostProcessingFeature} instead.
*/
@Deprecated(forRemoval = true)
public class MarkPostProcessingFeature extends org.betterx.bclib.api.v3.levelgen.features.features.MarkPostProcessingFeature {
}

View file

@ -0,0 +1,16 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import org.betterx.bclib.api.v3.levelgen.features.config.PlaceBlockFeatureConfig;
import com.mojang.serialization.Codec;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.PlaceBlockFeature} instead.
*/
@Deprecated(forRemoval = true)
public class PlaceBlockFeature<FC extends PlaceBlockFeatureConfig> extends org.betterx.bclib.api.v3.levelgen.features.features.PlaceBlockFeature<FC> {
public PlaceBlockFeature(Codec<FC> codec) {
super(codec);
}
}

View file

@ -0,0 +1,249 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import org.betterx.bclib.api.v2.levelgen.features.config.ScatterFeatureConfig;
import org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature;
import org.betterx.bclib.util.BlocksHelper;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import java.util.Optional;
@Deprecated(forRemoval = true)
public class ScatterFeature<FC extends ScatterFeatureConfig>
extends Feature<FC> implements UserGrowableFeature<FC> {
public ScatterFeature(Codec<FC> configCodec) {
super(configCodec);
}
@Override
public boolean place(FeaturePlaceContext<FC> featurePlaceContext) {
final WorldGenLevel level = featurePlaceContext.level();
final BlockPos origin = featurePlaceContext.origin();
final RandomSource random = featurePlaceContext.random();
ScatterFeatureConfig config = featurePlaceContext.config();
Optional<Direction> direction = getTipDirection(level, origin, random, config);
if (direction.isEmpty()) {
return false;
}
BlockPos basePos = origin.relative(direction.get(), -1);
int i = (int) (random.nextFloat() * (1 + config.maxHeight - config.minHeight) + config.minHeight);
growCenterPillar(level, origin, basePos, direction.get(), i, config, random);
return true;
}
protected void growCenterPillar(
LevelAccessor level,
BlockPos origin,
BlockPos basePos,
Direction direction,
int centerHeight,
ScatterFeatureConfig config,
RandomSource random
) {
if (config.isValidBase(level.getBlockState(basePos))) {
final Direction surfaceDirection = direction.getOpposite();
BlockPos.MutableBlockPos POS = new BlockPos.MutableBlockPos();
int adaptedHeight = freeHeight(level, direction, centerHeight, config, origin);
buildPillarWithBase(level, origin, basePos, direction, adaptedHeight, config, random, false);
final double distNormalizer = (config.maxSpread * Math.sqrt(2));
final int tryCount = config.spreadCount.sample(random);
for (int i = 0; i < tryCount; i++) {
int x = origin.getX() + (int) (random.nextGaussian() * config.maxSpread);
int z = origin.getZ() + (int) (random.nextGaussian() * config.maxSpread);
POS.set(x, basePos.getY(), z);
if (BlocksHelper.findSurroundingSurface(level, POS, surfaceDirection, 4, config::isValidBase)) {
int myHeight = freeHeight(
level,
direction,
centerHeight,
config,
POS
);
int dx = x - POS.getX();
int dz = z - POS.getZ();
float sizeFactor = (1 - (float) (Math.sqrt(dx * dx + dz * dz) / distNormalizer));
sizeFactor = (1 - (random.nextFloat() * config.sizeVariation)) * sizeFactor;
myHeight = (int) Math.min(Math.max(
config.minHeight,
config.minHeight + sizeFactor * (myHeight - config.minHeight)
), config.maxHeight);
BlockState baseState = level.getBlockState(POS.relative(direction.getOpposite()));
if (!config.isValidBase(baseState)) {
System.out.println("Starting from " + baseState + " at " + POS.relative(direction.getOpposite()));
}
buildPillarWithBase(level,
POS,
POS.relative(direction.getOpposite()),
direction,
myHeight,
config,
random, false
);
}
}
}
}
private int freeHeight(
LevelAccessor level,
Direction direction,
int defaultHeight,
ScatterFeatureConfig config,
BlockPos POS
) {
int myHeight;
if (config.growWhileFree) {
myHeight = BlocksHelper.blockCount(
level,
POS,
direction,
config.maxHeight,
BlocksHelper::isFree
);
} else {
myHeight = defaultHeight;
}
return Math.max(config.minHeight, myHeight);
}
private void buildPillarWithBase(
LevelAccessor level,
BlockPos origin,
BlockPos basePos,
Direction direction,
int height,
ScatterFeatureConfig config,
RandomSource random,
boolean force
) {
if (force || BlocksHelper.isFreeSpace(level, origin, direction, height, BlocksHelper::isFree)) {
createPatchOfBaseBlocks(level, random, basePos, config);
BlockState bottom = config.bottomBlock.getState(random, origin);
if (bottom.canSurvive(level, origin)) {
buildPillar(level, origin, direction, height, config, random);
}
}
}
private void buildPillar(
LevelAccessor level,
BlockPos origin,
Direction direction,
int height,
ScatterFeatureConfig config,
RandomSource random
) {
final BlockPos.MutableBlockPos POS = origin.mutable();
for (int size = 0; size < height; size++) {
BlockState state = config.createBlock(size, height - 1, random, POS);
BlocksHelper.setWithoutUpdate(level, POS, state);
POS.move(direction);
}
}
private Optional<Direction> getTipDirection(
LevelAccessor levelAccessor,
BlockPos blockPos,
RandomSource randomSource,
ScatterFeatureConfig config
) {
boolean onCeil = config.floorChance < 1 && config.isValidBase(levelAccessor.getBlockState(blockPos.above()));
boolean onFloor = config.floorChance > 0 && config.isValidBase(levelAccessor.getBlockState(blockPos.below()));
if (onCeil && onFloor) {
return Optional.of(config.isFloor(randomSource) ? Direction.DOWN : Direction.UP);
}
if (onCeil) {
return Optional.of(Direction.DOWN);
}
if (onFloor) {
return Optional.of(Direction.UP);
}
return Optional.empty();
}
private void createPatchOfBaseBlocks(
LevelAccessor levelAccessor,
RandomSource randomSource,
BlockPos blockPos,
ScatterFeatureConfig config
) {
if (config.baseState.isPresent() && config.baseReplaceChance > 0 && randomSource.nextFloat() < config.baseReplaceChance) {
final BlockState baseState = config.baseState.get();
BlockPos pos;
for (Direction direction : Direction.Plane.HORIZONTAL) {
if (randomSource.nextFloat() > config.chanceOfDirectionalSpread) continue;
pos = blockPos.relative(direction);
placeBaseBlockIfPossible(levelAccessor, pos, baseState);
if (randomSource.nextFloat() > config.chanceOfSpreadRadius2) continue;
pos = pos.relative(Direction.getRandom(randomSource));
placeBaseBlockIfPossible(levelAccessor, pos, baseState);
if (randomSource.nextFloat() > config.chanceOfSpreadRadius3) continue;
pos = pos.relative(Direction.getRandom(randomSource));
placeBaseBlockIfPossible(levelAccessor, pos, baseState);
}
placeBaseBlockIfPossible(levelAccessor, blockPos, baseState);
}
}
protected void placeBaseBlockIfPossible(
LevelAccessor levelAccessor,
BlockPos blockPos,
BlockState baseState
) {
BlockState blockState = levelAccessor.getBlockState(blockPos);
if (BlocksHelper.isTerrain(blockState)) {
levelAccessor.setBlock(blockPos, baseState, 2);
}
}
@Override
public boolean grow(
ServerLevelAccessor level,
BlockPos origin,
RandomSource random,
FC config
) {
Optional<Direction> oDirection = getTipDirection(level, origin, random, config);
if (oDirection.isEmpty()) {
return false;
}
Direction direction = oDirection.get();
BlockPos basePos = origin.relative(direction, -1);
if (config.isValidBase(level.getBlockState(basePos))) {
int centerHeight = (int) (random.nextFloat() * (1 + config.maxHeight - config.minHeight) + config.minHeight);
centerHeight = freeHeight(
level,
direction,
centerHeight,
config,
origin.relative(direction, 1)
) + 1;
buildPillarWithBase(level, origin, basePos, direction, centerHeight, config, random, true);
}
return false;
}
}

View file

@ -0,0 +1,9 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.SequenceFeature} instead.
*/
@Deprecated(forRemoval = true)
public class SequenceFeature extends org.betterx.bclib.api.v3.levelgen.features.features.SequenceFeature {
}

View file

@ -0,0 +1,51 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import org.betterx.bclib.util.BlocksHelper;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
import java.util.Optional;
@Deprecated(forRemoval = true)
public abstract class SurfaceFeature<T extends FeatureConfiguration> extends Feature<T> {
public static abstract class DefaultConfiguration extends SurfaceFeature<NoneFeatureConfiguration> {
protected DefaultConfiguration() {
super(NoneFeatureConfiguration.CODEC);
}
}
protected SurfaceFeature(Codec<T> codec) {
super(codec);
}
protected abstract boolean isValidSurface(BlockState state);
protected int minHeight(FeaturePlaceContext<T> ctx) {
return ctx.chunkGenerator().getSeaLevel();
}
@Override
public boolean place(FeaturePlaceContext<T> ctx) {
Optional<BlockPos> pos = BlocksHelper.findSurfaceBelow(
ctx.level(),
ctx.origin(),
minHeight(ctx),
this::isValidSurface
);
if (pos.isPresent()) {
generate(pos.get(), ctx);
return true;
}
return false;
}
protected abstract void generate(BlockPos centerPos, FeaturePlaceContext<T> ctx);
}

View file

@ -0,0 +1,16 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig;
import com.mojang.serialization.Codec;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.TemplateFeature} instead.
*/
@Deprecated(forRemoval = true)
public class TemplateFeature<FC extends TemplateFeatureConfig> extends org.betterx.bclib.api.v3.levelgen.features.features.TemplateFeature<FC> {
public TemplateFeature(Codec<FC> codec) {
super(codec);
}
}

View file

@ -0,0 +1,40 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.WeightedPlacedFeature;
import net.minecraft.world.level.levelgen.feature.configurations.RandomFeatureConfiguration;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
@Deprecated(forRemoval = true)
public class WeightedRandomSelectorFeature extends Feature<RandomFeatureConfiguration> {
public WeightedRandomSelectorFeature() {
super(RandomFeatureConfiguration.CODEC);
}
public boolean place(FeaturePlaceContext<RandomFeatureConfiguration> ctx) {
final WorldGenLevel level = ctx.level();
final ChunkGenerator generator = ctx.chunkGenerator();
final RandomFeatureConfiguration cfg = ctx.config();
final RandomSource random = ctx.random();
final BlockPos pos = ctx.origin();
PlacedFeature selected = cfg.defaultFeature.value();
if (!cfg.features.isEmpty()) {
final float totalWeight = cfg.features.stream().map(w -> w.chance).reduce(0.0f, (p, c) -> p + c);
float bar = random.nextFloat() * totalWeight;
for (WeightedPlacedFeature f : cfg.features) {
selected = f.feature.value();
bar -= f.chance;
if (bar < 0) break;
}
}
return selected.place(level, generator, random, pos);
}
}

View file

@ -0,0 +1,9 @@
package org.betterx.bclib.api.v2.levelgen.features.placement;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.All} instead.
*/
@Deprecated(forRemoval = true)
public class All extends org.betterx.bclib.api.v3.levelgen.features.placement.All {
}

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