Compare commits
1145 commits
1.17
...
1.18.2-bac
Author | SHA1 | Date | |
---|---|---|---|
|
0c557e119b | ||
|
cee6f167c7 | ||
|
92ff27f2f8 | ||
|
beb2d97304 | ||
|
c4077a42fd | ||
|
8861c0645f | ||
|
0a75eb0c5d | ||
|
020b8880e1 | ||
|
32e7ffec69 | ||
|
94c08e79b2 | ||
|
85e1d35496 | ||
|
a73bd23ddf | ||
|
3b0f609776 | ||
|
de94a808d0 | ||
|
faa347c5bb | ||
|
bb928d5feb | ||
|
44a008bcf0 | ||
|
9b537683ea | ||
|
5bf8c4d669 | ||
|
1186e962f6 | ||
|
7d3ef6c756 | ||
|
e670fea21a | ||
|
910314a1e8 | ||
|
f80c12f796 | ||
|
3be3fdd2ea | ||
|
f0fdcdba05 | ||
|
82023316c6 | ||
|
c042d00c23 | ||
|
e0f8cb24b6 | ||
|
25948c7c6b | ||
|
b3c58b42e9 | ||
|
0eb857e293 | ||
|
102db44595 | ||
|
3e81071b96 | ||
|
89737d5f6c | ||
|
2bfb7a0687 | ||
|
204f60174b | ||
|
c9baae0724 | ||
|
85f1d7b1e4 | ||
|
d6cf5a6cd9 | ||
|
a3aae4c016 | ||
|
600bb6f9ac | ||
|
e35fe997c1 | ||
|
e411dc10d9 | ||
|
90d0c191f5 | ||
|
c5c61a11fd | ||
|
aaba0cec25 | ||
|
b1e21b097b | ||
|
9e3dbbec76 | ||
|
f4b143965f | ||
|
6419e11414 | ||
|
b045627030 | ||
|
598d8f790b | ||
|
0e0b7a5f51 | ||
|
753d0dd658 | ||
|
65e9140812 | ||
|
2f6091f9d3 | ||
|
f83648a099 | ||
|
ae5b3e1417 | ||
|
37a26f25bb | ||
|
44a65e8bae | ||
|
e3b3ea7872 | ||
|
3f04b85565 | ||
|
1de4db3cde | ||
|
d5cf63427b | ||
|
59756e6dca | ||
|
185453b0c4 | ||
|
473ed9a165 | ||
|
600aa50212 | ||
|
4c54e6de31 | ||
|
dd27373063 | ||
|
e70823a587 | ||
|
a6218a1da3 | ||
|
278d8c6201 | ||
|
8850752f25 | ||
|
74e465a68c | ||
|
b37399523a | ||
|
4367babec5 | ||
|
5d85595c5a | ||
|
c1d5ca7b9b | ||
|
717f220af1 | ||
|
9d6bf741e3 | ||
|
52e1db4551 | ||
|
08d155c4d3 | ||
|
35918240f9 | ||
|
4dc07f6567 | ||
|
14443761d7 | ||
|
2f4f71b83a | ||
|
f1afb99b38 | ||
|
f007bcfa31 | ||
|
cb5852b841 | ||
|
decaf4af7a | ||
|
4307c11aab | ||
|
eb2d25ce11 | ||
|
f07a651279 | ||
|
6b2c997baf | ||
|
066153bbab | ||
|
4d6072ed36 | ||
|
cdbc6a6fcb | ||
|
fcb91b25e0 | ||
|
03a94d544d | ||
|
a064f2d6bb | ||
|
427d6dbdfd | ||
|
fb19ebaae6 | ||
|
877691011f | ||
|
2a03b46a98 | ||
|
ec764f99e6 | ||
|
e1883409fa | ||
|
fc280cac4c | ||
|
524f274b18 | ||
|
cc36c090a1 | ||
|
da1d4c6b40 | ||
|
3725fbb029 | ||
|
c2e5096056 | ||
|
ecb0f6fb99 | ||
|
70b01dc00e | ||
|
057e6de3de | ||
|
184f3a6448 | ||
|
25fa53541f | ||
|
67d09676c4 | ||
|
78c85b5b8b | ||
|
a651cb932e | ||
|
def4fc3566 | ||
|
c93a942eee | ||
|
586485fe48 | ||
|
73cd08fa69 | ||
|
35b968eb93 | ||
|
c74b7b2d28 | ||
|
d63d773bb8 | ||
|
8a41656182 | ||
|
a1d6de908c | ||
|
c4875b992a | ||
|
d3b6fa7d80 | ||
|
093f3bb21a | ||
|
f72081620a | ||
|
7c810cd3a7 | ||
|
90cfcaa6c4 | ||
|
c6d7050651 | ||
|
e850173d9e | ||
|
a83117b8a9 | ||
|
2253913c7c | ||
|
f6d0cc7a38 | ||
|
04fe6d33d7 | ||
|
b9ee21085b | ||
|
104b87d874 | ||
|
59529c6c94 | ||
|
aff1b66e19 | ||
|
db25e8e970 | ||
|
144422cfbc | ||
|
bb08368511 | ||
|
e49323be93 | ||
|
31ec9c58f2 | ||
|
e3c7b00758 | ||
|
c429ee3a38 | ||
|
c346e1df65 | ||
|
8eddf9f615 | ||
|
852e5a6abc | ||
|
079b51e3f6 | ||
|
722343c103 | ||
|
562a1d228a | ||
|
eb3e408613 | ||
|
393dbb5114 | ||
|
47935ccdf5 | ||
|
bd46ba7fb6 | ||
|
0c7f2a5b4d | ||
|
1e56418d26 | ||
|
f06bf41fa4 | ||
|
74f4708196 | ||
|
c87175159b | ||
|
b731ba6598 | ||
|
8227cf2f6a | ||
|
73b20f88c5 | ||
|
a7ba94a07c | ||
|
4d152c596e | ||
|
29d99fecfe | ||
|
af3fca0c41 | ||
|
223d35febe | ||
|
261099babd | ||
|
f7b4c8a618 | ||
|
db27e14429 | ||
|
72a707f456 | ||
|
7285ada6d6 | ||
|
55d243bf13 | ||
|
5ebf159965 | ||
|
f6d5f85ec1 | ||
|
c658a24750 | ||
|
37fbf7eafe | ||
|
2ac472f716 | ||
|
56e63f3ce2 | ||
|
505006ed17 | ||
|
b07aec8bb6 | ||
|
e52230e7e3 | ||
|
0bcfa65f0c | ||
|
c9daf3c5d3 | ||
|
f0d1f1e453 | ||
|
db0c89e7e7 | ||
|
f94a8f158f | ||
|
97a65a3707 | ||
|
90921451dd | ||
|
4f4ac722b1 | ||
|
9496de1438 | ||
|
e2b7f6849a | ||
|
c14928ea80 | ||
|
4a95927e1c | ||
|
0149b17a1a | ||
|
51bd560fa0 | ||
|
51a0e7cf74 | ||
|
e7486c8aa4 | ||
|
f82d8b6178 | ||
|
3a61c4cb86 | ||
|
49edee32a8 | ||
|
35ce65674b | ||
|
7b9936af05 | ||
|
6adf6486ac | ||
|
aa4133fad2 | ||
|
4321017c25 | ||
|
e05801277e | ||
|
3512f82775 | ||
|
2623a817d0 | ||
|
359ad3c46b | ||
|
aede1b6f2b | ||
|
38f337cd15 | ||
|
86a92c560b | ||
|
9ac0d78d30 | ||
|
95434107ec | ||
|
155d3663df | ||
|
5272861140 | ||
|
abe18ae923 | ||
|
2168787ac2 | ||
|
3e41a4630d | ||
|
5d970a27ba | ||
|
d985792cae | ||
|
50fe9c2342 | ||
|
bc565577cb | ||
|
3a514f7a78 | ||
|
f70db44ffd | ||
|
51ec0596da | ||
|
54b55be51a | ||
|
b8b12623bf | ||
|
6c10735874 | ||
|
7dd6d2de2c | ||
|
48afc2efc1 | ||
|
9e4931e1af | ||
|
bdf30109f6 | ||
|
e08d85b605 | ||
|
3bc2018333 | ||
|
a64e437c3c | ||
|
919e3c25c5 | ||
|
7a891d71ee | ||
|
9d0a640173 | ||
|
e10994a1e8 | ||
|
f0f6d7026a | ||
|
f4f2e85432 | ||
|
781d6d4709 | ||
|
dd0c259b56 | ||
|
faa20c608e | ||
|
f45eaddde6 | ||
|
7359c64fff | ||
|
7466048a22 | ||
|
0a5a608b7d | ||
|
ab0895d48c | ||
|
9f8409ebe0 | ||
|
fdc068f6bb | ||
|
375eb09bb9 | ||
|
e8dbbffa8e | ||
|
c6742982df | ||
|
d533f93113 | ||
|
47b537f65d | ||
|
ace65fa671 | ||
|
3ee10482ab | ||
|
cb9459f176 | ||
|
b84a5a1b5d | ||
|
5ff00cede0 | ||
|
558bd7d326 | ||
|
a57845f96b | ||
|
b6321cec93 | ||
|
aaa0acdd09 | ||
|
45b014cd3c | ||
|
38b8883b6a | ||
|
dc7a875679 | ||
|
b7a74e971b | ||
|
c7117fa180 | ||
|
cb7d8cf53d | ||
|
d4c2909f17 | ||
|
fe4b426b5e | ||
|
374c85cb6d | ||
|
d07afd9b01 | ||
|
05eac6f539 | ||
|
4ba3a71a68 | ||
|
50f623477f | ||
|
89440dd9d7 | ||
|
b60d20196f | ||
|
0d6d24b748 | ||
|
470d4a6c1e | ||
|
6a5ba519c8 | ||
|
819039d50d | ||
|
97d2e6d146 | ||
|
ab87100187 | ||
|
be2e206383 | ||
|
ff7eda1287 | ||
|
ea999a9ba5 | ||
|
c057ca3e99 | ||
|
dee3e89b3b | ||
|
a24e03e411 | ||
|
a52f46a933 | ||
|
cdb7b305d9 | ||
|
4b550b9e69 | ||
|
0e1627958a | ||
|
1fd01a5113 | ||
|
8f0b52a1fe | ||
|
0568510a88 | ||
|
57fdcafb6f | ||
|
7dc316e7e4 | ||
|
ecc792ed9a | ||
|
c783d007b1 | ||
|
12a8b0c23a | ||
|
5a30e60d31 | ||
|
a9c9a7359b | ||
|
61f9854cd5 | ||
|
a2acd50658 | ||
|
5f17127ba5 | ||
|
7e981ca1d9 | ||
|
4f7d0939e3 | ||
|
443042c6ef | ||
|
d817a69ae7 | ||
|
6bb218416f | ||
|
03eda716fe | ||
|
7cb0d5de6c | ||
|
e2c4f91f58 | ||
|
824e78abf7 | ||
|
6c015a9a53 | ||
|
2eccb1cb9e | ||
|
ff70a2b1a8 | ||
|
86a3480ce0 | ||
|
3c746a8dc4 | ||
|
305ac05ac1 | ||
|
2dbbfe04d8 | ||
|
48db196c7b | ||
|
557e69080b | ||
|
65eb31c3e9 | ||
|
8100e59baf | ||
|
31ea19552d | ||
|
74a156389e | ||
|
e013f80912 | ||
|
39d5f58f0c | ||
|
de7e7eb4ce | ||
|
b46875599d | ||
|
bed87c7d8a | ||
|
51e787c57e | ||
|
a5c3c97630 | ||
|
f756673128 | ||
|
1d43176853 | ||
|
29ef9f0eb6 | ||
|
0f26699bc0 | ||
|
0c24a03b18 | ||
|
7939c4ec2e | ||
|
a8fd496a8b | ||
|
b2ce0c155e | ||
|
0ed7fa9dcc | ||
|
513fea6968 | ||
|
c0ce7f6e35 | ||
|
aef34d30da | ||
|
009028624e | ||
|
89516870c9 | ||
|
96820cff84 | ||
|
0a438ae0e4 | ||
|
7ce674b1f6 | ||
|
a43c885db6 | ||
|
88dc5e5746 | ||
|
b3fba685b7 | ||
|
726a73bfad | ||
|
41260267c6 | ||
|
d802de90f9 | ||
|
ebba0dde6c | ||
|
89c5cd215b | ||
|
baf1e0f4b1 | ||
|
23df13bde8 | ||
|
01f728c282 | ||
|
c3e2c74283 | ||
|
14fcb93e3b | ||
|
fffbc6acc2 | ||
|
acfeee7b9b | ||
|
d5bc2de350 | ||
|
db3ed18399 | ||
|
0ff7799785 | ||
|
ba89751ea8 | ||
|
db082ef1be | ||
|
7ab5377a03 | ||
|
fe5dd3e32e | ||
|
b53ed9842d | ||
|
44517e0749 | ||
|
7b789ba57a | ||
|
d78319802d | ||
|
03635584d6 | ||
|
75338586ab | ||
|
611fb4d50c | ||
|
5aa06315d4 | ||
|
be3876f8a6 | ||
|
05ed11978d | ||
|
b0f6dd6bff | ||
|
11985395ae | ||
|
3584432a96 | ||
|
a62e3a5980 | ||
|
49a47de5f8 | ||
|
0594b9f420 | ||
|
7b96cfee10 | ||
|
be64c38293 | ||
|
4426f74d62 | ||
|
dfd72b9f1d | ||
|
b9307183d7 | ||
|
f2fd7ab55b | ||
|
ec61972724 | ||
|
8ebcf2352a | ||
|
95284640e0 | ||
|
fa3e82ed4e | ||
|
e53e14eb0e | ||
|
128aed9a14 | ||
|
76c33e9bdd | ||
|
637a5a25b2 | ||
|
11c116618a | ||
|
013e7d4aa6 | ||
|
83d2a578b3 | ||
|
2568319493 | ||
|
6b156e579a | ||
|
7dd8554aff | ||
|
83110b74a5 | ||
|
68ca682324 | ||
|
384fe3a327 | ||
|
03968ccda3 | ||
|
63946e049d | ||
|
9622fd393c | ||
|
f9325397ad | ||
|
287b411f88 | ||
|
192d78e959 | ||
|
9737493069 | ||
|
6c18b1e842 | ||
|
6100912627 | ||
|
93d29508a8 | ||
|
9a9bf2c6de | ||
|
663b0ef062 | ||
|
1f246b7d7d | ||
|
971806922d | ||
|
b03dc50726 | ||
|
75a0b3d7d2 | ||
|
c947af1211 | ||
|
3cb78dd5a5 | ||
|
8abf92eca7 | ||
|
fe8b20dcba | ||
|
6d279852f0 | ||
|
717c0d7fb8 | ||
|
544af449ee | ||
|
6abf34d8bc | ||
|
8bddd64216 | ||
|
2d8b7fe70a | ||
|
9ae9c318bd | ||
|
1150816af2 | ||
|
3cac6cad3d | ||
|
a27806620f | ||
|
c0d186fc9f | ||
|
eb827a58bd | ||
|
3e203d4e47 | ||
|
f17d7d9d1d | ||
|
cc202f7e35 | ||
|
5e88bd3c92 | ||
|
7549803896 | ||
|
8809151a8b | ||
|
572ee609d5 | ||
|
a8bb325bfb | ||
|
388226efa6 | ||
|
e8786eb939 | ||
|
7594b029c1 | ||
|
e6d481387d | ||
|
f0c77abba6 | ||
|
61cf9711a6 | ||
|
d738e8ae75 | ||
|
ba7489ee79 | ||
|
b60df41c6c | ||
|
1bb15de385 | ||
|
72728ffac4 | ||
|
3d45114584 | ||
|
192a537939 | ||
|
08e400ae20 | ||
|
0d6874085d | ||
|
704112d832 | ||
|
3e18eb839f | ||
|
43f6d72dda | ||
|
67d835b59b | ||
|
abd445114f | ||
|
5a0a6d4d3f | ||
|
81f6de2ac2 | ||
|
538003b0c7 | ||
|
294d59a88b | ||
|
75b8257085 | ||
|
79bce94121 | ||
|
77801986b5 | ||
|
d8de624fd1 | ||
|
5ca6a92dd0 | ||
|
23a36a785b | ||
|
d14e1c3952 | ||
|
0298ab131f | ||
|
0a3be15ff3 | ||
|
7f5e27397c | ||
|
4c41bb1764 | ||
|
835f4895b3 | ||
|
ff49e1325c | ||
|
3dc1470085 | ||
|
60ca138827 | ||
|
f38288b2c6 | ||
|
dc355679f4 | ||
|
6cb75406cb | ||
|
330530de73 | ||
|
7439286ae2 | ||
|
e6166918fe | ||
|
358c0099dd | ||
|
5be62802e8 | ||
|
7292c3cf3a | ||
|
38992bbf93 | ||
|
32280dc499 | ||
|
b6bc8acf6b | ||
|
84d465c0f6 | ||
|
2f680d4c13 | ||
|
9bcd3e6a56 | ||
|
9d0116b271 | ||
|
595063bf99 | ||
|
0868d8bd2b | ||
|
60ee68d8d1 | ||
|
9bde4c11b9 | ||
|
8b89808634 | ||
|
8db15979a0 | ||
|
45a24d4f8e | ||
|
21368e4e83 | ||
|
e72d1bcff2 | ||
|
46af6f07e2 | ||
|
28a2ce53fd | ||
|
6e18b633de | ||
|
19b0cb94fc | ||
|
8c7a11ee04 | ||
|
77cc26d548 | ||
|
f817981e91 | ||
|
e48e192970 | ||
|
cf9072cb79 | ||
|
51f40633bb | ||
|
a56269a971 | ||
|
dc5769f92b | ||
|
fb3147c6c5 | ||
|
bbde03c961 | ||
|
9e115261fb | ||
|
e09085aa3b | ||
|
ff94bb1a97 | ||
|
9ebc7e906f | ||
|
cb16ea86b3 | ||
|
ac279c29fc | ||
|
178bf6d270 | ||
|
2191733f37 | ||
|
38800dd38b | ||
|
b461fe83af | ||
|
1bd7b14785 | ||
|
daf9bd4c9a | ||
|
6d0b9cbf63 | ||
|
b6875f2c11 | ||
|
aa2f0f5e8a | ||
|
0f66f11628 | ||
|
2fcfd695f9 | ||
|
1f8fd77a46 | ||
|
6adbe5dd04 | ||
|
5e7214ff79 | ||
|
a724f81091 | ||
|
16b5dc3822 | ||
|
49bd5d8f80 | ||
|
30bc61a43a | ||
|
6b7996bdf4 | ||
|
6907a02141 | ||
|
34e984a8b3 | ||
|
1841bad98f | ||
|
99f55a9b85 | ||
|
60251a7b79 | ||
|
d983613872 | ||
|
c82e2900f6 | ||
|
3ceb8638d5 | ||
|
789982a266 | ||
|
c0b461d92b | ||
|
0a160541e2 | ||
|
58f1fd8253 | ||
|
0d8130cca7 | ||
|
bcee0f2239 | ||
|
bf1b3917f4 | ||
|
9609db9f9b | ||
|
3a06c128ed | ||
|
8809fa7dbc | ||
|
7f17e1261c | ||
|
f8eb65d600 | ||
|
3dacd0727f | ||
|
d6faafd4c0 | ||
|
1af5bf2e2d | ||
|
1a0cb36739 | ||
|
17ded1bbcc | ||
|
a5e6344cdd | ||
|
6118dcb2cf | ||
|
b14cae82ff | ||
|
ae6c0e9aac | ||
|
c7ce0b5547 | ||
|
92dae621f1 | ||
|
699332600b | ||
|
d09a3a06df | ||
|
d89b0887c1 | ||
|
77eba4b33f | ||
|
113118afbd | ||
|
d3273f609a | ||
|
8e3147f176 | ||
|
04a4f77e87 | ||
|
dea05bce0d | ||
|
5154513cd4 | ||
|
64c3d90458 | ||
|
6b5348de88 | ||
|
14451494ff | ||
|
e470610294 | ||
|
db07cd1887 | ||
|
8234a1c9dc | ||
|
eaba9bf698 | ||
|
39fe678d39 | ||
|
26fa4bb3fd | ||
|
fb79201b51 | ||
|
298aa47e7c | ||
|
7b64221b55 | ||
|
f5ee249bbb | ||
|
4ac4202555 | ||
|
c6dc52a60d | ||
|
39255e140f | ||
|
cd2b4e481e | ||
|
81801b3df9 | ||
|
750c98c177 | ||
|
97473004a8 | ||
|
1c3696fd02 | ||
|
31f61f3f37 | ||
|
6c2c943b0d | ||
|
7daf9f614c | ||
|
0bfefa460f | ||
|
59d2874c1a | ||
|
9f9849d9b0 | ||
|
3801c44aab | ||
|
801f9e5a74 | ||
|
72e29223a1 | ||
|
211d0fc751 | ||
|
c7c11d0b4c | ||
|
14ab0c878b | ||
|
7541e39cf9 | ||
|
6895d705f8 | ||
|
e1e09c4efa | ||
|
548cedcffe | ||
|
86dd202ca4 | ||
|
6d0b776649 | ||
|
1365339c1e | ||
|
9a842694dc | ||
|
e7b66af02e | ||
|
161c5ef1f3 | ||
|
48f4b69a98 | ||
|
97ba6b4df1 | ||
|
4e1ec1a148 | ||
|
7a073c6519 | ||
|
3b672af0f8 | ||
|
0b560b6dce | ||
|
39dda736af | ||
|
22bf4384c6 | ||
|
54f13847b5 | ||
|
4c1e8273f6 | ||
|
09c67e3e4a | ||
|
79710dead6 | ||
|
85b11f605c | ||
|
eb287422aa | ||
|
99840b8ee2 | ||
|
ddddfa1492 | ||
|
a8108045d4 | ||
|
2831882c6d | ||
|
53911dfe91 | ||
|
301701353f | ||
|
8a93be6ba6 | ||
|
4c576702ed | ||
|
af8c3ba6e8 | ||
|
c380f1d2dc | ||
|
0d325f7637 | ||
|
7d04b3c902 | ||
|
bfea622998 | ||
|
bf8368b515 | ||
|
e4ca217170 | ||
|
c734f83c13 | ||
|
2708eb989a | ||
|
7f95d19bce | ||
|
1003729208 | ||
|
c493a30361 | ||
|
a489655ddd | ||
|
f61845f0c4 | ||
|
0e31f9b22e | ||
|
cdbeede9f4 | ||
|
dde1276785 | ||
|
6f86e33d6c | ||
|
80df11526b | ||
|
a50fc71e33 | ||
|
94055c5454 | ||
|
32b7ca1608 | ||
|
41141ec251 | ||
|
99f958da37 | ||
|
3c606a9149 | ||
|
ffabd75e95 | ||
|
8439528e8e | ||
|
e0443a7a13 | ||
|
45216cdab5 | ||
|
3c5d560981 | ||
|
6cbd400636 | ||
|
65d9d7f2ce | ||
|
7def63ee01 | ||
|
5d5c215f48 | ||
|
16484d18b2 | ||
|
53080bea06 | ||
|
8d9a95da7a | ||
|
fc3dc71a44 | ||
|
42aedf2525 | ||
|
2969680b13 | ||
|
fd0488dd6a | ||
|
df214cdb24 | ||
|
c0b7ccca72 | ||
|
8ea432bc19 | ||
|
d8c5e84520 | ||
|
51d484f61e | ||
|
a2dec40b0a | ||
|
69d11c0e5a | ||
|
b2ba2976dd | ||
|
0e80d66097 | ||
|
2206674c0d | ||
|
cf8905ab1a | ||
|
9183293329 | ||
|
a6e538b004 | ||
|
8b314577ef | ||
|
87858fc6d5 | ||
|
c6a7a1d4f7 | ||
|
ce4cb8974f | ||
|
5ad8752e00 | ||
|
cae1d932a1 | ||
|
e45f1ac064 | ||
|
cc33a9138b | ||
|
60574d5b75 | ||
|
07d8c56e18 | ||
|
2cbfbe8047 | ||
|
8233174487 | ||
|
a879a82645 | ||
|
1b683b5564 | ||
|
5247a80549 | ||
|
c61856cdba | ||
|
473029c31f | ||
|
d04d75222e | ||
|
f648669c95 | ||
|
03e8733ba0 | ||
|
03bef36a45 | ||
|
143653e2b6 | ||
|
9a237f52f5 | ||
|
dfad74f4a0 | ||
|
cca21cd727 | ||
|
383346942f | ||
|
b7c83923ea | ||
|
3efa71b08a | ||
|
8eba063b40 | ||
|
2518759f7b | ||
|
cf31d76ee1 | ||
|
d33cb707fd | ||
|
b66c8cd5a6 | ||
|
449d7cc0d0 | ||
|
0c4ac239b8 | ||
|
8c535be429 | ||
|
6455cd2182 | ||
|
102f80c62e | ||
|
460f4a8e6c | ||
|
a5bf1ea3e6 | ||
|
7f7e4bfa4c | ||
|
9256faf0ba | ||
|
3888998aff | ||
|
1195bcf4c3 | ||
|
d47321c0f7 | ||
|
69ae8ed33e | ||
|
f88506f654 | ||
|
9b312bf4de | ||
|
ff8b366eb9 | ||
|
52146286fe | ||
|
e37d03fa77 | ||
|
da0d122166 | ||
|
bd4e61f14f | ||
|
3683ebd97a | ||
|
c2452ebcc3 | ||
|
073b473840 | ||
|
a52b18ba9b | ||
|
06df39445c | ||
|
c0810fd7ff | ||
|
2674940c91 | ||
|
57e2099650 | ||
|
18a64c267d | ||
|
5a1bd5e31b | ||
|
7d23a162ee | ||
|
c8d1b61006 | ||
|
40851c38cd | ||
|
74533e2e66 | ||
|
cbed2868ba | ||
|
08f35ab52c | ||
|
e2ab77658b | ||
|
23bcbe1977 | ||
|
8abcab32ec | ||
|
791db59c18 | ||
|
66e3bf483b | ||
|
c39c1673a9 | ||
|
7e2ea43817 | ||
|
7d9b56b6e7 | ||
|
329509d5c0 | ||
|
9a356dbf0d | ||
|
d473451295 | ||
|
0673a271ef | ||
|
1d9d752832 | ||
|
ae9463124f | ||
|
117ab3fa3d | ||
|
d1ef3b5807 | ||
|
11926ac63c | ||
|
4692831007 | ||
|
b959e17c18 | ||
|
9d7fa9f925 | ||
|
f3bdaaac8e | ||
|
617573ca09 | ||
|
59fcaf6ee9 | ||
|
16f4451803 | ||
|
939fe9ddca | ||
|
0816fd032d | ||
|
922e5c9f26 | ||
|
2344ef9606 | ||
|
3321d1c90e | ||
|
469e97b790 | ||
|
898668ae96 | ||
|
5c6d6a4cb0 | ||
|
63830a27d6 | ||
|
5e4f4d5b43 | ||
|
670928a604 | ||
|
4a4191c80d | ||
|
2b8ef4bcff | ||
|
4d56796244 | ||
|
4f8840da68 | ||
|
ffffeb78ee | ||
|
38c01b8c76 | ||
|
edb6631768 | ||
|
8f9ff14fac | ||
|
0adc5024f1 | ||
|
b369954c05 | ||
|
092e0a39e8 | ||
|
d31425623d | ||
|
9c2b7c9188 | ||
|
98121a30ef | ||
|
601b984430 | ||
|
fe7e1aa28d | ||
|
529565911f | ||
|
8d6dc18ce2 | ||
|
d88462e827 | ||
|
5ed75012cd | ||
|
6e2a539232 | ||
|
290bed1ffe | ||
|
0486b56ec3 | ||
|
5808bab08e | ||
|
fa4e086220 | ||
|
48c46e2105 | ||
|
7eb44ebb82 | ||
|
14d3c18945 | ||
|
dfe25a9599 | ||
|
d46e375501 | ||
|
698ecefba4 | ||
|
2d8c92d946 | ||
|
32e1b2edb4 | ||
|
ddaf8a0e01 | ||
|
3bb8fec4b2 | ||
|
71ad055ea5 | ||
|
8588191556 | ||
|
e9fc77ab0d | ||
|
55f00c664c | ||
|
29fe59b4b9 | ||
|
123a5e2dc4 | ||
|
b398e47056 | ||
|
de5e56cc04 | ||
|
6837c7d081 | ||
|
742ea56e34 | ||
|
132623419d | ||
|
03bef0ae88 | ||
|
0b3e4198cd | ||
|
fcf2d041cb | ||
|
31ae7a6d3a | ||
|
c7aeef43dd | ||
|
fdbde2e0a6 | ||
|
0b9d6093a0 | ||
|
7f80cad6f4 | ||
|
23b372252b | ||
|
9cd0ef1f04 | ||
|
1f239baeb9 | ||
|
5df6de1e3a | ||
|
f80b55aa50 | ||
|
895b605523 | ||
|
67be2ce342 | ||
|
9e051d9ab9 | ||
|
28228b35e2 | ||
|
1f0403d1b3 | ||
|
410ff87c88 | ||
|
25bcbd6369 | ||
|
09395921ce | ||
|
f28c3e0594 | ||
|
7e36ac4159 | ||
|
97ad8b44aa | ||
|
015d60aee1 | ||
|
c2015848b0 | ||
|
0acb947795 | ||
|
683427c312 | ||
|
34ecbb3f14 | ||
|
0e0944ba51 | ||
|
a9b8c57567 | ||
|
7845f5beb3 | ||
|
83d5a4d2bf | ||
|
49e833e147 | ||
|
e06543dac6 | ||
|
a21e7e115b | ||
|
ca721368ea | ||
|
911ca85b03 | ||
|
0f3df9b229 | ||
|
d479d49550 | ||
|
b46f9f90d3 | ||
|
28b3669f39 | ||
|
49279480ec | ||
|
56592f3034 | ||
|
c48c06d2f8 | ||
|
54e1b486a7 | ||
|
819514a699 | ||
|
990b024fdb | ||
|
862f3bb6b9 | ||
|
bc4ec0660f | ||
|
dffda00940 | ||
|
b232c764e0 | ||
|
add5b65e85 | ||
|
b2484cfd96 | ||
|
ace3156fb2 | ||
|
5c11fb5424 | ||
|
5bff609fb6 | ||
|
4d3f7aa34b | ||
|
3438c86de2 | ||
|
6883287c72 | ||
|
c28a566c78 | ||
|
d4c9695e5c | ||
|
391ca845cf | ||
|
d01b7923aa | ||
|
869dc762fa | ||
|
ae344c48ac | ||
|
119e94520c | ||
|
8397ef7cca | ||
|
feee3514a9 | ||
|
9b92c6cbf2 | ||
|
16cbba6e76 | ||
|
589151af81 | ||
|
ba38a32b86 | ||
|
ceea648858 | ||
|
9541ba528b | ||
|
427ae65856 | ||
|
5e9e30d80d | ||
|
433aa7a1c7 | ||
|
3de67d42b3 | ||
|
47655f9a01 | ||
|
d497f97663 | ||
|
b05f5a728f | ||
|
2ae840634a | ||
|
bcc3b076c2 | ||
|
70cfdf0dd8 | ||
|
90c8362c95 | ||
|
7bfe0b01cd | ||
|
3a5a264436 | ||
|
4fc9c06204 | ||
|
46a5603ff7 | ||
|
114574bff3 | ||
|
c621b0525e | ||
|
165c6e5b22 | ||
|
49e93e4309 | ||
|
24ce3fa4e8 | ||
|
05b89607a5 | ||
|
185da209ce | ||
|
b5ebabd824 | ||
|
6a1ab89e39 | ||
|
694b5c54c3 | ||
|
a53c503c4e | ||
|
032b4187bd | ||
|
0e2f876cd4 | ||
|
37348ccb0e | ||
|
2e624956aa | ||
|
fdcb5ad03a | ||
|
2f1a4e9518 | ||
|
d33187d204 | ||
|
dca77a58ae | ||
|
6b3957d04a | ||
|
56bbc93460 | ||
|
eb90792aca | ||
|
b74f680191 | ||
|
2260d547dc | ||
|
7632c4e498 | ||
|
8c0dfadc82 | ||
|
30b7c8043a | ||
|
b10e505ed3 | ||
|
eda626fe10 | ||
|
eb8c87468f | ||
|
8f680ac5db | ||
|
c4d7035ef2 | ||
|
d3db623de5 | ||
|
a247b17e7f | ||
|
f69e839a82 | ||
|
c93c271bd4 | ||
|
f853ac75d9 | ||
|
1ea7221f9f | ||
|
a6acb67428 | ||
|
d350425049 | ||
|
4a6d618598 | ||
|
5efab22561 | ||
|
ca9e32ae53 | ||
|
099ecf68b6 | ||
|
c8d9d9b252 | ||
|
4df19c2193 | ||
|
863562ac87 | ||
|
0e6fa962de | ||
|
0c73d69a93 | ||
|
ec61f22682 | ||
|
65aae74979 | ||
|
806992759a | ||
|
eb18fa63af | ||
|
b661aedd60 | ||
|
ddbdc0207d | ||
|
edf9e1d004 | ||
|
0eaaae2f99 | ||
|
eb4f70b8a2 | ||
|
a998c9fbe7 | ||
|
8dbce734d5 | ||
|
6385480e9e | ||
|
080bd36714 | ||
|
bd77ea46b9 | ||
|
3836e1f81f | ||
|
7be63c814d | ||
|
ceeb36fc2a | ||
|
551bf6865c | ||
|
50ccbace6d | ||
|
4e9fda44f8 | ||
|
d259931477 | ||
|
5a56064e86 | ||
|
89d548caf9 | ||
|
9e42fe7294 | ||
|
c64c6eca7b | ||
|
d179bd2ba8 | ||
|
37b85b4cee | ||
|
a6419d96e2 | ||
|
9878854f6b | ||
|
f981527200 | ||
|
a48c350aa2 | ||
|
51bff6b2ca | ||
|
5fbb2e03b5 | ||
|
2bfc985403 | ||
|
cdc9d85b53 | ||
|
e5e948ef4f | ||
|
2e12635e66 | ||
|
5455034da3 | ||
|
1dc2fea7e2 | ||
|
450b7d4205 | ||
|
1a4a9ef0a1 | ||
|
65b70f57ad | ||
|
cfa240892f | ||
|
dcb66ee5e0 | ||
|
66851baf0f | ||
|
4ad3ff8277 | ||
|
f595cdbbed | ||
|
cba8637122 | ||
|
e50ad6b6aa | ||
|
fa1c58b832 | ||
|
adadee7ef7 | ||
|
db8dc41c8f | ||
|
5e2961c91d | ||
|
c6afa74529 | ||
|
5cc430de6f | ||
|
9728dc8dc3 | ||
|
179ada3296 | ||
|
6266b30088 | ||
|
c677787f91 | ||
|
c39eca3bd4 | ||
|
5d2bb2c66c | ||
|
51235429db | ||
|
102c9ec0cc | ||
|
fee7cbc93a | ||
|
8d5f235684 | ||
|
d63aa5b92e | ||
|
280c6d5849 | ||
|
42d9f7f887 | ||
|
f8b88909cb | ||
|
5c6c4677b0 | ||
|
5afaf5f324 | ||
|
21f33f1fef | ||
|
71935a746a | ||
|
201df3746a | ||
|
940dd39e29 | ||
|
f10aa2776f | ||
|
f7955e60f0 | ||
|
5b9eb304bc | ||
|
aa59cb9844 | ||
|
036a94e171 | ||
|
1609b28595 | ||
|
bc4ff96f0b | ||
|
d8a620c589 | ||
|
0a2b6d9977 | ||
|
f4a88c277b | ||
|
8fca5aab41 | ||
|
cbff862594 | ||
|
992d755801 | ||
|
cb51137c87 | ||
|
f1ccd563b5 | ||
|
7d923ba04e | ||
|
e41b59d506 | ||
|
f4df53b171 | ||
|
5a9365e2bb | ||
|
d431f2555c | ||
|
370add478f | ||
|
644ea6a171 | ||
|
8d22f7a037 | ||
|
81eca6fe89 | ||
|
c8dd63136c | ||
|
1a049d05c4 | ||
|
4ece50ddb1 | ||
|
1ebb93d7ab | ||
|
daeca492ec | ||
|
c7b49be379 | ||
|
558b1db714 | ||
|
abcf90e0bc | ||
|
827018078f | ||
|
a53114779a | ||
|
7dfbe7bfaf | ||
|
24e16a1850 | ||
|
5512ece14e | ||
|
3143de77c3 | ||
|
947b790138 | ||
|
02068df82d | ||
|
7d79452220 | ||
|
dccb49f51c | ||
|
d7b82c3a17 | ||
|
0a29192bfe | ||
|
a4ff0b0721 | ||
|
508ae317fe | ||
|
553afd872a | ||
|
6e601179c5 | ||
|
849b28f7d3 | ||
|
cbc91f4523 |
837 changed files with 57020 additions and 18352 deletions
75
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
75
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal 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
18
.github/ISSUE_TEMPLATE/SUGGEST_FROM.yml
vendored
Normal 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
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
blank_issues_enabled: true
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -27,5 +27,6 @@ bin/
|
|||
# fabric
|
||||
|
||||
run/
|
||||
run-client/
|
||||
output/
|
||||
*.log
|
||||
|
|
40
BetterX_CodeFormat.xml
Normal file
40
BetterX_CodeFormat.xml
Normal 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
169
README.md
|
@ -1,41 +1,146 @@
|
|||
[](https://jitpack.io/#paulevsGitch/BCLib)
|
||||
[](https://jitpack.io/#quiqueck/BCLib)
|
||||
|
||||
# BCLib
|
||||
BCLib is a library mod for BetterX team mods, developed for Fabric, MC 1.16.5
|
||||
|
||||
## Features:
|
||||
### 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.
|
||||
BCLib is a library mod for BetterX team mods, developed for Fabric, MC 1.19
|
||||
|
||||
## Importing:
|
||||
* Clone repo
|
||||
* Edit gradle.properties if necessary
|
||||
* Run command line in folder: gradlew genSources eclipse (or Another-IDE-Name)
|
||||
* Import project to IDE
|
||||
|
||||
You can easily include BCLib into your own mod by adding the following to your `build.gradle`:
|
||||
|
||||
```
|
||||
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:
|
||||
|
||||
* Clone repo
|
||||
* Run command line in folder: gradlew build
|
||||
* Mod .jar will be in ./build/libs
|
||||
|
|
8
bclib-composit.gradle
Normal file
8
bclib-composit.gradle
Normal file
|
@ -0,0 +1,8 @@
|
|||
plugins {
|
||||
id 'idea'
|
||||
id 'eclipse'
|
||||
id 'fabric-loom'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
apply from: "bclib.gradle"
|
146
bclib.gradle
Normal file
146
bclib.gradle
Normal file
|
@ -0,0 +1,146 @@
|
|||
buildscript {
|
||||
dependencies {
|
||||
classpath 'org.kohsuke:github-api:1.114'
|
||||
}
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
|
||||
archivesBaseName = project.archives_base_name
|
||||
version = project.mod_version
|
||||
group = project.maven_group
|
||||
|
||||
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' }
|
||||
}
|
||||
|
||||
loom {
|
||||
accessWidenerPath = file("src/main/resources/bclib.accesswidener")
|
||||
}
|
||||
|
||||
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}"
|
||||
modCompileOnly "com.terraformersmc:modmenu:${project.modmenu_version}"
|
||||
|
||||
//useApi "vazkii.patchouli:Patchouli:1.16.4-${project.patchouli_version}"
|
||||
}
|
||||
|
||||
processResources {
|
||||
println "Version: ${project.mod_version}"
|
||||
inputs.property "version", project.mod_version
|
||||
|
||||
filesMatching("fabric.mod.json") {
|
||||
expand "version": project.mod_version
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
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("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
|
||||
}
|
160
build.gradle
160
build.gradle
|
@ -1,164 +1,8 @@
|
|||
buildscript {
|
||||
dependencies {
|
||||
classpath 'org.kohsuke:github-api:1.114'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'idea'
|
||||
id 'eclipse'
|
||||
id 'fabric-loom' version '0.7-SNAPSHOT'
|
||||
id 'fabric-loom' version "${loom_version}"
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
apply from: "bclib.gradle"
|
|
@ -1,18 +1,16 @@
|
|||
# Done to increase the memory available to gradle.
|
||||
org.gradle.jvmargs=-Xmx2G
|
||||
|
||||
#Loom
|
||||
loom_version=0.12-SNAPSHOT
|
||||
# Fabric Properties
|
||||
# check these on https://fabricmc.net/use
|
||||
minecraft_version=1.16.5
|
||||
yarn_mappings=6
|
||||
loader_version=0.11.3
|
||||
|
||||
# check these on https://fabricmc.net/versions.html
|
||||
minecraft_version=1.18.2
|
||||
loader_version=0.14.8
|
||||
fabric_version=0.57.0+1.19
|
||||
# Mod Properties
|
||||
mod_version = 0.1.45
|
||||
maven_group = ru.bclib
|
||||
mod_version=2.0.11
|
||||
maven_group=org.betterx.bclib
|
||||
archives_base_name=bclib
|
||||
|
||||
# Dependencies
|
||||
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
|
||||
patchouli_version=50-FABRIC
|
||||
fabric_version = 0.32.9+1.16
|
||||
modmenu_version=4.0.0
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
269
gradlew
vendored
Normal file → Executable file
269
gradlew
vendored
Normal file → Executable 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");
|
||||
# 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
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
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.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
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."
|
||||
fi
|
||||
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.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
|
@ -105,79 +140,95 @@ location of your Java installation."
|
|||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
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" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --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
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
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" "$@"
|
||||
|
|
22
gradlew.bat
vendored
22
gradlew.bat
vendored
|
@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
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_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
|
@ -64,28 +64,14 @@ echo location of your Java installation.
|
|||
|
||||
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
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@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
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
|
903
javadoc.css
Normal file
903
javadoc.css
Normal 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
6
jitpack.yml
Normal 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
|
12
src/main/java/org/anti_ad/mc/ipn/api/IPNIgnore.java
Normal file
12
src/main/java/org/anti_ad/mc/ipn/api/IPNIgnore.java
Normal 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 {
|
||||
}
|
165
src/main/java/org/betterx/bclib/BCLib.java
Normal file
165
src/main/java/org/betterx/bclib/BCLib.java
Normal file
|
@ -0,0 +1,165 @@
|
|||
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.BCLBiomeRegistry;
|
||||
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);
|
||||
|
||||
public static final boolean RUNS_NULLSCAPE = FabricLoader.getInstance()
|
||||
.getModContainer("nullscape")
|
||||
.isPresent();
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
LevelGenEvents.register();
|
||||
BlockPredicates.ensureStaticInitialization();
|
||||
BCLBiomeRegistry.ensureStaticallyLoaded();
|
||||
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);
|
||||
}
|
||||
}
|
183
src/main/java/org/betterx/bclib/BCLibPatch.java
Normal file
183
src/main/java/org/betterx/bclib/BCLibPatch.java
Normal 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;
|
||||
}
|
||||
}
|
201
src/main/java/org/betterx/bclib/api/v2/BonemealAPI.java
Normal file
201
src/main/java/org/betterx/bclib/api/v2/BonemealAPI.java
Normal 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.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.Random;
|
||||
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,
|
||||
Random 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,
|
||||
Random 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);
|
||||
}
|
||||
}
|
23
src/main/java/org/betterx/bclib/api/v2/ComposterAPI.java
Normal file
23
src/main/java/org/betterx/bclib/api/v2/ComposterAPI.java
Normal 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;
|
||||
}
|
||||
}
|
35
src/main/java/org/betterx/bclib/api/v2/DiggerItemSpeed.java
Normal file
35
src/main/java/org/betterx/bclib/api/v2/DiggerItemSpeed.java
Normal 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;
|
||||
}
|
||||
}
|
145
src/main/java/org/betterx/bclib/api/v2/LifeCycleAPI.java
Normal file
145
src/main/java/org/betterx/bclib/api/v2/LifeCycleAPI.java
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
149
src/main/java/org/betterx/bclib/api/v2/PostInitAPI.java
Normal file
149
src/main/java/org/betterx/bclib/api/v2/PostInitAPI.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
22
src/main/java/org/betterx/bclib/api/v2/ShovelAPI.java
Normal file
22
src/main/java/org/betterx/bclib/api/v2/ShovelAPI.java
Normal 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);
|
||||
}
|
||||
}
|
77
src/main/java/org/betterx/bclib/api/v2/WorldDataAPI.java
Normal file
77
src/main/java/org/betterx/bclib/api/v2/WorldDataAPI.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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><--</td>
|
||||
* <td>{@link HelloServer}</td>
|
||||
* <td>Sends the current BLib-Version installed on the Client</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link HelloClient}</td>
|
||||
* <td>--></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><--</td>
|
||||
* <td>{@link RequestFiles}</td>
|
||||
* <td>Request missing or out of sync Files from the Server</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link SendFiles}</td>
|
||||
* <td>--></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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}));
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
237
src/main/java/org/betterx/bclib/api/v2/datafixer/Patch.java
Normal file
237
src/main/java/org/betterx/bclib/api/v2/datafixer/Patch.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
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.biomesource.ReloadableBiomeSource;
|
||||
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()));
|
||||
} else if (initialBiomeSource instanceof ReloadableBiomeSource bs) {
|
||||
bs.reloadBiomes();
|
||||
}
|
||||
|
||||
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.useLegacyRandom()
|
||||
// );
|
||||
//
|
||||
//
|
||||
// return Holder.direct(noise);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,444 @@
|
|||
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.BCLBiomeRegistry;
|
||||
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 org.betterx.worlds.together.biomesource.ReloadableBiomeSource;
|
||||
|
||||
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>, ReloadableBiomeSource {
|
||||
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 BiomePicker endLandBiomePicker;
|
||||
private BiomePicker endVoidBiomePicker;
|
||||
private BiomePicker endCenterBiomePicker;
|
||||
private BiomePicker endBarrensBiomePicker;
|
||||
private List<BiomeDecider> deciders;
|
||||
|
||||
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;
|
||||
rebuildBiomePickers();
|
||||
|
||||
this.endLandFunction = GeneratorOptions.getEndLandFunction();
|
||||
this.pos = new Point();
|
||||
|
||||
if (initMaps) {
|
||||
initMap(seed);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private void rebuildBiomePickers() {
|
||||
var includeMap = Configs.BIOMES_CONFIG.getBiomeIncludeMap();
|
||||
var excludeList = Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.END);
|
||||
|
||||
this.deciders = BiomeDecider.DECIDERS.stream()
|
||||
.filter(d -> d.canProvideFor(this))
|
||||
.map(d -> d.createInstance(this))
|
||||
.toList();
|
||||
|
||||
this.endLandBiomePicker = new BiomePicker(biomeRegistry);
|
||||
this.endVoidBiomePicker = new BiomePicker(biomeRegistry);
|
||||
this.endCenterBiomePicker = new BiomePicker(biomeRegistry);
|
||||
this.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 != BCLBiomeRegistry.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 (biomeID.equals(BCLBiomeRegistry.EMPTY_BIOME.getID())
|
||||
|| bclBiome.getIntendedType().is(BiomeAPI.BiomeType.END_IGNORE)) {
|
||||
//we should not add this biome anywhere, so just ignore it
|
||||
} else {
|
||||
didForceAdd = false;
|
||||
for (BiomeDecider decider : deciders) {
|
||||
if (decider.addToPicker(bclBiome)) {
|
||||
didForceAdd = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!didForceAdd) {
|
||||
if (bclBiome.getIntendedType().is(BiomeAPI.BiomeType.END_CENTER)
|
||||
|| TheEndBiomesHelper.canGenerateAsMainIslandBiome(key)) {
|
||||
endCenterBiomePicker.addBiome(bclBiome);
|
||||
} else if (bclBiome.getIntendedType().is(BiomeAPI.BiomeType.END_LAND)
|
||||
|| TheEndBiomesHelper.canGenerateAsHighlandsBiome(key)) {
|
||||
if (!config.withVoidBiomes) endVoidBiomePicker.addBiome(bclBiome);
|
||||
endLandBiomePicker.addBiome(bclBiome);
|
||||
} else if (bclBiome.getIntendedType().is(BiomeAPI.BiomeType.END_BARRENS)
|
||||
|| TheEndBiomesHelper.canGenerateAsEndBarrens(key)) {
|
||||
endBarrensBiomePicker.addBiome(bclBiome);
|
||||
} else if (bclBiome.getIntendedType().is(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();
|
||||
|
||||
for (BiomeDecider decider : deciders) {
|
||||
decider.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected BCLBiomeSource cloneForDatapack(Set<Holder<Biome>> datapackBiomes) {
|
||||
datapackBiomes.addAll(getBclBiomes(this.biomeRegistry));
|
||||
return new BCLibEndBiomeSource(
|
||||
this.biomeRegistry,
|
||||
datapackBiomes.stream()
|
||||
.filter(b -> b.unwrapKey().orElse(null) != BCLBiomeRegistry.EMPTY_BIOME.getBiomeKey())
|
||||
.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) {
|
||||
if (BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.END_IGNORE)) return false;
|
||||
|
||||
return biome.is(BiomeTags.IS_END) ||
|
||||
BiomeAPI.wasRegisteredAsEndBiome(location) ||
|
||||
TheEndBiomesHelper.canGenerateInEnd(biome.unwrapKey().orElse(null));
|
||||
}
|
||||
|
||||
private static boolean isValidNonVanillaEndBiome(Holder<Biome> biome, ResourceLocation location) {
|
||||
if (BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.END_IGNORE)) return false;
|
||||
|
||||
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) {
|
||||
for (BiomeDecider decider : deciders) {
|
||||
decider.createMap((picker, size) -> config.mapVersion.mapBuilder.create(
|
||||
seed,
|
||||
size <= 0 ? config.landBiomesSize : size,
|
||||
picker
|
||||
));
|
||||
}
|
||||
this.mapLand = config.mapVersion.mapBuilder.create(
|
||||
seed,
|
||||
config.landBiomesSize,
|
||||
endLandBiomePicker
|
||||
);
|
||||
|
||||
this.mapVoid = config.mapVersion.mapBuilder.create(
|
||||
seed,
|
||||
config.voidBiomesSize,
|
||||
endVoidBiomePicker
|
||||
);
|
||||
|
||||
this.mapCenter = config.mapVersion.mapBuilder.create(
|
||||
seed,
|
||||
config.centerBiomesSize,
|
||||
endCenterBiomePicker
|
||||
);
|
||||
|
||||
this.mapBarrens = config.mapVersion.mapBuilder.create(
|
||||
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();
|
||||
for (BiomeDecider decider : deciders) {
|
||||
decider.clearMapCache();
|
||||
}
|
||||
}
|
||||
|
||||
BiomeAPI.BiomeType suggestedType;
|
||||
|
||||
if (config.generatorVersion == BCLEndBiomeSourceConfig.EndBiomeGeneratorType.VANILLA) {
|
||||
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 (dist <= (long) config.innerVoidRadiusSquared) {
|
||||
suggestedType = BiomeAPI.BiomeType.END_CENTER;
|
||||
} else {
|
||||
if (d > 0.25) {
|
||||
suggestedType = BiomeAPI.BiomeType.END_LAND; //highlands
|
||||
} else if (d >= -0.0625) {
|
||||
suggestedType = BiomeAPI.BiomeType.END_LAND; //midlands
|
||||
} else {
|
||||
suggestedType = d < -0.21875
|
||||
? BiomeAPI.BiomeType.END_VOID //small islands
|
||||
: (config.withVoidBiomes
|
||||
? BiomeAPI.BiomeType.END_BARRENS
|
||||
: BiomeAPI.BiomeType.END_LAND); //barrens
|
||||
}
|
||||
}
|
||||
|
||||
final BiomeAPI.BiomeType originalType = suggestedType;
|
||||
for (BiomeDecider decider : deciders) {
|
||||
suggestedType = decider
|
||||
.suggestType(
|
||||
originalType,
|
||||
suggestedType,
|
||||
d,
|
||||
maxHeight,
|
||||
posX,
|
||||
posY,
|
||||
posZ,
|
||||
biomeX,
|
||||
biomeY,
|
||||
biomeZ
|
||||
);
|
||||
}
|
||||
} else {
|
||||
pos.setLocation(biomeX, biomeZ);
|
||||
final BiomeAPI.BiomeType originalType = (dist <= (long) config.innerVoidRadiusSquared
|
||||
? BiomeAPI.BiomeType.END_CENTER
|
||||
: BiomeAPI.BiomeType.END_LAND);
|
||||
suggestedType = originalType;
|
||||
|
||||
for (BiomeDecider decider : deciders) {
|
||||
suggestedType = decider
|
||||
.suggestType(originalType, suggestedType, maxHeight, posX, posY, posZ, biomeX, biomeY, biomeZ);
|
||||
}
|
||||
}
|
||||
|
||||
BiomePicker.ActualBiome result;
|
||||
for (BiomeDecider decider : deciders) {
|
||||
if (decider.canProvideBiome(suggestedType)) {
|
||||
result = decider.provideBiome(suggestedType, posX, posY, posZ);
|
||||
if (result != null) return result.biome;
|
||||
}
|
||||
}
|
||||
|
||||
if (suggestedType.is(BiomeAPI.BiomeType.END_CENTER)) return mapCenter.getBiome(posX, posY, posZ).biome;
|
||||
if (suggestedType.is(BiomeAPI.BiomeType.END_VOID)) return mapVoid.getBiome(posX, posY, posZ).biome;
|
||||
if (suggestedType.is(BiomeAPI.BiomeType.END_BARRENS)) return mapBarrens.getBiome(posX, posY, posZ).biome;
|
||||
return mapLand.getBiome(posX, posY, 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadBiomes() {
|
||||
rebuildBiomePickers();
|
||||
this.initMap(currentSeed);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
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.config.MapBuilderFunction;
|
||||
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.BCLBiomeRegistry;
|
||||
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 org.betterx.worlds.together.biomesource.ReloadableBiomeSource;
|
||||
|
||||
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>, ReloadableBiomeSource {
|
||||
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 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;
|
||||
rebuildBiomePicker();
|
||||
if (initMaps) {
|
||||
initMap(seed);
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildBiomePicker() {
|
||||
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 != BCLBiomeRegistry.EMPTY_BIOME) {
|
||||
if (bclBiome.getParentBiome() == null) {
|
||||
biomePicker.addBiome(bclBiome);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
biomePicker.rebuild();
|
||||
}
|
||||
|
||||
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) {
|
||||
MapBuilderFunction 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.create(
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadBiomes() {
|
||||
rebuildBiomePicker();
|
||||
initMap(currentSeed);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
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.interfaces.BiomeMap;
|
||||
|
||||
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 java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Used to extend the BiomePlacement in the {@link BCLBiomeSource}
|
||||
*/
|
||||
public abstract class BiomeDecider {
|
||||
|
||||
/**
|
||||
* used to create new {@link BiomeMap} instances
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BiomeMapBuilderFunction {
|
||||
/**
|
||||
* Constructs a new {@link BiomeMap}
|
||||
*
|
||||
* @param picker The picker the BiomeMap should use
|
||||
* @param biomeSize The biomeSize the map will use or -1 for the default size
|
||||
* @return a new {@link BiomeMap} instance
|
||||
*/
|
||||
BiomeMap create(BiomePicker picker, int biomeSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* used to determine wether or not a decider can provide this biome
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BiomePredicate {
|
||||
boolean test(BCLBiome biome);
|
||||
}
|
||||
|
||||
protected BiomePicker picker;
|
||||
protected BiomeMap map;
|
||||
private final BiomePredicate predicate;
|
||||
|
||||
static List<BiomeDecider> DECIDERS = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Register a high priority Decider for the {@link BCLibEndBiomeSource}.
|
||||
* Normally you should not need to register a high priority decider and instead use
|
||||
* {@link BiomeDecider#registerDecider(ResourceLocation, BiomeDecider)}.
|
||||
* BetterEnd (for example) will add
|
||||
*
|
||||
* @param location The {@link ResourceLocation} for the decider
|
||||
* @param decider The initial decider Instance. Each Instance of the {@link BCLibEndBiomeSource}
|
||||
* will call {@link BiomeDecider#createInstance(BCLBiomeSource)} to build a
|
||||
* new instance of this decider
|
||||
*/
|
||||
public static void registerHighPriorityDecider(ResourceLocation location, BiomeDecider decider) {
|
||||
if (DECIDERS.size() == 0) DECIDERS.add(decider);
|
||||
else DECIDERS.add(0, decider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new Decider for the {@link BCLibEndBiomeSource}
|
||||
*
|
||||
* @param location The {@link ResourceLocation} for the decider
|
||||
* @param decider The initial decider Instance. Each Instance of the {@link BCLibEndBiomeSource}
|
||||
* will call {@link BiomeDecider#createInstance(BCLBiomeSource)} to build a
|
||||
* new instance of this decider
|
||||
*/
|
||||
public static void registerDecider(ResourceLocation location, BiomeDecider decider) {
|
||||
DECIDERS.add(decider);
|
||||
}
|
||||
|
||||
protected BiomeDecider(BiomePredicate predicate) {
|
||||
this(null, predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param biomeRegistry The biome registry assigned to the creating BiomeSource
|
||||
* @param predicate A predicate that decides if a given Biome can be provided by this decider
|
||||
*/
|
||||
protected BiomeDecider(
|
||||
Registry<Biome> biomeRegistry, BiomePredicate predicate
|
||||
) {
|
||||
this.predicate = predicate;
|
||||
this.map = null;
|
||||
if (biomeRegistry == null) {
|
||||
this.picker = null;
|
||||
} else {
|
||||
this.picker = new BiomePicker(biomeRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to test, if a decider is suitable for the given BiomeSource.
|
||||
*
|
||||
* @param source The BiomeSource that wants to use the decider
|
||||
* @return true, if this decider is usable by that source
|
||||
*/
|
||||
public abstract boolean canProvideFor(BiomeSource source);
|
||||
|
||||
/**
|
||||
* Called from the BiomeSource whenever it needs to create a new instance of this decider.
|
||||
* <p>
|
||||
* Inheriting classes should overwrite this method and return Instances of the class. For
|
||||
* the base {@link BiomeDecider} you would return <em>new BiomeDecider(biomeSource.biomeRegistry, this.predicate);</em>
|
||||
*
|
||||
* @param biomeSource The biome source this decider is used from
|
||||
* @return A new instance
|
||||
*/
|
||||
public abstract BiomeDecider createInstance(BCLBiomeSource biomeSource);
|
||||
|
||||
/**
|
||||
* Called when the BiomeSources needs to construct a new {@link BiomeMap} for the picker.
|
||||
* <p>
|
||||
* The default implementation creates a new map with the instances picker and a default biome size
|
||||
*
|
||||
* @param mapBuilder A function you can use to create a new {@link BiomeMap} that conforms to the settings
|
||||
* of the current BiomeSource.
|
||||
*/
|
||||
public void createMap(BiomeMapBuilderFunction mapBuilder) {
|
||||
this.map = mapBuilder.create(picker, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* called whenever the BiomeSource needs to clear caches
|
||||
*/
|
||||
public void clearMapCache() {
|
||||
map.clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method get's called whenever the BiomeSource populates the Biome Pickers. You need to
|
||||
* determine if the passed Biome is valid for your picker.
|
||||
* <p>
|
||||
* If this method returns false, the Biome wil not get added to any other Deciders/Pickers.
|
||||
* <p>
|
||||
* The default implementation will use the instances {@link BiomeDecider#predicate} to determine if
|
||||
* a biome should get added and return true if it was added.
|
||||
*
|
||||
* @param biome The biome that should get added if it matches the criteria of the picker
|
||||
* @return false, if other pickers/deciders are allowed to use the biome as well
|
||||
*/
|
||||
public boolean addToPicker(BCLBiome biome) {
|
||||
if (predicate.test(biome)) {
|
||||
picker.addBiome(biome);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the picker needs to rebuild it's contents
|
||||
*/
|
||||
public void rebuild() {
|
||||
picker.rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the BiomeSource to determine the type of Biome it needs to place.
|
||||
*
|
||||
* @param originalType The original biome type the source did select
|
||||
* @param suggestedType The currently suggested type. This will differ from <em>originalType</em> if other
|
||||
* {@link BiomeDecider} instances already had a new suggestion. You implementation should return the
|
||||
* <em>suggestedType</em> if it does not want to provide the Biome for this location
|
||||
* @param maxHeight The maximum terrain height for this world
|
||||
* @param blockX The block coordinate where we are at
|
||||
* @param blockY The block coordinate where we are at
|
||||
* @param blockZ The block coordinate where we are at
|
||||
* @param quarterX The quarter Block Coordinate (which is blockX/4)
|
||||
* @param quarterY The quarter Block Coordinate (which is blockY/4)
|
||||
* @param quarterZ The quarter Block Coordinate (which is blockZ/4)
|
||||
* @return The <em>suggestedType</em> if this decider does not plan to provide a Biome, or a unique BiomeType.
|
||||
* The Biome Source will call {@link BiomeDecider#canProvideBiome(BiomeAPI.BiomeType)} with the finally chosen type
|
||||
* for all available Deciders.
|
||||
*/
|
||||
public BiomeAPI.BiomeType suggestType(
|
||||
BiomeAPI.BiomeType originalType,
|
||||
BiomeAPI.BiomeType suggestedType,
|
||||
int maxHeight,
|
||||
int blockX,
|
||||
int blockY,
|
||||
int blockZ,
|
||||
int quarterX,
|
||||
int quarterY,
|
||||
int quarterZ
|
||||
) {
|
||||
return suggestType(
|
||||
originalType,
|
||||
suggestedType,
|
||||
0,
|
||||
maxHeight,
|
||||
blockX,
|
||||
blockY,
|
||||
blockZ,
|
||||
quarterX,
|
||||
quarterY,
|
||||
quarterZ
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the BiomeSource to determine the type of Biome it needs to place.
|
||||
*
|
||||
* @param originalType The original biome type the source did select
|
||||
* @param suggestedType The currently suggested type. This will differ from <em>originalType</em> if other
|
||||
* {@link BiomeDecider} instances already had a new suggestion. You implementation should return the
|
||||
* <em>suggestedType</em> if it does not want to provide the Biome for this location
|
||||
* @param density The terrain density at this location. Currently only valid if for {@link BCLibEndBiomeSource}
|
||||
* that use the {@link org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig.EndBiomeGeneratorType#VANILLA}
|
||||
* @param maxHeight The maximum terrain height for this world
|
||||
* @param blockX The block coordinate where we are at
|
||||
* @param blockY The block coordinate where we are at
|
||||
* @param blockZ The block coordinate where we are at
|
||||
* @param quarterX The quarter Block Coordinate (which is blockX/4)
|
||||
* @param quarterY The quarter Block Coordinate (which is blockY/4)
|
||||
* @param quarterZ The quarter Block Coordinate (which is blockZ/4)
|
||||
* @param maxHeight
|
||||
* @return The <em>suggestedType</em> if this decider does not plan to provide a Biome, or a unique BiomeType.
|
||||
* The Biome Source will call {@link BiomeDecider#canProvideBiome(BiomeAPI.BiomeType)} with the finally chosen type
|
||||
* for all available Deciders.
|
||||
*/
|
||||
public abstract BiomeAPI.BiomeType suggestType(
|
||||
BiomeAPI.BiomeType originalType,
|
||||
BiomeAPI.BiomeType suggestedType,
|
||||
double density,
|
||||
int maxHeight,
|
||||
int blockX,
|
||||
int blockY,
|
||||
int blockZ,
|
||||
int quarterX,
|
||||
int quarterY,
|
||||
int quarterZ
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Called to check if this decider can place a biome for the specified type
|
||||
*
|
||||
* @param suggestedType The type of biome we need to place
|
||||
* @return true, if this type of biome can be provided by the current picker. If true
|
||||
* is returned, the BiomeSource will call {@link BiomeDecider#provideBiome(BiomeAPI.BiomeType, int, int, int)}
|
||||
* next
|
||||
*/
|
||||
public abstract boolean canProvideBiome(BiomeAPI.BiomeType suggestedType);
|
||||
|
||||
/**
|
||||
* Called to check if this decider can place a biome for the specified type
|
||||
* <p>
|
||||
* The default implementation will return <em>map.getBiome(posX, posY, posZ)</em>
|
||||
*
|
||||
* @param suggestedType The type of biome we need to place
|
||||
* @return The methode should return a Biome from its {@link BiomeMap}. If null is returned, the next
|
||||
* decider (or the default map) will provide the biome
|
||||
*/
|
||||
public BiomePicker.ActualBiome provideBiome(BiomeAPI.BiomeType suggestedType, int posX, int posY, int posZ) {
|
||||
return map.getBiome(posX, posY, posZ);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
package org.betterx.bclib.api.v2.generator;
|
||||
|
||||
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome;
|
||||
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry;
|
||||
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(BCLBiomeRegistry.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(BCLBiomeRegistry.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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BiomePicker{" +
|
||||
"biomes=" + biomes.size() + " (" + all.size() + ")" +
|
||||
", biomeRegistry=" + biomeRegistry +
|
||||
", type=" + super.toString() +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.betterx.bclib.api.v2.generator;
|
||||
|
||||
public enum BiomeType {
|
||||
LAND, VOID
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
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);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public static void setEndLandFunction(BiFunction<Point, Integer, Boolean> endLandFunction) {
|
||||
///GeneratorOptions.endLandFunction = endLandFunction;
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public static BiFunction<Point, Integer, Boolean> getEndLandFunction() {
|
||||
return (a, b) -> true;//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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.betterx.bclib.api.v2.generator;
|
||||
|
||||
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
|
||||
public abstract class TypeBiomeDecider extends BiomeDecider {
|
||||
protected final BiomeAPI.BiomeType assignedType;
|
||||
|
||||
public TypeBiomeDecider(BiomeAPI.BiomeType assignedType) {
|
||||
this(null, assignedType);
|
||||
}
|
||||
|
||||
protected TypeBiomeDecider(Registry<Biome> biomeRegistry, BiomeAPI.BiomeType assignedType) {
|
||||
super(biomeRegistry, (biome) -> biome.getIntendedType().is(assignedType));
|
||||
this.assignedType = assignedType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProvideBiome(BiomeAPI.BiomeType suggestedType) {
|
||||
return suggestedType.equals(assignedType);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package org.betterx.bclib.api.v2.generator.config;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource;
|
||||
import org.betterx.bclib.api.v2.generator.map.hex.HexBiomeMap;
|
||||
import org.betterx.bclib.api.v2.generator.map.square.SquareBiomeMap;
|
||||
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,
|
||||
BCLib.RUNS_NULLSCAPE ? EndBiomeGeneratorType.VANILLA : EndBiomeGeneratorType.PAULEVS,
|
||||
BCLib.RUNS_NULLSCAPE ? false : 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 MapBuilderFunction mapBuilder;
|
||||
|
||||
EndBiomeMapType(String name, @NotNull MapBuilderFunction 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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package org.betterx.bclib.api.v2.generator.config;
|
||||
|
||||
import org.betterx.bclib.api.v2.generator.BCLibNetherBiomeSource;
|
||||
import org.betterx.bclib.api.v2.generator.map.hex.HexBiomeMap;
|
||||
import org.betterx.bclib.api.v2.generator.map.square.SquareBiomeMap;
|
||||
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 MapBuilderFunction mapBuilder;
|
||||
|
||||
NetherBiomeMapType(String name, MapBuilderFunction mapBuilder) {
|
||||
this.name = name;
|
||||
this.mapBuilder = mapBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerializedName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.betterx.bclib.api.v2.generator.config;
|
||||
|
||||
import org.betterx.bclib.api.v2.generator.BiomePicker;
|
||||
import org.betterx.bclib.interfaces.BiomeMap;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MapBuilderFunction {
|
||||
BiomeMap create(long seed, int biomeSize, BiomePicker picker);
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package org.betterx.bclib.api.v2.generator.map;
|
||||
|
||||
import org.betterx.bclib.api.v2.generator.BiomePicker;
|
||||
import org.betterx.bclib.api.v2.generator.config.MapBuilderFunction;
|
||||
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 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,
|
||||
MapBuilderFunction 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.create(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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
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.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(Random.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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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 org.betterx.worlds.together.worldPreset.WorldPreset;
|
||||
|
||||
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.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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
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 org.betterx.worlds.together.worldPreset.WorldPreset;
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,539 @@
|
|||
package org.betterx.bclib.api.v2.levelgen.biomes;
|
||||
|
||||
import org.betterx.bclib.util.WeightedList;
|
||||
|
||||
import com.mojang.datafixers.Products;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.data.BuiltinRegistries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.KeyDispatchDataCodec;
|
||||
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 java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
public class BCLBiome extends BCLBiomeSettings implements BiomeData {
|
||||
public static final Codec<BCLBiome> CODEC = RecordCodecBuilder.create(instance -> codecWithSettings(instance).apply(
|
||||
instance,
|
||||
BCLBiome::new
|
||||
));
|
||||
public static final KeyDispatchDataCodec<BCLBiome> KEY_CODEC = KeyDispatchDataCodec.of(CODEC);
|
||||
|
||||
public KeyDispatchDataCodec<? extends BCLBiome> codec() {
|
||||
return KEY_CODEC;
|
||||
}
|
||||
|
||||
private static class CodecAttributes<T extends BCLBiome> {
|
||||
public RecordCodecBuilder<T, Float> t0 = Codec.FLOAT.fieldOf("terrainHeight")
|
||||
.orElse(0.1f)
|
||||
.forGetter((T o1) -> o1.terrainHeight);
|
||||
|
||||
public RecordCodecBuilder<T, Float> t1 = Codec.FLOAT.fieldOf("fogDensity")
|
||||
.orElse(1.0f)
|
||||
.forGetter((T o1) -> o1.fogDensity);
|
||||
public RecordCodecBuilder<T, Float> t2 = Codec.FLOAT.fieldOf("genChance")
|
||||
.orElse(1.0f)
|
||||
.forGetter((T o1) -> o1.genChance);
|
||||
public RecordCodecBuilder<T, Integer> t3 = Codec.INT.fieldOf("edgeSize")
|
||||
.orElse(0)
|
||||
.forGetter((T o1) -> o1.edgeSize);
|
||||
public RecordCodecBuilder<T, Boolean> t4 = Codec.BOOL.fieldOf("vertical")
|
||||
.orElse(false)
|
||||
.forGetter((T o1) -> o1.vertical);
|
||||
public RecordCodecBuilder<T, Optional<ResourceLocation>> t5 =
|
||||
ResourceLocation.CODEC
|
||||
.optionalFieldOf("edge")
|
||||
.orElse(Optional.empty())
|
||||
.forGetter((T o1) -> o1.edge == null
|
||||
? Optional.empty()
|
||||
: Optional.of(o1.edge.biomeID));
|
||||
public RecordCodecBuilder<T, ResourceLocation> t6 =
|
||||
ResourceLocation.CODEC.fieldOf("biome")
|
||||
.forGetter((T o) -> ((BCLBiome) o).biomeID);
|
||||
public RecordCodecBuilder<T, Optional<List<Climate.ParameterPoint>>> t7 =
|
||||
Climate.ParameterPoint.CODEC.listOf()
|
||||
.optionalFieldOf("parameter_points")
|
||||
.orElse(Optional.of(List.of()))
|
||||
.forGetter((T o) ->
|
||||
o.parameterPoints == null || o.parameterPoints.isEmpty()
|
||||
? Optional.empty()
|
||||
: Optional.of(o.parameterPoints));
|
||||
|
||||
public RecordCodecBuilder<T, Optional<ResourceLocation>> t8 =
|
||||
ResourceLocation.CODEC.optionalFieldOf("parent")
|
||||
.orElse(Optional.empty())
|
||||
.forGetter(
|
||||
(T o1) ->
|
||||
((BCLBiome) o1).biomeParent == null
|
||||
? Optional.empty()
|
||||
: Optional.of(
|
||||
((BCLBiome) o1).biomeParent.biomeID));
|
||||
public RecordCodecBuilder<T, Optional<WeightedList<ResourceLocation>>> t9 =
|
||||
WeightedList.listCodec(
|
||||
ResourceLocation.CODEC,
|
||||
"biomes",
|
||||
"biome"
|
||||
)
|
||||
.optionalFieldOf("sub_biomes")
|
||||
.forGetter(
|
||||
(T o) -> {
|
||||
if (o.subbiomes == null
|
||||
|| o.subbiomes.isEmpty()
|
||||
|| (o.subbiomes.size() == 1 && o.subbiomes.contains(
|
||||
o))) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(
|
||||
o.subbiomes.map(
|
||||
b -> b.biomeID));
|
||||
});
|
||||
public RecordCodecBuilder<T, Optional<String>> t10 =
|
||||
Codec.STRING.optionalFieldOf("intended_for")
|
||||
.orElse(Optional.of(BiomeAPI.BiomeType.NONE.getName()))
|
||||
.forGetter((T o) ->
|
||||
((BCLBiome) o).intendedType == null
|
||||
? Optional.empty()
|
||||
: Optional.of(((BCLBiome) o).intendedType.getName()));
|
||||
}
|
||||
|
||||
public static <T extends BCLBiome, P12> Products.P12<RecordCodecBuilder.Mu<T>, Float, Float, Float, Integer, Boolean, Optional<ResourceLocation>, ResourceLocation, Optional<List<Climate.ParameterPoint>>, Optional<ResourceLocation>, Optional<WeightedList<ResourceLocation>>, Optional<String>, P12> codecWithSettings(
|
||||
RecordCodecBuilder.Instance<T> instance,
|
||||
final RecordCodecBuilder<T, P12> p12
|
||||
) {
|
||||
CodecAttributes<T> a = new CodecAttributes<>();
|
||||
return instance.group(a.t0, a.t1, a.t2, a.t3, a.t4, a.t5, a.t6, a.t7, a.t8, a.t9, a.t10, p12);
|
||||
}
|
||||
|
||||
public static <T extends BCLBiome, P12, P13> Products.P13<RecordCodecBuilder.Mu<T>, Float, Float, Float, Integer, Boolean, Optional<ResourceLocation>, ResourceLocation, Optional<List<Climate.ParameterPoint>>, Optional<ResourceLocation>, Optional<WeightedList<ResourceLocation>>, Optional<String>, P12, P13> codecWithSettings(
|
||||
RecordCodecBuilder.Instance<T> instance,
|
||||
final RecordCodecBuilder<T, P12> p12,
|
||||
final RecordCodecBuilder<T, P13> p13
|
||||
) {
|
||||
CodecAttributes<T> a = new CodecAttributes<>();
|
||||
return instance.group(a.t0, a.t1, a.t2, a.t3, a.t4, a.t5, a.t6, a.t7, a.t8, a.t9, a.t10, p12, p13);
|
||||
}
|
||||
|
||||
public static <T extends BCLBiome, P12, P13, P14> Products.P14<RecordCodecBuilder.Mu<T>, Float, Float, Float, Integer, Boolean, Optional<ResourceLocation>, ResourceLocation, Optional<List<Climate.ParameterPoint>>, Optional<ResourceLocation>, Optional<WeightedList<ResourceLocation>>, Optional<String>, P12, P13, P14> codecWithSettings(
|
||||
RecordCodecBuilder.Instance<T> instance,
|
||||
final RecordCodecBuilder<T, P12> p12,
|
||||
final RecordCodecBuilder<T, P13> p13,
|
||||
final RecordCodecBuilder<T, P14> p14
|
||||
) {
|
||||
CodecAttributes<T> a = new CodecAttributes<>();
|
||||
return instance.group(a.t0, a.t1, a.t2, a.t3, a.t4, a.t5, a.t6, a.t7, a.t8, a.t9, a.t10, p12, p13, p14);
|
||||
}
|
||||
|
||||
public static <T extends BCLBiome> Products.P11<RecordCodecBuilder.Mu<T>, Float, Float, Float, Integer, Boolean, Optional<ResourceLocation>, ResourceLocation, Optional<List<Climate.ParameterPoint>>, Optional<ResourceLocation>, Optional<WeightedList<ResourceLocation>>, Optional<String>> codecWithSettings(
|
||||
RecordCodecBuilder.Instance<T> instance
|
||||
) {
|
||||
CodecAttributes<T> a = new CodecAttributes<>();
|
||||
return instance.group(a.t0, a.t1, a.t2, a.t3, a.t4, a.t5, a.t6, a.t7, a.t8, a.t9, a.t10);
|
||||
}
|
||||
|
||||
protected 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;
|
||||
|
||||
protected final List<Climate.ParameterPoint> parameterPoints = Lists.newArrayList();
|
||||
|
||||
private BCLBiome biomeParent;
|
||||
|
||||
private BiomeAPI.BiomeType intendedType = BiomeAPI.BiomeType.NONE;
|
||||
|
||||
protected BCLBiome(
|
||||
float terrainHeight,
|
||||
float fogDensity,
|
||||
float genChance,
|
||||
int edgeSize,
|
||||
boolean vertical,
|
||||
Optional<ResourceLocation> edge,
|
||||
ResourceLocation biomeID,
|
||||
Optional<List<Climate.ParameterPoint>> parameterPoints,
|
||||
Optional<ResourceLocation> biomeParent,
|
||||
Optional<WeightedList<ResourceLocation>> subbiomes,
|
||||
Optional<String> intendedType
|
||||
) {
|
||||
super(terrainHeight, fogDensity, genChance, edgeSize, vertical, edge.map(BiomeAPI::getBiome).orElse(null));
|
||||
biomeToRegister = null;
|
||||
this.biomeID = biomeID;
|
||||
this.biomeKey = ResourceKey.create(Registry.BIOME_REGISTRY, biomeID);
|
||||
if (subbiomes.isEmpty() || subbiomes.get().size() == 0) {
|
||||
this.subbiomes.add(this, 1);
|
||||
} else {
|
||||
this.subbiomes.addAll(subbiomes.get().map(BiomeAPI::getBiome));
|
||||
}
|
||||
this.biomeParent = biomeParent.map(BiomeAPI::getBiome).orElse(null);
|
||||
if (parameterPoints.isPresent()) this.parameterPoints.addAll(parameterPoints.get());
|
||||
this.setIntendedType(intendedType.map(t -> BiomeAPI.BiomeType.create(t)).orElse(BiomeAPI.BiomeType.NONE));
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
//this constructor should become package private and not get removed
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the intended Type for this Biome
|
||||
*
|
||||
* @param type the new type
|
||||
* @return the same instance
|
||||
*/
|
||||
protected BCLBiome setIntendedType(BiomeAPI.BiomeType type) {
|
||||
return _setIntendedType(type);
|
||||
}
|
||||
|
||||
BCLBiome _setIntendedType(BiomeAPI.BiomeType type) {
|
||||
this.intendedType = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BiomeAPI.BiomeType getIntendedType() {
|
||||
return this.intendedType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
public ResourceKey<BCLBiome> getBCLBiomeKey() {
|
||||
return ResourceKey.create(BCLBiomeRegistry.BCL_BIOMES_REGISTRY, biomeID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
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}.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,919 @@
|
|||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the type for the Biome. The intended Type defines in which Dimension a
|
||||
* Biome is allowed to spawn. Currently each Biome can only spawn in one dimension
|
||||
*
|
||||
* @param type The intended type
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder intendedType(BiomeAPI.BiomeType type) {
|
||||
this.biomeType = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the intended type for the Biome to an EndLand Biome
|
||||
*
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder endLandBiome() {
|
||||
return intendedType(BiomeAPI.BiomeType.BCL_END_LAND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the intended type for the Biome to an EndVoid (aka small islands) Biome
|
||||
*
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder endVoidBiome() {
|
||||
return intendedType(BiomeAPI.BiomeType.BCL_END_VOID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the intended type for the Biome to an Endbarrens Biome
|
||||
*
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder endBarrensBiome() {
|
||||
return intendedType(BiomeAPI.BiomeType.BCL_END_BARRENS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the intended type for the Biome to an End Center Island Biome
|
||||
*
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder endCenterBiome() {
|
||||
return intendedType(BiomeAPI.BiomeType.BCL_END_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the intended type for the Biome to a Nether Biome
|
||||
*
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder netherBiome() {
|
||||
return intendedType(BiomeAPI.BiomeType.BCL_NETHER);
|
||||
}
|
||||
|
||||
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);
|
||||
if (biomeType != null)
|
||||
res._setIntendedType(biomeType);
|
||||
|
||||
|
||||
//carvers.forEach(cfg -> BiomeAPI.addBiomeCarver(biome, cfg.second, cfg.first));
|
||||
return res;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package org.betterx.bclib.api.v2.levelgen.biomes;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.worlds.together.WorldsTogether;
|
||||
import org.betterx.worlds.together.world.event.WorldBootstrap;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
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.data.BuiltinRegistries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.KeyDispatchDataCodec;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
public class BCLBiomeRegistry {
|
||||
public static final ResourceKey<Registry<BCLBiome>> BCL_BIOMES_REGISTRY =
|
||||
createRegistryKey(WorldsTogether.makeID("worldgen/betterx/biome"));
|
||||
|
||||
public static final ResourceKey<Registry<Codec<? extends BCLBiome>>> BCL_BIOME_CODEC_REGISTRY =
|
||||
createRegistryKey(WorldsTogether.makeID("worldgen/betterx/biome_codec"));
|
||||
|
||||
public static Registry<Codec<? extends BCLBiome>> BIOME_CODECS = Registry.registerSimple(
|
||||
BCL_BIOME_CODEC_REGISTRY,
|
||||
BCLBiomeRegistry::bootstrapCodecs
|
||||
);
|
||||
public static Registry<BCLBiome> BUILTIN_BCL_BIOMES = new MappedRegistry<>(
|
||||
BCL_BIOMES_REGISTRY,
|
||||
Lifecycle.stable(), null
|
||||
);
|
||||
|
||||
/**
|
||||
* 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());
|
||||
|
||||
public static Codec<? extends BCLBiome> registerBiomeCodec(
|
||||
ResourceLocation location,
|
||||
KeyDispatchDataCodec<? extends BCLBiome> codec
|
||||
) {
|
||||
Registry.register(BIOME_CODECS, location, codec.codec());
|
||||
return codec.codec();
|
||||
}
|
||||
|
||||
public static ResourceKey<BCLBiome> register(BCLBiome biome) {
|
||||
Registry.register(BUILTIN_BCL_BIOMES, biome.getBCLBiomeKey(), biome);
|
||||
return biome.getBCLBiomeKey();
|
||||
}
|
||||
|
||||
private static <T> ResourceKey<Registry<T>> createRegistryKey(ResourceLocation location) {
|
||||
return ResourceKey.createRegistryKey(location);
|
||||
}
|
||||
|
||||
private static Codec<? extends BCLBiome> bootstrapCodecs(Registry<Codec<? extends BCLBiome>> registry) {
|
||||
return Registry.register(registry, BCLib.makeID("biome"), BCLBiome.KEY_CODEC.codec());
|
||||
}
|
||||
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static Holder<BCLBiome> bootstrap(Registry<BCLBiome> registry) {
|
||||
BuiltinRegistries.register(registry, BiomeAPI.SMALL_END_ISLANDS.getBCLBiomeKey(), BiomeAPI.SMALL_END_ISLANDS);
|
||||
BuiltinRegistries.register(registry, BiomeAPI.END_BARRENS.getBCLBiomeKey(), BiomeAPI.END_BARRENS);
|
||||
BuiltinRegistries.register(registry, BiomeAPI.END_HIGHLANDS.getBCLBiomeKey(), BiomeAPI.END_HIGHLANDS);
|
||||
BuiltinRegistries.register(registry, BiomeAPI.END_MIDLANDS.getBCLBiomeKey(), BiomeAPI.END_MIDLANDS);
|
||||
BuiltinRegistries.register(registry, BiomeAPI.THE_END.getBCLBiomeKey(), BiomeAPI.THE_END);
|
||||
BuiltinRegistries.register(
|
||||
registry,
|
||||
BiomeAPI.BASALT_DELTAS_BIOME.getBCLBiomeKey(),
|
||||
BiomeAPI.BASALT_DELTAS_BIOME
|
||||
);
|
||||
BuiltinRegistries.register(
|
||||
registry,
|
||||
BiomeAPI.SOUL_SAND_VALLEY_BIOME.getBCLBiomeKey(),
|
||||
BiomeAPI.SOUL_SAND_VALLEY_BIOME
|
||||
);
|
||||
BuiltinRegistries.register(
|
||||
registry,
|
||||
BiomeAPI.WARPED_FOREST_BIOME.getBCLBiomeKey(),
|
||||
BiomeAPI.WARPED_FOREST_BIOME
|
||||
);
|
||||
BuiltinRegistries.register(
|
||||
registry,
|
||||
BiomeAPI.CRIMSON_FOREST_BIOME.getBCLBiomeKey(),
|
||||
BiomeAPI.CRIMSON_FOREST_BIOME
|
||||
);
|
||||
BuiltinRegistries.register(
|
||||
registry,
|
||||
BiomeAPI.NETHER_WASTES_BIOME.getBCLBiomeKey(),
|
||||
BiomeAPI.NETHER_WASTES_BIOME
|
||||
);
|
||||
return BuiltinRegistries.register(registry, EMPTY_BIOME.getBCLBiomeKey(), EMPTY_BIOME);
|
||||
}
|
||||
|
||||
public static BCLBiome get(ResourceLocation loc) {
|
||||
return get(WorldBootstrap.getLastRegistryAccessOrElseBuiltin(), loc);
|
||||
}
|
||||
|
||||
public static BCLBiome get(RegistryAccess access, ResourceLocation loc) {
|
||||
return getBclBiomesRegistry(access).get(loc);
|
||||
}
|
||||
|
||||
public static BCLBiome getOrElseEmpty(ResourceLocation loc) {
|
||||
return getOrElseEmpty(WorldBootstrap.getLastRegistryAccessOrElseBuiltin(), loc);
|
||||
}
|
||||
|
||||
public static BCLBiome getOrElseEmpty(RegistryAccess access, ResourceLocation loc) {
|
||||
BCLBiome res = get(access, loc);
|
||||
if (res == null) return EMPTY_BIOME;
|
||||
return res;
|
||||
}
|
||||
|
||||
public static Stream<ResourceKey<BCLBiome>> getAll(BiomeAPI.BiomeType dim) {
|
||||
return getAll(WorldBootstrap.getLastRegistryAccessOrElseBuiltin(), dim);
|
||||
}
|
||||
|
||||
public static Stream<ResourceKey<BCLBiome>> getAll(RegistryAccess access, BiomeAPI.BiomeType dim) {
|
||||
return getBclBiomesRegistry(access)
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(e -> e.getValue().getIntendedType().is(BiomeAPI.BiomeType.END))
|
||||
.map(e -> e.getKey());
|
||||
}
|
||||
|
||||
private static Registry<BCLBiome> getBclBiomesRegistry(RegistryAccess access) {
|
||||
if (access != null) {
|
||||
return ((Optional<Registry<BCLBiome>>) access
|
||||
.registry(BCLBiomeRegistry.BCL_BIOMES_REGISTRY))
|
||||
.orElse(BUILTIN_BCL_BIOMES);
|
||||
} else {
|
||||
return BUILTIN_BCL_BIOMES;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ensureStaticallyLoaded() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
BCLBiomeSettings(
|
||||
float terrainHeight,
|
||||
float fogDensity,
|
||||
float genChance,
|
||||
int edgeSize,
|
||||
boolean vertical,
|
||||
BCLBiome edge
|
||||
) {
|
||||
this.terrainHeight = terrainHeight;
|
||||
this.fogDensity = fogDensity;
|
||||
this.genChance = genChance;
|
||||
this.edgeSize = edgeSize;
|
||||
this.vertical = vertical;
|
||||
this.edge = edge;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
1093
src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BiomeAPI.java
Normal file
1093
src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BiomeAPI.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,15 @@
|
|||
package org.betterx.bclib.api.v2.levelgen.biomes;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.util.KeyDispatchDataCodec;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface BiomeData {
|
||||
Codec<BCLBiome> CODEC = BCLBiomeRegistry
|
||||
.BIOME_CODECS
|
||||
.byNameCodec()
|
||||
.dispatch(b -> b.codec().codec(), Function.identity());
|
||||
|
||||
KeyDispatchDataCodec<? extends BCLBiome> codec();
|
||||
}
|
|
@ -0,0 +1,378 @@
|
|||
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 = BCLBiomeRegistry.getAll(BiomeAPI.BiomeType.NETHER).map(k -> k.location());
|
||||
else if (mod.getKey() == Level.END)
|
||||
s = BCLBiomeRegistry.getAll(BiomeAPI.BiomeType.END).map(k -> k.location());
|
||||
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 <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 == BCLBiomeRegistry.EMPTY_BIOME) {
|
||||
bclBiome = new BCLBiome(biomeKey, setings);
|
||||
bclBiome._setIntendedType(type);
|
||||
}
|
||||
|
||||
BiomeAPI.registerBiome(bclBiome);
|
||||
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 == BCLBiomeRegistry.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 == BCLBiomeRegistry.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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
));
|
||||
}
|
||||
}
|
|
@ -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.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;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* @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, Random random) {
|
||||
return place(this.getFeature(), this.getConfiguration(), level, pos, random);
|
||||
}
|
||||
|
||||
private static boolean placeUnbound(
|
||||
Feature<?> feature,
|
||||
FeatureConfiguration config,
|
||||
ServerLevel level,
|
||||
BlockPos pos,
|
||||
Random 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,
|
||||
Random 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,
|
||||
Random random
|
||||
) {
|
||||
return placeUnbound(feature, config, level, pos, random);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
// }
|
||||
//
|
||||
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.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;
|
||||
import java.util.Random;
|
||||
|
||||
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(Random random) {
|
||||
return random.nextFloat() < floorChance;
|
||||
}
|
||||
|
||||
public abstract boolean isValidBase(BlockState state);
|
||||
|
||||
public abstract BlockState createBlock(int height, int maxHeight, Random 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, Random 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, Random 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, Random 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.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;
|
||||
import java.util.Random;
|
||||
|
||||
@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 Random 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,
|
||||
Random 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,
|
||||
Random 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,
|
||||
Random 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,
|
||||
Random Random,
|
||||
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(Random) ? 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,
|
||||
Random Random,
|
||||
BlockPos blockPos,
|
||||
ScatterFeatureConfig config
|
||||
) {
|
||||
if (config.baseState.isPresent() && config.baseReplaceChance > 0 && Random.nextFloat() < config.baseReplaceChance) {
|
||||
final BlockState baseState = config.baseState.get();
|
||||
BlockPos pos;
|
||||
for (Direction direction : Direction.Plane.HORIZONTAL) {
|
||||
if (Random.nextFloat() > config.chanceOfDirectionalSpread) continue;
|
||||
pos = blockPos.relative(direction);
|
||||
placeBaseBlockIfPossible(levelAccessor, pos, baseState);
|
||||
|
||||
if (Random.nextFloat() > config.chanceOfSpreadRadius2) continue;
|
||||
pos = pos.relative(Direction.getRandom(Random));
|
||||
placeBaseBlockIfPossible(levelAccessor, pos, baseState);
|
||||
|
||||
if (Random.nextFloat() > config.chanceOfSpreadRadius3) continue;
|
||||
pos = pos.relative(Direction.getRandom(Random));
|
||||
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,
|
||||
Random 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue