mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 17:55:08 +00:00
Compare commits
636
Commits
1.0.0-alpha7
...
2.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c5e5df290 | ||
|
|
39ee364418 | ||
|
|
4c9cae5e0b | ||
|
|
11b53c9f8f | ||
|
|
c769a08a4e | ||
|
|
90dac9f39d | ||
|
|
c5c4b290b1 | ||
|
|
4b46de4566 | ||
|
|
9f2a71be4e | ||
|
|
79fe08417c | ||
|
|
5de64abdef | ||
|
|
2e07465166 | ||
|
|
2b610b9aea | ||
|
|
ab192ddd2a | ||
|
|
cb5e2ed7d4 | ||
|
|
e914ef7563 | ||
|
|
748dbd55a1 | ||
|
|
140c0c2285 | ||
|
|
af26442b98 | ||
|
|
ef07ee9f77 | ||
|
|
1b8087e8a3 | ||
|
|
9107ea9ae5 | ||
|
|
938a6bda90 | ||
|
|
e9f6f72741 | ||
|
|
379330f997 | ||
|
|
2fecb3a05b | ||
|
|
c085af35bd | ||
|
|
a36aec36a2 | ||
|
|
bdd6259c3f | ||
|
|
d7d2228a6c | ||
|
|
cf9993fce4 | ||
|
|
3ae57ca594 | ||
|
|
9816cb2148 | ||
|
|
b1e496f56d | ||
|
|
c8e50227fb | ||
|
|
066381a7c0 | ||
|
|
9a0ea1ad65 | ||
|
|
8e26a0179b | ||
|
|
1d96af8e3a | ||
|
|
f7eb86cdb6 | ||
|
|
b38fafa831 | ||
|
|
7dc2625f8c | ||
|
|
3d97977050 | ||
|
|
5f41c60bc2 | ||
|
|
0325a96bc2 | ||
|
|
ad123d0bd5 | ||
|
|
4b89997985 | ||
|
|
f5c9fa3301 | ||
|
|
d6a6d3b525 | ||
|
|
f079bed977 | ||
|
|
c9ec40e94b | ||
|
|
8380f3853d | ||
|
|
534ae6b05c | ||
|
|
68d523d3df | ||
|
|
2f22a7b3ad | ||
|
|
7b79b90dd6 | ||
|
|
417f23f826 | ||
|
|
ca3c46c873 | ||
|
|
82fa9ade2b | ||
|
|
aaab8fd0bc | ||
|
|
bd56ccc4b8 | ||
|
|
e17f60308d | ||
|
|
1dd98978ad | ||
|
|
dae986eb0b | ||
|
|
5fdf84edd0 | ||
|
|
b19d023c11 | ||
|
|
41eafbd151 | ||
|
|
dd970b4c6a | ||
|
|
f79ad7f22a | ||
|
|
6947ffa3e2 | ||
|
|
969d2c3710 | ||
|
|
c5988d29c3 | ||
|
|
dc68c2c265 | ||
|
|
51533a81ec | ||
|
|
90eabb5d40 | ||
|
|
74843cb1f9 | ||
|
|
5e0344a529 | ||
|
|
3cdd934987 | ||
|
|
9d90ff137c | ||
|
|
bee9541797 | ||
|
|
b75a5d8f7d | ||
|
|
8d3ab41c53 | ||
|
|
48d032e654 | ||
|
|
1968914582 | ||
|
|
a70bd76ff3 | ||
|
|
db919b5f37 | ||
|
|
cde47ea423 | ||
|
|
5ef6cb11c0 | ||
|
|
b7015bf10e | ||
|
|
46602ab921 | ||
|
|
63e9fa0df1 | ||
|
|
dcea970a77 | ||
|
|
b29240d6da | ||
|
|
9d5a02ed21 | ||
|
|
b454483aae | ||
|
|
72b13543c6 | ||
|
|
36d98fcb22 | ||
|
|
44c5b9617a | ||
|
|
f3f1677db8 | ||
|
|
d396b611ad | ||
|
|
08f4c0f9db | ||
|
|
50b0d22dc2 | ||
|
|
7fed54264e | ||
|
|
be87401be6 | ||
|
|
94254006df | ||
|
|
f79774df65 | ||
|
|
2f2fc22df3 | ||
|
|
26300bbb1b | ||
|
|
88e4d2fd6c | ||
|
|
bb52476d27 | ||
|
|
6d60477b93 | ||
|
|
362724610c | ||
|
|
ea4a3cdf0b | ||
|
|
65959268d0 | ||
|
|
58d820cd7e | ||
|
|
685d68bd9c | ||
|
|
ad63d2d62c | ||
|
|
d2730ccca8 | ||
|
|
e9257b8739 | ||
|
|
3984225faa | ||
|
|
52db6b56d6 | ||
|
|
34bfe513f1 | ||
|
|
42d7ed58aa | ||
|
|
9a4f0f9b99 | ||
|
|
be90a01725 | ||
|
|
e5e8250d44 | ||
|
|
062a03ffc2 | ||
|
|
9f5afaaec3 | ||
|
|
b05f17a477 | ||
|
|
57caf08212 | ||
|
|
085ec79c21 | ||
|
|
38641268ae | ||
|
|
fe8e723638 | ||
|
|
16debfd518 | ||
|
|
8cfc3e1f4a | ||
|
|
2c808fae43 | ||
|
|
1649c3c0c6 | ||
|
|
b84286ba3b | ||
|
|
ee6c79802a | ||
|
|
d3366ad31a | ||
|
|
92b6dcc010 | ||
|
|
89132f3ef9 | ||
|
|
f0d45b0d62 | ||
|
|
173d986a5b | ||
|
|
473a690054 | ||
|
|
2515b3520f | ||
|
|
a32f9ca244 | ||
|
|
941fc88484 | ||
|
|
4106dd3865 | ||
|
|
61f6aa19a8 | ||
|
|
77875f7f91 | ||
|
|
241c32def1 | ||
|
|
a8107f97e8 | ||
|
|
8bec433699 | ||
|
|
2d39fb2397 | ||
|
|
cfd8eb1411 | ||
|
|
59c270a5b9 | ||
|
|
87ac03281c | ||
|
|
36b82fa2ce | ||
|
|
2fc2e2e38d | ||
|
|
33de1de9c4 | ||
|
|
55811b9d7b | ||
|
|
a10d92f52e | ||
|
|
7d2fe12ce9 | ||
|
|
006ded801e | ||
|
|
03afd6939e | ||
|
|
adc5421e00 | ||
|
|
dfe60e4b3e | ||
|
|
72c6cdfa60 | ||
|
|
c5daea86f8 | ||
|
|
f8958ee990 | ||
|
|
073260060b | ||
|
|
771fee124d | ||
|
|
680285bf54 | ||
|
|
df8784536e | ||
|
|
c8991d810c | ||
|
|
e3b02642ac | ||
|
|
2759dd6689 | ||
|
|
996dbaef2c | ||
|
|
72ca675c93 | ||
|
|
e6e230e270 | ||
|
|
43d7282ba5 | ||
|
|
4b2d57a32d | ||
|
|
06bf71126f | ||
|
|
4c0f2f2685 | ||
|
|
78334717e7 | ||
|
|
a77765a2c9 | ||
|
|
1a9fe45ee9 | ||
|
|
21ef5905c8 | ||
|
|
5b67eaa474 | ||
|
|
c9d59c3b90 | ||
|
|
fa7ff74fec | ||
|
|
a34a3ebd39 | ||
|
|
2e7476d287 | ||
|
|
3244071012 | ||
|
|
7035dbde02 | ||
|
|
97ae04c2fa | ||
|
|
e7fa7f62d0 | ||
|
|
ea39712749 | ||
|
|
66dd3eaa94 | ||
|
|
270697cd10 | ||
|
|
2b53a851ef | ||
|
|
34b6efde7b | ||
|
|
86edb721da | ||
|
|
39c5f4bf7b | ||
|
|
5dc54bb1da | ||
|
|
7924769ab1 | ||
|
|
8afe2e2ccf | ||
|
|
e8dd60e088 | ||
|
|
9a621405ba | ||
|
|
45a677030d | ||
|
|
4d531022d2 | ||
|
|
48ba2160ad | ||
|
|
334ec81f9b | ||
|
|
86c726e50b | ||
|
|
e6d778dad3 | ||
|
|
dce5013ffb | ||
|
|
6eef63619c | ||
|
|
a8153fa0f7 | ||
|
|
49c04cf776 | ||
|
|
85b0277a66 | ||
|
|
0525b31262 | ||
|
|
fdb2053c50 | ||
|
|
31abbcce32 | ||
|
|
063b2242e8 | ||
|
|
577f905dc5 | ||
|
|
071d2d4a39 | ||
|
|
891715b113 | ||
|
|
30a35ef25e | ||
|
|
33453b3b8c | ||
|
|
62e699f6d0 | ||
|
|
f107d173b7 | ||
|
|
d0948b36f3 | ||
|
|
ae1211f280 | ||
|
|
3d76cb3073 | ||
|
|
45fb120223 | ||
|
|
d7a196eb5c | ||
|
|
4d53e2af9b | ||
|
|
cb6336a365 | ||
|
|
5095021e0e | ||
|
|
f88ecbf68d | ||
|
|
7da9b9eb1c | ||
|
|
02c394108f | ||
|
|
720aaca1b7 | ||
|
|
1e71b1c4d8 | ||
|
|
df4c423e93 | ||
|
|
78e0074e5a | ||
|
|
0350ea2cb6 | ||
|
|
bed724ba59 | ||
|
|
6fa973dbc8 | ||
|
|
c4ad460f34 | ||
|
|
2a8404c6c2 | ||
|
|
108db9031b | ||
|
|
d6d8a5dad6 | ||
|
|
ad2b73edf9 | ||
|
|
bb5526598e | ||
|
|
611bbf02c8 | ||
|
|
ca16036604 | ||
|
|
f118211257 | ||
|
|
81d03ea552 | ||
|
|
67c8628aeb | ||
|
|
f9b9873637 | ||
|
|
c66bf8658e | ||
|
|
052824017d | ||
|
|
6a5d914a43 | ||
|
|
208373deaa | ||
|
|
f22a3f612b | ||
|
|
9ff1015d19 | ||
|
|
0023fe3da2 | ||
|
|
7e2399714c | ||
|
|
9029182353 | ||
|
|
e6039691b5 | ||
|
|
5db1edba33 | ||
|
|
d450461726 | ||
|
|
95f8afc0f3 | ||
|
|
19635e20ec | ||
|
|
ead4e11376 | ||
|
|
b0ceb5306a | ||
|
|
99a81369f0 | ||
|
|
e2ee822948 | ||
|
|
9fe7e800f6 | ||
|
|
debfcdfda0 | ||
|
|
df662188f4 | ||
|
|
842916405f | ||
|
|
09afe81290 | ||
|
|
292809d8e8 | ||
|
|
47503ce254 | ||
|
|
f738adbdef | ||
|
|
70d15f2383 | ||
|
|
d66b5577f4 | ||
|
|
e293f12fb8 | ||
|
|
b69eca6749 | ||
|
|
03342f08c6 | ||
|
|
b85614f983 | ||
|
|
5afb5d8f5a | ||
|
|
c353c83cf3 | ||
|
|
78072236d2 | ||
|
|
350fdcd7e4 | ||
|
|
3d98e57178 | ||
|
|
55c1786811 | ||
|
|
356d826387 | ||
|
|
c34d8548a5 | ||
|
|
81a000af75 | ||
|
|
711c23c722 | ||
|
|
5aa1d504b0 | ||
|
|
60ab3b7900 | ||
|
|
912e0e83b8 | ||
|
|
104eda0b51 | ||
|
|
7fb8cde66d | ||
|
|
0e4421f2ce | ||
|
|
3cf3d096fd | ||
|
|
ffa1193dd2 | ||
|
|
d180023469 | ||
|
|
762275a6da | ||
|
|
0e66bcf42f | ||
|
|
979ad344a1 | ||
|
|
5f328e9b57 | ||
|
|
2dba62bf20 | ||
|
|
0fd4d457e2 | ||
|
|
b56e803622 | ||
|
|
3ab965ded7 | ||
|
|
538b38898c | ||
|
|
e43cd6f18e | ||
|
|
38285087c9 | ||
|
|
deb6c2b6e5 | ||
|
|
e11985a913 | ||
|
|
4bbf39eb4d | ||
|
|
8c90f79c17 | ||
|
|
e4e657846d | ||
|
|
2dace708b4 | ||
|
|
1df1ec3530 | ||
|
|
d96ac28043 | ||
|
|
72c13cff67 | ||
|
|
f233ba65e3 | ||
|
|
c0d636d4f7 | ||
|
|
bd5cd350c3 | ||
|
|
88936d3713 | ||
|
|
cf5bf2e2e7 | ||
|
|
cfd2388c85 | ||
|
|
fd78f63f12 | ||
|
|
6cf119dfee | ||
|
|
8c5f4d2543 | ||
|
|
9e1fa7b80f | ||
|
|
6c99872459 | ||
|
|
f0809410ee | ||
|
|
98299b69d2 | ||
|
|
fed0aa77c5 | ||
|
|
f7fb3186c8 | ||
|
|
ab243bb936 | ||
|
|
60cce7ea1d | ||
|
|
b837b0571d | ||
|
|
7abc6213f5 | ||
|
|
b46643ae8c | ||
|
|
e3d1fedd48 | ||
|
|
ebcee499eb | ||
|
|
ad26b1ad6f | ||
|
|
73338e3c3e | ||
|
|
2c89244fbe | ||
|
|
991f658819 | ||
|
|
1588c7000d | ||
|
|
260d57e308 | ||
|
|
3a1d1b03b3 | ||
|
|
66563f41c8 | ||
|
|
a52adb9190 | ||
|
|
1c6f9aa131 | ||
|
|
8c584fed05 | ||
|
|
558ea0fe12 | ||
|
|
1315d8971a | ||
|
|
e33f2ff7e3 | ||
|
|
29072ee3f8 | ||
|
|
ff8315270b | ||
|
|
54f276a460 | ||
|
|
feef745722 | ||
|
|
88cb3faf47 | ||
|
|
f477675ee9 | ||
|
|
4294059fbe | ||
|
|
e40c9f2441 | ||
|
|
52e886adcc | ||
|
|
f38fcd1d82 | ||
|
|
fbebbd4049 | ||
|
|
03a0406199 | ||
|
|
1ef691ecc8 | ||
|
|
b242a13fd6 | ||
|
|
2274bfabfd | ||
|
|
7e4292bdba | ||
|
|
f79e112700 | ||
|
|
ba46feb20b | ||
|
|
33804c67da | ||
|
|
3c9353e143 | ||
|
|
77eb3ad46a | ||
|
|
8a56ab8ca1 | ||
|
|
fe065803aa | ||
|
|
f94268c384 | ||
|
|
f7a8b0f3bf | ||
|
|
8893cf2291 | ||
|
|
adc575c5ab | ||
|
|
35a056b73b | ||
|
|
fbbb0ed4f2 | ||
|
|
bc148ba09c | ||
|
|
1db3932af0 | ||
|
|
0fd4611a97 | ||
|
|
27243991b7 | ||
|
|
956eec0366 | ||
|
|
9b06c5a210 | ||
|
|
5511759b6d | ||
|
|
fb5aa81e01 | ||
|
|
6592e13e02 | ||
|
|
31174dc35a | ||
|
|
f0bd9de081 | ||
|
|
75617a5456 | ||
|
|
e158faada0 | ||
|
|
f684bc0a1b | ||
|
|
3d56b1c2fc | ||
|
|
6e60f671b5 | ||
|
|
323d2e726c | ||
|
|
88d5030aa9 | ||
|
|
894b9573db | ||
|
|
d8d8cd93c5 | ||
|
|
986f1bd6a1 | ||
|
|
e85ca85027 | ||
|
|
d96023f36c | ||
|
|
203b552993 | ||
|
|
96e2f42251 | ||
|
|
9a73dc9bfd | ||
|
|
f01cdfda4b | ||
|
|
daa1405ba4 | ||
|
|
846d70f0c0 | ||
|
|
e59e41bfdb | ||
|
|
eb8a826419 | ||
|
|
efe7c16c50 | ||
|
|
c4b4b79f6c | ||
|
|
2e5ce0abdb | ||
|
|
d317b05f5b | ||
|
|
cc51a464f1 | ||
|
|
3e6aa461c6 | ||
|
|
d236689e6a | ||
|
|
d15bbb56ee | ||
|
|
f4355bd4c4 | ||
|
|
5dbd244d72 | ||
|
|
367c82a225 | ||
|
|
852bcbb38b | ||
|
|
464af945b2 | ||
|
|
996b3df9cb | ||
|
|
22d28abce6 | ||
|
|
636b616f64 | ||
|
|
14b7178419 | ||
|
|
22bf48c093 | ||
|
|
4600bca2d0 | ||
|
|
fcb9536d93 | ||
|
|
51a6a72ae5 | ||
|
|
f41f27e706 | ||
|
|
346a305f1d | ||
|
|
da8f700068 | ||
|
|
f2445fabba | ||
|
|
bc3ca5209f | ||
|
|
00517079d2 | ||
|
|
7b8a64ec94 | ||
|
|
794e59b6de | ||
|
|
9710801cb1 | ||
|
|
c5bda3b19c | ||
|
|
f9678a3793 | ||
|
|
091290b218 | ||
|
|
d991b55af7 | ||
|
|
1530a8f6fe | ||
|
|
98156191fe | ||
|
|
956914efa3 | ||
|
|
51c3beea9e | ||
|
|
657ff921de | ||
|
|
c9539faf44 | ||
|
|
2ca4d5338f | ||
|
|
236a513705 | ||
|
|
6d49e82a0c | ||
|
|
2fea614a45 | ||
|
|
85ed121158 | ||
|
|
9316f42808 | ||
|
|
74b66746eb | ||
|
|
5abeb9709e | ||
|
|
5ec50f22d9 | ||
|
|
dfe3a4220b | ||
|
|
1e1e95f249 | ||
|
|
02b0d1b728 | ||
|
|
9c81c96bb3 | ||
|
|
fff5b2cb01 | ||
|
|
77f1e9b5a2 | ||
|
|
3158a33a65 | ||
|
|
f6f840235e | ||
|
|
793f668ef8 | ||
|
|
da01393799 | ||
|
|
3030be32f4 | ||
|
|
dbdeba9865 | ||
|
|
80ff6af14e | ||
|
|
c34290d182 | ||
|
|
6e3235a985 | ||
|
|
419fa9f0c0 | ||
|
|
c996e5e58c | ||
|
|
6d28df74a6 | ||
|
|
ce97ef876d | ||
|
|
8898a6f252 | ||
|
|
37f638ac34 | ||
|
|
f73c698a5a | ||
|
|
029da0e7fa | ||
|
|
e40ee19ac8 | ||
|
|
33832fe547 | ||
|
|
671a776952 | ||
|
|
445b6a3e68 | ||
|
|
ff65370cc5 | ||
|
|
ff290c19e2 | ||
|
|
d1e505308c | ||
|
|
594799103b | ||
|
|
8a65f471a0 | ||
|
|
d52e2b0454 | ||
|
|
9b31bae1c8 | ||
|
|
7cbb905bd7 | ||
|
|
8a50ed036d | ||
|
|
f1394382ab | ||
|
|
74e6c80aad | ||
|
|
da9d6bad56 | ||
|
|
58a6fabf3c | ||
|
|
f8c11967d1 | ||
|
|
6d1c4bf39c | ||
|
|
018a47e400 | ||
|
|
509017551a | ||
|
|
cf454f287c | ||
|
|
0e3e4283af | ||
|
|
8176e1cd1c | ||
|
|
d2cba7c321 | ||
|
|
f791b0dab3 | ||
|
|
5093531811 | ||
|
|
c942df386a | ||
|
|
1d24789362 | ||
|
|
b8b445322c | ||
|
|
38c72b7af0 | ||
|
|
466b0544e3 | ||
|
|
26da0c2b64 | ||
|
|
8c3ac9e310 | ||
|
|
6a7c2520d0 | ||
|
|
a0d782a1a5 | ||
|
|
aebaab8d6f | ||
|
|
05158317e8 | ||
|
|
4d39c90ba6 | ||
|
|
83cf0801ef | ||
|
|
db49a613a0 | ||
|
|
64087f53c5 | ||
|
|
efb039ff38 | ||
|
|
388929cc0c | ||
|
|
6691511e17 | ||
|
|
b956a44857 | ||
|
|
8863826d0e | ||
|
|
65d9bf8f2f | ||
|
|
3ee82e7157 | ||
|
|
3b61bb74a2 | ||
|
|
f378b6e7ba | ||
|
|
aff05e1812 | ||
|
|
806bcf2d6b | ||
|
|
3755e2595f | ||
|
|
0d79f5e193 | ||
|
|
9210cd3283 | ||
|
|
733ffff81f | ||
|
|
32e2efc1e5 | ||
|
|
d9a688f1d8 | ||
|
|
ba66a0602b | ||
|
|
d53a85e013 | ||
|
|
5f6c418315 | ||
|
|
15569aadc7 | ||
|
|
0f97c09fda | ||
|
|
fd43fbd9c9 | ||
|
|
2b4ea9fd96 | ||
|
|
1837dcec72 | ||
|
|
84e1fa284b | ||
|
|
c211d90faf | ||
|
|
537eb06b66 | ||
|
|
93b03847df | ||
|
|
85e44369bb | ||
|
|
fbc1b1b440 | ||
|
|
97152a6ace | ||
|
|
3cde764d33 | ||
|
|
45b20efc45 | ||
|
|
869f843e3e | ||
|
|
ad533bcae9 | ||
|
|
b100652a7d | ||
|
|
d497c301c9 | ||
|
|
17dddb6691 | ||
|
|
818f4d3cca | ||
|
|
9141137606 | ||
|
|
9b6e781a7c | ||
|
|
8229ed9aa0 | ||
|
|
71ea27ee3a | ||
|
|
f760a3e011 | ||
|
|
14c09770eb | ||
|
|
a0646456d3 | ||
|
|
7a783b9d45 | ||
|
|
b0828fb96a | ||
|
|
369893cfa3 | ||
|
|
a96a973d44 | ||
|
|
2c767f2970 | ||
|
|
2839e892e0 | ||
|
|
a16ed9460a | ||
|
|
921545ca1f | ||
|
|
717c918bd4 | ||
|
|
98b707e3e3 | ||
|
|
73c7ca94e1 | ||
|
|
cf3cfd806f | ||
|
|
c58870fdeb | ||
|
|
a8ccc8903a | ||
|
|
adb582cd1e | ||
|
|
364f3a9c00 | ||
|
|
c19c812c10 | ||
|
|
0460f6dbcd | ||
|
|
4fabe45ae7 | ||
|
|
ab54359f03 | ||
|
|
0c9ec5b23a | ||
|
|
20ae68aa62 | ||
|
|
46164ebac7 | ||
|
|
de0a600163 | ||
|
|
956f9e344e | ||
|
|
acc1f58778 | ||
|
|
1a38bd1a00 | ||
|
|
cfc19bb35e | ||
|
|
446c8e74ee | ||
|
|
74cdc561e6 | ||
|
|
67fbcc8ff0 | ||
|
|
d19b8d5302 | ||
|
|
ae0e03bead | ||
|
|
7d7d73a48f | ||
|
|
6dcc4649db | ||
|
|
5db5c7f683 | ||
|
|
21b9b8cb3f | ||
|
|
fc520de642 | ||
|
|
9916075569 | ||
|
|
6624a9adb3 | ||
|
|
c3216007c1 | ||
|
|
92b22b1827 | ||
|
|
f32c14f795 | ||
|
|
ef15577b15 | ||
|
|
ac60c01788 | ||
|
|
9410902937 |
No files matched your search
@@ -14,10 +14,18 @@ body:
|
||||
id: version
|
||||
attributes:
|
||||
label: Mod Version
|
||||
description: "What version of the Mod are you using? (Note: None have been officially released yet)"
|
||||
description: "What version of the Mod are you using?"
|
||||
placeholder: "Example: 1.0.0-alpha1"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: report
|
||||
attributes:
|
||||
label: Bug Report
|
||||
description: "Relevant information, as well as relevant logs attached such as `logs/fml-client-latest.log."
|
||||
placeholder: "Example: https://mclo.gs/ OR submit the file to github by dragging it to this textbox."
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: java
|
||||
attributes:
|
||||
@@ -31,15 +39,43 @@ body:
|
||||
- Java 19
|
||||
- Java 20
|
||||
- Java 21
|
||||
- Java 25
|
||||
- Other (Please Specify)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: report
|
||||
- type: dropdown
|
||||
id: graphics
|
||||
attributes:
|
||||
label: Bug Report
|
||||
description: "Relevant information, as well as relevant logs attached such as `logs/fml-client-latest.log."
|
||||
placeholder: "Example: https://mclo.gs/ OR submit the file to github by dragging it to this textbox."
|
||||
label: Graphics Card Vendor
|
||||
description: What graphics card vendor are you using?
|
||||
options:
|
||||
- Other (Please Specify)
|
||||
- NVidia
|
||||
- AMD/ATI
|
||||
- Intel iGPU
|
||||
- AMD iGPU
|
||||
- Mesa/Software
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: shaders
|
||||
attributes:
|
||||
label: Shaders
|
||||
description: Do you have a shaderpack enabled?
|
||||
options:
|
||||
- "No"
|
||||
- "Yes: Please put relevant information about shaderpack in the report(what shaderpack, any special settings of note, etc)"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: mcpf
|
||||
attributes:
|
||||
label: MCPF or other features disabled by default
|
||||
description: Do you have MCPF or any other features that are disabled by default enabled?
|
||||
options:
|
||||
- "No: Go ahead and create an issue"
|
||||
- "Yes: I will not create an issue"
|
||||
- "Yes: I don't read and shouldn't be creating an issue"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -60,4 +96,7 @@ body:
|
||||
required: true
|
||||
- label: "I have read the known incompatibilities and this is not related to one of those."
|
||||
required: true
|
||||
|
||||
- label: "I am running an officially released version. (Or, if I've compiled it myself I plan to fix the issue)"
|
||||
required: true
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Feature Request
|
||||
description: "Request a feature."
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "Please use this form to request features. We ask you check if it was already requested though"
|
||||
- type: input
|
||||
id: discord
|
||||
attributes:
|
||||
label: Your GTNH Discord Username
|
||||
description: Leave empty if you don't have one, but this will make it harder to contact you if we need additional info.
|
||||
placeholder: "Example: Wumpus#1234"
|
||||
- type: textarea
|
||||
id: request
|
||||
attributes:
|
||||
label: Feature Request
|
||||
description: "Relevant information, as well as relevant images"
|
||||
placeholder: "Example: https://mclo.gs/ OR submit the file to github by dragging it to this textbox."
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: final
|
||||
attributes:
|
||||
label: Final Checklist
|
||||
description: Certify that you read things
|
||||
options:
|
||||
- label: "I have searched the issues and haven't found a similar issue."
|
||||
required: true
|
||||
- label: "I have read the known incompatibilities and this is not related to one of those."
|
||||
required: true
|
||||
- label: "This issue is not related to a feature that is disabed by default - MCPF, etc. [They'll be enabled when they're ready for testing]"
|
||||
required: true
|
||||
@@ -1,6 +1,7 @@
|
||||
.gradle
|
||||
.settings
|
||||
/.idea/
|
||||
/.vs/
|
||||
/.vscode/
|
||||
/run/
|
||||
/build/
|
||||
@@ -30,3 +31,9 @@ src/main/resources/mixins.*([!.]).json
|
||||
*.bat
|
||||
*.DS_Store
|
||||
!gradlew.bat
|
||||
layout.json
|
||||
addon.local.gradle
|
||||
addon.local.gradle.kts
|
||||
addon.late.local.gradle
|
||||
addon.late.local.gradle.kts
|
||||
local/
|
||||
@@ -1,23 +1,55 @@
|
||||
# Angelica
|
||||
# Angelica<img src="img/logo-readme.png" align="right">
|
||||
|
||||
<sup>**[CurseForge](https://www.curseforge.com/minecraft/mc-mods/angelica) | [Modrinth](https://modrinth.com/mod/angelica)**</sup>
|
||||
|
||||
* The little Angle [sic] that supports shaders while saving your fps from a certain death -boubou
|
||||
* Is it obtuse, or accute? -Caedis
|
||||
* It's a cute angle -mitchej123
|
||||
* It's the angel that pulls you out of OptiFine's hell -Roadhog360
|
||||
|
||||
**Alpha Quality - Here there be Dragons**
|
||||
## **Beta Quality - Here there be Dragons**
|
||||
* Shaders - Beta
|
||||
* Complementary + Euphoria Patches is the primary compatibility target
|
||||
* Sildurs, Seus, and any other shaders might work
|
||||
|
||||
## Core Profile
|
||||
Targets GL 3.3+ core profile
|
||||
* FFP emulation via generated GLSL shaders
|
||||
* Display list emulation
|
||||
* Software matrix stacks and state management through GLSM
|
||||
* GL_QUADS → GL_TRIANGLES conversion
|
||||
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
|
||||
|
||||
|
||||
# Required Dependencies
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
|
||||
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.9.0
|
||||
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
|
||||
|
||||
# Known (temporary) Incompatibilities
|
||||
* Forge Relocation - Currently non Thread Safe; safe if not in use
|
||||
* ProjectRed Frames - Currently non Thread Safe; safe if not in use
|
||||
* EndlessIDs - Future compat planned
|
||||
* RPLE - Future compat planned
|
||||
|
||||
# Potential Incompatibilities
|
||||
* EndlessIDs - Compat commits have been made, might work, might not - not fully tested
|
||||
|
||||
# Known Incompatibilities
|
||||
* FalseTweaks - Not currently planned (Looking for volunteers) [Might work if you effectively disable most of the mod]
|
||||
* RPLE - Not currently planned (Looking for volunteers)
|
||||
* LiteLoader - Install [LiteloaderLoader](https://github.com/Midnight145/LiteloaderLoader)
|
||||
|
||||
# Permanent Incompatibilities
|
||||
* Optifine [Disabled, won't fix]
|
||||
* Fastcraft [Disable, won't fix]
|
||||
* LWJGL3ify 1.5.4 and below - Use the latest version available, 1.5.7+
|
||||
* ArchaicFix 0.6.2 and below - Use 0.7.0 or above
|
||||
* Hodgepodge 2.4.2 and below - Use 2.4.3 or above
|
||||
* CodeChickenCore/Lib <1.2.0 - Threading issues, use 1.2.0+
|
||||
* OptiFine [Disabled, won't fix]
|
||||
* FastCraft [Disabled, won't fix]
|
||||
* BetterFPS [Disabled, won't fix]
|
||||
* OptimizationsAndTweaks [Disabled, won't fix]
|
||||
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - Use 1.2.2+
|
||||
* [LWJGL3ify 1.5.4 and below](https://github.com/GTNewHorizons/lwjgl3ify/releases/) - Use the latest version available, 1.5.7+
|
||||
* [ArchaicFix 0.6.2 and below](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) - Use 0.7.0 or above
|
||||
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 2.4.5 or above
|
||||
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
|
||||
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
|
||||
* SmoothFont - Equivalent functionality added
|
||||
|
||||
# Credits
|
||||
* ShadersMod
|
||||
@@ -32,16 +64,37 @@
|
||||
* The [Iris](https://github.com/IrisShaders/Iris) Team: coderbot, IMS212, Justsnoopy30, FoundationGames
|
||||
* Asek3 for the Iris Forge port [Oculus](https://github.com/Asek3/Oculus/tree/839ce8eca8cf0c4f6b7a1322b906e5c99125ca69)
|
||||
* Backported code under the LGPL
|
||||
* Sodium
|
||||
* Sodium / Celeritas
|
||||
* JellySquid & the CaffeineMC team, for making Sodium in the first place
|
||||
* Embeddedt for [Embeddium](https://github.com/embeddedt/embeddium/tree/dc59ca357c25beefd6288f0d1d40b4cd8e670ab8) under the [LGPL 3.0](https://github.com/embeddedt/embeddium/blob/16.x/forge/LICENSE)
|
||||
* Embeddedt for Celeritas used under the LGPL 3.0
|
||||
* Reese's Sodium Options
|
||||
* FlashyReese for [Reese's Sodium Options](https://github.com/FlashyReese/reeses-sodium-options/tree/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b) under the [MIT license](https://github.com/FlashyReese/reeses-sodium-options/blob/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b/LICENSE.txt)
|
||||
* Neodymium
|
||||
* Makamys for [Neodymium](https://github.com/makamys/Neodymium) - Various Tessellator -> Quad functionality taken under the LGPL 3
|
||||
* MCPatcherForge
|
||||
* Original [MCPatcher](https://bitbucket.org/prupe/mcpatcher/src/master/): Paul Rupe
|
||||
* [Forge version](https://bitbucket.org/prupe/mcpatcher/src/master/): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
|
||||
* [Forge version](https://github.com/mist475/MCPatcherForge): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
|
||||
* HUDCaching
|
||||
* Created by [Moulberry](https://github.com/Moulberry/MCHUDCaching), backported from [Patcher](https://github.com/Sk1erLLC/Patcher) mod by Alexdoru under the CC-BY-SA-NC 4.0 license
|
||||
* Angelica
|
||||
* mitchej123, Omni, Makamys, Embeddedt, NanoLive, Caedis, Cardinalstar, Alexdoru, Eigenraven, mist475, Clepto for helping get Angelica up and running!
|
||||
* mitchej123, Omni, Makamys, Embeddedt, NanoLive, Caedis, Cardinalstar, Alexdoru, Eigenraven, mist475, Clepto, kurrycat for helping get Angelica up and running!
|
||||
* IMS, jss2a98aj, || dbp ||, roadhog360 for advice and moral support
|
||||
* Forge 1.12+
|
||||
* Some files have been taken from Minecraft Forge under the LGPL 2.1 license and are marked as such
|
||||
* FRAPI and Indigo
|
||||
* Model building API heavily inspired by/copied from Fabric's; upstream may use it under the original Apache 2.0
|
||||
license, see LICENSE in `com.gtnewhorizons.angelica.models`
|
||||
* Dynamic Lights
|
||||
* LambdAurora for [LambDynamicLights](https://github.com/LambdAurora/LambDynamicLights) under the MIT license, and the Forge port anthxnymc for [DynamicLightsReforged](https://github.com/anthxnymc/DynamicLightsReforged), also under the MIT license
|
||||
* Embeddedt and Ferri_Arnus for [glsl-transformation-lib](https://github.com/TauMC/glsl-transformation-lib)
|
||||
* Also for work on [Monocle](https://github.com/ferriarnus/Monocle) which was used as a reference for implementing glsl-transformation-lib
|
||||
* Code from both glsl-transformation-lib and Monocle taken under their respective LGPL 3.0 licenses
|
||||
* FalseTweaks
|
||||
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
|
||||
* Beddium
|
||||
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
|
||||
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Code ported and/or inspired from Mesa - under the MIT licensed
|
||||
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
|
||||
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
|
||||
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
|
||||
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
|
||||
@@ -1,7 +0,0 @@
|
||||
allprojects {
|
||||
gradle.projectsEvaluated {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << "-Xmaxerrs" << "2000"
|
||||
}
|
||||
}
|
||||
}
|
||||
-1614
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,68 @@
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhconvention")
|
||||
}
|
||||
|
||||
minecraft {
|
||||
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
|
||||
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
|
||||
// extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=true")
|
||||
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
|
||||
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
|
||||
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
}
|
||||
// On macOS ARM64, use an ARM64-compatible JVM for tests
|
||||
val isMacOsArm64 = System.getProperty("os.name").lowercase().contains("mac") &&
|
||||
System.getProperty("os.arch") == "aarch64"
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
if (isMacOsArm64) {
|
||||
vendor = JvmVendorSpec.AZUL
|
||||
}
|
||||
}
|
||||
dependsOn(tasks.extractNatives2)
|
||||
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
val projectVersion = project.version.toString()
|
||||
inputs.property("version", projectVersion)
|
||||
filesMatching("META-INF/rfb-plugin/*") {
|
||||
expand("version" to projectVersion)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Copy>("copyDependencies") {
|
||||
group = "Angelica"
|
||||
description = "Collect dependencies into the testDependencies folder"
|
||||
from(configurations.default)
|
||||
into("testDependencies")
|
||||
}
|
||||
|
||||
val embedOnly: Configuration by configurations
|
||||
|
||||
// Downgrade embedOnly jars for the tests
|
||||
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = embedOnly
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
dependsOn(downgradeEmbedOnlyForTest)
|
||||
classpath = classpath
|
||||
.minus(embedOnly)
|
||||
.plus(files(downgradeEmbedOnlyForTest.map { it.outputCollection }))
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
from(embedOnly.map(::zipTree))
|
||||
relocate("com.mitchej123", "com.mitchej123")
|
||||
relocate("org.embeddedt", "org.embeddedt")
|
||||
}
|
||||
|
||||
+85
-18
@@ -33,45 +33,112 @@
|
||||
*
|
||||
* For more details, see https://docs.gradle.org/8.0.1/userguide/java_library_plugin.html#sec:java_library_configurations_graph
|
||||
*/
|
||||
configurations {
|
||||
transformedMod // Mods that can be transformed - used for compiling angelica, but not necessary at runtime
|
||||
transformedMod.canBeConsumed = false
|
||||
transformedModCompileOnly
|
||||
transformedModCompileOnly.canBeConsumed = false
|
||||
|
||||
// Add the transformed mod dependencies to the compilation classpaths, but don't publish them in the Maven metadata
|
||||
compileClasspath.extendsFrom(transformedMod, transformedModCompileOnly)
|
||||
testCompileClasspath.extendsFrom(transformedMod, transformedModCompileOnly)
|
||||
|
||||
// Can be enabled for testing if desired
|
||||
// runtimeClasspath.extendsFrom(transformedMod)
|
||||
// testRuntimeClasspath.extendsFrom(transformedMod)
|
||||
|
||||
// Embed in JAR without relocation (compile only, shadowed into JAR)
|
||||
embedOnly
|
||||
embedOnly.canBeConsumed = false
|
||||
embedOnly.canBeResolved = true
|
||||
compileClasspath.extendsFrom(embedOnly)
|
||||
testCompileClasspath.extendsFrom(embedOnly)
|
||||
testRuntimeClasspath.extendsFrom(embedOnly)
|
||||
}
|
||||
|
||||
|
||||
// Exclude self-reference from all configurations - prevents transitive Angelica deps from resolving against this project
|
||||
configurations.all {
|
||||
exclude group: "com.github.GTNewHorizons", module: "Angelica"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.4.3")
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
|
||||
|
||||
compileOnly("org.projectlombok:lombok:1.18.22") {transitive = false }
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.22")
|
||||
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.7") { transitive = false }
|
||||
|
||||
compileOnly("org.projectlombok:lombok:1.18.42") { transitive = false }
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.42")
|
||||
|
||||
// Iris Shaders
|
||||
compileOnly('org.jetbrains:annotations:24.0.1')
|
||||
shadowImplementation("it.unimi.dsi:fastutil:8.2.1") // Apache 2.0
|
||||
shadowImplementation("org.joml:joml:1.10.5") // MIT
|
||||
compileOnly('org.jetbrains:annotations:26.0.2')
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.9.36:dev")
|
||||
|
||||
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
|
||||
implementation("io.github.douira:glsl-transformer:1.0.0")
|
||||
shadowImplementation("io.github.douira:glsl-transformer:1.0.0") { // glsl-transformer Noncommercial License 1.0.0
|
||||
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-26.g5ee7d96-GTNH") {
|
||||
exclude module: "antlr4" // we only want to shadow the runtime module
|
||||
}
|
||||
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
|
||||
compileOnly "org.apache.ant:ant:1.8.2"
|
||||
|
||||
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
|
||||
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.3-GTNH") { transitive = false }
|
||||
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
|
||||
|
||||
// Because who doesn't want NEI
|
||||
compileOnly('com.github.GTNewHorizons:NotEnoughItems:2.4.9-GTNH:dev')
|
||||
compileOnly('com.github.GTNewHorizons:CodeChickenCore:1.2.0:dev')
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7:dev')
|
||||
|
||||
// Notfine Deps
|
||||
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
|
||||
runtimeOnly("com.github.GTNewHorizons:Baubles:1.0.1.16:dev")
|
||||
compileOnly("com.github.GTNewHorizons:twilightforest:2.5.1:dev") {transitive = false }
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.2-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:twilightforest:2.7.13:dev") { transitive = false }
|
||||
compileOnly(rfg.deobf('curse.maven:witchery-69673:2234410'))
|
||||
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.14.6-GTNH:dev") { transitive = false }
|
||||
compileOnly("com.github.GTNewHorizons:Natura:2.8.9:dev")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.8.4:dev")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.3:dev") { transitive = false }
|
||||
|
||||
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.1:api") { transitive = false }
|
||||
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0003:dev") { transitive = false }
|
||||
|
||||
compileOnly(rfg.deobf("curse.maven:extrautils-225561:2264383"))
|
||||
compileOnly(rfg.deobf("curse.maven:dynamiclights-227874:2337326"))
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:1.5.3:dev") // Mixin Version
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
|
||||
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:Jabba:1.3.1:dev")
|
||||
// HMMMMM
|
||||
compileOnly(rfg.deobf("curse.maven:journeymap-32274:2367915"))
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev")
|
||||
|
||||
runtimeOnly(deobf("https://github.com/makamys/CoreTweaks/releases/download/0.3.3.2/CoreTweaks-1.7.10-0.3.3.2+nomixin.jar"))
|
||||
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5593_H261"))
|
||||
|
||||
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
|
||||
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
|
||||
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
|
||||
compileOnly(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
|
||||
|
||||
// For testing alternative splash screens — https://github.com/MalTeeez/ModernSplash/releases
|
||||
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
|
||||
|
||||
// Hodgepodge
|
||||
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
|
||||
|
||||
// Better Crashes
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.1-GTNH:dev")
|
||||
|
||||
afterEvaluate {
|
||||
dependencies {
|
||||
testImplementation(platform('org.junit:junit-bom:5.14.1'))
|
||||
testImplementation('org.junit.jupiter:junit-jupiter')
|
||||
testImplementation("org.mockito:mockito-core:3.+")
|
||||
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
|
||||
|
||||
testImplementation('org.junit.platform:junit-platform-engine')
|
||||
testImplementation('org.junit.platform:junit-platform-reporting')
|
||||
testImplementation('org.junit.platform:junit-platform-launcher')
|
||||
}
|
||||
}
|
||||
|
||||
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
# A specification for models
|
||||
This is borrowed from [the unofficial Minecraft wiki](https://minecraft.wiki/w/Tutorials/Models) under the CC BY-NC-SA 3.0.
|
||||
|
||||
The folder `assets/<namespace>/models/block` holds the model files for all the specified variants. The names of the files can be changed, but must always correspond with the names used in the variant files.
|
||||
|
||||
* `[TAG_compound]` The root tag
|
||||
* `[TAG_string]` **parent**: Loads a different model from the given path, in form of a [resource location](https://minecraft.wiki/w/Tutorials/Models#File_path). If both `"parent"` and `"elements"` are set, the `"elements"` tag overrides the `"elements"` tag from the previous model.
|
||||
* Can be set to `"builtin/generated"` to use a model that is created out of the specified icon. Note that only the first layer is supported, and rotation can be achieved only by using block states files.
|
||||
* `[TAG_byte]` **ambientocclusion**: Whether to use [ambient occlusion](http://en.wikipedia.org/wiki/Ambient_occlusion) (`true` - default), or not (`false`). Note:only works on Parent file
|
||||
* `[TAG_object]` **display**: Holds the different places where item models are displayed.
|
||||
* `[TAG_compound]` ***Position***: Named `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground`, or `fixed`. Place where an item model is displayed. Holds its rotation, translation and scale for the specified situation. '''fixed''' refers to item frames, while the rest are as their name states. Note that translations are applied to the model before rotations.
|
||||
* `[TAG_list]` **rotation**: Specifies the rotation of the model according to the scheme `[x, y, z]`.
|
||||
* `[TAG_list]` **translation**: Specifies the position of the model according to the scheme `[x, y, z]`. The values are clamped between -80 and 80.
|
||||
* `[TAG_list]` **scale**: Specifies the scale of the model according to the scheme `[x, y, z]`. If the value is greater than 4, it is displayed as 4.
|
||||
* `[TAG_compound]` **textures**: Holds the textures of the model, in form of a [resource location](https://minecraft.wiki/w/Tutorials/Models#File_path) or can be another texture variable.
|
||||
* `[TAG_string]` **particle**: What texture to load particles from. This texture is also used as an overlay if you are in a [nether portal](https://minecraft.wiki/w/Nether_portal), and used for [water](https://minecraft.wiki/w/water) and [lava](https://minecraft.wiki/w/Lava)'s still textures.<sup>[[1]](https://bugs.mojang.com/browse/MC-240042)</sup> This texture is also considered a texture variable that can be referenced as `"#particle"`. Note: All breaking particles from non-model blocks are hard-coded, such as for [barriers](https://minecraft.wiki/w/Barrier).
|
||||
* `[TAG_string]` ***Texture variable***: Defines a texture variable with `name` and assigns it to a texture or another variable.
|
||||
* `[TAG_list]` **elements**: Contains all the elements of the model. They can have only cubic forms. If both `"parent"` and `"elements"` are set, the `"elements"` tag overrides the `"elements"` tag from the previous model.
|
||||
* `[TAG_compound]` An element.
|
||||
* `[TAG_list]` **from**: Start point of a cuboid according to the scheme `[x, y, z]`. Values must be between -16 and 32.
|
||||
* `[TAG_list]` **to**: Stop point of a cuboid according to the scheme `[x, y, z]`. Values must be between -16 and 32.
|
||||
* `[TAG_compound]` **rotation**: Defines the rotation of an element.
|
||||
* `[TAG_list]` **origin**: Sets the center of the rotation according to the scheme `[x, y, z]`.
|
||||
* `[TAG_string]` **axis**: Specifies the direction of rotation, can be `"x"`, `"y"` or `"z"`.
|
||||
* `[TAG_float]` **angle**: Specifies the angle of rotation. Can be 45 through -45 degrees in 22.5 degree increments.
|
||||
* `[TAG_byte]` **rescale**: Specifies whether or not to scale the faces across the whole block. Can be true or false. Defaults to false.
|
||||
* `[TAG_byte]` **shade**: Defines if shadows are rendered (`true` - default), not (`false`).
|
||||
* `[TAG_compound]` **faces**: Holds all the faces of the cuboid. If a face is left out, it does not render.
|
||||
* `[TAG_compound]` ***Face***: Named `down`, `up`, `north`, `south`, `west` or `east`. Contains the properties of the specified face.
|
||||
* `[TAG_list]` **uv**: Defines the area of the texture to use according to the scheme `[x1, y1, x2, y2]`. The texture behavior is inconsistent if UV extends below 0 or above 16. If the numbers of `x1` and `x2` are swapped (e.g. from `0, 0, 16, 16` to `16, 0, 0, 16`), the texture flips. UV is optional, and if not supplied it automatically generates based on the element's position.
|
||||
* `[TAG_string]` **texture**: Specifies the texture in form of the texture variable prepended with a `#`.
|
||||
* `[TAG_string]` **cullface**: Specifies whether a face does not need to be rendered when there is a block touching it in the specified position. The position can be: `down`, `up`, `north`, `south`, `west`, or `east`. It also determines the side of the block to use the light level from for lighting the face, and if unset, defaults to the side.
|
||||
* `[TAG_int]` **rotation**: Rotates the texture clockwise by the specified number of degrees. Can be 0, 90, 180, or 270. Defaults to 0. Rotation does not affect which part of the texture is used. Instead, it amounts to permutation of the selected texture vertexes (selected implicitly, or explicitly though `uv`).
|
||||
* `[TAG_int]` **tintindex**: Determines whether to tint the texture using a hardcoded tint index. The default value, -1, indicates not to use the tint. Any other number is provided to BlockColors to get the tint value corresponding to that index. However, most blocks do not have a tint value defined (in which case white is used). Furthermore, no vanilla block currently uses multiple tint values, and thus the tint index value is ignored (as long as it is set to something other than -1); it could be used for modded blocks that need multiple distinct tint values in the same block though.
|
||||
File diff suppressed because it is too large.
Load diff
+140
-58
@@ -1,81 +1,126 @@
|
||||
# ExampleMod tag to use as Blowdryer (Spotless, etc.) settings version, leave empty to disable.
|
||||
# LOCAL to test local config updates.
|
||||
gtnh.settings.blowdryerTag = 0.2.2
|
||||
|
||||
# Human-readable mod name, available for mcmod.info population.
|
||||
modName = Angelica
|
||||
|
||||
# This is a case-sensitive string to identify your mod. Convention is to use lower case.
|
||||
# Case-sensitive identifier string, available for mcmod.info population and used for automatic mixin JSON generation.
|
||||
# Conventionally lowercase.
|
||||
modId = angelica
|
||||
|
||||
# Root package of the mod, used to find various classes in other properties,
|
||||
# mcmod.info substitution, enabling assertions in run tasks, etc.
|
||||
modGroup = com.gtnewhorizons.angelica
|
||||
|
||||
# WHY is there no version field?
|
||||
# The build script relies on git to provide a version via tags. It is super easy and will enable you to always know the
|
||||
# code base or your binary. Check out this tutorial: https://blog.mattclemente.com/2017/10/13/versioning-with-git-tags/
|
||||
# Whether to use modGroup as the maven publishing group.
|
||||
# When false, com.github.GTNewHorizons is used.
|
||||
useModGroupForPublishing = false
|
||||
|
||||
# Will update your build.gradle automatically whenever an update is available
|
||||
# Updates your build.gradle and settings.gradle automatically whenever an update is available.
|
||||
autoUpdateBuildScript = false
|
||||
|
||||
# Version of Minecraft to target
|
||||
minecraftVersion = 1.7.10
|
||||
|
||||
# Version of Minecraft Forge to target
|
||||
forgeVersion = 10.13.4.1614
|
||||
|
||||
# Specify a MCP channel and mappings version for dependency deobfuscation and the deobfParams task.
|
||||
# Specify an MCP channel for dependency deobfuscation and the deobfParams task.
|
||||
channel = stable
|
||||
|
||||
# Specify an MCP mappings version for dependency deobfuscation and the deobfParams task.
|
||||
mappingsVersion = 12
|
||||
|
||||
# Define other MCP mappings for dependency deobfuscation
|
||||
remoteMappings = https://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/
|
||||
# Defines other MCP mappings for dependency deobfuscation.
|
||||
remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/
|
||||
|
||||
# Select a username for testing your mod with breakpoints. You may leave this empty for a random username each time you
|
||||
# restart Minecraft in development. Choose this dependent on your mod:
|
||||
# Do you need consistent player progressing (for example Thaumcraft)? -> Select a name
|
||||
# Do you need to test how your custom blocks interacts with a player that is not the owner? -> leave name empty
|
||||
# Select a default username for testing your mod. You can always override this per-run by running
|
||||
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
|
||||
developmentEnvironmentUserName = Developer
|
||||
|
||||
# Enables using modern java syntax (up to version 17) via Jabel, while still targetting JVM 8.
|
||||
# See https://github.com/bsideup/jabel for details on how this works.
|
||||
enableModernJavaSyntax = true
|
||||
# Enables modern Java syntax support. Valid values:
|
||||
# - false: No modern syntax, Java 8 only
|
||||
# - jabel: Jabel syntax-only support, compiles to J8 bytecode
|
||||
# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs)
|
||||
# - modern: Native modern Java bytecode, no downgrading
|
||||
enableModernJavaSyntax = jvmDowngrader
|
||||
|
||||
# Enables injecting missing generics into the decompiled source code for a better coding experience
|
||||
# Turns most publically visible List, Map, etc. into proper List<Type>, Map<K, V> types
|
||||
# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues,
|
||||
# and should *not* be used in most situations. -1 disables this.
|
||||
# forceToolchainVersion = -1
|
||||
|
||||
# Target JVM version for JVM Downgrader bytecode downgrading.
|
||||
# Only used when enableModernJavaSyntax = jvmDowngrader
|
||||
# downgradeTargetVersion = 8
|
||||
|
||||
# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only).
|
||||
# Classes will be available in META-INF/versions/N/ for each version N in this list.
|
||||
# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade).
|
||||
jvmDowngraderMultiReleaseVersions = 17, 21, 25
|
||||
|
||||
# Specifies how JVM Downgrader API stubs are provided. Options:
|
||||
# - shade: Shade minimized stubs into the jar
|
||||
# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint)
|
||||
# - external: Another dependency provides stubs (no constraint, no warning)
|
||||
# - (empty): Warning reminding you to configure stubs
|
||||
# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md
|
||||
jvmDowngraderStubsProvider = gtnhlib
|
||||
|
||||
# Enables injecting missing generics into the decompiled source code for a better coding experience.
|
||||
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
|
||||
enableGenericInjection = true
|
||||
|
||||
# Define a source file of your project with:
|
||||
# public static final String VERSION = "GRADLETOKEN_VERSION";
|
||||
# The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's
|
||||
# version in @Mod([...], version = VERSION, [...])
|
||||
# Leave these properties empty to skip individual token replacements
|
||||
# Generate a class with a String field for the mod version named as defined below.
|
||||
# If generateGradleTokenClass is empty or not missing, no such class will be generated.
|
||||
# If gradleTokenVersion is empty or missing, the field will not be present in the class.
|
||||
generateGradleTokenClass = com.gtnewhorizons.angelica.Tags
|
||||
gradleTokenModId =
|
||||
gradleTokenModName =
|
||||
|
||||
# Name of the token containing the project's current version to generate/replace.
|
||||
gradleTokenVersion = VERSION
|
||||
gradleTokenGroupName =
|
||||
|
||||
# [DEPRECATED]
|
||||
# Multiple source files can be defined here by providing a comma-seperated list: Class1.java,Class2.java,Class3.java
|
||||
# Multiple source files can be defined here by providing a comma-separated list: Class1.java,Class2.java,Class3.java
|
||||
# public static final String VERSION = "GRADLETOKEN_VERSION";
|
||||
# The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's
|
||||
# version in @Mod([...], version = VERSION, [...])
|
||||
# Leave these properties empty to skip individual token replacements
|
||||
replaceGradleTokenInFile =
|
||||
# version in @Mod([...], version = VERSION, [...]).
|
||||
# Leave these properties empty to skip individual token replacements.
|
||||
# replaceGradleTokenInFile =
|
||||
|
||||
# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can
|
||||
# leave this property empty.
|
||||
# Example value: apiPackage = api + modGroup = com.myname.mymodid -> com.myname.mymodid.api
|
||||
apiPackage =
|
||||
# Example value: (apiPackage = api) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.api
|
||||
apiPackage = api
|
||||
|
||||
# Specify the configuration file for Forge's access transformers here. It must be placed into /src/main/resources/META-INF/
|
||||
# There can be multiple files in a comma-separated list.
|
||||
# Example value: mymodid_at.cfg,nei_at.cfg
|
||||
# There can be multiple files in a space-separated list.
|
||||
# Example value: mymodid_at.cfg nei_at.cfg
|
||||
accessTransformersFile = angelica_at.cfg notfine_at.cfg archaicfix_at.cfg mcpatcherforge_at.cfg
|
||||
|
||||
# Provides setup for Mixins if enabled. If you don't know what mixins are: Keep it disabled!
|
||||
usesMixins = true
|
||||
# Adds some debug arguments like verbose output and export
|
||||
|
||||
# Set to a non-empty string to configure mixins in a separate source set under src/VALUE, instead of src/main.
|
||||
# This can speed up compile times thanks to not running the mixin annotation processor on all input sources.
|
||||
# Mixin classes will have access to "main" classes, but not the other way around.
|
||||
separateMixinSourceSet = mixin
|
||||
|
||||
# Adds some debug arguments like verbose output and class export.
|
||||
usesMixinDebug = false
|
||||
|
||||
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
|
||||
mixinPlugin =
|
||||
# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail!
|
||||
|
||||
# Specify the package that contains all of your Mixins. The package must exist or
|
||||
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
|
||||
# it must match with this or the build will fail.
|
||||
mixinsPackage = mixins
|
||||
|
||||
# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
|
||||
# This parameter is for legacy compatibility only
|
||||
# Example value: coreModClass = asm.FMLPlugin + modGroup = com.myname.mymodid -> com.myname.mymodid.asm.FMLPlugin
|
||||
# Example value: (coreModClass = asm.FMLPlugin) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.asm.FMLPlugin
|
||||
coreModClass = loading.AngelicaTweaker
|
||||
|
||||
# If your project is only a consolidation of mixins or a core mod and does NOT contain a 'normal' mod ( = some class
|
||||
# that is annotated with @Mod) you want this to be true. When in doubt: leave it on false!
|
||||
containsMixinsAndOrCoreModOnly = false
|
||||
@@ -83,23 +128,39 @@ containsMixinsAndOrCoreModOnly = false
|
||||
# Enables Mixins even if this mod doesn't use them, useful if one of the dependencies uses mixins.
|
||||
forceEnableMixins = false
|
||||
|
||||
# If enabled, you may use 'shadowCompile' for dependencies. They will be integrated in your jar. It is your
|
||||
# responsibility check the licence and request permission for distribution, if required.
|
||||
# If enabled, you may use 'shadowCompile' for dependencies. They will be integrated into your jar. It is your
|
||||
# responsibility to check the license and request permission for distribution if required.
|
||||
usesShadowedDependencies = true
|
||||
# If disabled, won't remove unused classes from shaded dependencies. Some libraries use reflection to access
|
||||
|
||||
# If disabled, won't remove unused classes from shadowed dependencies. Some libraries use reflection to access
|
||||
# their own classes, making the minimization unreliable.
|
||||
minimizeShadowedDependencies = true
|
||||
|
||||
# If disabled, won't rename the shadowed classes.
|
||||
relocateShadowedDependencies = true
|
||||
|
||||
# Adds the GTNH maven, CurseMaven, IC2/Player maven, and some more well-known 1.7.10 repositories
|
||||
# Adds CurseMaven, Modrinth, and some more well-known 1.7.10 repositories.
|
||||
includeWellKnownRepositories = true
|
||||
|
||||
# Publishing to modrinth requires you to set the MODRINTH_TOKEN environment variable to your current modrinth API token.
|
||||
# A list of repositories to exclude from the includeWellKnownRepositories setting. Should be a space separated
|
||||
# list of strings, with the acceptable keys being(case does not matter):
|
||||
# cursemaven
|
||||
# modrinth
|
||||
excludeWellKnownRepositories =
|
||||
|
||||
# Change these to your Maven coordinates if you want to publish to a custom Maven repository instead of the default GTNH Maven.
|
||||
# Authenticate with the MAVEN_USER and MAVEN_PASSWORD environment variables.
|
||||
# If you need a more complex setup disable maven publishing here and add a publishing repository to addon.gradle.
|
||||
usesMavenPublishing = true
|
||||
|
||||
# Maven repository to publish the mod to.
|
||||
# mavenPublishUrl = https\://nexus.gtnewhorizons.com/repository/releases/
|
||||
|
||||
# Publishing to Modrinth requires you to set the MODRINTH_TOKEN environment variable to your current Modrinth API token.
|
||||
#
|
||||
# The project's ID on Modrinth. Can be either the slug or the ID.
|
||||
# Leave this empty if you don't want to publish on Modrinth.
|
||||
modrinthProjectId =
|
||||
# Leave this empty if you don't want to publish to Modrinth.
|
||||
modrinthProjectId = angelica
|
||||
|
||||
# The project's relations on Modrinth. You can use this to refer to other projects on Modrinth.
|
||||
# Syntax: scope1-type1:name1;scope2-type2:name2;...
|
||||
@@ -107,42 +168,63 @@ modrinthProjectId =
|
||||
# type can be one of [project, version],
|
||||
# and the name is the Modrinth project or version slug/id of the other mod.
|
||||
# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech
|
||||
# Note: GTNH Mixins is automatically set as a required dependency if usesMixins = true
|
||||
modrinthRelations =
|
||||
|
||||
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
|
||||
modrinthRelations = required-project\:gtnhlib
|
||||
|
||||
# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens.
|
||||
|
||||
#
|
||||
# The project's numeric ID on CurseForge. You can find this in the About Project box.
|
||||
# Leave this empty if you don't want to publish on CurseForge.
|
||||
curseForgeProjectId =
|
||||
curseForgeProjectId = 988067
|
||||
|
||||
# The project's relations on CurseForge. You can use this to refer to other projects on CurseForge.
|
||||
# Syntax: type1:name1;type2:name2;...
|
||||
# Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible],
|
||||
# and the name is the CurseForge project slug of the other mod.
|
||||
# Example: requiredDependency:railcraft;embeddedLibrary:cofhlib;incompatible:buildcraft
|
||||
# Note: GTNH Mixins is automatically set as a required dependency if usesMixins = true
|
||||
curseForgeRelations =
|
||||
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
|
||||
curseForgeRelations = requiredDependency\:gtnhlib
|
||||
|
||||
|
||||
# Optional parameter to customize the produced artifacts. Use this to preserver artifact naming when migrating older
|
||||
# Optional parameter to customize the produced artifacts. Use this to preserve artifact naming when migrating older
|
||||
# projects. New projects should not use this parameter.
|
||||
# customArchiveBaseName =
|
||||
|
||||
# Optional parameter to prevent the source code from being published
|
||||
# noPublishedSources =
|
||||
# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory.
|
||||
# runClientWorkingDirectory = run/client
|
||||
|
||||
# Uncomment this to disable spotless checks
|
||||
# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory.
|
||||
# runServerWorkingDirectory = run/server
|
||||
|
||||
# Optional parameter to have the build automatically fail if an illegal version is used.
|
||||
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
|
||||
# The check is ONLY performed if the version is a git tag.
|
||||
# Note: the specified string must be escaped, so e.g. 1\\.1\\.\\d+ instead of 1\.1\.\d+
|
||||
# versionPattern =
|
||||
|
||||
# Uncomment to prevent the source code from being published.
|
||||
# noPublishedSources = true
|
||||
|
||||
# Uncomment this to disable Spotless checks.
|
||||
# This should only be uncommented to keep it easier to sync with upstream/other forks.
|
||||
# That is, if there is no other active fork/upstream, NEVER change this.
|
||||
# Disabling to make easier to sync if some of the additional mods get any changes upstream
|
||||
disableSpotless = true
|
||||
|
||||
# Override the IDEA build type. Valid value is "" (leave blank, do not override), "idea" (force use native IDEA build), "gradle"
|
||||
# Uncomment this to disable Checkstyle checks (currently wildcard import check).
|
||||
# disableCheckstyle = true
|
||||
|
||||
# Override the IDEA build type. Valid values are: "" (leave blank, do not override), "idea" (force use native IDEA build), "gradle"
|
||||
# (force use delegated build).
|
||||
# This is meant to be set in $HOME/.gradle/gradle.properties.
|
||||
# e.g. add "systemProp.org.gradle.project.ideaOverrideBuildType=idea" will override the build type to be always native build.
|
||||
# e.g. add "systemProp.org.gradle.project.ideaOverrideBuildType=idea" will override the build type to be native build.
|
||||
# WARNING: If you do use this option, it will overwrite whatever you have in your existing projects. This might not be what you want!
|
||||
# Usually there is no need to uncomment this here as other developers do not necessarily use the same build type as you.
|
||||
# ideaOverrideBuildType = idea
|
||||
|
||||
# Whether IDEA should run spotless checks when pressing the Build button.
|
||||
# This is meant to be set in $HOME/.gradle/gradle.properties.
|
||||
# ideaCheckSpotlessOnBuild = true
|
||||
|
||||
# Non-GTNH properties
|
||||
org.gradle.configuration-cache = true
|
||||
org.gradle.jvmargs = -Xmx2g
|
||||
org.gradle.parallel = true
|
||||
@@ -0,0 +1,12 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e1118b4c7c2858d28154d086dbff010e/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/59a9771cad43219260d9aac9a8ec4d6a/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a4c09dd2e2d7079373d30e524bbc2829/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/8e1d9ee5d0f13e442218f6884a306da1/redirect
|
||||
toolchainVersion=25
|
||||
Vendored
BIN
Binary file not shown.
+2
-1
@@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 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.
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -83,7 +85,8 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -111,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -130,10 +132,13 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
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.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
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
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
@@ -141,7 +146,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -149,7 +154,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -166,7 +171,6 @@ fi
|
||||
# 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" )
|
||||
|
||||
@@ -198,16 +202,15 @@ fi
|
||||
# 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"'
|
||||
|
||||
# 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.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+13
-12
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
: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 %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
magick convert logo-1x.png -scale 200% \( +clone -background black -shadow 60x6+0+0 \) +swap -background none -layers merge logo-readme.png
|
||||
+29
-3
@@ -2,10 +2,36 @@
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = 'covers1624 maven'
|
||||
url = 'https://nexus.covers1624.net/repository/maven-hosted/'
|
||||
name = 'mavenpattern'
|
||||
url = 'https://mvn.falsepattern.com/releases'
|
||||
content {
|
||||
includeGroup 'com.falsepattern'
|
||||
}
|
||||
}
|
||||
maven {
|
||||
name = 'mega'
|
||||
url = 'https://mvn.falsepattern.com/gtmega_releases/'
|
||||
content {
|
||||
includeGroup 'mega'
|
||||
}
|
||||
}
|
||||
maven {
|
||||
name = 'taumc'
|
||||
url = 'https://maven.taumc.org/releases'
|
||||
content {
|
||||
includeGroup 'org.taumc'
|
||||
}
|
||||
}
|
||||
ivy {
|
||||
name = 'modernsplash-github'
|
||||
url = 'https://github.com/MalTeeez/ModernSplash/releases/download/'
|
||||
patternLayout {
|
||||
artifact '[revision]/[module]-[revision](-[classifier]).[ext]'
|
||||
}
|
||||
content {
|
||||
includeModule 'com.github.MalTeeez', 'modernsplash'
|
||||
}
|
||||
metadataSources {
|
||||
mavenPom()
|
||||
artifact()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
maven {
|
||||
// RetroFuturaGradle
|
||||
name "GTNH Maven"
|
||||
url "http://jenkins.usrv.eu:8081/nexus/content/groups/public/"
|
||||
allowInsecureProtocol = true
|
||||
mavenContent {
|
||||
includeGroup("com.gtnewhorizons.retrofuturagradle")
|
||||
}
|
||||
}
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.diffplug.blowdryerSetup' version '1.6.0'
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' // Provides java toolchains
|
||||
}
|
||||
|
||||
blowdryerSetup {
|
||||
repoSubfolder 'gtnhShared'
|
||||
github('GTNewHorizons/ExampleMod1.7.10', 'tag', '0.2.0')
|
||||
//devLocal '.' // Use this when testing config updates locally
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
maven {
|
||||
// RetroFuturaGradle
|
||||
name = "GTNH Maven"
|
||||
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
|
||||
mavenContent {
|
||||
includeGroup("com.gtnewhorizons")
|
||||
includeGroupByRegex("com\\.gtnewhorizons\\..+")
|
||||
}
|
||||
}
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhsettingsconvention") version("2.0.23")
|
||||
}
|
||||
@@ -2,21 +2,26 @@ package com.gtnewhorizons.angelica;
|
||||
|
||||
import com.gtnewhorizon.gtnhmixins.ILateMixinLoader;
|
||||
import com.gtnewhorizon.gtnhmixins.LateMixin;
|
||||
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
|
||||
import com.gtnewhorizons.angelica.mixins.Mixins;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@LateMixin
|
||||
@SuppressWarnings("unused")
|
||||
public class AngelicaLateMixins implements ILateMixinLoader {
|
||||
|
||||
@Override
|
||||
public String getMixinConfig() {
|
||||
int v = Runtime.version().feature();
|
||||
if (v >= 21) return "mixins.angelica.late.j21.json";
|
||||
if (v >= 17) return "mixins.angelica.late.j17.json";
|
||||
return "mixins.angelica.late.json";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getMixins(Set<String> loadedMods) {
|
||||
return Mixins.getLateMixins(loadedMods);
|
||||
return IMixins.getLateMixins(Mixins.class, loadedMods);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +1,63 @@
|
||||
package com.gtnewhorizons.angelica;
|
||||
|
||||
import com.gtnewhorizons.angelica.common.BlockError;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.config.ConfigMigrator;
|
||||
import com.gtnewhorizons.angelica.proxy.CommonProxy;
|
||||
import com.gtnewhorizons.angelica.utils.AnimationMode;
|
||||
import com.gtnewhorizons.angelica.utils.ManagedEnum;
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import cpw.mods.fml.common.Mod;
|
||||
import cpw.mods.fml.common.SidedProxy;
|
||||
import cpw.mods.fml.common.event.FMLInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
|
||||
|
||||
@Mod(
|
||||
modid = "angelica",
|
||||
modid = MOD_ID,
|
||||
name = "Angelica",
|
||||
version = Tags.VERSION,
|
||||
dependencies = " before:lwjgl3ify@[1.5.3,);" + " after:hodgepodge@[2.4.3,);" + " after:CodeChickenCore@[1.2.0,);",
|
||||
dependencies = """
|
||||
required-after:gtnhlib@[0.8.21,);\
|
||||
before:lwjgl3ify@[1.5.3,);\
|
||||
after:hodgepodge@[2.4.4,);\
|
||||
after:CodeChickenCore@[1.2.0,);\
|
||||
after:archaicfix@[0.7.0,);\
|
||||
""",
|
||||
acceptedMinecraftVersions = "[1.7.10]",
|
||||
acceptableRemoteVersions = "*")
|
||||
acceptableRemoteVersions = "*",
|
||||
guiFactory = "com.gtnewhorizons.angelica.config.AngelicaGuiConfigFactory")
|
||||
public class AngelicaMod {
|
||||
public static final String MOD_ID = "angelica";
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
private static SodiumGameOptions CONFIG;
|
||||
|
||||
public static SodiumGameOptions options() {
|
||||
if (CONFIG == null) {
|
||||
CONFIG = SodiumGameOptions.load(ConfigMigrator.handleConfigMigration("angelica-options.json"));
|
||||
}
|
||||
return CONFIG;
|
||||
}
|
||||
|
||||
@SidedProxy(clientSide = "com.gtnewhorizons.angelica.proxy.ClientProxy", serverSide = "com.gtnewhorizons.angelica.proxy.CommonProxy")
|
||||
public static CommonProxy proxy;
|
||||
/** Mixin Version */
|
||||
public static boolean isNEIDLoaded;
|
||||
/** ASM Version */
|
||||
public static boolean isOldNEIDLoaded;
|
||||
|
||||
public static final boolean lwjglDebug = Boolean.parseBoolean(System.getProperty("org.lwjgl.util.Debug", "false"));
|
||||
|
||||
public static final ManagedEnum<AnimationMode> animationsMode = new ManagedEnum<>(AnimationMode.VISIBLE_ONLY);
|
||||
|
||||
public static BlockError blockError;
|
||||
|
||||
@Mod.EventHandler
|
||||
public void preInit(FMLPreInitializationEvent event) {
|
||||
isNEIDLoaded = Loader.isModLoaded("neid");
|
||||
isOldNEIDLoaded = Loader.isModLoaded("notenoughIDs");
|
||||
ModStatus.preInit();
|
||||
proxy.preInit(event);
|
||||
blockError = new BlockError();
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
- [x] Frustum (Seems related to Frustrum)
|
||||
- [X] NativeImage
|
||||
- [ ] PoseStack
|
||||
- [ ] RenderType
|
||||
- [ ] MultiBufferSource.BufferSource
|
||||
- [ ] VertexBuffer
|
||||
- [ ] VertexConsumer
|
||||
- [ ] VertextFormat
|
||||
- [ ] TextureAtlas (Seems related to TextureManager) - PBR related
|
||||
- [ ] GUI
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public interface IBlockAccessExtended extends IBlockAccess {
|
||||
World getWorld();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
/**
|
||||
* To be used for modded items that don't have blocks. Baubles for example.
|
||||
*/
|
||||
public interface IDynamicLightProducer {
|
||||
int getLuminance();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ITexturesCache;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.util.IIcon;
|
||||
|
||||
/**
|
||||
* Exposes a method to the API that allow mods to add texture update compat
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class TextureServices {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void updateBlockTextureAnimation(IIcon icon, RenderBlocks renderBlocks) {
|
||||
if (renderBlocks instanceof ITexturesCache texturesCache) {
|
||||
texturesCache.getRenderedTextures().add(icon);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void updateTextureAnimation(IIcon icon) {
|
||||
if (icon instanceof SpriteExtension ext) {
|
||||
ext.celeritas$markActive();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Marks an ISBRH as thread-safe, allowing Angelica to run it off-thread.
|
||||
*
|
||||
* <p>Note: Any tessellator access <i>must</i> be done within the methods of the ISBRH and not cached in the class.</p>
|
||||
*/
|
||||
@Documented
|
||||
@Target({ TYPE })
|
||||
@Retention(RUNTIME)
|
||||
public @interface ThreadSafeISBRH {
|
||||
/**
|
||||
* Specifies whether Angelica should create a thread local instance of the class for each thread.
|
||||
* <ul>
|
||||
* <li>If this is set to <tt>true</tt>, a new instance will be created using the default constructor on each thread.</li>
|
||||
* <ul>
|
||||
* <li>Note: If your renderer requires arguments to the constructor, do <i>not</i> use this annotation;
|
||||
* instead use the {@link ThreadSafeISBRHFactory} interface and implement the {@link ThreadSafeISBRHFactory#newInstance} method. </li>
|
||||
* </ul>
|
||||
* <li>If this is set to <tt>false</tt>, the main thread instance will be used on all threads.
|
||||
* Use this option if your renderer class has no state, and can be safely used from multiple threads as is.</li>
|
||||
* <ul>
|
||||
* <li>No state/non final variables should be stored in the ISBRH as it is not guaranteed to be thread safe.</li>
|
||||
* </ul>
|
||||
* </ul>
|
||||
*/
|
||||
boolean perThread();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
|
||||
|
||||
/**
|
||||
* Marks an ISBRH as thread-safe using a thread local instance, allowing Angelica to run it off-thread.
|
||||
* Use this when your ISBRH uses a non-default constructor.
|
||||
* <p>To avoid a hard dependency on Angelica, you will likely want to mark this as an optional interface using:</p>
|
||||
* <pre>
|
||||
* @Optional.Interface(modid = "angelica", iface = "com.gtnewhorizons.angelica.api.ThreadSafeISBRHFactory")
|
||||
* </pre>
|
||||
*
|
||||
* <p><b>Notes</b></p>
|
||||
* <ul>
|
||||
* <li>Any tessellator access <i>must</i> be done within the methods of the ISBRH and not cached in the class.</li>
|
||||
* <li>If your class uses a default constructor or is thread-safe by default, consider using the simpler {@link ThreadSafeISBRH}
|
||||
* annotation instead.
|
||||
* </ul>
|
||||
*/
|
||||
public interface ThreadSafeISBRHFactory extends ISimpleBlockRenderingHandler {
|
||||
ThreadSafeISBRHFactory newInstance();
|
||||
}
|
||||
@@ -1,25 +1,36 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
|
||||
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import lombok.Setter;
|
||||
import net.coderbot.iris.gl.program.Program;
|
||||
import net.coderbot.iris.gl.program.ProgramBuilder;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
|
||||
/**
|
||||
* A batching replacement for {@code FontRenderer}
|
||||
@@ -30,131 +41,191 @@ public class BatchingFontRenderer {
|
||||
|
||||
/** The underlying FontRenderer object that's being accelerated */
|
||||
protected FontRenderer underlying;
|
||||
/** Cached locations for each unicode page atlas */
|
||||
private final ResourceLocation[] unicodePageLocations;
|
||||
/** Array of width of all the characters in default.png */
|
||||
protected int[] charWidth = new int[256];
|
||||
/** Array of the start/end column (in upper/lower nibble) for every glyph in the /font directory. */
|
||||
protected byte[] glyphWidth;
|
||||
protected int[] charWidth;
|
||||
/**
|
||||
* Array of RGB triplets defining the 16 standard chat colors followed by 16 darker version of the same colors for
|
||||
* drop shadows.
|
||||
*/
|
||||
private int[] colorCode;
|
||||
private final int[] colorCode;
|
||||
/** Location of the primary font atlas to bind. */
|
||||
protected final ResourceLocation locationFontTexture;
|
||||
/** The RenderEngine used to load and setup glyph textures. */
|
||||
private final TextureManager renderEngine;
|
||||
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
|
||||
|
||||
/** The full list of characters present in the default Minecraft font, excluding the Unicode font */
|
||||
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
|
||||
private final int AAMode;
|
||||
private final int AAStrength;
|
||||
private final int mvpMatrixLocation;
|
||||
private final int fontShaderId;
|
||||
|
||||
private static final short[] MCFONT_ASCII_LUT = new short[512];
|
||||
private static final Char2ShortOpenHashMap MCFONT_UNI_LUT = new Char2ShortOpenHashMap();
|
||||
final boolean isSGA;
|
||||
final boolean isSplash;
|
||||
|
||||
static {
|
||||
Arrays.fill(MCFONT_ASCII_LUT, (short) -1);
|
||||
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
|
||||
char ch = MCFONT_CHARS.charAt(i);
|
||||
if (ch < MCFONT_ASCII_LUT.length) {
|
||||
MCFONT_ASCII_LUT[ch] = i;
|
||||
} else {
|
||||
MCFONT_UNI_LUT.put(ch, i);
|
||||
/** For use with modded books. Affects calculations and forces some defaults. */
|
||||
@Setter
|
||||
boolean bookMode = false;
|
||||
|
||||
private static class FontAAShader {
|
||||
|
||||
private static Program fontShader = null;
|
||||
public static Program getProgram() {
|
||||
if (fontShader == null) {
|
||||
final String vsh = ShaderLoader.getShaderSource("angelica:fontFilter.vsh");
|
||||
final String fsh = ShaderLoader.getShaderSource("angelica:fontFilter.fsh");
|
||||
fontShader = ProgramBuilder.begin("fontFilter", vsh, null, fsh, ImmutableSet.of(0)).build();
|
||||
}
|
||||
return fontShader;
|
||||
}
|
||||
}
|
||||
|
||||
public static int lookupMcFontPosition(char ch) {
|
||||
if (ch < MCFONT_ASCII_LUT.length) {
|
||||
return MCFONT_ASCII_LUT[ch];
|
||||
} else {
|
||||
return MCFONT_UNI_LUT.getOrDefault(ch, (short) -1);
|
||||
}
|
||||
}
|
||||
|
||||
public BatchingFontRenderer(FontRenderer underlying, ResourceLocation[] unicodePageLocations, int[] charWidth,
|
||||
byte[] glyphWidth, int[] colorCode, ResourceLocation locationFontTexture, TextureManager renderEngine) {
|
||||
public BatchingFontRenderer(FontRenderer underlying, int[] charWidth, int[] colorCode, ResourceLocation locationFontTexture) {
|
||||
this.underlying = underlying;
|
||||
this.unicodePageLocations = unicodePageLocations;
|
||||
this.charWidth = charWidth;
|
||||
this.glyphWidth = glyphWidth;
|
||||
this.colorCode = colorCode;
|
||||
this.locationFontTexture = locationFontTexture;
|
||||
this.renderEngine = renderEngine;
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
batchCommandPool.add(new FontDrawCmd());
|
||||
}
|
||||
|
||||
this.isSGA = Objects.equals(this.locationFontTexture.getResourcePath(), "textures/font/ascii_sga.png");
|
||||
this.isSplash = FontStrategist.isSplashFontRendererActive(underlying);
|
||||
|
||||
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
|
||||
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
|
||||
|
||||
//noinspection deprecation
|
||||
fontShaderId = FontAAShader.getProgram().getProgramId();
|
||||
AAMode = GL20.glGetUniformLocation(fontShaderId, "aaMode");
|
||||
AAStrength = GL20.glGetUniformLocation(fontShaderId, "strength");
|
||||
mvpMatrixLocation = GL20.glGetUniformLocation(fontShaderId, "u_MVPMatrix");
|
||||
if (ebo == null) {
|
||||
ebo = new IndexBuffer();
|
||||
vbo = GL15.glGenBuffers();
|
||||
allocateBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
// === Batched rendering
|
||||
|
||||
private int batchDepth = 0;
|
||||
|
||||
private int vtxWriterIndex = 0;
|
||||
private int idxWriterIndex = 0;
|
||||
private static final int INITIAL_BATCH_SIZE = 256;
|
||||
private static final int INITIAL_BATCH_SIZE = 2048;
|
||||
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
|
||||
"this is invalid!");
|
||||
private FloatBuffer batchVtxPositions = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
|
||||
private ByteBuffer batchVtxColors = BufferUtils.createByteBuffer(INITIAL_BATCH_SIZE * 4);
|
||||
private FloatBuffer batchVtxTexCoords = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
|
||||
private IntBuffer batchIndices = BufferUtils.createIntBuffer(INITIAL_BATCH_SIZE / 2 * 3);
|
||||
|
||||
// Layout in data:
|
||||
// [v, v, t, t, c, c, c, c, tb, tb, tb, tb]
|
||||
// v, t and tb are floats, c is bytes; 36 bytes total
|
||||
private static final int VERTEX_SIZE = 36;
|
||||
private static int rawCapacity = INITIAL_BATCH_SIZE * VERTEX_SIZE;
|
||||
private static ByteBuffer vertexData = memAlloc(rawCapacity);
|
||||
private static long vertexDataAddress = memAddress0(vertexData);
|
||||
private static int vboCapacity;
|
||||
|
||||
|
||||
// OpenGL objects (static, can be used between multiple BatchingFontRenderer)
|
||||
private static int fontVAO = 0;
|
||||
private static int vbo;
|
||||
private static IndexBuffer ebo;
|
||||
|
||||
private int batchDepth = 0;
|
||||
|
||||
private int vertexDataPos = 0;
|
||||
private int idxWriterIndex = 0;
|
||||
|
||||
private final ObjectArrayList<FontDrawCmd> batchCommands = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
|
||||
private final ObjectArrayList<FontDrawCmd> batchCommandPool = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
|
||||
|
||||
/** */
|
||||
private void pushVtx(float x, float y, int rgba, float u, float v) {
|
||||
final int oldCap = batchVtxPositions.capacity() / 2;
|
||||
if (vtxWriterIndex >= oldCap) {
|
||||
final int newCap = oldCap * 2;
|
||||
batchVtxPositions = CompatMemoryUtil.memReallocDirect(batchVtxPositions, newCap * 2);
|
||||
batchVtxColors = CompatMemoryUtil.memReallocDirect(batchVtxColors, newCap * 4);
|
||||
batchVtxTexCoords = CompatMemoryUtil.memReallocDirect(batchVtxTexCoords, newCap * 2);
|
||||
final int oldIdxCap = batchIndices.capacity();
|
||||
final int newIdxCap = oldIdxCap * 2;
|
||||
batchIndices = CompatMemoryUtil.memReallocDirect(batchIndices, newIdxCap);
|
||||
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
|
||||
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
|
||||
|
||||
private void allocateBuffers() {
|
||||
populateEBO(rawCapacity / VERTEX_SIZE);
|
||||
}
|
||||
|
||||
private void populateEBO(int capacity) {
|
||||
final int quadCount = capacity * 6;
|
||||
final ByteBuffer data = memAlloc(quadCount * 6 * 2);
|
||||
long ptr = memAddress0(data);
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
int base = (i * 4);
|
||||
|
||||
// triangle 1
|
||||
memPutShort(ptr, (short) base);
|
||||
memPutShort(ptr + 2, (short) (base + 1));
|
||||
memPutShort(ptr + 4, (short) (base + 2));
|
||||
|
||||
// triangle 2
|
||||
memPutShort(ptr + 6, (short) (base + 2));
|
||||
memPutShort(ptr + 8, (short) (base + 1));
|
||||
memPutShort(ptr + 10, (short) (base + 3));
|
||||
ptr += 12;
|
||||
}
|
||||
final int idx = vtxWriterIndex;
|
||||
final int idx2 = idx * 2;
|
||||
final int idx4 = idx * 4;
|
||||
batchVtxPositions.put(idx2, x);
|
||||
batchVtxPositions.put(idx2 + 1, y);
|
||||
|
||||
ebo.upload(data);
|
||||
|
||||
memFree(data);
|
||||
|
||||
}
|
||||
|
||||
private void ensureCapacity() {
|
||||
if (vertexDataPos + (4 * VERTEX_SIZE) > rawCapacity) {
|
||||
rawCapacity *= 2;
|
||||
vertexData = memRealloc(vertexData, rawCapacity);
|
||||
vertexDataAddress = memAddress0(vertexData);
|
||||
|
||||
allocateBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
private void pushVtx(float x, float y, int rgba, float u, float v, float uMin, float uMax, float vMin, float vMax) {
|
||||
final long ptr = vertexDataAddress + vertexDataPos;
|
||||
|
||||
// v, v
|
||||
memPutFloat(ptr, x);
|
||||
memPutFloat(ptr + 4, y);
|
||||
|
||||
// t, t
|
||||
memPutFloat(ptr + 8, u);
|
||||
memPutFloat(ptr + 12, v);
|
||||
|
||||
// c, c, c, c
|
||||
// 0xAARRGGBB
|
||||
batchVtxColors.put(idx4, (byte) ((rgba >> 16) & 0xFF));
|
||||
batchVtxColors.put(idx4 + 1, (byte) ((rgba >> 8) & 0xFF));
|
||||
batchVtxColors.put(idx4 + 2, (byte) (rgba & 0xFF));
|
||||
batchVtxColors.put(idx4 + 3, (byte) ((rgba >> 24) & 0xFF));
|
||||
batchVtxTexCoords.put(idx2, u);
|
||||
batchVtxTexCoords.put(idx2 + 1, v);
|
||||
vtxWriterIndex++;
|
||||
memPutByte(ptr + 16, (byte) ((rgba >> 16) & 0xFF));
|
||||
memPutByte(ptr + 17, (byte) ((rgba >> 8) & 0xFF));
|
||||
memPutByte(ptr + 18, (byte) (rgba & 0xFF));
|
||||
memPutByte(ptr + 19, (byte) ((rgba >> 24) & 0xFF));
|
||||
|
||||
// tb, tb, tb, tb
|
||||
memPutFloat(ptr + 20, uMin);
|
||||
memPutFloat(ptr + 24, uMax);
|
||||
memPutFloat(ptr + 28, vMin);
|
||||
memPutFloat(ptr + 32, vMax);
|
||||
|
||||
vertexDataPos += VERTEX_SIZE;
|
||||
}
|
||||
|
||||
private void pushUntexRect(float x, float y, float w, float h, int rgba) {
|
||||
final int vtxId = vtxWriterIndex;
|
||||
pushVtx(x, y, rgba, 0, 0);
|
||||
pushVtx(x, y + h, rgba, 0, 0);
|
||||
pushVtx(x + w, y, rgba, 0, 0);
|
||||
pushVtx(x + w, y + h, rgba, 0, 0);
|
||||
pushQuadIdx(vtxId);
|
||||
ensureCapacity();
|
||||
pushVtx(x, y, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushVtx(x, y + h, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushVtx(x + w, y, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushVtx(x + w, y + h, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushQuadIdx();
|
||||
}
|
||||
|
||||
private int pushQuadIdx(int startV) {
|
||||
final int idx = idxWriterIndex;
|
||||
batchIndices.put(idx, startV);
|
||||
batchIndices.put(idx + 1, startV + 1);
|
||||
batchIndices.put(idx + 2, startV + 2);
|
||||
//
|
||||
batchIndices.put(idx + 3, startV + 2);
|
||||
batchIndices.put(idx + 4, startV + 1);
|
||||
batchIndices.put(idx + 5, startV + 3);
|
||||
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz) {
|
||||
ensureCapacity();
|
||||
pushVtx(x + itOff, y, rgba, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x - itOff, y + h, rgba, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushQuadIdx();
|
||||
}
|
||||
|
||||
private void pushQuadIdx() {
|
||||
idxWriterIndex += 6;
|
||||
return idx;
|
||||
}
|
||||
|
||||
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture) {
|
||||
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
|
||||
if (!batchCommands.isEmpty()) {
|
||||
final FontDrawCmd lastCmd = batchCommands.get(batchCommands.size() - 1);
|
||||
final int prevEndVtx = lastCmd.startVtx + lastCmd.idxCount;
|
||||
@@ -170,7 +241,7 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
final FontDrawCmd cmd = batchCommandPool.pop();
|
||||
cmd.reset(startIdx, idxCount, texture);
|
||||
cmd.reset(startIdx, idxCount, texture, isUnicode);
|
||||
batchCommands.add(cmd);
|
||||
}
|
||||
|
||||
@@ -178,12 +249,14 @@ public class BatchingFontRenderer {
|
||||
|
||||
public int startVtx;
|
||||
public int idxCount;
|
||||
public boolean isUnicode;
|
||||
public ResourceLocation texture;
|
||||
|
||||
public void reset(int startVtx, int vtxCount, ResourceLocation texture) {
|
||||
public void reset(int startVtx, int vtxCount, ResourceLocation texture, boolean isUnicode) {
|
||||
this.startVtx = startVtx;
|
||||
this.idxCount = vtxCount;
|
||||
this.texture = texture;
|
||||
this.isUnicode = isUnicode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -224,9 +297,6 @@ public class BatchingFontRenderer {
|
||||
* allow for easier optimizing of blocks of font rendering code.
|
||||
*/
|
||||
public void beginBatch() {
|
||||
if (batchDepth == Integer.MAX_VALUE) {
|
||||
throw new StackOverflowError("More than Integer.MAX_VALUE nested font rendering batch operations");
|
||||
}
|
||||
batchDepth++;
|
||||
}
|
||||
|
||||
@@ -242,25 +312,79 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Matrix4f scratchMvp = new Matrix4f();
|
||||
private int fontAAModeLast = -1;
|
||||
private int fontAAStrengthLast = -1;
|
||||
|
||||
private void flushBatch() {
|
||||
if (vertexDataPos == 0) {
|
||||
clearBatch();
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload first (to reduce stalls)
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
vertexData.limit(vertexDataPos);
|
||||
vboCapacity = StreamingUploader.upload(vertexData, vboCapacity);
|
||||
|
||||
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
|
||||
// Sort&Draw
|
||||
batchCommands.sort(FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
|
||||
final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
|
||||
final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
boolean textureChanged = false;
|
||||
|
||||
ResourceLocation lastTexture = DUMMY_RESOURCE_LOCATION;
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
|
||||
GLStateManager.glShadeModel(GL_FLAT);
|
||||
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glShadeModel(GL11.GL_FLAT);
|
||||
|
||||
glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);
|
||||
glTexCoordPointer(2, 0, batchVtxTexCoords);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glColorPointer(4, GL_UNSIGNED_BYTE, 0, batchVtxColors);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glVertexPointer(2, 0, batchVtxPositions);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glUseProgram(fontShaderId);
|
||||
if (FontConfig.fontAAMode != fontAAModeLast) {
|
||||
fontAAModeLast = FontConfig.fontAAMode;
|
||||
GL20.glUniform1i(AAMode, FontConfig.fontAAMode);
|
||||
}
|
||||
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
|
||||
fontAAStrengthLast = FontConfig.fontAAStrength;
|
||||
GL20.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
|
||||
}
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer mvpBuf = stack.mallocFloat(16);
|
||||
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), scratchMvp);
|
||||
scratchMvp.get(mvpBuf);
|
||||
GL20.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
|
||||
}
|
||||
|
||||
if (fontVAO == 0) {
|
||||
fontVAO = GL30.glGenVertexArrays();
|
||||
|
||||
GLStateManager.glBindVertexArray(fontVAO);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
|
||||
ebo.bind();
|
||||
|
||||
// position
|
||||
GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, VERTEX_SIZE, 0);
|
||||
GL20.glEnableVertexAttribArray(0);
|
||||
|
||||
// texcoords
|
||||
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, VERTEX_SIZE, 8);
|
||||
GL20.glEnableVertexAttribArray(1);
|
||||
|
||||
// color
|
||||
GL20.glVertexAttribPointer(2, 4, GL11.GL_UNSIGNED_BYTE, true, VERTEX_SIZE, 16);
|
||||
GL20.glEnableVertexAttribArray(2);
|
||||
|
||||
// tex bounds
|
||||
GL20.glVertexAttribPointer(3, 4, GL11.GL_FLOAT, false, VERTEX_SIZE, 20);
|
||||
GL20.glEnableVertexAttribArray(3);
|
||||
}
|
||||
|
||||
GLStateManager.glBindVertexArray(fontVAO);
|
||||
|
||||
// Use plain for loop to avoid allocations
|
||||
final FontDrawCmd[] cmdsData = batchCommands.elements();
|
||||
@@ -269,29 +393,41 @@ public class BatchingFontRenderer {
|
||||
final FontDrawCmd cmd = cmdsData[i];
|
||||
if (!Objects.equals(lastTexture, cmd.texture)) {
|
||||
if (lastTexture == null) {
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
} else if (cmd.texture == null) {
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
if (cmd.texture != null) {
|
||||
((FontRendererAccessor) (Object) underlying).angelica$bindTexture(cmd.texture);
|
||||
((FontRendererAccessor) underlying).angelica$bindTexture(cmd.texture);
|
||||
textureChanged = true;
|
||||
}
|
||||
lastTexture = cmd.texture;
|
||||
}
|
||||
batchIndices.limit(cmd.startVtx + cmd.idxCount);
|
||||
batchIndices.position(cmd.startVtx);
|
||||
glDrawElements(GL_TRIANGLES, batchIndices);
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, cmd.idxCount, GL11.GL_UNSIGNED_SHORT, (long) cmd.startVtx * 2L);
|
||||
}
|
||||
|
||||
glPopClientAttrib();
|
||||
|
||||
GLStateManager.glUseProgram(prevProgram);
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
if (isTextureEnabledBefore) {
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
if (textureChanged) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore);
|
||||
}
|
||||
|
||||
clearBatch();
|
||||
}
|
||||
|
||||
private void clearBatch() {
|
||||
// Clear for the next batch
|
||||
batchCommandPool.addAll(batchCommands);
|
||||
batchCommands.clear();
|
||||
vtxWriterIndex = 0;
|
||||
vertexDataPos = 0;
|
||||
idxWriterIndex = 0;
|
||||
batchIndices.limit(batchIndices.capacity());
|
||||
batchIndices.position(0);
|
||||
}
|
||||
|
||||
// === Actual text mesh generation
|
||||
@@ -300,17 +436,28 @@ public class BatchingFontRenderer {
|
||||
return (what >= fromInclusive) && (what <= toInclusive);
|
||||
}
|
||||
|
||||
private ResourceLocation getUnicodePageLocation(int page) {
|
||||
final ResourceLocation lookup = unicodePageLocations[page];
|
||||
if (lookup == null) {
|
||||
final ResourceLocation rl = new ResourceLocation(String.format(
|
||||
"textures/font/unicode_page_%02x.png",
|
||||
page));
|
||||
unicodePageLocations[page] = rl;
|
||||
return rl;
|
||||
} else {
|
||||
return lookup;
|
||||
}
|
||||
public boolean forceDefaults() {
|
||||
return this.bookMode || this.isSGA || this.isSplash;
|
||||
}
|
||||
|
||||
public float getGlyphScaleX() {
|
||||
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale * Math.pow(2, FontConfig.glyphAspect));
|
||||
}
|
||||
|
||||
public float getGlyphScaleY() {
|
||||
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale / Math.pow(2, FontConfig.glyphAspect));
|
||||
}
|
||||
|
||||
public float getGlyphSpacing() {
|
||||
return forceDefaults() ? 0 : FontConfig.glyphSpacing;
|
||||
}
|
||||
|
||||
public float getWhitespaceScale() {
|
||||
return forceDefaults() ? 1 : FontConfig.whitespaceScale;
|
||||
}
|
||||
|
||||
public float getShadowOffset() {
|
||||
return forceDefaults() ? 1 : FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
private static final char FORMATTING_CHAR = 167; // §
|
||||
@@ -319,10 +466,13 @@ public class BatchingFontRenderer {
|
||||
final boolean unicodeFlag, final CharSequence string, int stringOffset, int stringLength) {
|
||||
// noinspection SizeReplaceableByIsEmpty
|
||||
if (string == null || string.length() == 0) {
|
||||
return 0.0f;
|
||||
return anchorX + (enableShadow ? 1.0f : 0.0f);
|
||||
}
|
||||
final int shadowColor = (color & 0xfcfcfc) >> 2 | color & 0xff000000;
|
||||
|
||||
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
|
||||
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
|
||||
|
||||
this.beginBatch();
|
||||
float curX = anchorX;
|
||||
try {
|
||||
@@ -342,10 +492,15 @@ public class BatchingFontRenderer {
|
||||
boolean curStrikethrough = false;
|
||||
boolean curUnderline = false;
|
||||
|
||||
final float underlineY = anchorY + underlying.FONT_HEIGHT - 1.0f;
|
||||
float glyphScaleY = getGlyphScaleY();
|
||||
float glyphScaleX = getGlyphScaleX();
|
||||
float heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY / 2);
|
||||
|
||||
final float underlineY = heightNorth + (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY;
|
||||
float underlineStartX = 0.0f;
|
||||
float underlineEndX = 0.0f;
|
||||
final float strikethroughY = anchorY + (float) (underlying.FONT_HEIGHT / 2);
|
||||
|
||||
final float strikethroughY = heightNorth + ((float) (underlying.FONT_HEIGHT / 2) - 1.0f) * glyphScaleY;
|
||||
float strikethroughStartX = 0.0f;
|
||||
float strikethroughEndX = 0.0f;
|
||||
|
||||
@@ -357,8 +512,8 @@ public class BatchingFontRenderer {
|
||||
|
||||
if (curUnderline && underlineStartX != underlineEndX) {
|
||||
final int ulIdx = idxWriterIndex;
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
underlineStartX = underlineEndX;
|
||||
}
|
||||
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
|
||||
@@ -367,9 +522,9 @@ public class BatchingFontRenderer {
|
||||
strikethroughStartX,
|
||||
strikethroughY,
|
||||
strikethroughEndX - strikethroughStartX,
|
||||
1.0f,
|
||||
glyphScaleY,
|
||||
curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
strikethroughStartX = strikethroughEndX;
|
||||
}
|
||||
|
||||
@@ -414,148 +569,90 @@ public class BatchingFontRenderer {
|
||||
continue;
|
||||
}
|
||||
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
if (curRandom && lutIndex != -1) {
|
||||
int randomReplacementIndex;
|
||||
do {
|
||||
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
|
||||
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
|
||||
|
||||
lutIndex = randomReplacementIndex;
|
||||
chr = MCFONT_CHARS.charAt(lutIndex);
|
||||
if (FontConfig.enableCustomFont && FontConfig.enableGlyphReplacements) {
|
||||
String chrReplacement = GlyphReplacements.customGlyphs.get(String.valueOf(chr));
|
||||
if (chrReplacement != null) {
|
||||
char replacement = chrReplacement.charAt(0);
|
||||
boolean isReplacementCharAvailable =
|
||||
FontProviderCustom.getPrimary().isGlyphAvailable(replacement)
|
||||
|| FontProviderCustom.getFallback().isGlyphAvailable(replacement);
|
||||
if (isReplacementCharAvailable) {
|
||||
chr = replacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final float shadowOffset = unicodeFlag ? 0.5F : 1.0F;
|
||||
if (curRandom) {
|
||||
chr = FontProviderMC.get(this.isSGA).getRandomReplacement(chr);
|
||||
}
|
||||
|
||||
FontProvider fontProvider = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, unicodeFlag);
|
||||
|
||||
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2);
|
||||
float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY * fontProvider.getYScaleMultiplier();
|
||||
|
||||
// Check ASCII space, NBSP, NNBSP
|
||||
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
|
||||
curX += 4;
|
||||
curX += 4 * this.getWhitespaceScale() + (curBold ? 1 : 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
final float uStart;
|
||||
final float vStart;
|
||||
final float xAdvance;
|
||||
final float glyphW;
|
||||
final float uSz;
|
||||
final float vSz;
|
||||
final float uStart = fontProvider.getUStart(chr);
|
||||
final float vStart = fontProvider.getVStart(chr);
|
||||
final float xAdvance = fontProvider.getXAdvance(chr) * glyphScaleX;
|
||||
final float glyphW = fontProvider.getGlyphW(chr) * glyphScaleX;
|
||||
final float uSz = fontProvider.getUSize(chr);
|
||||
final float vSz = fontProvider.getVSize(chr);
|
||||
final float itOff = curItalic ? 1.0F : 0.0F; // italic offset
|
||||
final ResourceLocation texture;
|
||||
|
||||
if (lutIndex == -1 || unicodeFlag) {
|
||||
if (glyphWidth[chr] == 0) {
|
||||
continue;
|
||||
}
|
||||
// Draw unicode char
|
||||
final int uniPage = chr / 256;
|
||||
texture = getUnicodePageLocation(uniPage);
|
||||
final int startColumn = this.glyphWidth[chr] >>> 4;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
uStart = ((float) (chr % 16 * 16) + startColumnF) / 256.0f;
|
||||
vStart = ((float) ((chr & 255) / 16 * 16)) / 256.0f;
|
||||
final float chrWidth = endColumnF - startColumnF - 0.02F;
|
||||
glyphW = chrWidth / 2.0f + 1.0f;
|
||||
xAdvance = (endColumnF - startColumnF) / 2.0F + 1.0F;
|
||||
uSz = chrWidth / 256.0f;
|
||||
vSz = 15.98f / 256.0f;
|
||||
|
||||
} else {
|
||||
// Draw "ASCII" char
|
||||
uStart = ((lutIndex % 16) * 8) / 128.0F;
|
||||
vStart = (float) ((lutIndex / 16) * 8) / 128.0F;
|
||||
xAdvance = this.charWidth[lutIndex];
|
||||
if (xAdvance == 0) {
|
||||
continue;
|
||||
}
|
||||
glyphW = xAdvance - 0.01F;
|
||||
uSz = (glyphW - 1.0F) / 128.0F;
|
||||
vSz = 7.99F / 128.0F;
|
||||
texture = locationFontTexture;
|
||||
}
|
||||
|
||||
final int vtxId = vtxWriterIndex;
|
||||
final float shadowOffset = fontProvider.getShadowOffset();
|
||||
final int shadowCopies = FontConfig.shadowCopies;
|
||||
final int boldCopies = FontConfig.boldCopies;
|
||||
final ResourceLocation texture = fontProvider.getTexture(chr);
|
||||
final int idxId = idxWriterIndex;
|
||||
|
||||
int vtxCount = 0;
|
||||
|
||||
if (enableShadow) {
|
||||
pushVtx(curX + itOff + shadowOffset, anchorY + shadowOffset, curShadowColor, uStart, vStart);
|
||||
pushVtx(
|
||||
curX - itOff + shadowOffset,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart,
|
||||
vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F + itOff + shadowOffset,
|
||||
anchorY + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
vStart);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
for (int n = 1; n <= shadowCopies; n++) {
|
||||
final float shadowOffsetPart = shadowOffset * ((float) n / shadowCopies);
|
||||
pushTexRect(curX + shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
|
||||
|
||||
if (curBold) {
|
||||
final float shadowOffset2 = 2.0f * shadowOffset;
|
||||
pushVtx(curX + itOff + shadowOffset2, anchorY + shadowOffset, curShadowColor, uStart, vStart);
|
||||
pushVtx(
|
||||
curX - itOff + shadowOffset2,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart,
|
||||
vStart + vSz);
|
||||
pushVtx(
|
||||
curX + glyphW - 1.0F + itOff + shadowOffset2,
|
||||
anchorY + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
vStart);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset2,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
if (curBold) {
|
||||
pushTexRect(curX + 2.0f * shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pushVtx(curX + itOff, anchorY, curColor, uStart, vStart);
|
||||
pushVtx(curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff, anchorY + 7.99F, curColor, uStart + uSz, vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
pushTexRect(curX, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
|
||||
|
||||
if (curBold) {
|
||||
pushVtx(shadowOffset + curX + itOff, anchorY, curColor, uStart, vStart);
|
||||
pushVtx(shadowOffset + curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
|
||||
pushVtx(shadowOffset + curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
|
||||
pushVtx(shadowOffset + curX + glyphW - 1.0F - itOff,
|
||||
anchorY + 7.99F,
|
||||
curColor,
|
||||
uStart + uSz,
|
||||
vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
for (int n = 1; n <= boldCopies; n++) {
|
||||
final float shadowOffsetPart = shadowOffset * ((float) n / boldCopies);
|
||||
pushTexRect(curX + shadowOffsetPart, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
|
||||
}
|
||||
}
|
||||
|
||||
pushDrawCmd(idxId, vtxCount / 2 * 3, texture);
|
||||
curX += xAdvance + (curBold ? shadowOffset : 0.0f);
|
||||
/*
|
||||
Vertex-per-char counts for different configurations
|
||||
default: 4
|
||||
shadow only: 4(1 + shadowCopies)
|
||||
bold only: 4(1 + boldCopies)
|
||||
both: 4(1 + 2 * shadowCopies + boldCopies)
|
||||
*/
|
||||
int charCount = 1;
|
||||
if (enableShadow) { charCount += shadowCopies * (curBold ? 2 : 1); }
|
||||
if (curBold) { charCount += boldCopies; }
|
||||
final int vtxCount = 4 * charCount;
|
||||
pushDrawCmd(idxId, vtxCount / 2 * 3, texture, chr > 255);
|
||||
|
||||
curX += (xAdvance + (curBold ? 1.0f : 0.0f)) + getGlyphSpacing();
|
||||
if (bookMode) { curX = (int) curX; }
|
||||
underlineEndX = curX;
|
||||
strikethroughEndX = curX;
|
||||
}
|
||||
|
||||
if (curUnderline && underlineStartX != underlineEndX) {
|
||||
final int ulIdx = idxWriterIndex;
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
}
|
||||
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
|
||||
final int ulIdx = idxWriterIndex;
|
||||
@@ -563,15 +660,36 @@ public class BatchingFontRenderer {
|
||||
strikethroughStartX,
|
||||
strikethroughY,
|
||||
strikethroughEndX - strikethroughStartX,
|
||||
1.0f,
|
||||
glyphScaleY,
|
||||
curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
}
|
||||
|
||||
} finally {
|
||||
this.endBatch();
|
||||
}
|
||||
return curX;
|
||||
return curX + (enableShadow ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
public float getCharWidthFine(char chr) {
|
||||
if (chr == FORMATTING_CHAR) { return -1; }
|
||||
|
||||
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
|
||||
return 4 * this.getWhitespaceScale();
|
||||
}
|
||||
|
||||
FontProvider fp = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, underlying.getUnicodeFlag());
|
||||
|
||||
return fp.getXAdvance(chr) * this.getGlyphScaleX();
|
||||
}
|
||||
|
||||
public void overrideBlendFunc(int srcRgb, int dstRgb) {
|
||||
blendSrcRGB = srcRgb;
|
||||
blendDstRGB = dstRgb;
|
||||
}
|
||||
|
||||
public void resetBlendFunc() {
|
||||
blendSrcRGB = GL11.GL_SRC_ALPHA;
|
||||
blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public interface FontProvider {
|
||||
|
||||
/**
|
||||
* For use with §k. Should fetch a character of the same width as provided.
|
||||
*/
|
||||
char getRandomReplacement(char chr);
|
||||
boolean isGlyphAvailable(char chr);
|
||||
float getUStart(char chr);
|
||||
float getVStart(char chr);
|
||||
float getXAdvance(char chr);
|
||||
float getGlyphW(char chr);
|
||||
float getUSize(char chr);
|
||||
float getVSize(char chr);
|
||||
float getShadowOffset();
|
||||
ResourceLocation getTexture(char chr);
|
||||
float getYScaleMultiplier();
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.ITextureObject;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class FontProviderCustom implements FontProvider {
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
public static final String FONT_DIR = "fonts/custom/";
|
||||
static final int ATLAS_SIZE = 128;
|
||||
static final int ATLAS_COUNT = 512;
|
||||
private final byte id; // 0 - primary font, 1 - fallback font
|
||||
private FontAtlas[] fontAtlases = new FontAtlas[ATLAS_COUNT];
|
||||
private float currentFontQuality = FontConfig.customFontQuality;
|
||||
@Setter
|
||||
private Font font;
|
||||
|
||||
private FontProviderCustom(byte id) {
|
||||
this.id = id;
|
||||
Font[] availableFonts = FontStrategist.getAvailableFonts();
|
||||
String myFontName = switch (this.id) {
|
||||
case 0 -> FontConfig.customFontNamePrimary;
|
||||
case 1 -> FontConfig.customFontNameFallback;
|
||||
default -> null;
|
||||
};
|
||||
if (Objects.equals(myFontName, "(none)")) {
|
||||
this.font = null;
|
||||
return;
|
||||
}
|
||||
int fontPos = -1;
|
||||
for (int i = 0; i < availableFonts.length; i++) {
|
||||
if (Objects.equals(myFontName, availableFonts[i].getFontName())) {
|
||||
fontPos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fontPos == -1) {
|
||||
LOGGER.info("Could not find previously set font \"{}\". ", myFontName);
|
||||
this.font = null;
|
||||
return;
|
||||
}
|
||||
this.font = availableFonts[fontPos].deriveFont(this.currentFontQuality);
|
||||
}
|
||||
private static class InstLoader {
|
||||
static final FontProviderCustom instance0 = new FontProviderCustom((byte)0);
|
||||
static final FontProviderCustom instance1 = new FontProviderCustom((byte)1);
|
||||
}
|
||||
public static FontProviderCustom getPrimary() { return InstLoader.instance0; }
|
||||
public static FontProviderCustom getFallback() { return InstLoader.instance1; }
|
||||
|
||||
public void reloadFont(int fontID) {
|
||||
this.currentFontQuality = FontConfig.customFontQuality;
|
||||
this.font = FontStrategist.getAvailableFonts()[fontID].deriveFont(this.currentFontQuality);
|
||||
|
||||
File[] files = new File(getFontDir()).listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (!Files.isSymbolicLink(f.toPath())) {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextureManager tm = Minecraft.getMinecraft().getTextureManager();
|
||||
Map mapTextureObjects = tm.mapTextureObjects;
|
||||
for (int i = 0; i < ATLAS_COUNT; i++) {
|
||||
ResourceLocation key = new ResourceLocation(getAtlasResourceName(i));
|
||||
if (mapTextureObjects.containsKey(key)) {
|
||||
ITextureObject obj = (ITextureObject) mapTextureObjects.get(key);
|
||||
TextureUtil.deleteTexture(obj.getGlTextureId());
|
||||
mapTextureObjects.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
this.fontAtlases = new FontAtlas[ATLAS_COUNT];
|
||||
}
|
||||
|
||||
private String getFontDir() {
|
||||
return FONT_DIR + "f" + this.id + "/";
|
||||
}
|
||||
|
||||
private String getAtlasFilename(int atlasId) {
|
||||
return "f" + this.id + "p" + atlasId;
|
||||
}
|
||||
|
||||
String getAtlasResourceName(int atlasId) {
|
||||
return "minecraft:angelica_c" + getAtlasFilename(atlasId);
|
||||
}
|
||||
|
||||
String getAtlasFullPath(int atlasId) {
|
||||
return getFontDir() + getAtlasFilename(atlasId) + ".png";
|
||||
}
|
||||
|
||||
@Value
|
||||
private class GlyphData {
|
||||
float uStart;
|
||||
float vStart;
|
||||
float xAdvance;
|
||||
float glyphW;
|
||||
float uSz;
|
||||
float vSz;
|
||||
}
|
||||
|
||||
private class FontAtlas {
|
||||
|
||||
GlyphData[] glyphData = new GlyphData[ATLAS_SIZE];
|
||||
private ResourceLocation texture;
|
||||
private final int id;
|
||||
|
||||
FontAtlas(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
void construct(Font font) {
|
||||
int atlasChars = 0;
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
final char ch = (char) (i + ATLAS_SIZE * this.id);
|
||||
if (font.canDisplay(ch)) { atlasChars++; }
|
||||
}
|
||||
if (atlasChars == 0) { return; }
|
||||
|
||||
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
g2d.setFont(font);
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
g2d.dispose();
|
||||
|
||||
final int atlasTilesX = (int) Math.ceil(Math.sqrt(atlasChars) * 1.5f);
|
||||
final int atlasTilesY = (int) Math.ceil((double) atlasChars / atlasTilesX);
|
||||
final float charSeparator = currentFontQuality / 3f;
|
||||
int rowWidth = 0;
|
||||
int maxRowWidth = 0;
|
||||
atlasChars = 0;
|
||||
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
if (atlasChars % atlasTilesX == 0) {
|
||||
maxRowWidth = Math.max(maxRowWidth, rowWidth);
|
||||
rowWidth = 0;
|
||||
}
|
||||
final char ch = (char)(i + ATLAS_SIZE * this.id);
|
||||
if (font.canDisplay(ch)) {
|
||||
rowWidth += (int) (charSeparator + fm.charWidth(ch));
|
||||
atlasChars++;
|
||||
}
|
||||
}
|
||||
maxRowWidth = Math.max(maxRowWidth, rowWidth);
|
||||
|
||||
final int lineHeight = fm.getHeight();
|
||||
final float desc = fm.getDescent();
|
||||
|
||||
final int imageWidth = (int) (maxRowWidth + charSeparator);
|
||||
final int imageHeight = (int) ((charSeparator + lineHeight) * atlasTilesY + charSeparator);
|
||||
|
||||
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
g2d = image.createGraphics();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
g2d.setFont(font);
|
||||
fm = g2d.getFontMetrics();
|
||||
|
||||
int tileX = 0, tileY = 0; // position in atlas tiles
|
||||
int imgX = (int) charSeparator; // position in pixels
|
||||
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
final char ch = (char) (i + ATLAS_SIZE * this.id);
|
||||
if (!font.canDisplay(ch)) { continue; }
|
||||
|
||||
if (tileX >= atlasTilesX) {
|
||||
tileX = 0;
|
||||
imgX = (int) charSeparator;
|
||||
tileY++;
|
||||
}
|
||||
|
||||
final int charWidth = fm.charWidth(ch);
|
||||
final float charAspectRatio = (float) charWidth / lineHeight;
|
||||
final float inset = currentFontQuality / 16;
|
||||
g2d.drawString(Character.toString(ch), imgX, (lineHeight + charSeparator) * (tileY + 1) - desc);
|
||||
final float uStart = (float) (imgX - inset * charAspectRatio) / imageWidth;
|
||||
final float vStart = ((lineHeight + charSeparator) * (tileY + 1) - lineHeight - inset) / imageHeight;
|
||||
final float xAdvance = charAspectRatio * 8 * charWidth / (charWidth + 2 * inset * charAspectRatio);
|
||||
final float glyphW = charAspectRatio * 8 + 1;
|
||||
final float uSz = (float) (charWidth + 2 * inset * charAspectRatio) / imageWidth;
|
||||
final float vSz = (float) (lineHeight + 2 * inset) / imageHeight;
|
||||
this.glyphData[i] = new GlyphData(uStart, vStart, xAdvance, glyphW, uSz, vSz);
|
||||
imgX += (int) (charWidth + charSeparator);
|
||||
tileX++;
|
||||
}
|
||||
g2d.dispose();
|
||||
try {
|
||||
Files.createDirectories(Paths.get(getFontDir()));
|
||||
ImageIO.write(image, "png", new File(getAtlasFullPath(this.id)));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.texture = new ResourceLocation(getAtlasResourceName(this.id));
|
||||
}
|
||||
}
|
||||
|
||||
private FontAtlas getAtlas(char chr) {
|
||||
int id = chr / ATLAS_SIZE;
|
||||
FontAtlas fa = this.fontAtlases[id];
|
||||
if (fa == null) {
|
||||
fa = new FontAtlas(id);
|
||||
fa.construct(this.font);
|
||||
this.fontAtlases[id] = fa;
|
||||
}
|
||||
return fa;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
if (this.font == null) { return false; }
|
||||
return (getAtlas(chr).glyphData[chr % ATLAS_SIZE] != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getRandomReplacement(char chr) {
|
||||
return chr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].xAdvance * FontConfig.customFontScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].glyphW * FontConfig.customFontScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uSz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vSz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
return getAtlas(chr).texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return FontConfig.customFontScale;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public final class FontProviderMC implements FontProvider {
|
||||
|
||||
/**
|
||||
* The full list of characters present in the default Minecraft font, excluding the Unicode font
|
||||
*/
|
||||
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u2302\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
|
||||
|
||||
private FontProviderMC() {}
|
||||
private static class InstLoader {
|
||||
static final FontProviderMC instance = new FontProviderMC();
|
||||
static final FontProviderMC instanceSGA = new FontProviderMC();
|
||||
}
|
||||
public static FontProviderMC get(boolean isSGA) {
|
||||
return (isSGA ? InstLoader.instanceSGA : InstLoader.instance);
|
||||
}
|
||||
|
||||
private static final Char2ShortOpenHashMap MCFONT_ASCII_MAP = new Char2ShortOpenHashMap();
|
||||
ResourceLocation locationFontTexture = null;
|
||||
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
|
||||
public int[] charWidth;
|
||||
|
||||
static {
|
||||
MCFONT_ASCII_MAP.defaultReturnValue((short) -1);
|
||||
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
|
||||
MCFONT_ASCII_MAP.put(MCFONT_CHARS.charAt(i), i);
|
||||
}
|
||||
}
|
||||
|
||||
public int lookupMcFontPosition(char ch) {
|
||||
return MCFONT_ASCII_MAP.get(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
return MCFONT_ASCII_MAP.containsKey(chr);
|
||||
}
|
||||
|
||||
public char getRandomReplacement(char chr){
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
if (lutIndex != -1) {
|
||||
int randomReplacementIndex;
|
||||
do {
|
||||
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
|
||||
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
|
||||
|
||||
lutIndex = randomReplacementIndex;
|
||||
return MCFONT_CHARS.charAt(lutIndex);
|
||||
}
|
||||
return chr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return ((lutIndex % 16) * 8) / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return (float) ((lutIndex / 16) * 8) / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return charWidth[lutIndex];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return charWidth[lutIndex] - 0.01F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return (charWidth[lutIndex] - 1.01F) / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return 7.99F / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
return locationFontTexture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public final class FontProviderUnicode implements FontProvider {
|
||||
|
||||
private FontProviderUnicode() {
|
||||
try {
|
||||
InputStream inputstream = Minecraft.getMinecraft().getResourceManager()
|
||||
.getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
inputstream.read(this.glyphWidth);
|
||||
}
|
||||
catch (IOException ioexception) {
|
||||
throw new RuntimeException(ioexception);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InstLoader { static final FontProviderUnicode instance = new FontProviderUnicode(); }
|
||||
public static FontProviderUnicode get() { return FontProviderUnicode.InstLoader.instance; }
|
||||
|
||||
private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256];
|
||||
private final byte[] glyphWidth = new byte[65536];
|
||||
|
||||
private ResourceLocation getUnicodePageLocation(int page) {
|
||||
final ResourceLocation lookup = unicodePageLocations[page];
|
||||
if (lookup == null) {
|
||||
final ResourceLocation rl = new ResourceLocation(String.format(
|
||||
"textures/font/unicode_page_%02x.png",
|
||||
page));
|
||||
unicodePageLocations[page] = rl;
|
||||
return rl;
|
||||
} else {
|
||||
return lookup;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getRandomReplacement(char chr) {
|
||||
return chr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
final float startColumnF = (float)((this.glyphWidth[chr] >>> 4) & 15);
|
||||
return ((float) (chr % 16 * 16) + startColumnF + 0.21f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
return ((float) ((chr & 255) / 16 * 16) + 0.21f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
return (endColumnF - startColumnF) / 2.0F + 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
final float chrWidth = endColumnF - startColumnF - 0.02F;
|
||||
return chrWidth / 2.0f + 1.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
final float chrWidth = endColumnF - startColumnF - 0.02F;
|
||||
return (chrWidth - 0.42f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return (16.0f - 0.42f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset * FontConfig.fontShadowOffsetUC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
final int uniPage = chr / 256;
|
||||
return getUnicodePageLocation(uniPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ResourceAccessor;
|
||||
import cpw.mods.fml.client.SplashProgress;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.resources.DefaultResourcePack;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FontStrategist {
|
||||
|
||||
@Getter
|
||||
private static final Font[] availableFonts;
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
static {
|
||||
if (GraphicsEnvironment.isHeadless()) {
|
||||
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Custom fonts will be unavailable. This is likely a MacOS issue.");
|
||||
availableFonts = new Font[0];
|
||||
} else {
|
||||
// get available fonts without duplicates (250 copies of dialog.plain need not apply)
|
||||
Font[] availableFontsDirty = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
|
||||
HashMap<String, Font> fontSet = new HashMap<>();
|
||||
HashMultiset<String> duplicates = HashMultiset.create(); // for debugging
|
||||
|
||||
for (Font font : availableFontsDirty) {
|
||||
String fontName = font.getFontName();
|
||||
if (fontSet.containsKey(fontName)) {
|
||||
duplicates.add(fontName);
|
||||
} else {
|
||||
fontSet.put(fontName, font);
|
||||
}
|
||||
}
|
||||
|
||||
if (!duplicates.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder(duplicates.size() + " duplicate font(s) found in the list reported by Java: ");
|
||||
for (Iterator<String> iter = duplicates.stream().distinct().iterator(); iter.hasNext(); ) {
|
||||
String dupe = iter.next();
|
||||
sb.append(duplicates.count(dupe)).append("x ").append(dupe);
|
||||
if (iter.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(". Some fonts may be missing from the font selection menu.");
|
||||
LOGGER.warn(sb.toString());
|
||||
}
|
||||
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
|
||||
|
||||
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, availableFonts.length);
|
||||
}
|
||||
|
||||
// create and add the resource pack that provides fonts
|
||||
HashMap<String, File> packMap = new HashMap<>();
|
||||
for (int i = 0; i < FontProviderCustom.ATLAS_COUNT; i++) {
|
||||
packMap.put(FontProviderCustom.getPrimary().getAtlasResourceName(i), new File(FontProviderCustom.getPrimary().getAtlasFullPath(i)));
|
||||
packMap.put(FontProviderCustom.getFallback().getAtlasResourceName(i), new File(FontProviderCustom.getFallback().getAtlasFullPath(i)));
|
||||
}
|
||||
|
||||
DefaultResourcePack fontResourcePack = new DefaultResourcePack(packMap);
|
||||
List defaultResourcePacks = ((ResourceAccessor) Minecraft.getMinecraft()).angelica$getDefaultResourcePacks();
|
||||
defaultResourcePacks.add(fontResourcePack);
|
||||
}
|
||||
|
||||
/**
|
||||
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether
|
||||
SGA is on, if we're in a splash screen, if a font can even display a character in the first place, etc.
|
||||
*/
|
||||
public static FontProvider getFontProvider(BatchingFontRenderer me, char chr, boolean customFontEnabled, boolean forceUnicode) {
|
||||
if (me.isSGA && FontProviderMC.get(true).isGlyphAvailable(chr)) {
|
||||
return FontProviderMC.get(true);
|
||||
}
|
||||
if (me.bookMode) {
|
||||
return FontProviderUnicode.get();
|
||||
}
|
||||
if (customFontEnabled && !me.isSplash) {
|
||||
FontProvider fp;
|
||||
fp = FontProviderCustom.getPrimary();
|
||||
if (fp.isGlyphAvailable(chr)) { return fp; }
|
||||
fp = FontProviderCustom.getFallback();
|
||||
if (fp.isGlyphAvailable(chr)) { return fp; }
|
||||
return FontProviderUnicode.get();
|
||||
} else {
|
||||
if (!forceUnicode && FontProviderMC.get(false).isGlyphAvailable(chr)) {
|
||||
return FontProviderMC.get(false);
|
||||
} else {
|
||||
return FontProviderUnicode.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void reloadCustomFontProviders() {
|
||||
FontProviderCustom.getPrimary().setFont(null);
|
||||
FontProviderCustom.getFallback().setFont(null);
|
||||
for (int i = 0; i < availableFonts.length; i++) {
|
||||
if (Objects.equals(FontConfig.customFontNamePrimary, availableFonts[i].getFontName())) {
|
||||
FontProviderCustom.getPrimary().reloadFont(i);
|
||||
}
|
||||
if (Objects.equals(FontConfig.customFontNameFallback, availableFonts[i].getFontName())) {
|
||||
FontProviderCustom.getFallback().reloadFont(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSplashFontRendererActive(FontRenderer fontRenderer) {
|
||||
// noinspection deprecation
|
||||
boolean active = fontRenderer instanceof SplashProgress.SplashFontRenderer;
|
||||
|
||||
try {
|
||||
Class<?> customSplashClass = Class.forName("gkappa.modernsplash.CustomSplash$SplashFontRenderer");
|
||||
active = active || customSplashClass.isInstance(fontRenderer);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
return active;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfigStorage;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityTypeEntry;
|
||||
import jss.notfine.core.Settings;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.Option;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionGroup;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionImpl;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionImpact;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.control.TickBoxControl;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DynamicLightsOptionPages {
|
||||
|
||||
public static OptionPage dynamicLights() {
|
||||
if (!DynamicLights.configEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<OptionGroup> groups = new ArrayList<>();
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(Settings.DYNAMIC_LIGHTS.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_SHADER_FORCE.option)
|
||||
.build());
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(Settings.DYNAMIC_LIGHTS_FRUSTUM_CULLING.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_ADAPTIVE_TICKING.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_CULL_TIMEOUT.option)
|
||||
.build());
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(Settings.DYNAMIC_LIGHTS_SLOW_DIST.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_SLOWER_DIST.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_BACKGROUND_DIST.option)
|
||||
.build());
|
||||
|
||||
// Entity type toggles - grouped by mod ID
|
||||
List<EntityTypeEntry> allEntities = EntityLightConfig.getAllEntityTypes();
|
||||
Map<String, List<EntityTypeEntry>> byMod = new LinkedHashMap<>();
|
||||
|
||||
for (EntityTypeEntry entry : allEntities) {
|
||||
byMod.computeIfAbsent(entry.getModId(), _ -> new ArrayList<>()).add(entry);
|
||||
}
|
||||
|
||||
// Create a group for each mod's entities
|
||||
for (Map.Entry<String, List<EntityTypeEntry>> modEntry : byMod.entrySet()) {
|
||||
String modId = modEntry.getKey();
|
||||
List<EntityTypeEntry> entities = modEntry.getValue();
|
||||
|
||||
OptionGroup.Builder groupBuilder = OptionGroup.createBuilder();
|
||||
|
||||
for (EntityTypeEntry entity : entities) {
|
||||
Option<Boolean> option = createEntityOption(entity);
|
||||
if (option != null) {
|
||||
groupBuilder.add(option);
|
||||
}
|
||||
}
|
||||
|
||||
// Only add group if it has options
|
||||
OptionGroup group = groupBuilder.build();
|
||||
if (!group.getOptions().isEmpty()) {
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
|
||||
return new OptionPage(I18n.format("options.dynamiclights.page"), ImmutableList.copyOf(groups));
|
||||
}
|
||||
|
||||
private static Option<Boolean> createEntityOption(EntityTypeEntry entry) {
|
||||
final String className = entry.getClassName();
|
||||
|
||||
// Resolve class once during option creation, not on every get/set
|
||||
Class<?> resolvedClass;
|
||||
try {
|
||||
resolvedClass = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Class not loaded, skip this option
|
||||
return null;
|
||||
}
|
||||
|
||||
final Class<?> entityClass = resolvedClass;
|
||||
|
||||
return OptionImpl.createBuilder(Boolean.class, EntityLightConfigStorage.INSTANCE)
|
||||
.setName(entry.getDisplayName() + " (" + entry.getModId() + ")")
|
||||
.setTooltip(I18n.format("options.dynamiclights.entity.tooltip", entry.getDisplayName()))
|
||||
.setControl(TickBoxControl::new)
|
||||
.setBinding(
|
||||
(_, value) -> EntityLightConfig.setEntityTypeEnabled(entityClass, value),
|
||||
storage -> EntityLightConfig.isEntityTypeEnabled(entityClass)
|
||||
)
|
||||
.setImpact(OptionImpact.LOW)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,572 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.ConfigException;
|
||||
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
|
||||
import com.gtnewhorizons.angelica.client.font.FontStrategist;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import net.coderbot.iris.gui.element.widget.IrisButton;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.gui.GuiSlot;
|
||||
import net.minecraft.client.gui.GuiTextField;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class FontConfigScreen extends GuiScreen {
|
||||
|
||||
private static final Font[] availableFonts = FontStrategist.getAvailableFonts();
|
||||
private final GuiScreen parent;
|
||||
private final String title;
|
||||
private final String searchPrompt;
|
||||
private final String testAreaPrompt;
|
||||
private List<String> testAreaInfo;
|
||||
private List<String> noFontsWarning;
|
||||
private String currentPrimaryFontName;
|
||||
private String currentFallbackFontName;
|
||||
private FontList fontList;
|
||||
private int selectedPrimaryFontListPos = -1;
|
||||
private int selectedFallbackFontListPos = -1;
|
||||
private ArrayList<Font> displayedFonts;
|
||||
private GuiTextField searchBox;
|
||||
private GuiTextField testArea;
|
||||
private int lastSliderPageIndex = 0;
|
||||
private int displayedSliderPage = 0;
|
||||
private final int sliderWidth = 160;
|
||||
private final int sliderHeight = 20;
|
||||
|
||||
// need to save these separately
|
||||
private IrisButton fwdButton;
|
||||
private IrisButton backButton;
|
||||
|
||||
SliderClone.Option optFontQuality = new SliderClone.Option(6, 72, 6);
|
||||
SliderClone.Option optShadowOffset = new SliderClone.Option(0, 2, 0.05f);
|
||||
SliderClone.Option optShadowCopies = new SliderClone.Option(1, 8, 1);
|
||||
SliderClone.Option optBoldCopies = new SliderClone.Option(1, 8, 1);
|
||||
SliderClone.Option optGlyphAspect = new SliderClone.Option(-1, 1, 0.05f);
|
||||
SliderClone.Option optGlyphScale = new SliderClone.Option(0.1f, 3, 0.05f);
|
||||
SliderClone.Option optWhitespaceScale = new SliderClone.Option(0.1f, 3, 0.05f);
|
||||
SliderClone.Option optGlyphSpacing = new SliderClone.Option(-2f, 2f, 0.05f);
|
||||
SliderClone.Option optFontAAMode = new SliderClone.Option(0, 2, 1);
|
||||
SliderClone.Option optFontAAStrength = new SliderClone.Option(1, 24, 1);
|
||||
SliderClone.Option optCustomFontScale = new SliderClone.Option(0.1f, 3, 0.05f);
|
||||
SliderClone.Option optShadowOffsetUC = new SliderClone.Option(0, 2, 0.05f);
|
||||
|
||||
public FontConfigScreen(GuiScreen parent) {
|
||||
this.title = I18n.format("options.angelica.fontconfig.title");
|
||||
this.searchPrompt = I18n.format("options.angelica.fontconfig.searchprompt");
|
||||
this.testAreaPrompt = I18n.format("options.angelica.fontconfig.testareaprompt");
|
||||
this.parent = parent;
|
||||
this.currentPrimaryFontName = FontConfig.customFontNamePrimary;
|
||||
this.currentFallbackFontName = FontConfig.customFontNameFallback;
|
||||
this.displayedFonts = new ArrayList<>(Arrays.asList(availableFonts));
|
||||
for (int i = 0; i < availableFonts.length; i++) {
|
||||
if (Objects.equals(this.currentPrimaryFontName, availableFonts[i].getFontName())) {
|
||||
selectedPrimaryFontListPos = i;
|
||||
}
|
||||
if (Objects.equals(this.currentFallbackFontName, availableFonts[i].getFontName())) {
|
||||
selectedFallbackFontListPos = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void initGui() {
|
||||
Keyboard.enableRepeatEvents(true);
|
||||
searchBox = new GuiTextField(this.fontRendererObj, this.width / 2 - 120, 24, 240, 20);
|
||||
searchBox.setMaxStringLength(64);
|
||||
fontList = new FontList();
|
||||
testArea = new GuiTextField(this.fontRendererObj, this.width * 5 / 6 - this.width / 7, 63 - 10, this.width * 2 / 7, 20);
|
||||
testArea.setMaxStringLength(512);
|
||||
testArea.setText(I18n.format("options.angelica.fontconfig.testareaexample"));
|
||||
initButtons();
|
||||
}
|
||||
|
||||
private void initButtons() {
|
||||
final int halfWidth = this.width / 2;
|
||||
final int sliderSpacing = 4;
|
||||
|
||||
final int x1 = halfWidth - sliderWidth - sliderSpacing;
|
||||
final int x2 = halfWidth;
|
||||
final int x3 = halfWidth + sliderWidth + sliderSpacing;
|
||||
final int y3 = this.height - 65 + 2 * (sliderHeight + sliderSpacing);
|
||||
|
||||
this.buttonList.add(new IrisButton(x1 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
|
||||
FontConfig.enableCustomFont ? I18n.format("options.angelica.fontconfig.disable_custom_font") :
|
||||
I18n.format("options.angelica.fontconfig.enable_custom_font"), this::toggleCustomFont));
|
||||
this.buttonList.add(new IrisButton(x2 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
|
||||
I18n.format("options.angelica.fontconfig.reset_values"), this::resetValues));
|
||||
this.buttonList.add(new IrisButton(x3 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
|
||||
I18n.format("gui.done"), button -> this.onClose()));
|
||||
|
||||
backButton = new IrisButton(halfWidth - 20 - 224, this.height - 89 - 10, 40, 20,
|
||||
"<", button -> this.switchPage(-1));
|
||||
fwdButton = new IrisButton(halfWidth - 20 + 224, this.height - 89 - 10, 40, 20,
|
||||
">", button -> this.switchPage(1));
|
||||
this.buttonList.add(fwdButton);
|
||||
this.buttonList.add(backButton);
|
||||
|
||||
initSliders();
|
||||
}
|
||||
|
||||
private void initSliders() {
|
||||
ArrayList<SliderClone> sliders = new ArrayList<>();
|
||||
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optFontQuality)
|
||||
.initialValue(FontConfig.customFontQuality)
|
||||
.setter(this::setFontQuality)
|
||||
.langKey("options.angelica.fontconfig.font_quality")
|
||||
.formatString("%2.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optShadowOffset)
|
||||
.initialValue(FontConfig.fontShadowOffset)
|
||||
.setter(value -> FontConfig.fontShadowOffset = value)
|
||||
.langKey("options.angelica.fontconfig.shadow_offset")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optGlyphAspect)
|
||||
.initialValue(FontConfig.glyphAspect)
|
||||
.setter(value -> FontConfig.glyphAspect = value)
|
||||
.langKey("options.angelica.fontconfig.glyph_aspect")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optGlyphScale)
|
||||
.initialValue(FontConfig.glyphScale)
|
||||
.setter(value -> FontConfig.glyphScale = value)
|
||||
.langKey("options.angelica.fontconfig.glyph_scale")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optFontAAStrength)
|
||||
.initialValue(FontConfig.fontAAStrength)
|
||||
.setter(value -> FontConfig.fontAAStrength = value.intValue())
|
||||
.langKey("options.angelica.fontconfig.font_aa_strength")
|
||||
.formatString("%.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optFontAAMode)
|
||||
.initialValue(FontConfig.fontAAMode)
|
||||
.setter(value -> FontConfig.fontAAMode = value.intValue())
|
||||
.formatter(this::fontAAModeFormat)
|
||||
.langKey("options.angelica.fontconfig.aamode")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optWhitespaceScale)
|
||||
.initialValue(FontConfig.whitespaceScale)
|
||||
.setter(value -> FontConfig.whitespaceScale = value)
|
||||
.langKey("options.angelica.fontconfig.whitespace_scale")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optGlyphSpacing)
|
||||
.initialValue(FontConfig.glyphSpacing)
|
||||
.setter(value -> FontConfig.glyphSpacing = value)
|
||||
.langKey("options.angelica.fontconfig.glyph_spacing")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optShadowCopies)
|
||||
.initialValue(FontConfig.shadowCopies)
|
||||
.setter(value -> FontConfig.shadowCopies = value.intValue())
|
||||
.langKey("options.angelica.fontconfig.shadow_copies")
|
||||
.formatString("%.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optBoldCopies)
|
||||
.initialValue(FontConfig.boldCopies)
|
||||
.setter(value -> FontConfig.boldCopies = value.intValue())
|
||||
.langKey("options.angelica.fontconfig.bold_copies")
|
||||
.formatString("%.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optCustomFontScale)
|
||||
.initialValue(FontConfig.customFontScale)
|
||||
.setter(value -> FontConfig.customFontScale = value)
|
||||
.langKey("options.angelica.fontconfig.custom_font_scale")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optShadowOffsetUC)
|
||||
.initialValue(FontConfig.fontShadowOffsetUC)
|
||||
.setter(value -> FontConfig.fontShadowOffsetUC = value)
|
||||
.langKey("options.angelica.fontconfig.shadow_offset_unicode")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
|
||||
final int halfWidth = this.width / 2;
|
||||
final int sliderSpacing = 4;
|
||||
|
||||
final int x1 = halfWidth - sliderWidth - sliderSpacing;
|
||||
final int x2 = halfWidth;
|
||||
final int x3 = halfWidth + sliderWidth + sliderSpacing;
|
||||
final int y1 = this.height - 65;
|
||||
final int y2 = this.height - 65 + sliderHeight + sliderSpacing;
|
||||
|
||||
this.lastSliderPageIndex = (sliders.size() - 1) / 6;
|
||||
this.displayedSliderPage = Math.min(this.displayedSliderPage, this.lastSliderPageIndex);
|
||||
|
||||
this.backButton.enabled = (this.displayedSliderPage != 0);
|
||||
this.fwdButton.enabled = (this.displayedSliderPage != this.lastSliderPageIndex);
|
||||
|
||||
final int start = 6 * this.displayedSliderPage;
|
||||
final int end = Math.min(6 * (this.displayedSliderPage + 1), sliders.size());
|
||||
List<SliderClone> s = sliders.subList(start, end);
|
||||
try {
|
||||
s.get(0).setCenterPosition(x1, y1);
|
||||
s.get(1).setCenterPosition(x1, y2);
|
||||
s.get(2).setCenterPosition(x2, y1);
|
||||
s.get(3).setCenterPosition(x2, y2);
|
||||
s.get(4).setCenterPosition(x3, y1);
|
||||
s.get(5).setCenterPosition(x3, y2);
|
||||
} catch (IndexOutOfBoundsException ignored) {
|
||||
// we're on the last page and it has less than 6 sliders to display
|
||||
}
|
||||
this.buttonList.addAll(s);
|
||||
}
|
||||
|
||||
private void onClose() {
|
||||
applyChanges(true);
|
||||
this.mc.displayGuiScreen(parent);
|
||||
}
|
||||
|
||||
private void applyChanges(boolean finalApply) {
|
||||
int pos;
|
||||
pos = selectedPrimaryFontListPos;
|
||||
if (pos >= 0 && pos < displayedFonts.size()) {
|
||||
FontConfig.customFontNamePrimary = displayedFonts.get(pos).getFontName();
|
||||
}
|
||||
pos = selectedFallbackFontListPos;
|
||||
if (pos >= 0 && pos < displayedFonts.size()) {
|
||||
FontConfig.customFontNameFallback = displayedFonts.get(pos).getFontName();
|
||||
}
|
||||
|
||||
FontStrategist.reloadCustomFontProviders();
|
||||
|
||||
if (finalApply) {
|
||||
ConfigurationManager.save(FontConfig.class);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetValues(IrisButton button) {
|
||||
try {
|
||||
ConfigurationManager.getConfigElements(FontConfig.class).forEach(elem -> elem.set(elem.getDefault()));
|
||||
} catch (ConfigException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
currentPrimaryFontName = null;
|
||||
currentFallbackFontName = null;
|
||||
selectedPrimaryFontListPos = -1;
|
||||
selectedFallbackFontListPos = -1;
|
||||
super.buttonList.clear();
|
||||
this.initButtons();
|
||||
}
|
||||
|
||||
private void toggleCustomFont(IrisButton button) {
|
||||
FontConfig.enableCustomFont = !FontConfig.enableCustomFont;
|
||||
applyChanges(false);
|
||||
button.displayString = FontConfig.enableCustomFont ?
|
||||
I18n.format("options.angelica.fontconfig.disable_custom_font") :
|
||||
I18n.format("options.angelica.fontconfig.enable_custom_font");
|
||||
}
|
||||
|
||||
private void switchPage(int offset) {
|
||||
this.displayedSliderPage = MathHelper.clamp_int(this.displayedSliderPage + offset, 0, this.lastSliderPageIndex);
|
||||
this.buttonList.removeIf(guiButton -> guiButton instanceof SliderClone);
|
||||
this.initSliders();
|
||||
}
|
||||
|
||||
private int qualityLast = FontConfig.customFontQuality;
|
||||
|
||||
private void setFontQuality(float quality) {
|
||||
FontConfig.customFontQuality = (int) quality;
|
||||
if (qualityLast != (int) quality) {
|
||||
applyChanges(false);
|
||||
}
|
||||
qualityLast = (int) quality;
|
||||
}
|
||||
|
||||
private String fontAAModeFormat(float AAmode) {
|
||||
return switch ((int) AAmode) {
|
||||
case 2 -> I18n.format("options.angelica.fontconfig.aamode.aa_16x");
|
||||
case 1 -> I18n.format("options.angelica.fontconfig.aamode.aa_4x");
|
||||
default -> I18n.format("options.angelica.fontconfig.aamode.aa_none");
|
||||
};
|
||||
}
|
||||
|
||||
private float lastMouseX = 0;
|
||||
private float lastMouseY = 0;
|
||||
private long lastStillTime = 0;
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float delta) {
|
||||
drawBackground(0);
|
||||
|
||||
this.fontList.drawScreen(mouseX, mouseY, delta);
|
||||
if (availableFonts.length == 0) {
|
||||
if (this.noFontsWarning == null) {
|
||||
this.noFontsWarning = this.fontRendererObj.listFormattedStringToWidth(
|
||||
I18n.format("options.angelica.fontconfig.no_fonts_warning"), this.width / 2
|
||||
);
|
||||
}
|
||||
int y = 80;
|
||||
for (String s : this.noFontsWarning) {
|
||||
drawCenteredString(this.fontRendererObj, s, this.width / 3, y, 0xFFFFFF);
|
||||
y += this.fontRendererObj.FONT_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
this.searchBox.drawTextBox();
|
||||
if (!this.searchBox.isFocused() && this.searchBox.getText().isEmpty()) {
|
||||
this.drawCenteredString(this.fontRendererObj, this.searchPrompt,
|
||||
this.searchBox.xPosition + this.fontRendererObj.getStringWidth(this.searchPrompt) / 2 + 4,
|
||||
this.searchBox.yPosition + this.searchBox.height / 2 - 4, 0xFFFFFF);
|
||||
}
|
||||
drawCenteredString(this.fontRendererObj, this.title, (int) (this.width * 0.5), 8, 0xFFFFFF);
|
||||
drawCenteredString(this.fontRendererObj, I18n.format("options.angelica.fontconfig.currentfonts",
|
||||
FontConfig.customFontNamePrimary, FontConfig.customFontNameFallback), (int) (this.width * 0.5), this.height - 92, 0xFFFFFF);
|
||||
|
||||
if (this.testAreaInfo == null) {
|
||||
this.testAreaInfo = this.fontRendererObj.listFormattedStringToWidth(
|
||||
I18n.format("options.angelica.fontconfig.testareainfo"), this.width / 4
|
||||
);
|
||||
}
|
||||
int cumulativeY = 73;
|
||||
for (String s : this.testAreaInfo) {
|
||||
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
|
||||
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
|
||||
}
|
||||
cumulativeY += 4;
|
||||
this.testArea.yPosition = cumulativeY;
|
||||
this.testArea.drawTextBox();
|
||||
if (!this.testArea.isFocused() && this.testArea.getText().isEmpty()) {
|
||||
this.drawCenteredString(this.fontRendererObj, this.testAreaPrompt,
|
||||
this.testArea.xPosition + this.fontRendererObj.getStringWidth(this.testAreaPrompt) / 2 + 4,
|
||||
this.testArea.yPosition + this.testArea.height / 2 - 4, 0xFFFFFF);
|
||||
}
|
||||
List<String> testText = this.fontRendererObj.listFormattedStringToWidth(
|
||||
this.testArea.getText().replace('&', '§'), this.width / 4
|
||||
);
|
||||
cumulativeY += this.testArea.height + 4;
|
||||
for (String s : testText) {
|
||||
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
|
||||
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
|
||||
}
|
||||
|
||||
super.drawScreen(mouseX, mouseY, delta);
|
||||
|
||||
for (GuiButton guiButton : buttonList) {
|
||||
if (!(guiButton instanceof SliderClone slider)) { continue; }
|
||||
final int top = slider.yPosition;
|
||||
final int bot = slider.yPosition + slider.height;
|
||||
final int left = slider.xPosition;
|
||||
final int right = slider.xPosition + slider.width;
|
||||
if (mouseY < top || mouseY >= bot || mouseX < left || mouseX >= right) { continue; }
|
||||
if (mouseX == lastMouseX && mouseY == lastMouseY) {
|
||||
if (lastStillTime == 0) {
|
||||
lastStillTime = System.currentTimeMillis();
|
||||
}
|
||||
if (lastStillTime + 500L < System.currentTimeMillis()) {
|
||||
displayTooltip(mouseX, mouseY, slider.tooltipKey);
|
||||
}
|
||||
} else {
|
||||
lastStillTime = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
lastMouseX = mouseX;
|
||||
lastMouseY = mouseY;
|
||||
}
|
||||
|
||||
private void displayTooltip(int x, int y, String langKey) {
|
||||
List<String> lines = this.fontRendererObj.listFormattedStringToWidth(I18n.format(langKey), this.width / 2);
|
||||
this.drawHoveringText(lines, x, y, this.fontRendererObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton guiButton) {
|
||||
if (guiButton.enabled) {
|
||||
if (guiButton instanceof IrisButton irisButton) {
|
||||
irisButton.onPress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void keyTyped(char typedChar, int keyCode) {
|
||||
if (this.searchBox.isFocused()) {
|
||||
if (keyCode == Keyboard.KEY_ESCAPE) {
|
||||
this.searchBox.setFocused(false);
|
||||
return;
|
||||
}
|
||||
this.searchBox.textboxKeyTyped(typedChar, keyCode);
|
||||
this.displayedFonts = filterFonts(this.searchBox.getText().toLowerCase());
|
||||
}
|
||||
if (this.testArea.isFocused()) {
|
||||
if (keyCode == Keyboard.KEY_ESCAPE) {
|
||||
this.testArea.setFocused(false);
|
||||
return;
|
||||
}
|
||||
this.testArea.textboxKeyTyped(typedChar, keyCode);
|
||||
}
|
||||
if (keyCode == Keyboard.KEY_ESCAPE) {
|
||||
this.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
|
||||
super.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
this.searchBox.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
this.testArea.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
this.fontList.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScreen() {
|
||||
this.searchBox.updateCursorCounter();
|
||||
this.testArea.updateCursorCounter();
|
||||
}
|
||||
|
||||
// this could be more efficient, but it's good enough as is
|
||||
private ArrayList<Font> filterFonts(String search) {
|
||||
ArrayList<Font> results;
|
||||
if (search == null || search.isEmpty()) {
|
||||
results = new ArrayList<>(Arrays.asList(availableFonts));
|
||||
} else {
|
||||
results = Arrays.stream(availableFonts).filter((font -> font.getFontName().toLowerCase().contains(search))).collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
selectedPrimaryFontListPos = -1;
|
||||
selectedFallbackFontListPos = -1;
|
||||
for (int i = 0; i < results.size(); i++) {
|
||||
if (Objects.equals(currentPrimaryFontName, results.get(i).getFontName())) {
|
||||
selectedPrimaryFontListPos = i;
|
||||
}
|
||||
if (Objects.equals(currentFallbackFontName, results.get(i).getFontName())) {
|
||||
selectedFallbackFontListPos = i;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
class FontList extends GuiSlot {
|
||||
|
||||
public FontList() {
|
||||
super(FontConfigScreen.this.mc, FontConfigScreen.this.width * 2 / 3, FontConfigScreen.this.height, 52, FontConfigScreen.this.height - 102, 18);
|
||||
}
|
||||
|
||||
private void mouseClicked(int mouseX, int mouseY, int mouseButton) {
|
||||
if (mouseY < this.top || mouseY > this.bottom) { return; }
|
||||
|
||||
int listLeftBound = this.width / 2 - this.getListWidth() / 2;
|
||||
int listRightBound = this.width / 2 + this.getListWidth() / 2;
|
||||
if (mouseX < listLeftBound || mouseX > listRightBound) { return; }
|
||||
|
||||
int mousePosInList = mouseY - this.top - this.headerPadding + (int)this.amountScrolled - 4;
|
||||
int slotIndex = mousePosInList / this.slotHeight;
|
||||
if (slotIndex < 0 || slotIndex >= this.getSize()) { return; }
|
||||
|
||||
if (mouseButton == 0) {
|
||||
onElemClicked(slotIndex, false);
|
||||
} else if (mouseButton == 1) {
|
||||
onElemClicked(slotIndex, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onElemClicked(int index, boolean rightClick) {
|
||||
if (!rightClick) {
|
||||
selectedPrimaryFontListPos = index;
|
||||
currentPrimaryFontName = displayedFonts.get(index).getFontName();
|
||||
} else {
|
||||
selectedFallbackFontListPos = index;
|
||||
currentFallbackFontName = displayedFonts.get(index).getFontName();
|
||||
}
|
||||
applyChanges(false);
|
||||
}
|
||||
|
||||
protected boolean isSelected(int index) {
|
||||
return (index == selectedPrimaryFontListPos || index == selectedFallbackFontListPos);
|
||||
}
|
||||
|
||||
protected int getSize() {
|
||||
return displayedFonts.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getContentHeight() {
|
||||
return this.getSize() * 18;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getScrollBarX() {
|
||||
return this.width * 15 / 16 - 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getListWidth() {
|
||||
return this.width * 2 / 3;
|
||||
}
|
||||
|
||||
// see onElemClicked for the proper version that supports right-clicking
|
||||
protected void elementClicked(int index, boolean doubleClicked, int mouseX, int mouseY) {}
|
||||
|
||||
protected void drawBackground() {
|
||||
// do nothing, the font list doesn't occupy the entire screen
|
||||
}
|
||||
|
||||
protected void drawSlot(int index, int x, int y, int p_148126_4_, Tessellator tessellator, int p_148126_6_, int p_148126_7_) {
|
||||
int color = 0xffffff;
|
||||
if (index == selectedPrimaryFontListPos) {
|
||||
color &= 0xffff55;
|
||||
}
|
||||
if (index == selectedFallbackFontListPos) {
|
||||
color &= 0x55ffff;
|
||||
}
|
||||
drawCenteredString(fontRendererObj, displayedFonts.get(index).getFontName(), this.width / 2, y + 1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.mojang.Element;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import org.lwjgl.input.Mouse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ScrollableGuiScreen extends GuiScreen {
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
|
||||
super.drawScreen(mouseX, mouseY, partialTicks);
|
||||
handleMouseScroll(mouseX, mouseY, partialTicks);
|
||||
}
|
||||
|
||||
public abstract List<? extends Element> children();
|
||||
|
||||
protected void handleMouseScroll(int mouseX, int mouseY, float partialTicks) {
|
||||
for (; !this.mc.gameSettings.touchscreen && Mouse.next(); this.mc.currentScreen.handleMouseInput()) {
|
||||
int dWheel = Mouse.getEventDWheel();
|
||||
|
||||
if (dWheel != 0) {
|
||||
for(Element child : children()) {
|
||||
child.mouseScrolled(mouseX, mouseY, dWheel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SliderClone extends GuiButton {
|
||||
|
||||
private final Function<Float, String> displayStringFormatter;
|
||||
private final Consumer<Float> valueSetter;
|
||||
private final Option option;
|
||||
private float sliderPercentage;
|
||||
public final String tooltipKey;
|
||||
public boolean mouseDragging;
|
||||
|
||||
@AllArgsConstructor
|
||||
public static class Option {
|
||||
float min;
|
||||
float max;
|
||||
float step;
|
||||
|
||||
float denormalize(float alpha) {
|
||||
float lerp = min * (1 - alpha) + max * alpha;
|
||||
return Math.round(lerp / step) * step;
|
||||
}
|
||||
float normalize(float value) {
|
||||
return (value - min) / (max - min);
|
||||
}
|
||||
}
|
||||
|
||||
public SliderClone(int x, int y, int width, int height, Option option, float initialValue,
|
||||
Consumer<Float> setter, Function<Float, String> formatter, String tooltipKey) {
|
||||
super(0, x, y, width, height, "");
|
||||
this.option = option;
|
||||
this.sliderPercentage = option.normalize(initialValue);
|
||||
this.displayString = formatter.apply(initialValue);
|
||||
this.valueSetter = setter;
|
||||
this.displayStringFormatter = formatter;
|
||||
this.tooltipKey = tooltipKey;
|
||||
}
|
||||
|
||||
public static SliderCloneBuilder builder() {
|
||||
return new SliderCloneBuilder();
|
||||
}
|
||||
|
||||
public void setCenterPosition(int x, int y) {
|
||||
this.xPosition = x - this.width / 2;
|
||||
this.yPosition = y - this.height / 2;
|
||||
}
|
||||
|
||||
@Accessors(fluent = true) @Setter
|
||||
public static class SliderCloneBuilder {
|
||||
private int x = 0, y = 0; // center position, not a corner
|
||||
private int width = 144, height = 20;
|
||||
private Option option;
|
||||
private float initialValue;
|
||||
private Consumer<Float> setter;
|
||||
private String formatString = "%3.2f";
|
||||
private String langKey;
|
||||
|
||||
// overrides: if null, these get set to default values
|
||||
private String tooltipKey;
|
||||
private Function<Float, String> formatter;
|
||||
|
||||
public SliderClone build() {
|
||||
if (this.formatter == null) {
|
||||
this.formatter = value -> I18n.format(this.langKey, String.format(this.formatString, value));
|
||||
}
|
||||
|
||||
if (this.tooltipKey == null) {
|
||||
this.tooltipKey = this.langKey + ".tooltip";
|
||||
}
|
||||
|
||||
return new SliderClone(
|
||||
this.x - this.width / 2,
|
||||
this.y - this.height / 2,
|
||||
this.width,
|
||||
this.height,
|
||||
this.option,
|
||||
this.initialValue,
|
||||
this.setter,
|
||||
this.formatter,
|
||||
this.tooltipKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns 0 if the button is disabled, 1 if the mouse is NOT hovering over this button and 2 if it IS hovering over
|
||||
* this button.
|
||||
*/
|
||||
public int getHoverState(boolean mouseOver) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the mouse button is dragged. Equivalent of MouseListener.mouseDragged(MouseEvent e).
|
||||
*/
|
||||
protected void mouseDragged(Minecraft mc, int mouseX, int mouseY) {
|
||||
if (this.visible) {
|
||||
if (this.mouseDragging) {
|
||||
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
|
||||
|
||||
if (this.sliderPercentage < 0.0F) {
|
||||
this.sliderPercentage = 0.0F;
|
||||
}
|
||||
|
||||
if (this.sliderPercentage > 1.0F) {
|
||||
this.sliderPercentage = 1.0F;
|
||||
}
|
||||
|
||||
float f = this.option.denormalize(this.sliderPercentage);
|
||||
valueSetter.accept(f);
|
||||
this.sliderPercentage = this.option.normalize(f);
|
||||
this.displayString = displayStringFormatter.apply(f);
|
||||
}
|
||||
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)), this.yPosition, 0, 66, 4, 20);
|
||||
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the mouse has been pressed on this control. Equivalent of MouseListener.mousePressed(MouseEvent e).
|
||||
*/
|
||||
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) {
|
||||
if (super.mousePressed(mc, mouseX, mouseY)) {
|
||||
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
|
||||
|
||||
if (this.sliderPercentage < 0.0F) {
|
||||
this.sliderPercentage = 0.0F;
|
||||
}
|
||||
|
||||
if (this.sliderPercentage > 1.0F) {
|
||||
this.sliderPercentage = 1.0F;
|
||||
}
|
||||
|
||||
float f = this.option.denormalize(this.sliderPercentage);
|
||||
valueSetter.accept(f);
|
||||
this.sliderPercentage = this.option.normalize(f);
|
||||
this.displayString = displayStringFormatter.apply(f);
|
||||
this.mouseDragging = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the mouse button is released. Equivalent of MouseListener.mouseReleased(MouseEvent e).
|
||||
*/
|
||||
public void mouseReleased(int mouseX, int mouseY) {
|
||||
this.mouseDragging = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.gtnewhorizons.angelica.commands;
|
||||
|
||||
// Debug commands adapted from Beddium by Ven and FalsePattern
|
||||
|
||||
import com.gtnewhorizons.angelica.debug.ChunkDebugMinimap;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.ICommand;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class AngelicaCommand extends CommandBase {
|
||||
|
||||
private static final List<String> SUBCOMMANDS = Arrays.asList("wireframe", "fog", "minimap", "help");
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return "angelica";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandUsage(ICommandSender sender) {
|
||||
return "/angelica <wireframe|fog|minimap|help>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequiredPermissionLevel() {
|
||||
return 0; // Available to all players (client-side only)
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> addTabCompletionOptions(ICommandSender sender, String[] args) {
|
||||
if (args.length == 1) {
|
||||
return getListOfStringsMatchingLastWord(args, SUBCOMMANDS.toArray(new String[0]));
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processCommand(ICommandSender sender, String[] args) {
|
||||
if (args.length == 0) {
|
||||
sendHelp(sender);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "wireframe" -> handleWireframe(sender);
|
||||
case "fog" -> handleFog(sender);
|
||||
case "minimap" -> handleMinimap(sender);
|
||||
default -> sendHelp(sender);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleWireframe(ICommandSender sender) {
|
||||
CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE = !CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE;
|
||||
final String state = CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE ? "ON" : "OFF";
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Wireframe mode: " + state));
|
||||
}
|
||||
|
||||
private void handleFog(ICommandSender sender) {
|
||||
CeleritasDebugScreenHandler.showFogDebug = !CeleritasDebugScreenHandler.showFogDebug;
|
||||
final String f3State = CeleritasDebugScreenHandler.showFogDebug ? "ON" : "OFF";
|
||||
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Fog debug (F3): " + f3State));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " " + CeleritasDebugScreenHandler.getFogDebugString()));
|
||||
}
|
||||
|
||||
private void handleMinimap(ICommandSender sender) {
|
||||
ChunkDebugMinimap.toggle();
|
||||
final String state = ChunkDebugMinimap.isEnabled() ? "ON" : "OFF";
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Chunk debug minimap: " + state));
|
||||
}
|
||||
|
||||
private void sendHelp(ICommandSender sender) {
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] Debug Commands:"));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica wireframe" + EnumChatFormatting.WHITE + " - Toggle wireframe rendering"));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica fog" + EnumChatFormatting.WHITE + " - Toggle fog debug on F3"));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica minimap" + EnumChatFormatting.WHITE + " - Toggle chunk debug overlay"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
if (o instanceof ICommand cmd) {
|
||||
return this.getCommandName().compareTo(cmd.getCommandName());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.gtnewhorizons.angelica.common;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraftforge.client.event.TextureStitchEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
/**
|
||||
* This Block is not actually getting registered. A static instance is created in AngelicaMod.
|
||||
* Actually registering the block on the client but not the server can cause block IDs to
|
||||
* different between client/server, which is not good.
|
||||
|
||||
* We're essentially injecting the block's texture, so that we may use this block as if it were
|
||||
* any normal block for rendering purposes with RenderBlocks and such, but not have to actually
|
||||
* register it as a block.
|
||||
*/
|
||||
|
||||
public class BlockError extends Block {
|
||||
|
||||
public static final IIcon[] icons = new IIcon[2];
|
||||
|
||||
public BlockError() {
|
||||
super(Material.rock);
|
||||
|
||||
MinecraftForge.EVENT_BUS.register(new EventHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public IIcon getIcon(int side, int meta) {
|
||||
return meta >= 0 && meta < icons.length ? icons[meta] : null;
|
||||
}
|
||||
|
||||
public static class EventHandler{
|
||||
@SubscribeEvent
|
||||
@SideOnly(Side.CLIENT)
|
||||
public void onTextureStitch(TextureStitchEvent.Pre event) {
|
||||
if (event.map.getTextureType() == 0) {
|
||||
for (int i = 0; i < icons.length; i++) {
|
||||
icons[i] = event.map.registerIcon("angelica:error_block_" + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.common;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.client.model.ModelISBRH.JSON_ISBRH_ID;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/// Unline {@link BlockError}, this *is* registered on both sides. It's intended to test functionality in a SP
|
||||
/// development environment.
|
||||
public class BlockTest extends Block {
|
||||
|
||||
public BlockTest() {
|
||||
super(Material.wood);
|
||||
setHardness(0.7f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaqueCube() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a block is placed using its ItemBlock. Args: World, X, Y, Z, side, hitX, hitY, hitZ, block metadata
|
||||
*/
|
||||
@Override
|
||||
public int onBlockPlaced(@NotNull World worldIn, int x, int y, int z, int side, float subX, float subY, float subZ, int meta) {
|
||||
|
||||
// Face NORTH if placed up or down
|
||||
final var s = ForgeDirection.getOrientation(side);
|
||||
if (s == ForgeDirection.UP || s == ForgeDirection.DOWN)
|
||||
return 2;
|
||||
|
||||
// Face the placed side
|
||||
return side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRenderType() {
|
||||
return JSON_ISBRH_ID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.gtnewhorizons.angelica.compat;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.ARBDebugOutput;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
/**
|
||||
* Checks for known problematic driver configurations and applies workarounds. Adapted from Embeddium/Sodium's LateDriverScanner.
|
||||
*
|
||||
* <p>The NVIDIA workaround can be disabled via: {@code -Dangelica.disableNvidiaWorkaround=true}
|
||||
*/
|
||||
public class DriverCompatabilityCheck {
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
private static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
|
||||
|
||||
/**
|
||||
* Should be called after GL context is created and GLStateManager is initialized.
|
||||
*/
|
||||
public static void checkDriverCompatibility() {
|
||||
final String vendor = GL11.glGetString(GL11.GL_VENDOR);
|
||||
final String renderer = GL11.glGetString(GL11.GL_RENDERER);
|
||||
final String version = GL11.glGetString(GL11.GL_VERSION);
|
||||
|
||||
LOGGER.info("OpenGL Vendor: {}", vendor);
|
||||
LOGGER.info("OpenGL Renderer: {}", renderer);
|
||||
LOGGER.info("OpenGL Version: {}", version);
|
||||
|
||||
if (vendor != null && vendor.contains("NVIDIA")) {
|
||||
if (DISABLE_NVIDIA_WORKAROUND) {
|
||||
LOGGER.info("NVIDIA workaround disabled via -Dangelica.disableNvidiaWorkaround=true");
|
||||
} else {
|
||||
applyNvidiaWorkaround();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/godotengine/godot/issues/33969#issuecomment-917846774
|
||||
* Attempt to force the driver to disable its submission thread by enabling synchronous debug output.
|
||||
* This usually suppresses bad behavior from NVIDIA's "Threaded Optimizations" feature.
|
||||
*/
|
||||
private static void applyNvidiaWorkaround() {
|
||||
if (!GLStateManager.capabilities.GL_ARB_debug_output) {
|
||||
LOGGER.warn("Cannot apply NVIDIA workaround: ARB_debug_output not available");
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("Enabling workaround for NVIDIA threaded optimizations");
|
||||
GL11.glEnable(ARBDebugOutput.GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
package com.gtnewhorizons.angelica.compat;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.mixins.early.sodium.MixinExtendedBlockStorage;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ExtendedNibbleArray;
|
||||
import com.falsepattern.chunk.api.DataRegistry;
|
||||
import com.gtnewhorizons.neid.mixins.interfaces.IExtendedBlockStorageMixin;
|
||||
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.NibbleArray;
|
||||
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
|
||||
import ru.fewizz.idextender.Hooks;
|
||||
|
||||
public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
|
||||
public boolean hasSky;
|
||||
@@ -16,54 +15,59 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
|
||||
this.hasSky = hasSky;
|
||||
}
|
||||
|
||||
public ExtendedBlockStorageExt(ExtendedBlockStorage storage) {
|
||||
super(((MixinExtendedBlockStorage) storage).getYBase(), storage.getSkylightArray() != null);
|
||||
public ExtendedBlockStorageExt(Chunk chunk, ExtendedBlockStorage storage) {
|
||||
super(storage.yBase, storage.getSkylightArray() != null);
|
||||
|
||||
int arrayLen;
|
||||
if (AngelicaMod.isNEIDLoaded){
|
||||
final short[] block16BArray = ((IExtendedBlockStorageMixin)(Object)this).getBlock16BArray();
|
||||
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BArray(), 0, block16BArray, 0, block16BArray.length);
|
||||
if(storage.getBlockMSBArray() != null) {
|
||||
this.setBlockMSBArray(new NibbleArray(block16BArray.length, 4));
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
|
||||
if (ModStatus.isChunkAPILoaded) {
|
||||
if (storage.getSkylightArray() != null) {
|
||||
hasSky = true;
|
||||
}
|
||||
arrayLen = block16BArray.length;
|
||||
}
|
||||
else if (AngelicaMod.isOldNEIDLoaded){
|
||||
final short[] blockLSBArray = Hooks.get(this);
|
||||
System.arraycopy(Hooks.get(storage), 0, blockLSBArray, 0, blockLSBArray.length);
|
||||
// getBlockMSBArray is nuked in asm version
|
||||
arrayLen = blockLSBArray.length;
|
||||
}
|
||||
else {
|
||||
final byte[] blockLSBArray = this.getBlockLSBArray();
|
||||
System.arraycopy(storage.getBlockLSBArray(), 0, blockLSBArray, 0, blockLSBArray.length);
|
||||
if(storage.getBlockMSBArray() != null) {
|
||||
this.setBlockMSBArray(new NibbleArray(blockLSBArray.length, 4));
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
|
||||
DataRegistry.cloneSubChunk(chunk, storage, this);
|
||||
} else {
|
||||
int arrayLen;
|
||||
if (ModStatus.isNEIDLoaded){
|
||||
final short[] block16BArray = ((IExtendedBlockStorageMixin)(Object)this).getBlock16BArray();
|
||||
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BArray(), 0, block16BArray, 0, block16BArray.length);
|
||||
if(storage.getBlockMSBArray() != null) {
|
||||
this.setBlockMSBArray(new NibbleArray(block16BArray.length, 4));
|
||||
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
|
||||
}
|
||||
arrayLen = block16BArray.length;
|
||||
if (ModStatus.isNEIDMetadataExtended) {
|
||||
final short[] block16BMetaArray = ((IExtendedBlockStorageMixin)(Object)this).getBlock16BMetaArray();
|
||||
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BMetaArray(), 0, block16BMetaArray, 0, block16BMetaArray.length);
|
||||
}
|
||||
}
|
||||
else {
|
||||
final byte[] blockLSBArray = this.getBlockLSBArray();
|
||||
System.arraycopy(storage.getBlockLSBArray(), 0, blockLSBArray, 0, blockLSBArray.length);
|
||||
if(storage.getBlockMSBArray() != null) {
|
||||
this.setBlockMSBArray(new NibbleArray(blockLSBArray.length, 4));
|
||||
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
|
||||
}
|
||||
arrayLen = blockLSBArray.length;
|
||||
}
|
||||
arrayLen = blockLSBArray.length;
|
||||
}
|
||||
|
||||
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getMetadataArray(), (ExtendedNibbleArray)this.getMetadataArray());
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getBlocklightArray(), (ExtendedNibbleArray)this.getBlocklightArray());
|
||||
if(storage.getSkylightArray() != null) {
|
||||
hasSky = true;
|
||||
if(this.getSkylightArray() == null) {
|
||||
this.setSkylightArray(new NibbleArray(arrayLen, 4));
|
||||
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray(storage.getMetadataArray(), this.getMetadataArray());
|
||||
copyNibbleArray(storage.getBlocklightArray(), this.getBlocklightArray());
|
||||
if(storage.getSkylightArray() != null) {
|
||||
hasSky = true;
|
||||
if(this.getSkylightArray() == null) {
|
||||
this.setSkylightArray(new NibbleArray(arrayLen, 4));
|
||||
}
|
||||
copyNibbleArray(storage.getSkylightArray(), this.getSkylightArray());
|
||||
}
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getSkylightArray(), (ExtendedNibbleArray) this.getSkylightArray());
|
||||
}
|
||||
((MixinExtendedBlockStorage) this).setBlockRefCount(((MixinExtendedBlockStorage) storage).getBlockRefCount());
|
||||
this.blockRefCount = storage.blockRefCount;
|
||||
}
|
||||
|
||||
|
||||
private static void copyNibbleArray(ExtendedNibbleArray srcArray, ExtendedNibbleArray dstArray) {
|
||||
private static void copyNibbleArray(NibbleArray srcArray, NibbleArray dstArray) {
|
||||
if (srcArray == null || dstArray == null) {
|
||||
throw new RuntimeException("NibbleArray is null src: " + (srcArray == null) + " dst: " + (dstArray == null));
|
||||
}
|
||||
final byte[] data = srcArray.getData();
|
||||
System.arraycopy(data, 0, dstArray.getData(), 0, data.length);
|
||||
final byte[] data = srcArray.data;
|
||||
System.arraycopy(data, 0, dstArray.data, 0, data.length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.gtnewhorizons.angelica.compat;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
|
||||
import mods.battlegear2.Battlegear;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class ModStatus {
|
||||
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
|
||||
|
||||
public static boolean isBetterCrashesLoaded;
|
||||
public static boolean isNEIDLoaded;
|
||||
public static boolean isNEIDMetadataExtended;
|
||||
public static boolean isLotrLoaded;
|
||||
public static boolean isChunkAPILoaded;
|
||||
public static boolean isEIDBiomeLoaded;
|
||||
public static boolean isXaerosMinimapLoaded;
|
||||
public static boolean isHoloInventoryLoaded;
|
||||
public static boolean isBattlegearLoaded;
|
||||
public static boolean isBackhandLoaded;
|
||||
public static boolean isThaumcraftLoaded;
|
||||
public static boolean isThaumicHorizonsLoaded;
|
||||
public static boolean isBaublesLoaded;
|
||||
public static boolean isFluidLoggedLoaded;
|
||||
|
||||
public static void preInit() {
|
||||
if (Loader.isModLoaded("backhand")) {
|
||||
isBackhandLoaded = BackhandReflectionCompat.isBackhandLoaded();
|
||||
}
|
||||
|
||||
isBetterCrashesLoaded = Loader.isModLoaded("bettercrashes");
|
||||
isNEIDLoaded = Loader.isModLoaded("neid");
|
||||
isLotrLoaded = Loader.isModLoaded("lotr");
|
||||
isChunkAPILoaded = Loader.isModLoaded("chunkapi");
|
||||
isEIDBiomeLoaded = Loader.isModLoaded("endlessids_biome");
|
||||
isXaerosMinimapLoaded = Loader.isModLoaded("XaeroMinimap");
|
||||
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
|
||||
isBattlegearLoaded = Loader.isModLoaded("battlegear2");
|
||||
isThaumcraftLoaded = Loader.isModLoaded("Thaumcraft");
|
||||
isThaumicHorizonsLoaded = Loader.isModLoaded("ThaumicHorizons");
|
||||
isBaublesLoaded = Loader.isModLoaded("Baubles");
|
||||
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
|
||||
|
||||
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
|
||||
|
||||
// remove compat with original release of BG2
|
||||
if (isBattlegearLoaded){
|
||||
isBattlegearLoaded = new DefaultArtifactVersion("1.2.0")
|
||||
.compareTo(
|
||||
LoadControllerHelper.getOwningMod(Battlegear.class).getProcessedVersion()
|
||||
) <= 0;
|
||||
}
|
||||
|
||||
isNEIDMetadataExtended = false;
|
||||
if (isNEIDLoaded) {
|
||||
final int majorVersion = Integer.parseInt(Loader.instance().getIndexedModList().get("neid").getVersion().split("\\.")[0]);
|
||||
if (majorVersion >= 2) {
|
||||
isNEIDMetadataExtended = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.gtnewhorizons.angelica.compat.backhand;
|
||||
|
||||
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
|
||||
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
|
||||
import cpw.mods.fml.relauncher.ReflectionHelper;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
|
||||
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
|
||||
|
||||
public class BackhandReflectionCompat {
|
||||
private static final String BACKHAND_CLASS = "xonin.backhand.Backhand";
|
||||
private static final String ITEM_RENDERER_HOOKS_CLASS = "xonin.backhand.client.hooks.ItemRendererHooks";
|
||||
private static final String BACKHAND_UTILS_CLASS = "xonin.backhand.api.core.BackhandUtils";
|
||||
|
||||
private static boolean isLoaded;
|
||||
private static final MethodHandle renderOffhandReturn;
|
||||
private static final MethodHandle getOffhandItem;
|
||||
|
||||
static {
|
||||
MethodHandle renderOffhandReturnTemp = null;
|
||||
MethodHandle getOffhandItemTemp = null;
|
||||
|
||||
try {
|
||||
final Class<?> backhandClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), BACKHAND_CLASS);
|
||||
final boolean versionCheck = new DefaultArtifactVersion("1.6.9").compareTo(LoadControllerHelper.getOwningMod(backhandClass).getProcessedVersion()) <= 0;
|
||||
|
||||
if (versionCheck) {
|
||||
final MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
final Class<?> itemRendererHooksClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), ITEM_RENDERER_HOOKS_CLASS);
|
||||
final Class<?> backhandUtilsClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), BACKHAND_UTILS_CLASS);
|
||||
|
||||
renderOffhandReturnTemp = lookup.findStatic(itemRendererHooksClass, "renderOffhandReturn", MethodType.methodType(void.class, float.class));
|
||||
getOffhandItemTemp = lookup.findStatic(backhandUtilsClass, "getOffhandItem", MethodType.methodType(ItemStack.class, EntityPlayer.class));
|
||||
isLoaded = true;
|
||||
LOGGER.info("Backhand compat loaded");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load Backhand compat", e);
|
||||
isLoaded = false;
|
||||
}
|
||||
|
||||
renderOffhandReturn = renderOffhandReturnTemp;
|
||||
getOffhandItem = getOffhandItemTemp;
|
||||
}
|
||||
|
||||
public static boolean isBackhandLoaded() {
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
public static void renderOffhand(float partialTicks) {
|
||||
if (isLoaded && renderOffhandReturn != null) {
|
||||
try {
|
||||
renderOffhandReturn.invokeExact(partialTicks);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemStack getOffhandItem(EntityPlayer player) {
|
||||
if (isLoaded && getOffhandItem != null) {
|
||||
try {
|
||||
return (ItemStack) getOffhandItem.invokeExact(player);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.compat.battlegear2;
|
||||
|
||||
import mods.battlegear2.api.core.IInventoryPlayerBattle;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
/**
|
||||
* This class exists to isolate classes that exist in Battlegear 2 but not
|
||||
* in Battlegear 2 for Backhand in order to avoid class loading exceptions.
|
||||
*/
|
||||
public class Battlegear2Compat {
|
||||
public static ItemStack getBattlegear2Offhand(EntityPlayer player) {
|
||||
return ((IInventoryPlayerBattle) player.inventory).battlegear2$getCurrentOffhandWeapon();
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.compat.bettercrashes;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IRenderGlobalExt;
|
||||
import cpw.mods.fml.common.Optional;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import vfyjxf.bettercrashes.utils.StateManager;
|
||||
|
||||
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
|
||||
|
||||
@Optional.Interface(modid = "angelica", iface = "vfyjxf.bettercrashes.utils.StateManager.IResettable")
|
||||
public class BetterCrashesCompat implements StateManager.IResettable {
|
||||
private static BetterCrashesCompat INSTANCE;
|
||||
|
||||
public static void init() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new BetterCrashesCompat();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public BetterCrashesCompat() {
|
||||
LOGGER.info("BetterCrashesCompat initialized");
|
||||
this.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetState() {
|
||||
LOGGER.info("Reloading SodiumRenderer");
|
||||
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).angelica$reload();
|
||||
LOGGER.info("Resetting GLStateManager");
|
||||
GLStateManager.reset();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.gtnewhorizons.angelica.compat.endlessids;
|
||||
|
||||
import com.falsepattern.endlessids.mixin.helpers.ChunkBiomeHook;
|
||||
import lombok.val;
|
||||
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
import static com.falsepattern.endlessids.constants.ExtendedConstants.biomeIDMask;
|
||||
import static com.falsepattern.endlessids.constants.ExtendedConstants.biomeIDNull;
|
||||
|
||||
public class EndlessIDsCompat {
|
||||
public static void sodium$populateBiomes(Chunk chunk) {
|
||||
if (!(chunk instanceof ChunkBiomeHook hook))
|
||||
return;
|
||||
|
||||
val biomes = hook.getBiomeShortArray();
|
||||
if (biomes == null)
|
||||
return;
|
||||
|
||||
val cX = chunk.xPosition << 4;
|
||||
val cZ = chunk.zPosition << 4;
|
||||
|
||||
val manager = chunk.worldObj.getWorldChunkManager();
|
||||
for(var z = 0; z < 16; z++) {
|
||||
for(var x = 0; x < 16; x++) {
|
||||
val idx = (z << 4) + x;
|
||||
val biome = biomes[idx] & biomeIDMask;
|
||||
if(biome == biomeIDNull) {
|
||||
val generated = manager.getBiomeGenAt(cX + x, cZ + z);
|
||||
if (generated == null)
|
||||
continue;
|
||||
biomes[idx] = (short)(generated.biomeID & biomeIDMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package com.gtnewhorizons.angelica.compat.holoinventory;
|
||||
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
|
||||
|
||||
public class HoloInventoryReflectionCompat {
|
||||
private static final String RENDERER_CLASS = "net.dries007.holoInventory.client.Renderer";
|
||||
|
||||
private static boolean isLoaded;
|
||||
private static final MethodHandle renderEventPostMethod;
|
||||
private static final MethodHandle renderEventMethod;
|
||||
private static final MethodHandle angelicaOverrideSetter;
|
||||
|
||||
static {
|
||||
MethodHandle renderEventPostMethodTemp = null;
|
||||
MethodHandle renderEventMethodTemp = null;
|
||||
MethodHandle angelicaOverrideSetterTemp = null;
|
||||
final Object rendererInstance;
|
||||
final Class<?> rendererClass;
|
||||
|
||||
try {
|
||||
rendererClass = Class.forName(RENDERER_CLASS);
|
||||
|
||||
// Get and cache the Renderer.INSTANCE
|
||||
final Field instanceField = rendererClass.getDeclaredField("INSTANCE");
|
||||
instanceField.setAccessible(true);
|
||||
rendererInstance = instanceField.get(null);
|
||||
|
||||
final MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
try {
|
||||
renderEventPostMethodTemp = lookup.findVirtual(rendererClass, "renderEvent", MethodType.methodType(void.class, RenderGameOverlayEvent.Post.class));
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Fallback for 2.5.0-GTNH and earlier
|
||||
renderEventMethodTemp = lookup.findVirtual(rendererClass, "renderEvent", MethodType.methodType(void.class, RenderGameOverlayEvent.class));
|
||||
}
|
||||
|
||||
final Field angelicaOverrideField = rendererClass.getDeclaredField("angelicaOverride");
|
||||
angelicaOverrideField.setAccessible(true);
|
||||
angelicaOverrideSetterTemp = lookup.unreflectSetter(angelicaOverrideField);
|
||||
|
||||
// Bind the method handles to the instance
|
||||
if (rendererInstance != null) {
|
||||
if (renderEventPostMethodTemp != null) {
|
||||
renderEventPostMethodTemp = renderEventPostMethodTemp.bindTo(rendererInstance);
|
||||
}
|
||||
if (renderEventMethodTemp != null) {
|
||||
renderEventMethodTemp = renderEventMethodTemp.bindTo(rendererInstance);
|
||||
}
|
||||
angelicaOverrideSetterTemp = angelicaOverrideSetterTemp.bindTo(rendererInstance);
|
||||
}
|
||||
|
||||
isLoaded = true;
|
||||
LOGGER.info("Successfully initialized HoloInventory compatibility layer");
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Failed to initialize HoloInventory compatibility layer", e);
|
||||
isLoaded = false;
|
||||
}
|
||||
|
||||
renderEventPostMethod = renderEventPostMethodTemp;
|
||||
renderEventMethod = renderEventMethodTemp;
|
||||
angelicaOverrideSetter = angelicaOverrideSetterTemp;
|
||||
}
|
||||
|
||||
public static void renderEvent(RenderGameOverlayEvent event) {
|
||||
if (!isLoaded) return;
|
||||
try {
|
||||
if (event instanceof RenderGameOverlayEvent.Post && renderEventPostMethod != null) {
|
||||
renderEventPostMethod.invokeExact((RenderGameOverlayEvent.Post) event);
|
||||
} else {
|
||||
renderEventMethod.invokeExact(event);
|
||||
}
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setAngelicaOverride(boolean value) {
|
||||
if (!isLoaded) return;
|
||||
try {
|
||||
angelicaOverrideSetter.invokeExact(value);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.compat.iris;
|
||||
|
||||
/**
|
||||
* Interface implemented by BiomeGenBase via mixin to cache biome category.
|
||||
* This avoids recomputing the category every frame for shader uniforms.
|
||||
*/
|
||||
public interface BiomeCategoryCache {
|
||||
/**
|
||||
* Gets the cached biome category ordinal, or -1 if not yet computed.
|
||||
*/
|
||||
int iris$getCachedCategory();
|
||||
|
||||
/**
|
||||
* Sets the cached biome category ordinal.
|
||||
*/
|
||||
void iris$setCachedCategory(int category);
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package com.gtnewhorizons.angelica.compat.iris;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import net.coderbot.iris.parsing.BiomeCategories;
|
||||
import net.minecraft.world.biome.BiomeGenBase;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Detects and categorizes biomes from popular modded biome mods using cached class checks.
|
||||
* Currently supports: BiomesOPlenty (BOP), Realistic World Gen (RWG), lotr (b36.15)
|
||||
*/
|
||||
public class ModdedBiomeDetector {
|
||||
private static boolean initialized = false;
|
||||
|
||||
// BiomesOPlenty (BOP) cache - dimension-specific base classes
|
||||
// BOP 1.7.10 hierarchy: BOPBiome -> BOPEndBiome/BOPNetherBiome/BOPOverworldBiome -> specific biomes
|
||||
private static Class<?> bopEndBiomeClass = null;
|
||||
private static Class<?> bopNetherBiomeClass = null;
|
||||
private static Class<?> bopOceanBiomeClass = null;
|
||||
|
||||
// Realistic World Gen (RWG) cache - base biome classes that extend BiomeGenBase
|
||||
// LinkedHashMap maintains insertion order for deterministic behavior
|
||||
private static final Map<Class<?>, BiomeCategories> rwgClassMap = new LinkedHashMap<>();
|
||||
|
||||
private static Class<?> lotrBiomeClass = null;
|
||||
|
||||
private static synchronized void initialize() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
initializeBOP();
|
||||
initializeRWG();
|
||||
initializeLOTR();
|
||||
}
|
||||
|
||||
private static void initializeBOP() {
|
||||
try {
|
||||
bopEndBiomeClass = Class.forName("biomesoplenty.common.biome.BOPEndBiome");
|
||||
bopNetherBiomeClass = Class.forName("biomesoplenty.common.biome.BOPNetherBiome");
|
||||
bopOceanBiomeClass = Class.forName("biomesoplenty.common.biome.BOPOceanBiome");
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// BOP not installed
|
||||
}
|
||||
}
|
||||
|
||||
private static void initializeRWG() {
|
||||
// RWG adds actual BiomeGenBase subclasses in rwg.biomes.base package
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeOcean", BiomeCategories.OCEAN);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeRiver", BiomeCategories.RIVER);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomePlains", BiomeCategories.PLAINS);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeColdPlains", BiomeCategories.PLAINS);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotPlains", BiomeCategories.PLAINS);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotDesert", BiomeCategories.DESERT);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeSnowDesert", BiomeCategories.DESERT); // Cold desert, but still desert
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeJungle", BiomeCategories.JUNGLE);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeTropicalIsland", BiomeCategories.JUNGLE); // Tropical islands are jungle-like
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeRedwood", BiomeCategories.FOREST); // Redwood/Sequoia forests
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeTemperateForest", BiomeCategories.FOREST);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeColdForest", BiomeCategories.TAIGA); // Cold forests are taiga-like
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotForest", BiomeCategories.FOREST);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeSnowForest", BiomeCategories.TAIGA); // Snow forests are taiga
|
||||
}
|
||||
|
||||
private static void initializeLOTR() {
|
||||
if (ModStatus.isLotrLoaded) {
|
||||
try {
|
||||
lotrBiomeClass = Class.forName("lotr.common.world.biome.LOTRBiome");
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// lotr not installed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void tryAddRWGClass(String className, BiomeCategories category) {
|
||||
try {
|
||||
final Class<?> clazz = Class.forName(className);
|
||||
rwgClassMap.put(clazz, category);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// Class not available, RWG not installed or different version
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to detect the biome category for modded biomes.
|
||||
* Returns null if no modded biome detection applies.
|
||||
*/
|
||||
public static BiomeCategories detectModdedBiome(BiomeGenBase biome) {
|
||||
if (!initialized) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
// BiomesOPlenty detection - dimension-specific classes give us clear categories
|
||||
if (bopEndBiomeClass != null && bopEndBiomeClass.isInstance(biome)) {
|
||||
return BiomeCategories.THE_END;
|
||||
}
|
||||
if (bopNetherBiomeClass != null && bopNetherBiomeClass.isInstance(biome)) {
|
||||
return BiomeCategories.NETHER;
|
||||
}
|
||||
if (bopOceanBiomeClass != null && bopOceanBiomeClass.isInstance(biome)) {
|
||||
return BiomeCategories.OCEAN;
|
||||
}
|
||||
// For other BOP overworld biomes (e.g., Alps, Arctic, BambooForest),
|
||||
// fall through to name/property-based detection
|
||||
|
||||
// Realistic World Gen detection
|
||||
if (biome.getBiomeClass().getName().contains("rwg.biomes")) {
|
||||
for (Map.Entry<Class<?>, BiomeCategories> entry : rwgClassMap.entrySet()) {
|
||||
if (entry.getKey().isInstance(biome)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lotr
|
||||
if (lotrBiomeClass!= null && lotrBiomeClass.isInstance(biome)) {
|
||||
return detectLOTRBiome(biome);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static BiomeCategories detectLOTRBiome(BiomeGenBase biome) {
|
||||
return switch (biome.biomeName) {
|
||||
case "river", "farHaradJungleLake", "lake" -> BiomeCategories.RIVER;
|
||||
case "rohan", "rivendell", "rhunIsland", "rhunLandHills", "rhunLandSteppe", "rhunLand", "andrast",
|
||||
"blackrootVale", "lamedon", "imlothMelui", "towerHills", "dorwinion", "dale", "shireMoors", "wold",
|
||||
"anduinVale", "rhun", "lebennin", "pertorogwaith", "breeland", "island", "pinnathGelin",
|
||||
"minhiriath", "pelennor", "adornland", "nanCurunir", "ithilienWasteland", "fangornClearing",
|
||||
"barrowDowns", "dorEnErnilHills", "dorEnErnil", "nurn", "wilderland", "eastBight", "lindon",
|
||||
"eregion", "enedwaith", "meneltarma", "anduinHills", "eriador", "angle", "loneLandsHills",
|
||||
"loneLands", "pelargir", "rohanUrukHighlands", "celebrant", "gondor", "shire", "nurnen", "farHarad" ->
|
||||
BiomeCategories.PLAINS;
|
||||
case "mistyMountains", "windMountains", "farHaradVolcano", "redMountains", "haradMountains",
|
||||
"angmarMountains", "blueMountains", "greyMountains", "whiteMountains", "mordorMountains" ->
|
||||
BiomeCategories.MOUNTAIN;
|
||||
case "shireWoodlands", "rhunIslandForest", "rhunRedForest", "pukel", "lossarnach", "gulfHaradForest",
|
||||
"nearHaradFertileForest", "dolGuldur", "rhunForest", "tauredainClearing", "umbarForest",
|
||||
"farHaradForest", "chetwood", "woodlandRealmHills", "mirkwoodNorth", "whiteDowns", "ithilienHills",
|
||||
"gondorWoodlands", "rohanWoodlands", "fangornWasteland", "mirkwoodMountains", "lindonWoodlands",
|
||||
"lothlorienEdge", "erynVorn", "oldForest", "fangorn", "dunland", "ithilien", "mirkwoodCorrupted",
|
||||
"woodlandRealm", "trollshaws", "lothlorien", "farHaradKanuka" -> BiomeCategories.FOREST;
|
||||
case "mordor", "lastDesert", "harnedor", "easternDesolation", "morgulVale", "gorgoroth", "udun",
|
||||
"nearHaradRedDesert", "nearHaradSemiDesert", "lostladen",
|
||||
"nearHaradHills", "nearHarad", "nanUngol", "dagorlad", "brownLands" -> BiomeCategories.DESERT;
|
||||
case "ironHills", "rivendellHills", "windMountainsFoothills", "lamedonHills", "redMountainsFoothills",
|
||||
"tolfalas", "dorwinionHills", "whiteMountainsFoothills", "blueMountainsFoothills",
|
||||
"greyMountainsFoothills", "mistyMountainsFoothills", "erebor", "angmar", "eriadorDowns",
|
||||
"ettenmoors", "emynMuil" -> BiomeCategories.EXTREME_HILLS;
|
||||
case "deadMarshes", "shireMarshes", "farHaradMangrove", "farHaradSwamp", "swanfleet", "nindalf",
|
||||
"longMarshes", "entwashMouth", "nurnMarshes", "gladdenFields", "midgewater", "anduinMouth" ->
|
||||
BiomeCategories.SWAMP;
|
||||
case "harondor", "nearHaradOasis", "nearHaradRiverbank", "farHaradBushlandHills", "farHaradBushland",
|
||||
"gulfHarad", "nearHaradFertile", "umbarHills", "umbar", "farHaradAridHills", "farHaradArid" ->
|
||||
BiomeCategories.SAVANNA;
|
||||
case "ocean" -> BiomeCategories.OCEAN;
|
||||
case "forodwaith", "forodwaithGlacier", "tundra", "forodwaithMountains" -> BiomeCategories.ICY;
|
||||
case "lindonCoast", "beachWhite", "farHaradCoast", "forodwaithCoast", "beachGravel", "beach" ->
|
||||
BiomeCategories.BEACH;
|
||||
case "coldfells", "wilderlandNorth", "taiga" -> BiomeCategories.TAIGA;
|
||||
case "farHaradJungle", "halfTrollForest", "farHaradJungleMountains",
|
||||
"farHaradCloudForest", "farHaradJungleEdge" -> BiomeCategories.JUNGLE;
|
||||
case "utumno" -> BiomeCategories.NONE;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.lwjgl;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class CompatMemoryUtil {
|
||||
public static ByteBuffer memReallocDirect(ByteBuffer old, int capacity) {
|
||||
ByteBuffer newBuf = BufferUtils.createByteBuffer(capacity);
|
||||
int oldPos = old.position();
|
||||
old.rewind();
|
||||
newBuf.put(old);
|
||||
newBuf.position(Math.min(capacity, oldPos));
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
public static IntBuffer memReallocDirect(IntBuffer old, int capacity) {
|
||||
IntBuffer newBuf = BufferUtils.createIntBuffer(capacity);
|
||||
int oldPos = old.position();
|
||||
old.rewind();
|
||||
newBuf.put(old);
|
||||
newBuf.position(Math.min(capacity, oldPos));
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
public static FloatBuffer memReallocDirect(FloatBuffer old, int capacity) {
|
||||
FloatBuffer newBuf = BufferUtils.createFloatBuffer(capacity);
|
||||
int oldPos = old.position();
|
||||
old.rewind();
|
||||
newBuf.put(old);
|
||||
newBuf.position(Math.min(capacity, oldPos));
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backported from LWJGL3 under the BSD 3-Clause "New" or "Revised" License license
|
||||
*
|
||||
* <p>This class provides functionality for managing native memory.
|
||||
*
|
||||
* <p>All methods in this class will make use of {@link sun.misc.Unsafe} if it's available, for performance. If Unsafe is not available, the fallback
|
||||
* implementations make use of reflection and, in the worst-case, JNI.</p>
|
||||
*
|
||||
* <p>Method names in this class are prefixed with {@code mem} to avoid ambiguities when used with static imports.</p>
|
||||
*/
|
||||
|
||||
static final sun.misc.Unsafe UNSAFE;
|
||||
|
||||
static {
|
||||
UNSAFE = getUnsafeInstance();
|
||||
}
|
||||
|
||||
private static sun.misc.Unsafe getUnsafeInstance() {
|
||||
java.lang.reflect.Field[] fields = sun.misc.Unsafe.class.getDeclaredFields();
|
||||
|
||||
/*
|
||||
Different runtimes use different names for the Unsafe singleton,
|
||||
so we cannot use .getDeclaredField and we scan instead. For example:
|
||||
|
||||
Oracle: theUnsafe
|
||||
PERC : m_unsafe_instance
|
||||
Android: THE_ONE
|
||||
*/
|
||||
for (java.lang.reflect.Field field : fields) {
|
||||
if (!field.getType().equals(sun.misc.Unsafe.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int modifiers = field.getModifiers();
|
||||
if (!(java.lang.reflect.Modifier.isStatic(modifiers) && java.lang.reflect.Modifier.isFinal(modifiers))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
return (sun.misc.Unsafe)field.get(null);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("LWJGL requires sun.misc.Unsafe to be available.");
|
||||
}
|
||||
|
||||
|
||||
public static void memPutByte(long ptr, byte value) { UNSAFE.putByte(null, ptr, value); }
|
||||
public static void memPutShort(long ptr, short value) { UNSAFE.putShort(null, ptr, value); }
|
||||
public static void memPutInt(long ptr, int value) { UNSAFE.putInt(null, ptr, value); }
|
||||
public static void memPutLong(long ptr, long value) { UNSAFE.putLong(null, ptr, value); }
|
||||
public static void memPutFloat(long ptr, float value) { UNSAFE.putFloat(null, ptr, value); }
|
||||
public static void memPutDouble(long ptr, double value) { UNSAFE.putDouble(null, ptr, value); }
|
||||
|
||||
public static boolean memGetBoolean(long ptr) { return UNSAFE.getByte(null, ptr) != 0; }
|
||||
public static byte memGetByte(long ptr) { return UNSAFE.getByte(null, ptr); }
|
||||
public static short memGetShort(long ptr) { return UNSAFE.getShort(null, ptr); }
|
||||
public static int memGetInt(long ptr) { return UNSAFE.getInt(null, ptr); }
|
||||
public static long memGetLong(long ptr) { return UNSAFE.getLong(null, ptr); }
|
||||
public static float memGetFloat(long ptr) { return UNSAFE.getFloat(null, ptr); }
|
||||
public static double memGetDouble(long ptr) { return UNSAFE.getDouble(null, ptr); }
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadFacing;
|
||||
|
||||
public enum Axis {
|
||||
X,
|
||||
Y,
|
||||
Z;
|
||||
|
||||
public static Axis fromDirection(ModelQuadFacing dir) {
|
||||
return switch (dir) {
|
||||
case DOWN, UP -> Y;
|
||||
case NORTH, SOUTH -> Z;
|
||||
case WEST, EAST -> X;
|
||||
default -> null;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.world.ChunkPosition;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import static com.google.common.math.IntMath.log2;
|
||||
|
||||
// Should we keep this?
|
||||
public class BlockPos extends Vector3i {
|
||||
|
||||
private static final int SIZE_BITS_X = 1 + log2(MathHelper.roundUpToPowerOfTwo(30000000), RoundingMode.UNNECESSARY);
|
||||
private static final int SIZE_BITS_Z = SIZE_BITS_X;
|
||||
private static final int SIZE_BITS_Y = 64 - SIZE_BITS_X - SIZE_BITS_Z;
|
||||
private static final long BITS_X = (1L << SIZE_BITS_X) - 1L;
|
||||
private static final long BITS_Y = (1L << SIZE_BITS_Y) - 1L;
|
||||
private static final long BITS_Z = (1L << SIZE_BITS_Z) - 1L;
|
||||
private static final int BIT_SHIFT_Z = SIZE_BITS_Y;
|
||||
private static final int BIT_SHIFT_X = SIZE_BITS_Y + SIZE_BITS_Z;
|
||||
|
||||
public BlockPos() {
|
||||
super();
|
||||
}
|
||||
public BlockPos(int x, int y, int z) {
|
||||
super(x, y, z);
|
||||
}
|
||||
|
||||
public BlockPos(ChunkPosition chunkPosition) {
|
||||
super(chunkPosition.chunkPosX, chunkPosition.chunkPosY, chunkPosition.chunkPosZ);
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return this.x;
|
||||
}
|
||||
public int getY() {
|
||||
return this.y;
|
||||
}
|
||||
public int getZ() {
|
||||
return this.z;
|
||||
}
|
||||
|
||||
public BlockPos set(int x, int y, int z) {
|
||||
super.set(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method does NOT mutate the BlockPos
|
||||
*/
|
||||
public BlockPos offset(ForgeDirection d) {
|
||||
return new BlockPos(this.x + d.offsetX, this.y + d.offsetY, this.z + d.offsetZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method does NOT mutate the BlockPos
|
||||
*/
|
||||
public BlockPos down() {
|
||||
return offset(ForgeDirection.DOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method does NOT mutate the BlockPos
|
||||
*/
|
||||
public BlockPos up() {
|
||||
return offset(ForgeDirection.UP);
|
||||
}
|
||||
|
||||
public long asLong() {
|
||||
return asLong(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
public static long asLong(int x, int y, int z) {
|
||||
long l = 0L;
|
||||
l |= ((long)x & BITS_X) << BIT_SHIFT_X;
|
||||
l |= ((long)y & BITS_Y) << 0;
|
||||
l |= ((long)z & BITS_Z) << BIT_SHIFT_Z;
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
public static int unpackLongX(long packedPos) {
|
||||
return (int)(packedPos << 64 - BIT_SHIFT_X - SIZE_BITS_X >> 64 - SIZE_BITS_X);
|
||||
}
|
||||
|
||||
public static int unpackLongY(long packedPos) {
|
||||
return (int)(packedPos << 64 - SIZE_BITS_Y >> 64 - SIZE_BITS_Y);
|
||||
}
|
||||
|
||||
public static int unpackLongZ(long packedPos) {
|
||||
return (int)(packedPos << 64 - BIT_SHIFT_Z - SIZE_BITS_Z >> 64 - SIZE_BITS_Z);
|
||||
}
|
||||
|
||||
public static class Mutable extends BlockPos {
|
||||
|
||||
}
|
||||
}
|
||||
+1
@@ -15,6 +15,7 @@ public class ByteBufferBackedInputStream extends InputStream {
|
||||
this.buf = buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (!buf.hasRemaining()) {
|
||||
return -1;
|
||||
|
||||
@@ -1,40 +1,73 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderingState;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.MathHelper;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector4f;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
/**
|
||||
* Singleton camera instance that tracks the current camera position and orientation.
|
||||
*/
|
||||
@Getter
|
||||
public class Camera {
|
||||
final Vector3d pos = new Vector3d();
|
||||
final BlockPos.Mutable blockPos = new BlockPos.Mutable();
|
||||
float pitch;
|
||||
float yaw;
|
||||
EntityLivingBase entity;
|
||||
boolean thirdPerson;
|
||||
final float partialTicks;
|
||||
public static final Camera INSTANCE = new Camera();
|
||||
|
||||
public Camera(EntityLivingBase entity, float partialTicks) {
|
||||
this.partialTicks = partialTicks;
|
||||
final Vector4f offset = new Vector4f(); // third person offset
|
||||
final Matrix4f inverseModelView = new Matrix4f(RenderingState.INSTANCE.getModelViewMatrix()).invert();
|
||||
inverseModelView.transform(offset);
|
||||
private final Vector3d pos = new Vector3d();
|
||||
private final Vector3d entityPos = new Vector3d();
|
||||
private final BlockPos blockPos = new BlockPos();
|
||||
private final Vector3f offset = new Vector3f();
|
||||
private final Matrix4f inverseModelView = new Matrix4f();
|
||||
|
||||
final double camX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks + offset.x;
|
||||
final double camY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks + offset.y;
|
||||
final double camZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks + offset.z;
|
||||
this.entity = entity;
|
||||
|
||||
pos.set(camX, camY, camZ);
|
||||
blockPos.set((int)entity.posX, (int)entity.posY, (int)entity.posZ);
|
||||
pitch = entity.cameraPitch;
|
||||
yaw = entity.rotationYaw;
|
||||
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView == 1;
|
||||
private float pitch;
|
||||
private float yaw;
|
||||
private EntityLivingBase entity;
|
||||
private boolean thirdPerson;
|
||||
private float partialTicks;
|
||||
|
||||
private Camera() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the camera position and orientation based on the current entity and partial ticks.
|
||||
*
|
||||
* @param entity The entity to track (usually the render view entity)
|
||||
* @param partialTicks The partial tick time for interpolation
|
||||
*/
|
||||
public void update(EntityLivingBase entity, float partialTicks) {
|
||||
this.partialTicks = partialTicks;
|
||||
this.entity = entity;
|
||||
|
||||
// Entity position (interpolated, at eye level)
|
||||
final double entityX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks;
|
||||
final double entityY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks;
|
||||
final double entityZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks;
|
||||
final double eyeHeight = entity.getEyeHeight();
|
||||
|
||||
entityPos.set(entityX, entityY + eyeHeight, entityZ);
|
||||
|
||||
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView > 0;
|
||||
|
||||
final double camX, camY, camZ;
|
||||
offset.set(0, 0, 0);
|
||||
|
||||
if (thirdPerson) {
|
||||
// Third person: use inverse modelview to find camera offset
|
||||
inverseModelView.set(RenderingState.INSTANCE.getModelViewMatrix()).invert();
|
||||
inverseModelView.transformPosition(offset);
|
||||
}
|
||||
camX = entityX + offset.x;
|
||||
camY = entityY + eyeHeight + offset.y;
|
||||
camZ = entityZ + offset.z;
|
||||
|
||||
pos.set(camX, camY, camZ);
|
||||
blockPos.set(MathHelper.floor_double(camX), MathHelper.floor_double(camY), MathHelper.floor_double(camZ));
|
||||
pitch = entity.cameraPitch;
|
||||
yaw = entity.rotationYaw;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
|
||||
import it.unimi.dsi.fastutil.ints.IntPriorityQueue;
|
||||
import me.jellysquid.mods.sodium.common.util.DirectionUtil;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import java.util.BitSet;
|
||||
@@ -17,7 +17,7 @@ public class ChunkOcclusionDataBuilder {
|
||||
private static final int STEP_X = (int)Math.pow(16.0, 0.0);
|
||||
private static final int STEP_Z = (int)Math.pow(16.0, 1.0);
|
||||
private static final int STEP_Y = (int)Math.pow(16.0, 2.0);
|
||||
private static final ForgeDirection[] DIRECTIONS = DirectionUtil.ALL_DIRECTIONS;
|
||||
private static final ForgeDirection[] DIRECTIONS = ForgeDirection.VALID_DIRECTIONS;
|
||||
private final BitSet closed = new BitSet(4096);
|
||||
private static final int[] EDGE_POINTS = new int[1352];
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
|
||||
// See if we can merge/mixin/extend ChunkCoordIntPair?
|
||||
public class ChunkPos {
|
||||
public static long INT_MASK = (1L << Integer.SIZE) - 1;
|
||||
@@ -38,6 +40,7 @@ public class ChunkPos {
|
||||
return (long)x & 4294967295L | ((long)z & 4294967295L) << 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int i = 1664525 * this.x + 1013904223;
|
||||
@@ -45,6 +48,7 @@ public class ChunkPos {
|
||||
return i ^ j;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
// See if we can merge/mixin/extend ChunkPosition maybe?
|
||||
@@ -35,10 +36,10 @@ public class ChunkSectionPos extends Vector3i {
|
||||
return coord & 15;
|
||||
}
|
||||
|
||||
public static short packLocal(BlockPos pos) {
|
||||
int i = getLocalCoord(pos.x);
|
||||
int j = getLocalCoord(pos.y);
|
||||
int k = getLocalCoord(pos.z);
|
||||
public static short packLocal(int x, int y, int z) {
|
||||
final int i = getLocalCoord(x);
|
||||
final int j = getLocalCoord(y);
|
||||
final int k = getLocalCoord(z);
|
||||
return (short)(i << 8 | k << 4 | j << 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
public interface Drawable {
|
||||
default void render(int mouseX, int mouseY, float delta) {}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
public interface Element {
|
||||
default boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean mouseDragged(double mouseX, double mouseY, int button) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean mouseReleased(double mouseX, double mouseY, int button) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean mouseScrolled(double mouseX, double mouseY, double amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean keyTyped(char typedChar, int keyCode) { return false; }
|
||||
|
||||
default boolean isMouseOver(double mouseX, double mouseY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean changeFocus(boolean lookForwards) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.PlayerControllerMP;
|
||||
|
||||
public final class GameModeUtil {
|
||||
private GameModeUtil() {
|
||||
}
|
||||
|
||||
public static boolean isSpectator() {
|
||||
final PlayerControllerMP controller = Minecraft.getMinecraft().playerController;
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
return controller.currentGameType.getID() == 3;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,20 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
public enum InteractionHand {
|
||||
MAIN_HAND,
|
||||
OFF_HAND;
|
||||
|
||||
InteractionHand() {
|
||||
public ItemStack getItemInHand(EntityPlayer player) {
|
||||
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND) { // off hand (requires backhand)
|
||||
return BackhandReflectionCompat.getOffhandItem(player);
|
||||
} else { // main hand
|
||||
return player.getHeldItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.Iris;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.Iris;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
// TBD
|
||||
public class NativeImage extends BufferedImage {
|
||||
@@ -59,13 +59,20 @@ public class NativeImage extends BufferedImage {
|
||||
|
||||
// final int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_WIDTH);
|
||||
// final int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_HEIGHT);
|
||||
IntBuffer buffer = BufferUtils.createIntBuffer(size);
|
||||
int[] data = new int[size];
|
||||
|
||||
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, level, format.glFormat, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
|
||||
buffer.get(data);
|
||||
setRGB(0, 0, width, height, data, 0, width);
|
||||
final IntBuffer buffer = MemoryUtilities.memAllocInt(size);
|
||||
|
||||
try {
|
||||
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, level, format.glFormat, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
|
||||
|
||||
int[] data = new int[size];
|
||||
buffer.get(data);
|
||||
setRGB(0, 0, width, height, data, 0, width);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeToFile(File file) throws IOException{
|
||||
try {
|
||||
ImageIO.write(this, "png", file);
|
||||
@@ -103,18 +110,15 @@ public class NativeImage extends BufferedImage {
|
||||
|
||||
|
||||
public enum Format {
|
||||
RGBA(4, GL11.GL_RGBA, BufferedImage.TYPE_INT_ARGB),
|
||||
RGB(3, GL11.GL_RGB, BufferedImage.TYPE_INT_RGB);
|
||||
RGBA(4, GL11.GL_RGBA),
|
||||
RGB(3, GL11.GL_RGB);
|
||||
|
||||
private final int components;
|
||||
private final int glFormat;
|
||||
private final int imageType;
|
||||
|
||||
|
||||
Format(int components, int glFormat, int imageType) {
|
||||
Format(int components, int glFormat) {
|
||||
this.components = components;
|
||||
this.glFormat = glFormat;
|
||||
this.imageType = imageType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface ParentElement extends Element {
|
||||
List<? extends Element> children();
|
||||
|
||||
default Optional<Element> hoveredElement(double mouseX, double mouseY) {
|
||||
for(Element element : this.children()) {
|
||||
if (element.isMouseOver(mouseX, mouseY)) {
|
||||
return Optional.of(element);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
for(Element element : this.children()) {
|
||||
if (element.mouseClicked(mouseX, mouseY, button)) {
|
||||
this.setFocused(element);
|
||||
if (button == 0) {
|
||||
this.setDragging(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean mouseReleased(double mouseX, double mouseY, int button) {
|
||||
this.setDragging(false);
|
||||
return this.hoveredElement(mouseX, mouseY).filter((element) -> element.mouseReleased(mouseX, mouseY, button)).isPresent();
|
||||
}
|
||||
|
||||
default boolean mouseDragged(double mouseX, double mouseY, int button) {
|
||||
return this.getFocused() != null && this.isDragging() && button == 0 && this.getFocused().mouseDragged(mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
boolean isDragging();
|
||||
|
||||
void setDragging(boolean dragging);
|
||||
|
||||
default boolean mouseScrolled(double mouseX, double mouseY, double amount) {
|
||||
return this.hoveredElement(mouseX, mouseY).filter((element) -> element.mouseScrolled(mouseX, mouseY, amount)).isPresent();
|
||||
}
|
||||
|
||||
default boolean keyTyped(char typedChar, int keyCode) {
|
||||
return this.getFocused() != null && this.getFocused().keyTyped(typedChar, keyCode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Element getFocused();
|
||||
|
||||
void setFocused(@Nullable Element focused);
|
||||
|
||||
default boolean changeFocus(boolean lookForwards) {
|
||||
final Element element = this.getFocused();
|
||||
if (element != null && element.changeFocus(lookForwards)) {
|
||||
return true;
|
||||
} else {
|
||||
final List<? extends Element> list = this.children();
|
||||
final int i = list.indexOf(element);
|
||||
final int j;
|
||||
if (element != null && i >= 0) {
|
||||
j = i + (lookForwards ? 1 : 0);
|
||||
} else if (lookForwards) {
|
||||
j = 0;
|
||||
} else {
|
||||
j = list.size();
|
||||
}
|
||||
|
||||
|
||||
final ListIterator<? extends Element> listIterator = list.listIterator(j);
|
||||
final BooleanSupplier booleanSupplier = lookForwards ? listIterator::hasNext : listIterator::hasPrevious;
|
||||
final Supplier<? extends Element> supplier = lookForwards ? listIterator::next : listIterator::previous;
|
||||
|
||||
Element element2;
|
||||
do {
|
||||
if (!booleanSupplier.getAsBoolean()) {
|
||||
this.setFocused(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
element2 = supplier.get();
|
||||
} while(!element2.changeFocus(lookForwards));
|
||||
|
||||
this.setFocused(element2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
|
||||
public class VertexBuffer implements AutoCloseable {
|
||||
private int id;
|
||||
private int vertexCount;
|
||||
|
||||
public VertexBuffer() {
|
||||
this.id = GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
public void bind() {
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.id);
|
||||
}
|
||||
|
||||
public void unbind() {
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
public void upload(ByteBuffer buffer, int vertexCount) {
|
||||
if (this.id == -1) return;
|
||||
this.vertexCount = vertexCount;
|
||||
this.bind();
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
|
||||
this.unbind();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (this.id >= 0) {
|
||||
GL15.glDeleteBuffers(this.id);
|
||||
this.id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(FloatBuffer floatBuffer, int mode) {
|
||||
GL11.glPushMatrix();
|
||||
GL11.glLoadIdentity();
|
||||
GL11.glMultMatrix(floatBuffer);
|
||||
GL11.glDrawArrays(mode, 0, this.vertexCount);
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
|
||||
@Getter
|
||||
public class VertexFormatElement {
|
||||
protected final Type type;
|
||||
protected final Usage usage;
|
||||
protected final int index;
|
||||
protected final int count;
|
||||
protected final int byteSize;
|
||||
|
||||
public VertexFormatElement(int index, Type type, Usage usage, int count) {
|
||||
this.index = index;
|
||||
this.type = type;
|
||||
this.usage = usage;
|
||||
this.count = count;
|
||||
this.byteSize = type.getSize() * count;
|
||||
}
|
||||
|
||||
public void setupBufferState(long l, int i) {
|
||||
this.usage.setupBufferState(this.count, this.type.getGlType(), i, l, this.index);
|
||||
}
|
||||
|
||||
public void clearBufferState() {
|
||||
this.usage.clearBufferState(this.index);
|
||||
}
|
||||
|
||||
public enum Usage {
|
||||
POSITION("Position", (i, j, k, l, m) -> {
|
||||
GL11.glVertexPointer(i, j, k, l);
|
||||
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
}, (i) -> GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY)),
|
||||
NORMAL("Normal", (i, j, k, l, m) -> {
|
||||
GL11.glNormalPointer(j, k, l);
|
||||
GL11.glEnableClientState(GL11.GL_NORMAL_ARRAY);
|
||||
}, (i) -> GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY)),
|
||||
COLOR("Vertex Color", (i, j, k, l, m) -> {
|
||||
GL11.glColorPointer(i, j, k, l);
|
||||
GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);
|
||||
}, i -> GL11.glDisableClientState(GL11.GL_COLOR_ARRAY)),
|
||||
UV("UV", (i, j, k, l, m) -> {
|
||||
GL13.glClientActiveTexture(GL13.GL_TEXTURE0 + m);
|
||||
GL11.glTexCoordPointer(i, j, k, l);
|
||||
GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
|
||||
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
}, i -> {
|
||||
GL13.glClientActiveTexture(GL13.GL_TEXTURE0 + i);
|
||||
GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
|
||||
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
}),
|
||||
PADDING("Padding", (i, j, k, l, m) -> {}, i-> {}),
|
||||
GENERIC("Generic", (i, j, k, l, m) -> {
|
||||
GL20.glEnableVertexAttribArray(m);
|
||||
GL20.glVertexAttribPointer(m, i, j, false, k, l);
|
||||
}, GL20::glDisableVertexAttribArray);
|
||||
|
||||
@Getter private final String name;
|
||||
private final SetupState setupState;
|
||||
private final IntConsumer clearState;
|
||||
|
||||
Usage(String name, SetupState setupState, IntConsumer clearState) {
|
||||
this.name = name;
|
||||
this.setupState = setupState;
|
||||
this.clearState = clearState;
|
||||
}
|
||||
|
||||
private void setupBufferState(int i, int j, int k, long l, int m) {
|
||||
this.setupState.setupBufferState(i, j, k, l, m);
|
||||
}
|
||||
|
||||
public void clearBufferState(int i) {
|
||||
this.clearState.accept(i);
|
||||
}
|
||||
|
||||
interface SetupState {
|
||||
void setupBufferState(int i, int j, int k, long l, int m);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public enum Type {
|
||||
FLOAT(4, "Float", GL11.GL_FLOAT),
|
||||
UBYTE(1, "Unsigned Byte", GL11.GL_UNSIGNED_BYTE),
|
||||
BYTE(1, "Byte", GL11.GL_BYTE),
|
||||
USHORT(2, "Unsigned Short", GL11.GL_UNSIGNED_SHORT),
|
||||
SHORT(2, "Short", GL11.GL_SHORT),
|
||||
UINT(4, "Unsigned Int", GL11.GL_UNSIGNED_INT),
|
||||
INT(4, "Int", GL11.GL_INT);
|
||||
|
||||
private final int size;
|
||||
private final String name;
|
||||
private final int glType;
|
||||
|
||||
Type(int size, String name, int glType) {
|
||||
this.size = size;
|
||||
this.name = name;
|
||||
this.glType = glType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
public class BufferWriter {
|
||||
// Adapted from Neodymium
|
||||
|
||||
private final ByteBuffer buf;
|
||||
|
||||
private final FloatBuffer floatBuffer;
|
||||
private final ShortBuffer shortBuffer;
|
||||
private final IntBuffer intBuffer;
|
||||
|
||||
public BufferWriter(ByteBuffer buf) {
|
||||
this.buf = buf;
|
||||
this.floatBuffer = buf.asFloatBuffer();
|
||||
this.shortBuffer = buf.asShortBuffer();
|
||||
this.intBuffer = buf.asIntBuffer();
|
||||
}
|
||||
|
||||
private void incrementPosition(int add) {
|
||||
buf.position(buf.position() + add);
|
||||
floatBuffer.position(buf.position() / 4);
|
||||
shortBuffer.position(buf.position() / 2);
|
||||
intBuffer.position(buf.position() / 4);
|
||||
}
|
||||
|
||||
public void writeFloat(float x) {
|
||||
try {
|
||||
floatBuffer.put(x);
|
||||
|
||||
incrementPosition(4);
|
||||
} catch(Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeInt(int x) {
|
||||
intBuffer.put(x);
|
||||
|
||||
incrementPosition(4);
|
||||
}
|
||||
|
||||
public void writeByte(byte x) {
|
||||
buf.put(x); // this increments the buffer position by 1
|
||||
|
||||
incrementPosition(0);
|
||||
}
|
||||
|
||||
public int position() {
|
||||
return buf.position();
|
||||
}
|
||||
|
||||
public void writeShort(short s) {
|
||||
shortBuffer.put(s);
|
||||
|
||||
incrementPosition(2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.model.quad.ModelQuadView;
|
||||
import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadFacing;
|
||||
import me.jellysquid.mods.sodium.client.render.pipeline.BlockRenderer;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Quad implements ModelQuadView {
|
||||
// Adapted from Neodymium
|
||||
|
||||
private final static int DEFAULT_BRIGHTNESS = 15 << 20 | 15 << 4;
|
||||
private final static int DEFAULT_COLOR = 0xFFFFFFFF;
|
||||
|
||||
public float[] xs = new float[4];
|
||||
public float[] ys = new float[4];
|
||||
public float[] zs = new float[4];
|
||||
public float minX = Float.POSITIVE_INFINITY;
|
||||
public float minY = Float.POSITIVE_INFINITY;
|
||||
public float minZ = Float.POSITIVE_INFINITY;
|
||||
public float maxX = Float.NEGATIVE_INFINITY;
|
||||
public float maxY = Float.NEGATIVE_INFINITY;
|
||||
public float maxZ = Float.NEGATIVE_INFINITY;
|
||||
public float[] us = new float[4];
|
||||
public float[] vs = new float[4];
|
||||
public int[] bs = new int[4];
|
||||
public int[] cs = new int[4];
|
||||
// TODO normals?
|
||||
public boolean deleted;
|
||||
|
||||
public ModelQuadFacing normal;
|
||||
public int offset;
|
||||
public BlockRenderer.Flags flags;
|
||||
|
||||
// Is positive U direction parallel to edge 0-1?
|
||||
public boolean uDirectionIs01;
|
||||
|
||||
public boolean isRectangle;
|
||||
|
||||
// 0: quads glued together on edge 1-2 or 3-0 ("megaquad row length")
|
||||
// 1: quads glued together on edge 0-1 or 2-3 ("megaquad column length")
|
||||
private final int[] quadCountByDirection = {1, 1};
|
||||
|
||||
private final Vector3f vectorA = new Vector3f(), vectorB = new Vector3f(), vectorC = new Vector3f();
|
||||
|
||||
private boolean hasColor;
|
||||
private boolean hasShade;
|
||||
|
||||
public boolean hasColor() {
|
||||
return this.hasColor;
|
||||
}
|
||||
|
||||
public int[] getColors() {
|
||||
return this.cs;
|
||||
}
|
||||
|
||||
public boolean hasShade() {
|
||||
return this.hasShade;
|
||||
}
|
||||
|
||||
public ForgeDirection getFace() {
|
||||
// TODO: Sodium/Quad Facing
|
||||
return ForgeDirection.UP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getX(int idx) {
|
||||
return xs[idx];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getY(int idx) {
|
||||
return ys[idx];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getZ(int idx) {
|
||||
return zs[idx];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor(int idx) {
|
||||
return cs[idx];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTexU(int idx) {
|
||||
return us[idx];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTexV(int idx) {
|
||||
return vs[idx];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFlags() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLight(int idx) {
|
||||
return bs[idx];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNormal(int idx) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColorIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureAtlasSprite rubidium$getSprite() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void read(int[] rawBuffer, int offset, float offsetX, float offsetY, float offsetZ, int drawMode, BlockRenderer.Flags flags) {
|
||||
int vertices = drawMode == GL11.GL_TRIANGLES ? 3 : 4;
|
||||
for(int vi = 0; vi < vertices; vi++) {
|
||||
int i = offset + vi * 8;
|
||||
|
||||
xs[vi] = Float.intBitsToFloat(rawBuffer[i + 0]) + offsetX;
|
||||
ys[vi] = Float.intBitsToFloat(rawBuffer[i + 1]) + offsetY;
|
||||
zs[vi] = Float.intBitsToFloat(rawBuffer[i + 2]) + offsetZ;
|
||||
|
||||
us[vi] = Float.intBitsToFloat(rawBuffer[i + 3]);
|
||||
vs[vi] = Float.intBitsToFloat(rawBuffer[i + 4]);
|
||||
|
||||
bs[vi] = flags.hasBrightness ? rawBuffer[i + 7] : DEFAULT_BRIGHTNESS;
|
||||
cs[vi] = flags.hasColor ? rawBuffer[i + 5] : DEFAULT_COLOR;
|
||||
this.hasColor |= flags.hasColor;
|
||||
this.hasShade |= flags.hasBrightness;
|
||||
|
||||
i += 8;
|
||||
}
|
||||
|
||||
if(vertices == 3) {
|
||||
// Quadrangulate!
|
||||
xs[3] = xs[2];
|
||||
ys[3] = ys[2];
|
||||
zs[3] = zs[2];
|
||||
|
||||
us[3] = us[2];
|
||||
vs[3] = vs[2];
|
||||
|
||||
bs[3] = bs[2];
|
||||
cs[3] = cs[2];
|
||||
}
|
||||
}
|
||||
|
||||
public void setState(int[] rawBuffer, int offset, BlockRenderer.Flags flags, int drawMode, float offsetX, float offsetY, float offsetZ) {
|
||||
resetState();
|
||||
|
||||
read(rawBuffer, offset, offsetX, offsetY, offsetZ, drawMode, flags);
|
||||
|
||||
if(xs[0] == xs[1] && xs[1] == xs[2] && xs[2] == xs[3] && ys[0] == ys[1] && ys[1] == ys[2] && ys[2] == ys[3]) {
|
||||
// ignore empty quads (e.g. alpha pass of EnderIO item conduits)
|
||||
deleted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
uDirectionIs01 = us[0] != us[1];
|
||||
|
||||
updateMinMaxXYZ();
|
||||
updateIsRectangle();
|
||||
|
||||
vectorA.set(xs[1] - xs[0], ys[1] - ys[0], zs[1] - zs[0]);
|
||||
vectorB.set(xs[2] - xs[1], ys[2] - ys[1], zs[2] - zs[1]);
|
||||
vectorA.cross(vectorB, vectorC);
|
||||
|
||||
normal = ModelQuadFacing.fromVector(vectorC);
|
||||
}
|
||||
|
||||
private void resetState() {
|
||||
Arrays.fill(xs, 0);
|
||||
Arrays.fill(ys, 0);
|
||||
Arrays.fill(zs, 0);
|
||||
Arrays.fill(us, 0);
|
||||
Arrays.fill(vs, 0);
|
||||
Arrays.fill(bs, 0);
|
||||
Arrays.fill(cs, 0);
|
||||
|
||||
minX = Float.POSITIVE_INFINITY;
|
||||
minY = Float.POSITIVE_INFINITY;
|
||||
minZ = Float.POSITIVE_INFINITY;
|
||||
maxX = Float.NEGATIVE_INFINITY;
|
||||
maxY = Float.NEGATIVE_INFINITY;
|
||||
maxZ = Float.NEGATIVE_INFINITY;
|
||||
|
||||
deleted = false;
|
||||
normal = null;
|
||||
offset = 0;
|
||||
flags = null;
|
||||
uDirectionIs01 = false;
|
||||
Arrays.fill(quadCountByDirection, 1);
|
||||
}
|
||||
|
||||
public void writeToBuffer(BufferWriter out) throws IOException {
|
||||
for(int vertexI = 0; vertexI < 4; vertexI++) {
|
||||
int vi = vertexI;
|
||||
int provokingI = 3;
|
||||
|
||||
float x = xs[vi];
|
||||
float y = ys[vi];
|
||||
float z = zs[vi];
|
||||
|
||||
out.writeFloat(x);
|
||||
out.writeFloat(y);
|
||||
out.writeFloat(z);
|
||||
|
||||
float u = us[vi];
|
||||
float v = vs[vi];
|
||||
|
||||
out.writeFloat(u);
|
||||
out.writeFloat(v);
|
||||
|
||||
int b = bs[vi];
|
||||
|
||||
out.writeInt(b);
|
||||
|
||||
int c = cs[vi];
|
||||
|
||||
out.writeInt(c);
|
||||
|
||||
assert out.position() % getStride() == 0;
|
||||
|
||||
//System.out.println("[" + vertexI + "] x: " + x + ", y: " + y + " z: " + z + ", u: " + u + ", v: " + v + ", b: " + b + ", c: " + c);
|
||||
}
|
||||
}
|
||||
|
||||
public int quadCountByUVDirection(boolean v) {
|
||||
if(v) {
|
||||
return quadCountByDirection[uDirectionIs01 ? 0 : 1];
|
||||
} else {
|
||||
return quadCountByDirection[uDirectionIs01 ? 1 : 0];
|
||||
}
|
||||
}
|
||||
|
||||
public static int getStride() {
|
||||
return
|
||||
3 * 4 // XYZ (float)
|
||||
+ 2 * (/*Config.shortUV*/false ? 2 : 4) // UV (float)
|
||||
+ 4 // B (int)
|
||||
+ 4 // C (int)
|
||||
;
|
||||
}
|
||||
|
||||
private boolean isTranslatedCopyOf(Quad o, boolean checkValid) {
|
||||
if((!isValid(this) && checkValid) || !isValid(o) || normal != o.normal) return false;
|
||||
|
||||
for(int i = 1; i < 4; i++) {
|
||||
double relX = xs[i] - xs[0];
|
||||
double relY = ys[i] - ys[0];
|
||||
double relZ = zs[i] - zs[0];
|
||||
|
||||
if(o.xs[i] != o.xs[0] + relX || o.ys[i] != o.ys[0] + relY || o.zs[i] != o.zs[0] + relZ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(us[i] != o.us[i] || vs[i] != o.vs[i] || bs[i] != o.bs[i] || cs[i] != o.cs[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void copyVertexFrom(Quad o, int src, int dest) {
|
||||
xs[dest] = o.xs[src];
|
||||
ys[dest] = o.ys[src];
|
||||
zs[dest] = o.zs[src];
|
||||
us[dest] = o.us[src];
|
||||
vs[dest] = o.vs[src];
|
||||
bs[dest] = o.bs[src];
|
||||
cs[dest] = o.cs[src];
|
||||
|
||||
updateMinMaxXYZ(); // TODO isn't doing this a waste? I should get rid of the min/maxXYZ variables entirely.
|
||||
}
|
||||
|
||||
private void updateMinMaxXYZ() {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
minX = Math.min(minX, xs[i]);
|
||||
minY = Math.min(minY, ys[i]);
|
||||
minZ = Math.min(minZ, zs[i]);
|
||||
maxX = Math.max(maxX, xs[i]);
|
||||
maxY = Math.max(maxY, ys[i]);
|
||||
maxZ = Math.max(maxZ, zs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIsRectangle() {
|
||||
isRectangle =
|
||||
vertexExists(minX, minY, minZ) &&
|
||||
vertexExists(minX, minY, maxZ) &&
|
||||
vertexExists(minX, maxY, minZ) &&
|
||||
vertexExists(minX, maxY, maxZ) &&
|
||||
vertexExists(maxX, minY, minZ) &&
|
||||
vertexExists(maxX, minY, maxZ) &&
|
||||
vertexExists(maxX, maxY, minZ) &&
|
||||
vertexExists(maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
private boolean vertexExists(float x, float y, float z) {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(xs[i] == x && ys[i] == y && zs[i] == z) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isValid(Quad q) {
|
||||
return q != null && !q.deleted;
|
||||
}
|
||||
|
||||
public boolean isClockwiseXZ() {
|
||||
return (xs[1] - xs[0]) * (zs[2] - zs[0]) - (xs[2] - xs[0]) * (zs[1] - zs[0]) < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.ENGLISH, "%s(%.1f, %.1f, %.1f -- %.1f, %.1f, %.1f)", deleted ? "XXX " : "", minX, minY, minZ, maxX, maxY, maxZ);
|
||||
//return String.format(Locale.ENGLISH, "%s[(%.1f, %.1f, %.1f), (%.1f, %.1f, %.1f), (%.1f, %.1f, %.1f), (%.1f, %.1f, %.1f)]", deleted ? "XXX " : "", xs[0], ys[0], zs[0], xs[1], ys[1], zs[1], xs[2], ys[2], zs[2], xs[3], ys[3], zs[3]);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class RecyclingList<T> {
|
||||
// Adapted from Neodymium
|
||||
|
||||
private Supplier<T> constructor;
|
||||
|
||||
int nextIndex;
|
||||
private final List<T> list;
|
||||
|
||||
public RecyclingList(Supplier<T> constructor) {
|
||||
this.constructor = constructor;
|
||||
this.list = new ArrayList<>();
|
||||
}
|
||||
|
||||
public T get(int i) {
|
||||
while(list.size() <= i) {
|
||||
list.add(constructor.get());
|
||||
}
|
||||
return list.get(i);
|
||||
}
|
||||
|
||||
public T next() {
|
||||
return get(nextIndex++);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
if(nextIndex == 0) {
|
||||
throw new IllegalStateException("Tried to remove from empty list");
|
||||
}
|
||||
nextIndex--;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return nextIndex == 0;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
nextIndex = 0;
|
||||
}
|
||||
|
||||
public List<T> getAsList() {
|
||||
return list.subList(0, nextIndex);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.toremove;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.VertexFormatElement.Type;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.VertexFormatElement.Usage;
|
||||
|
||||
|
||||
public class DefaultVertexFormat {
|
||||
|
||||
public static final VertexFormatElement POSITION_ELEMENT = new VertexFormatElement(0, Type.FLOAT, Usage.POSITION, 3);
|
||||
public static final VertexFormatElement COLOR_ELEMENT = new VertexFormatElement(0, Type.UBYTE, Usage.COLOR, 4);
|
||||
public static final VertexFormatElement TEXTURE_0_ELEMENT = new VertexFormatElement(0, Type.FLOAT, Usage.UV, 2);
|
||||
public static final VertexFormatElement OVERLAY_ELEMENT = new VertexFormatElement(1, Type.SHORT, Usage.UV, 2);
|
||||
public static final VertexFormatElement LIGHT_ELEMENT = new VertexFormatElement(2, Type.SHORT, Usage.UV, 2);
|
||||
public static final VertexFormatElement NORMAL_ELEMENT = new VertexFormatElement(0, Type.BYTE, Usage.NORMAL, 3);
|
||||
public static final VertexFormatElement PADDING_ELEMENT = new VertexFormatElement(0, Type.BYTE, Usage.PADDING, 1);
|
||||
public static final VertexFormat POSITION_COLOR_TEXTURE_LIGHT_NORMAL = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).add(TEXTURE_0_ELEMENT).add(LIGHT_ELEMENT).add(NORMAL_ELEMENT).add(PADDING_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).add(TEXTURE_0_ELEMENT).add(OVERLAY_ELEMENT).add(LIGHT_ELEMENT).add(NORMAL_ELEMENT).add(PADDING_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_TEXTURE_COLOR_LIGHT = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(TEXTURE_0_ELEMENT).add(COLOR_ELEMENT).add(LIGHT_ELEMENT).build());
|
||||
public static final VertexFormat POSITION = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_COLOR = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_COLOR_LIGHT = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).add(LIGHT_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_TEXTURE = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(TEXTURE_0_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_COLOR_TEXTURE = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).add(TEXTURE_0_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_TEXTURE_COLOR = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(TEXTURE_0_ELEMENT).add(COLOR_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_COLOR_TEX_LIGHTMAP = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).add(TEXTURE_0_ELEMENT).add(LIGHT_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_TEXTURE_LIGHT_COLOR = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(TEXTURE_0_ELEMENT).add(LIGHT_ELEMENT).add(COLOR_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_TEXTURE_COLOR_NORMAL = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(TEXTURE_0_ELEMENT).add(COLOR_ELEMENT).add(NORMAL_ELEMENT).add(PADDING_ELEMENT).build());
|
||||
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.toremove;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Deprecated
|
||||
public class DrawState {
|
||||
@Getter
|
||||
private final VertexFormat format;
|
||||
@Getter
|
||||
private final int vertexCount;
|
||||
@Getter
|
||||
private final int mode;
|
||||
|
||||
public DrawState(VertexFormat format, int vertexCount, int mode) {
|
||||
this.format = format;
|
||||
this.vertexCount = vertexCount;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.compat.toremove;
|
||||
import com.google.common.collect.Queues;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
|
||||
import java.util.Deque;
|
||||
|
||||
@@ -33,10 +34,17 @@ public class MatrixStack {
|
||||
public void pop() {
|
||||
this.matrixStack.removeLast();
|
||||
}
|
||||
public boolean clear() {
|
||||
public boolean isEmpty() {
|
||||
return this.matrixStack.size() == 1;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
while (this.matrixStack.size() > 1) {
|
||||
this.matrixStack.removeLast();
|
||||
}
|
||||
loadIdentity();
|
||||
}
|
||||
|
||||
public void translate(double d, double e, double f) {
|
||||
final Entry lv = (Entry)this.matrixStack.getLast();
|
||||
lv.model.translate((float)d, (float)e, (float)f);
|
||||
@@ -79,6 +87,27 @@ public class MatrixStack {
|
||||
|
||||
}
|
||||
|
||||
public void multiply(Quaternionf quaternion) {
|
||||
MatrixStack.Entry entry = this.matrixStack.getLast();
|
||||
entry.model.rotate(quaternion);
|
||||
entry.normal.rotate(quaternion);
|
||||
}
|
||||
|
||||
public void multiply(Quaternionf quaternion, float originX, float originY, float originZ) {
|
||||
MatrixStack.Entry entry = this.matrixStack.getLast();
|
||||
entry.model.rotateAround(quaternion, originX, originY, originZ);
|
||||
entry.normal.rotate(quaternion);
|
||||
}
|
||||
|
||||
public void loadIdentity() {
|
||||
MatrixStack.Entry entry = this.matrixStack.getLast();
|
||||
entry.model.identity();
|
||||
entry.normal.identity();
|
||||
}
|
||||
|
||||
public void multiplyPositionMatrix(Matrix4f matrix) {
|
||||
this.matrixStack.getLast().model.mul(matrix);
|
||||
}
|
||||
|
||||
private static float invSqrt(float x) {
|
||||
float xhalf = 0.5f * x;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.toremove;
|
||||
|
||||
@Deprecated
|
||||
public class OverlayTexture {
|
||||
|
||||
// This is definitely wrong
|
||||
public static final int NO_OVERLAY = 0;
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.compat.toremove;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import it.unimi.dsi.fastutil.Hash;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
|
||||
import lombok.Getter;
|
||||
@@ -56,7 +60,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
}
|
||||
|
||||
public static RenderLayer getOutline(ResourceLocation texture, RenderPhase.Cull cull) {
|
||||
return of("outline", DefaultVertexFormat.POSITION_COLOR_TEXTURE, 7, 256, RenderLayer.MultiPhaseParameters.builder().texture(new RenderPhase.Texture(texture, false, false)).cull(cull).depthTest(ALWAYS_DEPTH_TEST).alpha(ONE_TENTH_ALPHA).texturing(OUTLINE_TEXTURING).fog(NO_FOG).target(OUTLINE_TARGET).build(RenderLayer.OutlineMode.IS_OUTLINE));
|
||||
return of("outline", DefaultVertexFormat.POSITION_COLOR_TEXTURE, 7, 256, RenderLayer.MultiPhaseParameters.builder().texture(new RenderPhase.Texture(texture, false, false)).cull(cull).depthTest(ALWAYS_DEPTH_TEST).alpha(ONE_TENTH_ALPHA).fog(NO_FOG).target(OUTLINE_TARGET).build(RenderLayer.OutlineMode.IS_OUTLINE));
|
||||
}
|
||||
|
||||
public int mode() {
|
||||
@@ -68,7 +72,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
private final MultiPhaseParameters phases;
|
||||
private final int hash;
|
||||
private final Optional<RenderLayer> affectedOutline;
|
||||
private final boolean outline;
|
||||
|
||||
private MultiPhase(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent, MultiPhaseParameters phases) {
|
||||
super(name, vertexFormat, drawMode, expectedBufferSize, () -> {
|
||||
@@ -80,7 +83,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.affectedOutline = phases.outlineMode == RenderLayer.OutlineMode.AFFECTS_OUTLINE ? phases.texture.getId().map((arg2) -> {
|
||||
return getOutline(arg2, phases.cull);
|
||||
}) : Optional.empty();
|
||||
this.outline = phases.outlineMode == RenderLayer.OutlineMode.IS_OUTLINE;
|
||||
this.hash = Objects.hash(new Object[]{super.hashCode(), phases});
|
||||
}
|
||||
|
||||
@@ -93,20 +95,23 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
return this.affectedOutline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object object) {
|
||||
return this == object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RenderType[" + this.phases + ']';
|
||||
}
|
||||
|
||||
static {
|
||||
CACHE = new ObjectOpenCustomHashSet(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
|
||||
CACHE = new ObjectOpenCustomHashSet<>(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
|
||||
}
|
||||
|
||||
static enum HashStrategy implements Hash.Strategy<MultiPhase> {
|
||||
@@ -138,17 +143,15 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
private final RenderPhase.DepthTest depthTest;
|
||||
private final RenderPhase.Cull cull;
|
||||
private final RenderPhase.Lightmap lightmap;
|
||||
private final RenderPhase.Overlay overlay;
|
||||
private final RenderPhase.Fog fog;
|
||||
private final RenderPhase.Layering layering;
|
||||
private final RenderPhase.Target target;
|
||||
private final RenderPhase.Texturing texturing;
|
||||
private final RenderPhase.WriteMaskState writeMaskState;
|
||||
private final RenderPhase.LineWidth lineWidth;
|
||||
private final OutlineMode outlineMode;
|
||||
private final ImmutableList<RenderPhase> phases;
|
||||
|
||||
private MultiPhaseParameters(RenderPhase.Texture texture, RenderPhase.Transparency transparency, RenderPhase.DiffuseLighting diffuseLighting, RenderPhase.ShadeModel shadeModel, RenderPhase.Alpha alpha, RenderPhase.DepthTest depthTest, RenderPhase.Cull cull, RenderPhase.Lightmap lightmap, RenderPhase.Overlay overlay, RenderPhase.Fog fog, RenderPhase.Layering layering, RenderPhase.Target target, RenderPhase.Texturing texturing, RenderPhase.WriteMaskState writeMaskState, RenderPhase.LineWidth lineWidth, OutlineMode outlineMode) {
|
||||
private MultiPhaseParameters(RenderPhase.Texture texture, RenderPhase.Transparency transparency, RenderPhase.DiffuseLighting diffuseLighting, RenderPhase.ShadeModel shadeModel, RenderPhase.Alpha alpha, RenderPhase.DepthTest depthTest, RenderPhase.Cull cull, RenderPhase.Lightmap lightmap, RenderPhase.Fog fog, RenderPhase.Layering layering, RenderPhase.Target target, RenderPhase.Texturing texturing, RenderPhase.WriteMaskState writeMaskState, OutlineMode outlineMode) {
|
||||
this.texture = texture;
|
||||
this.transparency = transparency;
|
||||
this.diffuseLighting = diffuseLighting;
|
||||
@@ -157,17 +160,16 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.depthTest = depthTest;
|
||||
this.cull = cull;
|
||||
this.lightmap = lightmap;
|
||||
this.overlay = overlay;
|
||||
this.fog = fog;
|
||||
this.layering = layering;
|
||||
this.target = target;
|
||||
this.texturing = texturing;
|
||||
this.writeMaskState = writeMaskState;
|
||||
this.lineWidth = lineWidth;
|
||||
this.outlineMode = outlineMode;
|
||||
this.phases = ImmutableList.of(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.overlay, this.fog, this.layering, this.target, new RenderPhase[]{this.texturing, this.writeMaskState, this.lineWidth});
|
||||
this.phases = ImmutableList.of(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering, this.target, this.texturing, this.writeMaskState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
@@ -179,10 +181,12 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(new Object[]{this.phases, this.outlineMode});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CompositeState[" + this.phases + ", outlineProperty=" + this.outlineMode + ']';
|
||||
}
|
||||
@@ -200,13 +204,11 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
private RenderPhase.DepthTest depthTest;
|
||||
private RenderPhase.Cull cull;
|
||||
private RenderPhase.Lightmap lightmap;
|
||||
private RenderPhase.Overlay overlay;
|
||||
private RenderPhase.Fog fog;
|
||||
private RenderPhase.Layering layering;
|
||||
private RenderPhase.Target target;
|
||||
private RenderPhase.Texturing texturing;
|
||||
private RenderPhase.WriteMaskState writeMaskState;
|
||||
private RenderPhase.LineWidth lineWidth;
|
||||
|
||||
private Builder() {
|
||||
this.texture = RenderPhase.NO_TEXTURE ;
|
||||
@@ -217,13 +219,11 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.depthTest = RenderPhase.LEQUAL_DEPTH_TEST;
|
||||
this.cull = RenderPhase.ENABLE_CULLING;
|
||||
this.lightmap = RenderPhase.DISABLE_LIGHTMAP;
|
||||
this.overlay = RenderPhase.DISABLE_OVERLAY_COLOR;
|
||||
this.fog = RenderPhase.FOG;
|
||||
this.layering = RenderPhase.NO_LAYERING;
|
||||
this.target = RenderPhase.MAIN_TARGET;
|
||||
this.texturing = RenderPhase.DEFAULT_TEXTURING;
|
||||
this.writeMaskState = RenderPhase.ALL_MASK;
|
||||
this.lineWidth = RenderPhase.FULL_LINE_WIDTH;
|
||||
}
|
||||
|
||||
public Builder texture(RenderPhase.Texture texture) {
|
||||
@@ -261,11 +261,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder overlay(RenderPhase.Overlay overlay) {
|
||||
this.overlay = overlay;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fog(RenderPhase.Fog fog) {
|
||||
this.fog = fog;
|
||||
return this;
|
||||
@@ -286,7 +281,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
}
|
||||
|
||||
public MultiPhaseParameters build(OutlineMode outlineMode) {
|
||||
return new MultiPhaseParameters(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.overlay, this.fog, this.layering, this.target, this.texturing, this.writeMaskState, this.lineWidth, outlineMode);
|
||||
return new MultiPhaseParameters(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering, this.target, this.texturing, this.writeMaskState, outlineMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,6 +297,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.compat.toremove;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import me.jellysquid.mods.sodium.client.SodiumClientMod;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
@@ -11,7 +11,6 @@ import org.lwjgl.opengl.GL11;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
|
||||
|
||||
public abstract class RenderPhase {
|
||||
@@ -64,13 +63,8 @@ public abstract class RenderPhase {
|
||||
protected static final Texture BLOCK_ATLAS_TEXTURE;
|
||||
protected static final Texture NO_TEXTURE;
|
||||
protected static final Texturing DEFAULT_TEXTURING;
|
||||
protected static final Texturing OUTLINE_TEXTURING;
|
||||
protected static final Texturing GLINT_TEXTURING;
|
||||
protected static final Texturing ENTITY_GLINT_TEXTURING;
|
||||
protected static final Lightmap ENABLE_LIGHTMAP;
|
||||
protected static final Lightmap DISABLE_LIGHTMAP;
|
||||
protected static final Overlay ENABLE_OVERLAY_COLOR;
|
||||
protected static final Overlay DISABLE_OVERLAY_COLOR;
|
||||
protected static final DiffuseLighting ENABLE_DIFFUSE_LIGHTING;
|
||||
protected static final DiffuseLighting DISABLE_DIFFUSE_LIGHTING;
|
||||
protected static final Cull ENABLE_CULLING;
|
||||
@@ -82,19 +76,12 @@ public abstract class RenderPhase {
|
||||
protected static final WriteMaskState COLOR_MASK;
|
||||
protected static final WriteMaskState DEPTH_MASK;
|
||||
protected static final Layering NO_LAYERING;
|
||||
protected static final Layering POLYGON_OFFSET_LAYERING;
|
||||
protected static final Layering VIEW_OFFSET_Z_LAYERING;
|
||||
protected static final Fog NO_FOG;
|
||||
protected static final Fog FOG;
|
||||
protected static final Fog BLACK_FOG;
|
||||
protected static final Target MAIN_TARGET;
|
||||
protected static final Target OUTLINE_TARGET;
|
||||
protected static final Target TRANSLUCENT_TARGET;
|
||||
protected static final Target PARTICLES_TARGET;
|
||||
protected static final Target WEATHER_TARGET;
|
||||
protected static final Target CLOUDS_TARGET;
|
||||
protected static final Target ITEM_TARGET;
|
||||
protected static final LineWidth FULL_LINE_WIDTH;
|
||||
|
||||
public RenderPhase(String name, Runnable beginAction, Runnable endAction) {
|
||||
this.name = name;
|
||||
@@ -110,6 +97,7 @@ public abstract class RenderPhase {
|
||||
this.endAction.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
@@ -121,28 +109,16 @@ public abstract class RenderPhase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
private static void setupGlintTexturing(float scale) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.pushMatrix();
|
||||
// RenderSystem.loadIdentity();
|
||||
// long l = Util.getMeasuringTimeMs() * 8L;
|
||||
// float g = (float)(l % 110000L) / 110000.0F;
|
||||
// float h = (float)(l % 30000L) / 30000.0F;
|
||||
// RenderSystem.translatef(-g, h, 0.0F);
|
||||
// RenderSystem.rotatef(10.0F, 0.0F, 0.0F, 1.0F);
|
||||
// RenderSystem.scalef(scale, scale, scale);
|
||||
// RenderSystem.matrixMode(5888);
|
||||
}
|
||||
|
||||
private static ResourceLocation ATLAS = TextureMap.locationBlocksTexture;
|
||||
static {
|
||||
// TODO: Sodium - SpriteAtlasTexture
|
||||
@@ -151,34 +127,9 @@ public abstract class RenderPhase {
|
||||
NO_TEXTURE = new Texture();
|
||||
DEFAULT_TEXTURING = new Texturing("default_texturing", () -> {
|
||||
}, () -> {
|
||||
});
|
||||
OUTLINE_TEXTURING = new Texturing("outline_texturing", () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.setupOutline();
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.teardownOutline();
|
||||
});
|
||||
GLINT_TEXTURING = new Texturing("glint_texturing", () -> {
|
||||
setupGlintTexturing(8.0F);
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.popMatrix();
|
||||
// RenderSystem.matrixMode(5888);
|
||||
});
|
||||
ENTITY_GLINT_TEXTURING = new Texturing("entity_glint_texturing", () -> {
|
||||
setupGlintTexturing(0.16F);
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.popMatrix();
|
||||
// RenderSystem.matrixMode(5888);
|
||||
});
|
||||
ENABLE_LIGHTMAP = new Lightmap(true);
|
||||
DISABLE_LIGHTMAP = new Lightmap(false);
|
||||
ENABLE_OVERLAY_COLOR = new Overlay(true);
|
||||
DISABLE_OVERLAY_COLOR = new Overlay(false);
|
||||
ENABLE_DIFFUSE_LIGHTING = new DiffuseLighting(true);
|
||||
DISABLE_DIFFUSE_LIGHTING = new DiffuseLighting(false);
|
||||
ENABLE_CULLING = new Cull(true);
|
||||
@@ -189,33 +140,14 @@ public abstract class RenderPhase {
|
||||
ALL_MASK = new WriteMaskState(true, true);
|
||||
COLOR_MASK = new WriteMaskState(true, false);
|
||||
DEPTH_MASK = new WriteMaskState(false, true);
|
||||
NO_LAYERING = new Layering("no_layering", () -> {
|
||||
}, () -> {
|
||||
});
|
||||
POLYGON_OFFSET_LAYERING = new Layering("polygon_offset_layering", () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.polygonOffset(-1.0F, -10.0F);
|
||||
// RenderSystem.enablePolygonOffset();
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.polygonOffset(0.0F, 0.0F);
|
||||
// RenderSystem.disablePolygonOffset();
|
||||
});
|
||||
VIEW_OFFSET_Z_LAYERING = new Layering("view_offset_z_layering", () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.pushMatrix();
|
||||
// RenderSystem.scalef(0.99975586F, 0.99975586F, 0.99975586F);
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem::popMatrix();
|
||||
});
|
||||
NO_LAYERING = new Layering("no_layering", () -> {}, () -> {});
|
||||
NO_FOG = new Fog("no_fog", () -> {
|
||||
}, () -> {
|
||||
});
|
||||
FOG = new Fog("fog", () -> {
|
||||
// Unclear what this should do
|
||||
// BackgroundRenderer.setFogBlack(), also levelFogColor()
|
||||
SodiumClientMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
AngelicaMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
GLStateManager.enableFog();
|
||||
}, GLStateManager::disableFog);
|
||||
BLACK_FOG = new Fog("black_fog", () -> {
|
||||
@@ -224,7 +156,7 @@ public abstract class RenderPhase {
|
||||
}, () -> {
|
||||
// Unclear what this should do
|
||||
// BackgroundRenderer.setFogBlack(), also levelFogColor()
|
||||
SodiumClientMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
AngelicaMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
GLStateManager.disableFog();
|
||||
});
|
||||
MAIN_TARGET = new Target("main_target", () -> {
|
||||
@@ -232,7 +164,7 @@ public abstract class RenderPhase {
|
||||
});
|
||||
OUTLINE_TARGET = new Target("outline_target", () -> {
|
||||
// TODO: Sodium
|
||||
SodiumClientMod.LOGGER.debug("NOT enabling the entity outline framebuffer");
|
||||
AngelicaMod.LOGGER.debug("NOT enabling the entity outline framebuffer");
|
||||
//MinecraftClient.getInstance().worldRenderer.getEntityOutlinesFramebuffer().beginWrite(false);
|
||||
}, () -> {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
|
||||
@@ -240,7 +172,7 @@ public abstract class RenderPhase {
|
||||
TRANSLUCENT_TARGET = new Target("translucent_target", () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
// TODO: Sodium
|
||||
SodiumClientMod.LOGGER.debug("NOT enabling the translucent framebuffer");
|
||||
AngelicaMod.LOGGER.debug("NOT enabling the translucent framebuffer");
|
||||
// MinecraftClient.getInstance().worldRenderer.getTranslucentFramebuffer().beginWrite(false);
|
||||
}
|
||||
|
||||
@@ -250,103 +182,6 @@ public abstract class RenderPhase {
|
||||
}
|
||||
|
||||
});
|
||||
PARTICLES_TARGET = new Target("particles_target", () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().worldRenderer.getParticlesFramebuffer().beginWrite(false);
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
|
||||
}
|
||||
|
||||
});
|
||||
WEATHER_TARGET = new Target("weather_target", () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().worldRenderer.getWeatherFramebuffer().beginWrite(false);
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
|
||||
}
|
||||
|
||||
});
|
||||
CLOUDS_TARGET = new Target("clouds_target", () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().worldRenderer.getCloudsFramebuffer().beginWrite(false);
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
|
||||
}
|
||||
|
||||
});
|
||||
ITEM_TARGET = new Target("item_entity_target", () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().worldRenderer.getEntityFramebuffer().beginWrite(false);
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
|
||||
}
|
||||
|
||||
});
|
||||
FULL_LINE_WIDTH = new LineWidth(OptionalDouble.of(1.0));
|
||||
}
|
||||
|
||||
|
||||
public static class LineWidth extends RenderPhase {
|
||||
private final OptionalDouble width;
|
||||
|
||||
public LineWidth(OptionalDouble optionalDouble) {
|
||||
super("line_width", () -> {
|
||||
if (!Objects.equals(optionalDouble, OptionalDouble.of(1.0))) {
|
||||
if (optionalDouble.isPresent()) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.lineWidth((float)optionalDouble.getAsDouble());
|
||||
} else {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.lineWidth(Math.max(2.5F, (float)MinecraftClient.getInstance().getWindow().getFramebufferWidth() / 1920.0F * 2.5F));
|
||||
}
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (!Objects.equals(optionalDouble, OptionalDouble.of(1.0))) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.lineWidth(1.0F);
|
||||
}
|
||||
|
||||
});
|
||||
this.width = optionalDouble;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
} else if (object != null && this.getClass() == object.getClass()) {
|
||||
return !super.equals(object) ? false : Objects.equals(this.width, ((LineWidth)object).width);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), this.width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name + '[' + (this.width.isPresent() ? this.width.getAsDouble() : "window_scale") + ']';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -505,31 +340,12 @@ public abstract class RenderPhase {
|
||||
}
|
||||
|
||||
|
||||
public static class Overlay extends Toggleable {
|
||||
public Overlay(boolean overlayColor) {
|
||||
super("overlay", () -> {
|
||||
if (overlayColor) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().gameRenderer.getOverlayTexture().setupOverlayColor();
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (overlayColor) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().gameRenderer.getOverlayTexture().teardownOverlayColor();
|
||||
}
|
||||
|
||||
}, overlayColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Lightmap extends Toggleable {
|
||||
public Lightmap(boolean lightmap) {
|
||||
super("lightmap", () -> {
|
||||
if (lightmap) {
|
||||
// TODO: Sodium - LightmapTextureManager
|
||||
SodiumClientMod.LOGGER.debug("Lightmap - enable (not implemented)");
|
||||
AngelicaMod.LOGGER.debug("Lightmap - enable (not implemented)");
|
||||
// throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().enable();
|
||||
|
||||
@@ -538,7 +354,7 @@ public abstract class RenderPhase {
|
||||
}, () -> {
|
||||
if (lightmap) {
|
||||
// TODO: Sodium - LightmapTextureManager
|
||||
SodiumClientMod.LOGGER.debug("Lightmap - disable (not implemented)");
|
||||
AngelicaMod.LOGGER.debug("Lightmap - disable (not implemented)");
|
||||
// throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().disable();
|
||||
}
|
||||
@@ -580,93 +396,6 @@ public abstract class RenderPhase {
|
||||
}
|
||||
|
||||
|
||||
public static final class PortalTexturing extends Texturing {
|
||||
private final int layer;
|
||||
|
||||
public PortalTexturing(int layer) {
|
||||
super("portal_texturing", () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.pushMatrix();
|
||||
// RenderSystem.loadIdentity();
|
||||
// RenderSystem.translatef(0.5F, 0.5F, 0.0F);
|
||||
// RenderSystem.scalef(0.5F, 0.5F, 1.0F);
|
||||
// RenderSystem.translatef(17.0F / (float)layer, (2.0F + (float)layer / 1.5F) * ((float)(Util.getMeasuringTimeMs() % 800000L) / 800000.0F), 0.0F);
|
||||
// RenderSystem.rotatef(((float)(layer * layer) * 4321.0F + (float)layer * 9.0F) * 2.0F, 0.0F, 0.0F, 1.0F);
|
||||
// RenderSystem.scalef(4.5F - (float)layer / 4.0F, 4.5F - (float)layer / 4.0F, 1.0F);
|
||||
// RenderSystem.mulTextureByProjModelView();
|
||||
// RenderSystem.matrixMode(5888);
|
||||
// RenderSystem.setupEndPortalTexGen();
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.popMatrix();
|
||||
// RenderSystem.matrixMode(5888);
|
||||
// RenderSystem.clearTexGen();
|
||||
});
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
} else if (object != null && this.getClass() == object.getClass()) {
|
||||
PortalTexturing lv = (PortalTexturing)object;
|
||||
return this.layer == lv.layer;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Integer.hashCode(this.layer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static final class OffsetTexturing extends Texturing {
|
||||
private final float x;
|
||||
private final float y;
|
||||
|
||||
public OffsetTexturing(float x, float y) {
|
||||
super("offset_texturing", () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.pushMatrix();
|
||||
// RenderSystem.loadIdentity();
|
||||
// RenderSystem.translatef(x, y, 0.0F);
|
||||
// RenderSystem.matrixMode(5888);
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.popMatrix();
|
||||
// RenderSystem.matrixMode(5888);
|
||||
});
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
} else if (object != null && this.getClass() == object.getClass()) {
|
||||
OffsetTexturing lv = (OffsetTexturing)object;
|
||||
return Float.compare(lv.x, this.x) == 0 && Float.compare(lv.y, this.y) == 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.x, this.y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Texturing extends RenderPhase {
|
||||
public Texturing(String string, Runnable runnable, Runnable runnable2) {
|
||||
super(string, runnable, runnable2);
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.toremove;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.VertexFormatElement;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
import net.coderbot.iris.vertices.ImmediateState;
|
||||
import net.coderbot.iris.vertices.IrisVertexFormats;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class VertexFormat {
|
||||
@Getter
|
||||
protected final ImmutableList<VertexFormatElement> elements;
|
||||
protected final IntList offsets = new IntArrayList();
|
||||
@Getter
|
||||
protected final int vertexSize;
|
||||
|
||||
public VertexFormat(ImmutableList<VertexFormatElement> elements) {
|
||||
this.elements = elements;
|
||||
int i = 0;
|
||||
for (VertexFormatElement element : elements) {
|
||||
offsets.add(i);
|
||||
i += element.getByteSize();
|
||||
}
|
||||
vertexSize = i;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setupBufferState(long l) {
|
||||
if (BlockRenderingSettings.INSTANCE.shouldUseExtendedVertexFormat() && ImmediateState.renderWithExtendedVertexFormat) {
|
||||
if (this == DefaultVertexFormat.POSITION_COLOR_TEXTURE_LIGHT_NORMAL || this == DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP) {
|
||||
IrisVertexFormats.TERRAIN.setupBufferState(l);
|
||||
return;
|
||||
} else if (this == DefaultVertexFormat.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL) {
|
||||
IrisVertexFormats.ENTITY.setupBufferState(l);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final int i = this.getVertexSize();
|
||||
final List<VertexFormatElement> list = this.getElements();
|
||||
|
||||
for(int j = 0; j < list.size(); ++j) {
|
||||
((VertexFormatElement)list.get(j)).setupBufferState(l + (long)this.offsets.getInt(j), i);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void clearBufferState() {
|
||||
if (BlockRenderingSettings.INSTANCE.shouldUseExtendedVertexFormat() && ImmediateState.renderWithExtendedVertexFormat) {
|
||||
if (this == DefaultVertexFormat.POSITION_COLOR_TEXTURE_LIGHT_NORMAL || this == DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP) {
|
||||
IrisVertexFormats.TERRAIN.clearBufferState();
|
||||
return;
|
||||
} else if ( this == DefaultVertexFormat.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL) {
|
||||
IrisVertexFormats.ENTITY.clearBufferState();
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (VertexFormatElement vertexformatelement : this.getElements()) {
|
||||
vertexformatelement.clearBufferState();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +1,67 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
@Config(modid = "angelica")
|
||||
import com.gtnewhorizon.gtnhlib.config.Config;
|
||||
|
||||
@Config(modid = "angelica", filename = "angelica-modules")
|
||||
public class AngelicaConfig {
|
||||
@Config.Comment("Enable Sodium rendering")
|
||||
@Config.Comment("Enable Celeritas terrain rendering")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableSodium;
|
||||
public static boolean enableCeleritas;
|
||||
|
||||
@Config.Comment("Enable Sodium fluid rendering")
|
||||
@Config.Comment("Enable multi-threaded chunk building for improved performance [Requires Celeritas]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresWorldRestart
|
||||
public static boolean enableThreadedChunkBuilding;
|
||||
|
||||
@Config.Comment("Number of chunk builder threads. 0 = auto-detect, -1 = use single-threaded fallback")
|
||||
@Config.DefaultInt(0)
|
||||
@Config.RangeInt(min = -1, max = 16)
|
||||
@Config.RequiresWorldRestart
|
||||
public static int chunkBuilderThreadCount;
|
||||
|
||||
@Config.Comment("Enable NotFine Options")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean enableNotFineOptions;
|
||||
|
||||
@Config.Comment("Enable Reese's Sodium Options")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean enableReesesSodiumOptions;
|
||||
|
||||
@Config.Comment("Inject BakedModel rendering into some vanilla blocks")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableSodiumFluidRendering;
|
||||
public static boolean injectQPRendering;
|
||||
|
||||
@Config.Comment("Enable Iris Shaders [Requires Sodium]")
|
||||
@Config.Comment("Enable Angelica's test blocks")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.Ignore()
|
||||
public static boolean enableTestBlocks;
|
||||
|
||||
@Config.Comment("Enable Iris Shaders [Requires Celeritas]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableIris;
|
||||
|
||||
@Config.Comment("Enable MCPatcherForge features, still in Alpha. Individual features are toggled in mcpatcher.json")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableMCPatcherForgeFeatures;
|
||||
|
||||
@Config.Comment("Enable NotFine optimizations")
|
||||
@Config.Comment("Replace main menu panorama with modern equivalent.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableNotFineOptimizations;
|
||||
public static boolean enablePanoramaBlurShader;
|
||||
|
||||
@Config.Comment("Replace cloud renderer with a VBO version.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableVBOClouds;
|
||||
|
||||
@Config.Comment("Uses cached attributes for VBO rendering, resulting in less CPU overhead. Disable if you notice any graphical issues.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableVAO;
|
||||
|
||||
@Config.Comment("Enable NotFine features")
|
||||
@Config.DefaultBoolean(true)
|
||||
@@ -48,23 +84,40 @@ public class AngelicaConfig {
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean showSplashMemoryBar;
|
||||
|
||||
|
||||
@Config.Comment("Renders the HUD elements once per tick and reuses the pixels to improve performance. [Experimental]")
|
||||
@Config.Comment("Renders the HUD elements once per 20 frames (by default) and reuses the pixels to improve performance. [Experimental]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableHudCaching;
|
||||
@Config.Comment("Inject a conditional early return into all RenderGameOverlayEvent receivers; Requires enableHudCaching")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableHudCachingEventTransformer;
|
||||
|
||||
@Config.Comment("Batch drawScreen fonts [Experimental]")
|
||||
@Config.Comment("Enable HUD Caching at runtime. Requires enableHudCaching to be on at startup. [Experimental]")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean hudCachingActive;
|
||||
|
||||
@Config.Comment("The amount of frames to wait before updating the HUD elements. [Experimental]")
|
||||
@Config.DefaultInt(20)
|
||||
@Config.RangeInt(min = 1, max = 60)
|
||||
public static int hudCachingFPS = 20;
|
||||
|
||||
@Config.Comment("Batch drawScreen fonts")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableFontRenderer;
|
||||
|
||||
@Config.Comment("Enable Dynamic Lights")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableDynamicLights;
|
||||
|
||||
@Config.Comment("Optimize world update light. [From Hodgepodge]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean optimizeWorldUpdateLight;
|
||||
|
||||
@Config.Comment("Speedup Animations. [From Hodgepodge]")
|
||||
@Config.Comment("Optimize Texture Animations. [From Hodgepodge]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean speedupAnimations;
|
||||
@@ -74,8 +127,132 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean optimizeTextureLoading;
|
||||
|
||||
@Config.Comment("Optimize slow glGetInteger in Jabba w/ Nvidia Drivers")
|
||||
@Config.Comment("Fix RenderBlockFluid reading the block type from the world access multiple times")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean optimizeJabbaGetInteger;
|
||||
public static boolean fixFluidRendererCheckingBlockAgain;
|
||||
|
||||
@Config.Comment("Dynamically modifies the render distance of dropped items entities to preserve performance."
|
||||
+ " It starts reducing the render distance when exceeding the threshold set below.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean dynamicItemRenderDistance;
|
||||
|
||||
@Config.Comment("Max amount of dropped item rendered")
|
||||
@Config.DefaultInt(256)
|
||||
@Config.RangeInt(min = 32, max = 2048)
|
||||
public static int droppedItemLimit;
|
||||
|
||||
@Config.Comment("Use total world time instead of normal world time. Allows most shader animations to play when "
|
||||
+ "doDaylightCycle is off, but causes shader animations to desync from time of day.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean useTotalWorldTime;
|
||||
|
||||
@Config.Comment("Enable Debug Logging")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableDebugLogging;
|
||||
|
||||
@Config.Comment("Enables PBR atlas dumping")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.Name("Enable PBR Debug")
|
||||
public static boolean enablePBRDebug;
|
||||
|
||||
@Config.Comment("Enable Zoom")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableZoom;
|
||||
|
||||
@Config.Comment("Optimizes in-world item rendering")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean optimizeInWorldItemRendering;
|
||||
|
||||
@Config.Comment("Upper limit for the amount of VBO's to cache for optimized item rendering. Higher number can potentially use more VRAM.")
|
||||
@Config.DefaultInt(512)
|
||||
@Config.RangeInt(min = 256, max = 1024)
|
||||
public static int itemRendererCacheSize;
|
||||
|
||||
@Config.Comment("Render distance for the spinning mob inside mod spawners")
|
||||
@Config.DefaultDouble(16D)
|
||||
@Config.RangeDouble(min = 16D, max = 64D)
|
||||
public static double mobSpawnerRenderDistance;
|
||||
|
||||
@Config.Comment("Switches to an alternate FPS limiter that gives more stable frametimes, in exchange for slightly " +
|
||||
"more latency. Will never introduce more than one frame of latency, and has a lower impact at higher framerates.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean sleepBeforeSwap;
|
||||
|
||||
@Config.Comment("Allows unicode languages to use an odd gui scale")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean removeUnicodeEvenScaling;
|
||||
|
||||
@Config.Comment({"Block corners and edges between chunks might have \"cracks\" (various lines/dots) in them.",
|
||||
"While using \"Compact Vertex Format\" makes the situation even worse.",
|
||||
"This option fixes it.",
|
||||
"Requires texture reloading (F3 + T) after changing this option to take effect"})
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean fixBlockCrack;
|
||||
|
||||
@Config.Comment({
|
||||
"The \"epsilon\" value for the fixBlockCrack option. ",
|
||||
"Set this a bit higher if you can still see lines/dots between solid blocks in dark areas.",
|
||||
"May cause intense flickering (z-fighting) between blocks if the value is too high"
|
||||
})
|
||||
@Config.RangeDouble(min = 0, max = 0.005)
|
||||
@Config.DefaultDouble(0.001)
|
||||
public static double blockCrackFixEpsilon;
|
||||
|
||||
@Config.Comment("Block classes that have bugs when rendering with the fixBlockCrack can be put here to avoid manipulating them")
|
||||
@Config.DefaultStringList({
|
||||
"net.minecraft.block.BlockCauldron",
|
||||
"net.minecraft.block.BlockStairs"
|
||||
})
|
||||
public static String[] blockCrackFixBlacklist;
|
||||
|
||||
@Config.Comment({"Block classes that have render pass other than 0 but still need to be manipulated.",
|
||||
"Add a block class here if you see flickering (z-fighting) with fixBlockCrack enabled"
|
||||
})
|
||||
@Config.DefaultStringList({
|
||||
"gregtech.common.blocks.BlockOres",
|
||||
"gregtech.common.blocks.GTBlockOre",
|
||||
"shukaro.artifice.block.world.BlockOre",
|
||||
"bartworks.system.material.BWMetaGeneratedOres",
|
||||
"gtPlusPlus.core.block.base.BlockBaseOre",
|
||||
"org.pfaa.geologica.block.BrokenGeoBlock",
|
||||
"org.pfaa.geologica.block.BrickGeoBlock",
|
||||
})
|
||||
public static String[] blockCrackFixRenderPassWhitelist__;
|
||||
|
||||
@Config.Comment("Register HardcodedCustomUniforms in Iris Shaders. May help with compatibility in certain shader packs")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean enableHardcodedCustomUniforms;
|
||||
|
||||
@Config.Comment("Define IS_IRIS in shader macros.")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean defineIsIris;
|
||||
|
||||
@Config.Comment("Renders chunks before neighbors are ready. Improves loading at render distance edges, useful for low render distance servers.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean useVanillaChunkTracking;
|
||||
|
||||
@Config.Comment("Disables additional F3 information added by Angelica.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean disableF3Additions;
|
||||
|
||||
@Config.Comment("Replaces various FFP uploads with statically allocated VBO's.")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean replaceFFPUploads;
|
||||
|
||||
@Config.Comment("Pinned OpenGL version an integer (e.g. 46, 41, 33). 0 = auto-detect. [33, 46]. (Disable with disableGLVersionPinning=true)")
|
||||
@Config.DefaultInt(0)
|
||||
@Config.RangeInt(min = 0, max = 46)
|
||||
@Config.RequiresMcRestart
|
||||
public static int pinnedGLVersion;
|
||||
|
||||
@Config.Comment("Disable automatic GL version pinning. When true, always probes from highest on every launch.")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean disableGLVersionPinning;
|
||||
|
||||
}
|
||||
@@ -1,9 +1,18 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.ConfigException;
|
||||
import com.gtnewhorizon.gtnhlib.config.SimpleGuiConfig;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
|
||||
public class AngelicaGuiConfig extends SimpleGuiConfig {
|
||||
public AngelicaGuiConfig(GuiScreen parent) throws ConfigException {
|
||||
super(parent, AngelicaConfig.class, "archaicfix", "ArchaicFix");
|
||||
super(parent,
|
||||
"angelica",
|
||||
"Angelica",
|
||||
true,
|
||||
AngelicaConfig.class,
|
||||
CompatConfig.class,
|
||||
FontConfig.class
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.SimpleGuiFactory;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
|
||||
public class AngelicaGuiConfigFactory implements SimpleGuiFactory {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.Config;
|
||||
|
||||
@Config(modid = "angelica", filename = "angelica-compat", category = "compat")
|
||||
public class CompatConfig {
|
||||
|
||||
@Config.Comment("Apply fixes to the LOTR mod")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixLotr;
|
||||
|
||||
@Config.Comment("Apply fixes to Extra Utilities")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixExtraUtils;
|
||||
|
||||
@Config.Comment("Apply fixes to Stacks on Stacks")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixStacksOnStacks;
|
||||
|
||||
@Config.Comment("Apply fixes to Minefactory Reloaded")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixMinefactoryReloaded;
|
||||
|
||||
@Config.Comment("Apply fixes to Thaumcraft")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixThaumcraft;
|
||||
|
||||
@Config.Comment("Apply fixes to ThaumicHorizons")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixThaumicHorizons;
|
||||
|
||||
@Config.Comment("Apply fixes to Immersive Engineering")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixImmersiveEngineering;
|
||||
|
||||
@Config.Comment("Apply fixes to Security Craft")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixSecurityCraft;
|
||||
|
||||
@Config.Comment("Apply tweaks to NTM:Space")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean tweakNTMSpace;
|
||||
|
||||
@Config.Comment("Apply fixes to NTM:Space")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixNTMSpace;
|
||||
|
||||
@Config.Comment("Disables rendering of the planet below the player at high altitudes [Requires tweakNTMSpace]")
|
||||
@Config.DefaultEnum("ShadersOnly")
|
||||
public static altitudePlanetRendererState NTMSpace_disableAltitudePlanetRenderer;
|
||||
public enum altitudePlanetRendererState {
|
||||
Always,
|
||||
ShadersOnly,
|
||||
Never
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Config {
|
||||
/**
|
||||
* The mod id that this configuration is associated with.
|
||||
*/
|
||||
String modid();
|
||||
|
||||
/**
|
||||
* Root element category, defaults to "general". You must not specify an empty string.
|
||||
*/
|
||||
String category() default "general";
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
@interface LangKey {
|
||||
String value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface Comment {
|
||||
String[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface Ignore {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface DefaultBoolean {
|
||||
boolean value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface RangeInt {
|
||||
int min() default Integer.MIN_VALUE;
|
||||
|
||||
int max() default Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface DefaultInt {
|
||||
int value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface RangeFloat {
|
||||
float min() default Float.MIN_VALUE;
|
||||
|
||||
float max() default Float.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface DefaultFloat {
|
||||
float value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface DefaultString {
|
||||
String value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface Pattern {
|
||||
String value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface DefaultEnum {
|
||||
String value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface DefaultStringList {
|
||||
String[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface Name {
|
||||
String value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
@interface RequiresMcRestart {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
@interface RequiresWorldRestart {
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
/**
|
||||
* A really basic wrapper for config to simplify handling them in external code.
|
||||
*/
|
||||
public class ConfigException extends Exception {
|
||||
|
||||
public ConfigException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ConfigException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class ConfigMigrator {
|
||||
public static Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
/**
|
||||
* Tries to migrate the equivalent config file from Rubidium to the Angelica name if possible.
|
||||
*/
|
||||
public static Path handleConfigMigration(String fileName) {
|
||||
final Path configDir = Minecraft.getMinecraft().mcDataDir.toPath().resolve("config");
|
||||
final Path mainPath = configDir.resolve(fileName);
|
||||
try {
|
||||
if(Files.notExists(mainPath)) {
|
||||
final String legacyName = fileName.replace("angelica", "rubidium");
|
||||
final Path legacyPath = configDir.resolve(legacyName);
|
||||
if(Files.exists(legacyPath)) {
|
||||
Files.move(legacyPath, mainPath);
|
||||
LOGGER.warn("Migrated {} config file to {}", legacyName, fileName);
|
||||
}
|
||||
}
|
||||
} catch(IOException | RuntimeException e) {
|
||||
LOGGER.error("Exception encountered while attempting config migration", e);
|
||||
}
|
||||
|
||||
return mainPath;
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import cpw.mods.fml.client.config.IConfigElement;
|
||||
import cpw.mods.fml.client.event.ConfigChangedEvent;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.val;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraftforge.common.config.ConfigElement;
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Class for controlling the loading of configuration files.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class ConfigurationManager {
|
||||
private static final Logger LOGGER = LogManager.getLogger("ArchaicConfig");
|
||||
private static final Map<String, Configuration> configs = new HashMap<>();
|
||||
|
||||
private static final Map<Configuration, Set<Class<?>>> configToClassMap = new HashMap<>();
|
||||
|
||||
private static final ConfigurationManager instance = new ConfigurationManager();
|
||||
|
||||
private static boolean initialized = false;
|
||||
|
||||
private static Path configDir;
|
||||
|
||||
/**
|
||||
* Registers a configuration class to be loaded. This should be done in preInit.
|
||||
*
|
||||
* @param configClass The class to register.
|
||||
*/
|
||||
public static void registerConfig(Class<?> configClass) throws ConfigException {
|
||||
init();
|
||||
val cfg = Optional.ofNullable(configClass.getAnnotation(Config.class))
|
||||
.orElseThrow(() -> new ConfigException(
|
||||
"Class " + configClass.getName() + " does not have a @Config annotation!"));
|
||||
val category = Optional.of(cfg.category().trim())
|
||||
.map((cat) -> cat.length() == 0 ? null : cat)
|
||||
.orElseThrow(() -> new ConfigException(
|
||||
"Config class " + configClass.getName() + " has an empty category!"));
|
||||
val rawConfig = configs.computeIfAbsent(cfg.modid(), (ignored) -> {
|
||||
val c = new Configuration(configDir.resolve(cfg.modid() + ".cfg").toFile());
|
||||
c.load();
|
||||
return c;
|
||||
});
|
||||
configToClassMap.computeIfAbsent(rawConfig, (ignored) -> new HashSet<>()).add(configClass);
|
||||
try {
|
||||
processConfigInternal(configClass, category, rawConfig);
|
||||
rawConfig.save();
|
||||
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException | NoSuchFieldException e) {
|
||||
throw new ConfigException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void processConfigInternal(Class<?> configClass, String category, Configuration rawConfig)
|
||||
throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException,
|
||||
ConfigException {
|
||||
val cat = rawConfig.getCategory(category);
|
||||
for (val field : configClass.getDeclaredFields()) {
|
||||
if (field.getAnnotation(Config.Ignore.class) != null) {
|
||||
continue;
|
||||
}
|
||||
field.setAccessible(true);
|
||||
val comment = Optional.ofNullable(field.getAnnotation(Config.Comment.class))
|
||||
.map(Config.Comment::value)
|
||||
.map((lines) -> String.join("\n", lines))
|
||||
.orElse("");
|
||||
val name = Optional.ofNullable(field.getAnnotation(Config.Name.class))
|
||||
.map(Config.Name::value)
|
||||
.orElse(field.getName());
|
||||
val langKey = Optional.ofNullable(field.getAnnotation(Config.LangKey.class))
|
||||
.map(Config.LangKey::value)
|
||||
.orElse(name);
|
||||
val fieldClass = field.getType();
|
||||
var boxed = false;
|
||||
if ((boxed = fieldClass.equals(Boolean.class)) || fieldClass.equals(boolean.class)) {
|
||||
val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultBoolean.class))
|
||||
.map(Config.DefaultBoolean::value)
|
||||
.orElse(boxed ? (Boolean) field.get(null) : field.getBoolean(null));
|
||||
field.setBoolean(null, rawConfig.getBoolean(name, category, defaultValue, comment, langKey));
|
||||
} else if ((boxed = fieldClass.equals(Integer.class)) || fieldClass.equals(int.class)) {
|
||||
val range = Optional.ofNullable(field.getAnnotation(Config.RangeInt.class));
|
||||
val min = range.map(Config.RangeInt::min).orElse(Integer.MIN_VALUE);
|
||||
val max = range.map(Config.RangeInt::max).orElse(Integer.MAX_VALUE);
|
||||
val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultInt.class))
|
||||
.map(Config.DefaultInt::value)
|
||||
.orElse(boxed ? (Integer) field.get(null) : field.getInt(null));
|
||||
field.setInt(null, rawConfig.getInt(name, category, defaultValue, min, max, comment, langKey));
|
||||
} else if ((boxed = fieldClass.equals(Float.class)) || fieldClass.equals(float.class)) {
|
||||
val range = Optional.ofNullable(field.getAnnotation(Config.RangeFloat.class));
|
||||
val min = range.map(Config.RangeFloat::min).orElse(Float.MIN_VALUE);
|
||||
val max = range.map(Config.RangeFloat::max).orElse(Float.MAX_VALUE);
|
||||
val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultFloat.class))
|
||||
.map(Config.DefaultFloat::value)
|
||||
.orElse(boxed ? (Float) field.get(null) : field.getFloat(null));
|
||||
field.setFloat(null, rawConfig.getFloat(name, category, defaultValue, min, max, comment, langKey));
|
||||
} else if (fieldClass.equals(String.class)) {
|
||||
val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultString.class))
|
||||
.map(Config.DefaultString::value)
|
||||
.orElse((String) field.get(null));
|
||||
val pattern = Optional.ofNullable(field.getAnnotation(Config.Pattern.class))
|
||||
.map(Config.Pattern::value)
|
||||
.map(Pattern::compile)
|
||||
.orElse(null);
|
||||
field.set(null, rawConfig.getString(name, category, defaultValue, comment, langKey, pattern));
|
||||
} else if (fieldClass.isEnum()) {
|
||||
val enumValues = Arrays.stream((Object[]) fieldClass.getDeclaredMethod("values").invoke(null))
|
||||
.map((obj) -> (Enum<?>) obj)
|
||||
.collect(Collectors.toList());
|
||||
val defaultValue = (Enum<?>) Optional.ofNullable(field.getAnnotation(Config.DefaultEnum.class))
|
||||
.map(Config.DefaultEnum::value)
|
||||
.map((defName) -> extractField(fieldClass, defName))
|
||||
.map(ConfigurationManager::extractValue)
|
||||
.orElse(field.get(null));
|
||||
val possibleValues = enumValues.stream().map(Enum::name).toArray(String[]::new);
|
||||
var value = rawConfig.getString(name,
|
||||
category,
|
||||
defaultValue.name(),
|
||||
comment + "\nPossible values: " + Arrays.toString(possibleValues) +
|
||||
"\n",
|
||||
possibleValues,
|
||||
langKey);
|
||||
|
||||
try {
|
||||
if (!Arrays.asList(possibleValues).contains(value)) {
|
||||
throw new NoSuchFieldException();
|
||||
}
|
||||
val enumField = fieldClass.getDeclaredField(value);
|
||||
if (!enumField.isEnumConstant()) {
|
||||
throw new NoSuchFieldException();
|
||||
}
|
||||
field.set(null, enumField.get(null));
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOGGER
|
||||
.warn("Invalid value " + value + " for enum configuration field " + field.getName() +
|
||||
" of type " + fieldClass.getName() + " in config class " +
|
||||
configClass.getName() + "! Using default value of " + defaultValue + "!");
|
||||
field.set(null, defaultValue);
|
||||
}
|
||||
} else if (fieldClass.isArray() && fieldClass.getComponentType().equals(String.class)) {
|
||||
val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultStringList.class)).map(Config.DefaultStringList::value).orElse((String[])field.get(null));
|
||||
var value = rawConfig.getStringList(name, category, defaultValue, comment, null, langKey);
|
||||
field.set(null, value);
|
||||
} else {
|
||||
throw new ConfigException("Illegal config field: " + field.getName() + " in " + configClass.getName() +
|
||||
": Unsupported type " + fieldClass.getName() +
|
||||
"! Did you forget an @Ignore annotation?");
|
||||
}
|
||||
if (field.isAnnotationPresent(Config.RequiresMcRestart.class)) {
|
||||
cat.setRequiresMcRestart(true);
|
||||
}
|
||||
if (field.isAnnotationPresent(Config.RequiresWorldRestart.class)) {
|
||||
cat.setRequiresWorldRestart(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static Field extractField(Class<?> clazz, String field) {
|
||||
return clazz.getDeclaredField(field);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static Object extractValue(Field field) {
|
||||
return field.get(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the configuration into a list of config elements usable in config GUI code.
|
||||
*
|
||||
* @param configClass The class to process.
|
||||
*
|
||||
* @return The configuration elements.
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public static List<IConfigElement> getConfigElements(Class<?> configClass) throws ConfigException {
|
||||
init();
|
||||
val cfg = Optional.ofNullable(configClass.getAnnotation(Config.class))
|
||||
.orElseThrow(() -> new ConfigException(
|
||||
"Class " + configClass.getName() + " does not have a @Config annotation!"));
|
||||
val rawConfig = Optional.ofNullable(configs.get(cfg.modid()))
|
||||
.map((conf) -> Optional.ofNullable(configToClassMap.get(conf))
|
||||
.map((l) -> l.contains(configClass))
|
||||
.orElse(false) ? conf : null)
|
||||
.orElseThrow(() -> new ConfigException(
|
||||
"Tried to get config elements for non-registered config class!"));
|
||||
val category = cfg.category();
|
||||
val elements = new ConfigElement<>(rawConfig.getCategory(category)).getChildElements();
|
||||
return elements.stream().map((element) -> new IConfigElementProxy(element, () -> {
|
||||
try {
|
||||
processConfigInternal(configClass, category, rawConfig);
|
||||
rawConfig.save();
|
||||
} catch (IllegalAccessException |
|
||||
NoSuchMethodException |
|
||||
InvocationTargetException |
|
||||
NoSuchFieldException |
|
||||
ConfigException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
})).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static File minecraftHome() {
|
||||
return Launch.minecraftHome != null ? Launch.minecraftHome : new File(".");
|
||||
}
|
||||
|
||||
private static void init() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
configDir = minecraftHome().toPath().resolve("config");
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal, do not use.
|
||||
*/
|
||||
public static void registerBus() {
|
||||
FMLCommonHandler.instance().bus().register(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal, do not use.
|
||||
*
|
||||
* @param event The event.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event) {
|
||||
init();
|
||||
val config = configs.get(event.modID);
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
val configClasses = configToClassMap.get(config);
|
||||
configClasses.forEach((configClass) -> {
|
||||
try {
|
||||
val category = Optional.ofNullable(configClass.getAnnotation(Config.class))
|
||||
.map(Config::category)
|
||||
.orElseThrow(() -> new ConfigException(
|
||||
"Failed to get config category for class " + configClass.getName()));
|
||||
processConfigInternal(configClass, category, config);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.Config;
|
||||
|
||||
@Config(modid = "angelica", category = "customfont")
|
||||
public class FontConfig {
|
||||
@Config.Comment("Whether or not to use custom fonts when drawing text.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean enableCustomFont;
|
||||
|
||||
@Config.Comment("Name of the primary custom font. Best not to set it from here.")
|
||||
@Config.DefaultString("(none)")
|
||||
public static String customFontNamePrimary;
|
||||
|
||||
@Config.Comment("Name of the fallback custom font. Best not to set it from here.")
|
||||
@Config.DefaultString("(none)")
|
||||
public static String customFontNameFallback;
|
||||
|
||||
@Config.Comment("The quality at which custom fonts are rendered, making them less pixelated but increasing memory usage.")
|
||||
@Config.DefaultInt(30)
|
||||
@Config.RangeInt(min = 6, max = 72)
|
||||
public static int customFontQuality;
|
||||
|
||||
@Config.Comment("Controls the distance at which the font's shadow is drawn.")
|
||||
@Config.DefaultFloat(1F)
|
||||
@Config.RangeFloat(min = 0F, max = 2F)
|
||||
public static float fontShadowOffset;
|
||||
|
||||
@Config.Comment("The number of shadows to be drawn behind text at various offsets.")
|
||||
@Config.DefaultInt(1)
|
||||
@Config.RangeInt(min = 1, max = 8)
|
||||
public static int shadowCopies;
|
||||
|
||||
@Config.Comment("The number of bold copies to be drawn behind text at various offsets.")
|
||||
@Config.DefaultInt(2)
|
||||
@Config.RangeInt(min = 1, max = 8)
|
||||
public static int boldCopies;
|
||||
|
||||
@Config.Comment("Influences the aspect ratio of each glyph.")
|
||||
@Config.DefaultFloat(0F)
|
||||
@Config.RangeFloat(min = -1F, max = 1F)
|
||||
public static float glyphAspect;
|
||||
|
||||
@Config.Comment("Scale of each glyph, whitespace excluded.")
|
||||
@Config.DefaultFloat(1F)
|
||||
@Config.RangeFloat(min = 0.1F, max = 3F)
|
||||
public static float glyphScale;
|
||||
|
||||
@Config.Comment("Whitespace scale.")
|
||||
@Config.DefaultFloat(1F)
|
||||
@Config.RangeFloat(min = 0.1F, max = 3F)
|
||||
public static float whitespaceScale;
|
||||
|
||||
@Config.Comment("Adds extra spacing between glyphs.")
|
||||
@Config.DefaultFloat(0F)
|
||||
@Config.RangeFloat(min = -2F, max = 2F)
|
||||
public static float glyphSpacing;
|
||||
|
||||
@Config.Comment("Controls font antialiasing. 0 = none, 1 = 4x MSAA, 2 = 16x MSAA.")
|
||||
@Config.DefaultInt(2)
|
||||
@Config.RangeInt(min = 0, max = 2)
|
||||
public static int fontAAMode;
|
||||
|
||||
@Config.Comment("Affects font antialiasing sample spacing. Higher values increase blur.")
|
||||
@Config.DefaultInt(7)
|
||||
@Config.RangeInt(min = 1, max = 24)
|
||||
public static int fontAAStrength;
|
||||
|
||||
@Config.Comment("Custom font scale multiplier, for bugfixes.")
|
||||
@Config.DefaultFloat(1.5F)
|
||||
@Config.RangeFloat(min = 0.1F, max = 3F)
|
||||
public static float customFontScale;
|
||||
|
||||
@Config.Comment("A shadow offset multiplier applied only to the Unicode font.")
|
||||
@Config.DefaultFloat(0.5F)
|
||||
@Config.RangeFloat(min = 0F, max = 2F)
|
||||
public static float fontShadowOffsetUC;
|
||||
|
||||
@Config.Comment({"Mods can provide Angelica with a mapping of special characters to either get replaced with " +
|
||||
"different characters or rendered using the default Unicode font. Setting this to false disables the " +
|
||||
"replacement, which is useful in cases where custom fonts have direct support for these special characters. ",
|
||||
"See for additional details: https://github.com/GTNewHorizons/Angelica/issues/1239#issuecomment-3729877936"})
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean enableGlyphReplacements;
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import cpw.mods.fml.client.config.ConfigGuiType;
|
||||
import cpw.mods.fml.client.config.GuiConfigEntries;
|
||||
import cpw.mods.fml.client.config.GuiEditArrayEntries;
|
||||
import cpw.mods.fml.client.config.IConfigElement;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class IConfigElementProxy<T> implements IConfigElement<T> {
|
||||
private final IConfigElement<T> proxied;
|
||||
private final Runnable onUpdate;
|
||||
|
||||
public IConfigElementProxy(IConfigElement<T> proxied, Runnable onUpdate) {
|
||||
this.proxied = proxied;
|
||||
this.onUpdate = onUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProperty() {
|
||||
return proxied.isProperty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public Class<? extends GuiConfigEntries.IConfigEntry> getConfigEntryClass() {
|
||||
return proxied.getConfigEntryClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends GuiEditArrayEntries.IArrayEntry> getArrayEntryClass() {
|
||||
return proxied.getArrayEntryClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return proxied.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQualifiedName() {
|
||||
return proxied.getQualifiedName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguageKey() {
|
||||
return proxied.getLanguageKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
return proxied.getComment();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public List<IConfigElement> getChildElements() {
|
||||
return proxied.getChildElements();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigGuiType getType() {
|
||||
return proxied.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isList() {
|
||||
return proxied.isList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isListLengthFixed() {
|
||||
return proxied.isListLengthFixed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxListLength() {
|
||||
return proxied.getMaxListLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefault() {
|
||||
return proxied.isDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDefault() {
|
||||
return proxied.getDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getDefaults() {
|
||||
return proxied.getDefaults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToDefault() {
|
||||
proxied.setToDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresWorldRestart() {
|
||||
return proxied.requiresWorldRestart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showInGui() {
|
||||
return proxied.showInGui();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresMcRestart() {
|
||||
return proxied.requiresMcRestart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get() {
|
||||
return proxied.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getList() {
|
||||
return proxied.getList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T value) {
|
||||
proxied.set(value);
|
||||
onUpdate.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T[] aVal) {
|
||||
proxied.set(aVal);
|
||||
onUpdate.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getValidValues() {
|
||||
return proxied.getValidValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getMinValue() {
|
||||
return proxied.getMinValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getMaxValue() {
|
||||
return proxied.getMaxValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pattern getValidationPattern() {
|
||||
return proxied.getValidationPattern();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import cpw.mods.fml.client.config.GuiConfig;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class SimpleGuiConfig extends GuiConfig {
|
||||
public SimpleGuiConfig(GuiScreen parent, Class<?> configClass, String modID, String modName) throws ConfigException {
|
||||
super(parent, ConfigurationManager.getConfigElements(configClass), modID, false, false, modName + " Configuration");
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import cpw.mods.fml.client.IModGuiFactory;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public interface SimpleGuiFactory extends IModGuiFactory {
|
||||
@Override
|
||||
default void initialize(Minecraft minecraftInstance) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
default Set<RuntimeOptionCategoryElement> runtimeGuiCategories() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
default RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Adapated from: Beddium for usage in Angelica
|
||||
*
|
||||
* Copyright (C) 2025 Ven, FalsePattern
|
||||
* All Rights Reserved
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, only version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaRenderSectionManager;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ChunkProviderClient;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.world.chunk.EmptyChunk;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Debug overlay that renders a minimap showing chunk loading and rendering state. Toggle via /angelica minimap
|
||||
*/
|
||||
public class ChunkDebugMinimap {
|
||||
private static final ChunkDebugMinimap INSTANCE = new ChunkDebugMinimap();
|
||||
@Getter
|
||||
private static volatile boolean enabled = false;
|
||||
|
||||
private static final int RANGE = 32; // Chunks in each direction from player
|
||||
|
||||
public static synchronized void toggle() {
|
||||
if (enabled) disable();
|
||||
else enable();
|
||||
}
|
||||
|
||||
public static synchronized void enable() {
|
||||
if (enabled) return;
|
||||
enabled = true;
|
||||
MinecraftForge.EVENT_BUS.register(INSTANCE);
|
||||
}
|
||||
|
||||
public static synchronized void disable() {
|
||||
if (!enabled) return;
|
||||
enabled = false;
|
||||
MinecraftForge.EVENT_BUS.unregister(INSTANCE);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onRenderOverlay(RenderGameOverlayEvent.Text event) {
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
if (mc.theWorld == null || mc.thePlayer == null) return;
|
||||
|
||||
final CeleritasWorldRenderer renderer;
|
||||
try {
|
||||
renderer = CeleritasWorldRenderer.getInstance();
|
||||
} catch (IllegalStateException e) {
|
||||
return; // Renderer not initialized
|
||||
}
|
||||
|
||||
final AngelicaRenderSectionManager manager = renderer.getRenderSectionManager();
|
||||
if (manager == null) return;
|
||||
|
||||
final float partialTicks = event.partialTicks;
|
||||
final double pX = mc.thePlayer.prevPosX + (mc.thePlayer.posX - mc.thePlayer.prevPosX) * partialTicks;
|
||||
final double pZ = mc.thePlayer.prevPosZ + (mc.thePlayer.posZ - mc.thePlayer.prevPosZ) * partialTicks;
|
||||
|
||||
final int playerChunkX = ((int) Math.floor(pX)) >> 4;
|
||||
final int playerChunkZ = ((int) Math.floor(pZ)) >> 4;
|
||||
|
||||
// Save GL state
|
||||
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
|
||||
GLStateManager.glPushMatrix();
|
||||
|
||||
// Setup rendering
|
||||
GLStateManager.glScaled(2, 2, 1);
|
||||
GLStateManager.glTranslated(32, 32, 0);
|
||||
|
||||
GLStateManager.disableLighting();
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
tess.startDrawingQuads();
|
||||
|
||||
final ChunkProviderClient chunkProvider = (ChunkProviderClient) mc.theWorld.getChunkProvider();
|
||||
|
||||
for (int x = playerChunkX - RANGE; x < playerChunkX + RANGE; x++) {
|
||||
for (int z = playerChunkZ - RANGE; z < playerChunkZ + RANGE; z++) {
|
||||
drawChunk(tess, x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
|
||||
}
|
||||
}
|
||||
|
||||
tess.draw();
|
||||
|
||||
// Restore GL state
|
||||
GLStateManager.enableLighting();
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glPopAttrib();
|
||||
}
|
||||
|
||||
private void drawChunk(Tessellator tess, int chunkX, int chunkZ, int playerChunkX, int playerChunkZ, AngelicaRenderSectionManager manager, ChunkProviderClient chunkProvider) {
|
||||
boolean initialized = false;
|
||||
float r = 0, g = 0, b = 0;
|
||||
|
||||
// Check all 16 sections in the chunk column
|
||||
for (int y = 0; y < 16; y++) {
|
||||
if (manager.isSectionBuilt(chunkX, y, chunkZ) && !manager.isSectionVisuallyEmpty(chunkX, y, chunkZ)) {
|
||||
initialized = true;
|
||||
g = 1; // Green = built
|
||||
if (manager.isSectionVisible(chunkX, y, chunkZ)) {
|
||||
b = 1; // Cyan = visible
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
// Check if chunk is loaded
|
||||
final var chunk = chunkProvider.provideChunk(chunkX, chunkZ);
|
||||
if (chunk instanceof EmptyChunk) {
|
||||
r = 1; // Red = unloaded
|
||||
} else {
|
||||
b = 1; // Blue = loaded but not built
|
||||
}
|
||||
}
|
||||
|
||||
tess.setColorRGBA_F(r, g, b, 0.8f);
|
||||
|
||||
final int xStart = chunkX - playerChunkX;
|
||||
final int zStart = chunkZ - playerChunkZ;
|
||||
final int xEnd = xStart + 1;
|
||||
final int zEnd = zStart + 1;
|
||||
|
||||
tess.addVertex(xStart, zStart, 0);
|
||||
tess.addVertex(xStart, zEnd, 0);
|
||||
tess.addVertex(xEnd, zEnd, 0);
|
||||
tess.addVertex(xEnd, zStart, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public class F3Direction {
|
||||
public static void renderWorldDirectionsEvent(Minecraft mc, RenderGameOverlayEvent.Pre event) {
|
||||
if (mc.gameSettings.showDebugInfo && mc.gameSettings.thirdPersonView == 0) {
|
||||
final ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
final int width = scaledresolution.getScaledWidth();
|
||||
final int height = scaledresolution.getScaledHeight();
|
||||
|
||||
GLStateManager.glPushMatrix();
|
||||
|
||||
GLStateManager.glTranslatef((float) (width / 2), (float) (height / 2), -90);
|
||||
|
||||
final Entity entity = mc.renderViewEntity;
|
||||
GLStateManager.glRotatef(entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * event.partialTicks, -1.0F, 0.0F, 0.0F);
|
||||
GLStateManager.glRotatef(entity.prevRotationYaw + (entity.rotationYaw - entity.prevRotationYaw) * event.partialTicks, 0.0F, 1.0F, 0.0F);
|
||||
|
||||
GLStateManager.glScalef(-1.0F, -1.0F, -1.0F);
|
||||
|
||||
renderWorldDirections();
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
}
|
||||
public static void renderWorldDirections() {
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glDepthMask(false);
|
||||
final Tessellator tessellator = Tessellator.instance;
|
||||
|
||||
GLStateManager.glLineWidth(2.0F);
|
||||
tessellator.startDrawing(GL11.GL_LINES);
|
||||
|
||||
//X
|
||||
tessellator.setColorRGBA_F(255, 0, 0, 1);
|
||||
tessellator.addVertex(0.0D, 0.0D, 0.0D);
|
||||
tessellator.addVertex(10, 0.0D, 0.0D);
|
||||
|
||||
//Z
|
||||
tessellator.setColorRGBA_F(0, 0, 255, 1);
|
||||
tessellator.addVertex(0.0D, 0.0D, 0.0D);
|
||||
tessellator.addVertex(0.0D, 0.0D, 10);
|
||||
|
||||
//Y
|
||||
tessellator.setColorRGBA_F(0, 255, 0, 1);
|
||||
tessellator.addVertex(0.0D, 0.0D, 0.0D);
|
||||
tessellator.addVertex(0.0D, 10, 0.0D);
|
||||
|
||||
tessellator.draw();
|
||||
|
||||
GLStateManager.glLineWidth(1.0F);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.enableTexture();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
|
||||
import static com.gtnewhorizons.angelica.glsm.GLStateManager.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL11.GL_FLOAT;
|
||||
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
|
||||
import static org.lwjgl.opengl.GL15.glBufferData;
|
||||
import static org.lwjgl.opengl.GL15.glGenBuffers;
|
||||
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1f;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1i;
|
||||
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
|
||||
|
||||
public abstract class F3Graph {
|
||||
private static final int NUM_SAMPLES = 240;
|
||||
// Two floats (x,y)
|
||||
private static final int VERT_FLOATS = 2;
|
||||
private static final int VERT_COUNT = 4;
|
||||
private static final int BORDER = 1;
|
||||
private static final int HEIGHT = 60;
|
||||
private static final int WIDTH = NUM_SAMPLES;
|
||||
private static final int FONT_COLOR = 0xFFE0E0E0;
|
||||
// Due to GLSL 120 limitations, it's just easier to use floats
|
||||
private final FloatBuffer sampleBuf = BufferUtils.createFloatBuffer(NUM_SAMPLES);
|
||||
private final long[] samples = new long[NUM_SAMPLES]; // long version for calculations
|
||||
// Circular buffer holding the last 240 samples, in nanoseconds
|
||||
private int samplesHead = 0; // one ahead of the position of the last sample
|
||||
private boolean initialized = false;
|
||||
private ShaderProgram shader;
|
||||
private int aPos;
|
||||
private int uFBWidth;
|
||||
private int uFBHeight;
|
||||
private int uScaleFactor;
|
||||
private int uHeadIdx;
|
||||
private int uSamples;
|
||||
private int vertBuf;
|
||||
private int vao;
|
||||
private final ResourceLocation texture;
|
||||
private final float pxPerNs;
|
||||
private final boolean left;
|
||||
private long sum;
|
||||
|
||||
protected F3Graph(ResourceLocation texture, float pxPerNs, boolean left) {
|
||||
this.texture = texture;
|
||||
this.pxPerNs = pxPerNs;
|
||||
this.left = left;
|
||||
}
|
||||
|
||||
public void putSample(long time) {
|
||||
// Manage running trackers
|
||||
sum -= samples[samplesHead];
|
||||
sum += time;
|
||||
samples[samplesHead] = time;
|
||||
|
||||
sampleBuf.put(samplesHead, (float) time);
|
||||
samplesHead = (samplesHead + 1) % NUM_SAMPLES;
|
||||
}
|
||||
|
||||
private int getVertX(ScaledResolution sres, int idx) {
|
||||
if (left) {
|
||||
return switch (idx) {
|
||||
case 0, 3 -> WIDTH + BORDER * 2;
|
||||
case 1, 2 -> 0;
|
||||
default -> throw new RuntimeException("Tried to get out-of-bounds vertex for graph!");
|
||||
};
|
||||
}
|
||||
|
||||
final int displayWidth = sres.getScaledWidth();
|
||||
return switch (idx) {
|
||||
case 0, 3 -> displayWidth;
|
||||
case 1, 2 -> displayWidth - WIDTH - BORDER * 2;
|
||||
default -> throw new RuntimeException("Tried to get out-of-bounds vertex for graph!");
|
||||
};
|
||||
}
|
||||
|
||||
private int getVertY(ScaledResolution sres, int idx) {
|
||||
final int displayHeight = sres.getScaledHeight();
|
||||
return switch (idx) {
|
||||
case 0, 1 -> displayHeight - HEIGHT - BORDER * 2;
|
||||
case 2, 3 -> displayHeight;
|
||||
default -> throw new RuntimeException("Tried to get out-of-bounds vertex for graph!");
|
||||
};
|
||||
}
|
||||
|
||||
private void init() {
|
||||
shader = new ShaderProgram(
|
||||
"angelica",
|
||||
"shaders/debug_graph.vert.glsl",
|
||||
"shaders/debug_graph.frag.glsl");
|
||||
shader.use();
|
||||
|
||||
// Attribute location is declared in shader via layout(location = 0)
|
||||
aPos = 0;
|
||||
|
||||
// Register uniforms
|
||||
uFBWidth = shader.getUniformLocation("fbWidth");
|
||||
uFBHeight = shader.getUniformLocation("fbHeight");
|
||||
uScaleFactor = shader.getUniformLocation("scaleFactor");
|
||||
uSamples = shader.getUniformLocation("samples");
|
||||
uHeadIdx = shader.getUniformLocation("headIdx");
|
||||
final int uPxPerNs = shader.getUniformLocation("pxPerNs");
|
||||
final int uLeft = shader.getUniformLocation("left");
|
||||
|
||||
vao = GL30.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
vertBuf = glGenBuffers();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
|
||||
try (final MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer vertices = stack.mallocFloat(VERT_COUNT * VERT_FLOATS);
|
||||
// Since we use a triangle strip, we only need 4 vertices. The quad extends to the top of the screen so spikes
|
||||
// don't get truncated. The max height is replaced in the vert shader, no need to be precise.
|
||||
vertices.put(new float[]{
|
||||
BORDER, BORDER,
|
||||
WIDTH + BORDER, BORDER,
|
||||
BORDER, Float.MAX_VALUE,
|
||||
WIDTH + BORDER, Float.MAX_VALUE
|
||||
});
|
||||
vertices.rewind();
|
||||
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
// Configure vertex attribute in the VAO
|
||||
glVertexAttribPointer(aPos, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
|
||||
glEnableVertexAttribArray(aPos);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
|
||||
// Load initial value for uniforms
|
||||
glUniform1f(uFBWidth, mc.displayWidth);
|
||||
glUniform1f(uFBHeight, mc.displayHeight);
|
||||
glUniform1f(uScaleFactor, sr.getScaleFactor());
|
||||
glUniform1i(uHeadIdx, samplesHead);
|
||||
glUniform1(uSamples, sampleBuf);
|
||||
glUniform1f(uPxPerNs, pxPerNs);
|
||||
glUniform1i(uLeft, left ? 1 : 0); // this is how you load bool uniforms
|
||||
|
||||
ShaderProgram.clear();
|
||||
}
|
||||
|
||||
public void render() {
|
||||
if (!initialized) {
|
||||
init();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* We try to copy modern vanilla's tracker.
|
||||
* It is 240 (FRAME_TIMES) wide by 60 tall, including the 1px borders.
|
||||
* We instead make it 242x62 including borders to prevent the borders from overlapping the samples.
|
||||
* The background is ARGB 90505050, the borders FFFFFFFF, and the text FFE0E0E0.
|
||||
* First the background is rendered, then the samples, then the foreground texture and the text.
|
||||
* The background is drawn via a 242x62 translucent rect.
|
||||
* Next, the samples are rendered by a shader on a transparent 240x60 rect.
|
||||
* Finally, the foreground texture and the text is rendered on top.
|
||||
* The shader pipeline is only needed for the sample draw.
|
||||
*/
|
||||
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
|
||||
// Setup GL state
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Draw background
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glColor4f(0x50 / 255.0F, 0x50 / 255.0F, 0x50 / 255.0F, 0x90 / 255.0F);
|
||||
tess.startDrawingQuads();
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
tess.addVertex(getVertX(sr, i), getVertY(sr, i), 0);
|
||||
}
|
||||
tess.draw();
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.enableTexture();
|
||||
|
||||
|
||||
// Draw samples
|
||||
shader.use();
|
||||
|
||||
// Load uniforms
|
||||
glUniform1f(uFBWidth, mc.displayWidth);
|
||||
glUniform1f(uFBHeight, mc.displayHeight);
|
||||
glUniform1f(uScaleFactor, sr.getScaleFactor());
|
||||
glUniform1i(uHeadIdx, samplesHead);
|
||||
glUniform1(uSamples, sampleBuf);
|
||||
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
// Left side graphs are inverted, so temporarily disable culling
|
||||
if (!left) GLStateManager.disableCull();
|
||||
|
||||
GLStateManager.glDrawArrays(GL_TRIANGLE_STRIP, 0, VERT_COUNT);
|
||||
|
||||
if (!left) GLStateManager.enableCull();
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
ShaderProgram.clear();
|
||||
|
||||
// Draw foreground
|
||||
|
||||
mc.getTextureManager().bindTexture(texture);
|
||||
tess.startDrawingQuads();
|
||||
tess.addVertexWithUV(getVertX(sr, 0), getVertY(sr, 0), 0.0D, 1, 0);
|
||||
tess.addVertexWithUV(getVertX(sr, 1), getVertY(sr, 1), 0.0D, 0, 0);
|
||||
tess.addVertexWithUV(getVertX(sr, 2), getVertY(sr, 2), 0.0D, 0, 1);
|
||||
tess.addVertexWithUV(getVertX(sr, 3), getVertY(sr, 3), 0.0D, 1, 1);
|
||||
tess.draw();
|
||||
|
||||
// Reset GL state
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.disableBlend();
|
||||
|
||||
// Draw text
|
||||
// Ensure running counters are reset on first sample
|
||||
long min = Long.MAX_VALUE;
|
||||
long max = Long.MIN_VALUE;
|
||||
for (int i = 0; i < NUM_SAMPLES; ++i) {
|
||||
min = Math.min(min, samples[i]);
|
||||
max = Math.max(max, samples[i]);
|
||||
}
|
||||
String minStr = String.format("%.1f ms min", min / 1_000_000D);
|
||||
String avgStr = String.format("%.1f ms avg", sum / 1_000_000 / (double) NUM_SAMPLES);
|
||||
String maxStr = String.format("%.1f ms max", max / 1_000_000D);
|
||||
final FontRenderer fr = mc.fontRenderer;
|
||||
final int top = sr.getScaledHeight() - HEIGHT - BORDER * 2 - fr.FONT_HEIGHT;
|
||||
final int scaledWidth = sr.getScaledWidth();
|
||||
|
||||
if (left) {
|
||||
fr.drawString(minStr, BORDER * 2, top, FONT_COLOR, true);
|
||||
fr.drawString(avgStr, BORDER + WIDTH / 2 - fr.getStringWidth(avgStr) / 2, top, FONT_COLOR, true);
|
||||
fr.drawString(maxStr, BORDER * 2 + WIDTH - fr.getStringWidth(maxStr), top, FONT_COLOR, true);
|
||||
} else {
|
||||
fr.drawString(minStr, scaledWidth - (BORDER * 2 + WIDTH), top, FONT_COLOR, true);
|
||||
fr.drawString(avgStr, scaledWidth - (BORDER + WIDTH / 2 + fr.getStringWidth(avgStr) / 2), top, FONT_COLOR, true);
|
||||
fr.drawString(maxStr, scaledWidth - (BORDER * 2 + fr.getStringWidth(maxStr)), top, FONT_COLOR, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public class FrametimeGraph extends F3Graph {
|
||||
public FrametimeGraph() {
|
||||
// At 1x scale, 30 FPS should be 60 px. 30 FPS = 33_333_333ns per frame, 60px/33_333_333ns = 0.0000018f px/ns
|
||||
super(
|
||||
new ResourceLocation("angelica:textures/frametimes_fg.png"),
|
||||
0.0000018f,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public class TPSGraph extends F3Graph {
|
||||
public TPSGraph() {
|
||||
// At 1x scale, 20 TPS should be 60 px. 20 FPS = 50_000_000ns per frame, 60px/50_000_000ns = 0.0000012 px/ns
|
||||
super(
|
||||
new ResourceLocation("angelica:textures/tps_fg.png"),
|
||||
0.0000012f,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Calculates the appropriate tick mode for a dynamic light source based on distance from camera and position relative to camera direction.
|
||||
*/
|
||||
public final class AdaptiveTickCalculator {
|
||||
|
||||
// Distance thresholds in blocks (configurable via settings)
|
||||
private static int slowDistance = 32; // ~2 chunks
|
||||
private static int slowerDistance = 64; // ~4 chunks
|
||||
private static int backgroundDistance = 128; // ~8 chunks
|
||||
|
||||
// Squared thresholds for efficient distance comparison
|
||||
private static int slowDistanceSq = slowDistance * slowDistance;
|
||||
private static int slowerDistanceSq = slowerDistance * slowerDistance;
|
||||
private static int backgroundDistanceSq = backgroundDistance * backgroundDistance;
|
||||
|
||||
private AdaptiveTickCalculator() {}
|
||||
|
||||
/**
|
||||
* Calculate the appropriate tick mode for a light source.
|
||||
*/
|
||||
public static AdaptiveTickMode calculate(@NotNull IDynamicLightSource source, double cameraX, double cameraY, double cameraZ, double lookDirX, double lookDirZ) {
|
||||
double dx = source.angelica$getDynamicLightX() - cameraX;
|
||||
double dy = source.angelica$getDynamicLightY() - cameraY;
|
||||
double dz = source.angelica$getDynamicLightZ() - cameraZ;
|
||||
|
||||
double distSq = dx * dx + dy * dy + dz * dz;
|
||||
|
||||
// Check if behind camera (dot product of direction to source with look direction)
|
||||
// Only use X and Z for horizontal check (more stable as player looks up/down)
|
||||
double dot = dx * lookDirX + dz * lookDirZ;
|
||||
boolean behindCamera = dot < 0;
|
||||
|
||||
// Behind camera always gets BACKGROUND mode
|
||||
if (behindCamera) {
|
||||
return AdaptiveTickMode.BACKGROUND;
|
||||
}
|
||||
|
||||
// Distance-based mode selection
|
||||
if (distSq > backgroundDistanceSq) {
|
||||
return AdaptiveTickMode.BACKGROUND;
|
||||
}
|
||||
if (distSq > slowerDistanceSq) {
|
||||
return AdaptiveTickMode.SLOWER;
|
||||
}
|
||||
if (distSq > slowDistanceSq) {
|
||||
return AdaptiveTickMode.SLOW;
|
||||
}
|
||||
|
||||
return AdaptiveTickMode.REAL_TIME;
|
||||
}
|
||||
|
||||
public static void setSlowDistance(int distance) {
|
||||
slowDistance = distance;
|
||||
slowDistanceSq = distance * distance;
|
||||
}
|
||||
|
||||
public static void setSlowerDistance(int distance) {
|
||||
slowerDistance = distance;
|
||||
slowerDistanceSq = distance * distance;
|
||||
}
|
||||
|
||||
public static void setBackgroundDistance(int distance) {
|
||||
backgroundDistance = distance;
|
||||
backgroundDistanceSq = distance * distance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
/**
|
||||
* Defines tick modes for adaptive dynamic light updates. Distant or off-screen light sources update less frequently to save CPU.
|
||||
*/
|
||||
public enum AdaptiveTickMode {
|
||||
/** Updates every tick - for nearby, visible light sources */
|
||||
REAL_TIME(1),
|
||||
/** Updates every 5 ticks - for light sources at moderate distance */
|
||||
SLOW(5),
|
||||
/** Updates every 10 ticks - for light sources at far distance */
|
||||
SLOWER(10),
|
||||
/** Updates every 20 ticks - for light sources behind the camera or very far */
|
||||
BACKGROUND(20);
|
||||
|
||||
private final int delay;
|
||||
|
||||
AdaptiveTickMode(int delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
public int getDelay() {
|
||||
return delay;
|
||||
}
|
||||
|
||||
public boolean shouldTickThisFrame(int worldTick, int sourceHash) {
|
||||
if (delay <= 1) {
|
||||
return true;
|
||||
}
|
||||
return (worldTick % delay) == Math.abs(sourceHash % delay);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.Viewport;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* Manages deferred chunk rebuilds for dynamic lights with optional frustum culling. Instead of scheduling chunk rebuilds immediately
|
||||
* when light sources move, this tracks pending rebuilds and only schedules visible chunks.
|
||||
*/
|
||||
public class ChunkRebuildManager {
|
||||
|
||||
/** Maps packed chunk position to ticks waiting */
|
||||
private final Long2IntOpenHashMap pendingRebuilds = new Long2IntOpenHashMap();
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
/** Maximum ticks a pending rebuild can wait before being forced through */
|
||||
private static int maxTicksWaiting = 100;
|
||||
|
||||
/** Reusable lists for processVisible - only used from render thread */
|
||||
private final LongArrayList candidates = new LongArrayList();
|
||||
private final LongArrayList toRebuild = new LongArrayList();
|
||||
private final LongArrayList toIncrement = new LongArrayList();
|
||||
|
||||
public static void setMaxTicksWaiting(int ticks) {
|
||||
maxTicksWaiting = Math.max(20, Math.min(ticks, 600)); // Clamp 1-30 seconds (20-600 ticks)
|
||||
}
|
||||
|
||||
public void requestRebuild(int x, int y, int z) {
|
||||
final long packed = CoordinatePacker.pack(x, y, z);
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Reset wait counter if already pending, otherwise add new entry
|
||||
pendingRebuilds.put(packed, 0);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void processVisible(Viewport viewport, @NotNull IDynamicLightWorldRenderer renderer) {
|
||||
candidates.clear();
|
||||
toRebuild.clear();
|
||||
toIncrement.clear();
|
||||
|
||||
// Phase 1: Collect all pending chunks with read lock (fast)
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
if (pendingRebuilds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// Collect candidates that might need processing
|
||||
final var iterator = pendingRebuilds.long2IntEntrySet().fastIterator();
|
||||
while (iterator.hasNext()) {
|
||||
final var entry = iterator.next();
|
||||
final long packed = entry.getLongKey();
|
||||
final int ticksWaiting = entry.getIntValue();
|
||||
|
||||
// Force process if no viewport or waited too long
|
||||
if (viewport == null || ticksWaiting >= maxTicksWaiting) {
|
||||
toRebuild.add(packed);
|
||||
} else {
|
||||
candidates.add(packed);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
|
||||
// Phase 2: Check visibility
|
||||
for (int i = 0; i < candidates.size(); i++) {
|
||||
long packed = candidates.getLong(i);
|
||||
int x = CoordinatePacker.unpackX(packed);
|
||||
int y = CoordinatePacker.unpackY(packed);
|
||||
int z = CoordinatePacker.unpackZ(packed);
|
||||
|
||||
if (isChunkVisible(viewport, x, y, z)) {
|
||||
toRebuild.add(packed);
|
||||
} else {
|
||||
toIncrement.add(packed);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Update map with write lock (brief)
|
||||
if (!toRebuild.isEmpty() || !toIncrement.isEmpty()) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Remove chunks we're going to rebuild
|
||||
LongIterator rebuildIter = toRebuild.iterator();
|
||||
while (rebuildIter.hasNext()) {
|
||||
pendingRebuilds.remove(rebuildIter.nextLong());
|
||||
}
|
||||
LongIterator incIter = toIncrement.iterator();
|
||||
while (incIter.hasNext()) {
|
||||
long packed = incIter.nextLong();
|
||||
int current = pendingRebuilds.get(packed);
|
||||
if (pendingRebuilds.containsKey(packed)) {
|
||||
pendingRebuilds.put(packed, current + 1);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 4: Schedule rebuilds outside of lock
|
||||
for (int i = 0; i < toRebuild.size(); i++) {
|
||||
final long packed = toRebuild.getLong(i);
|
||||
renderer.scheduleRebuildForChunk(
|
||||
CoordinatePacker.unpackX(packed),
|
||||
CoordinatePacker.unpackY(packed),
|
||||
CoordinatePacker.unpackZ(packed),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isChunkVisible(Viewport viewport, int chunkX, int chunkY, int chunkZ) {
|
||||
// Convert chunk section coords to block coords
|
||||
int blockX = chunkX << 4;
|
||||
int blockY = chunkY << 4;
|
||||
int blockZ = chunkZ << 4;
|
||||
|
||||
// Check if the 16x16x16 chunk section box is visible
|
||||
return viewport.isBoxVisible(
|
||||
blockX, blockY, blockZ,
|
||||
blockX + 16, blockY + 16, blockZ + 16
|
||||
);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
pendingRebuilds.clear();
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public int getPendingCount() {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return pendingRebuilds.size();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
import baubles.common.lib.PlayerHandler;
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.IBlockPos;
|
||||
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
|
||||
import com.gtnewhorizons.angelica.api.IDynamicLightProducer;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import com.gtnewhorizons.angelica.compat.battlegear2.Battlegear2Compat;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.PrimedEntityAccessor;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import mods.battlegear2.api.core.IBattlePlayer;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.item.EntityItem;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.item.ItemBlock;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.Viewport;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class DynamicLights {
|
||||
private static DynamicLights instance;
|
||||
private static IDynamicLightWorldRenderer activeRenderer;
|
||||
public static DynamicLightsMode Mode = DynamicLightsMode.OFF;
|
||||
public static boolean ShaderForce = false;
|
||||
public static boolean FrustumCullingEnabled = true;
|
||||
public static boolean AdaptiveTickingEnabled = true;
|
||||
|
||||
public static final boolean configEnabled = AngelicaConfig.enableDynamicLights;
|
||||
|
||||
private static final double MAX_RADIUS = 7.75;
|
||||
private static final double MAX_RADIUS_SQUARED = MAX_RADIUS * MAX_RADIUS;
|
||||
private final Set<IDynamicLightSource> dynamicLightSources = new ObjectOpenHashSet<>();
|
||||
private final ReentrantReadWriteLock lightSourcesLock = new ReentrantReadWriteLock();
|
||||
private volatile int lightSourceCount = 0;
|
||||
private final ChunkRebuildManager chunkRebuildManager = new ChunkRebuildManager();
|
||||
private long lastUpdate = System.currentTimeMillis();
|
||||
private int lastUpdateCount = 0;
|
||||
|
||||
public static DynamicLights get() {
|
||||
if (instance == null)
|
||||
instance = new DynamicLights();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return configEnabled &&
|
||||
Mode.isEnabled() &&
|
||||
// if shader force is enabled then true
|
||||
// if not, then true when shaders are not in use
|
||||
(ShaderForce || !IrisApi.getInstance().isShaderPackInUse());
|
||||
}
|
||||
|
||||
public boolean hasLightSources() {
|
||||
return lightSourceCount > 0;
|
||||
}
|
||||
|
||||
public static IDynamicLightWorldRenderer getActiveRenderer() {
|
||||
return activeRenderer;
|
||||
}
|
||||
|
||||
public static void setActiveRenderer(IDynamicLightWorldRenderer renderer) {
|
||||
activeRenderer = renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all light sources.
|
||||
*
|
||||
* @param renderer the renderer
|
||||
*/
|
||||
public void updateAll(@NotNull IDynamicLightWorldRenderer renderer) {
|
||||
if (!isEnabled())
|
||||
return;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if (now >= this.lastUpdate + Mode.getDelay()) {
|
||||
this.lastUpdate = now;
|
||||
this.lastUpdateCount = 0;
|
||||
|
||||
// Get camera info for adaptive ticking
|
||||
double cameraX = 0, cameraY = 0, cameraZ = 0;
|
||||
double lookDirX = 0, lookDirZ = 1;
|
||||
int worldTick = 0;
|
||||
|
||||
if (AdaptiveTickingEnabled) {
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
if (mc.theWorld != null && mc.renderViewEntity != null) {
|
||||
cameraX = mc.renderViewEntity.posX;
|
||||
cameraY = mc.renderViewEntity.posY + mc.renderViewEntity.getEyeHeight();
|
||||
cameraZ = mc.renderViewEntity.posZ;
|
||||
worldTick = (int) (mc.theWorld.getTotalWorldTime() & 0x7FFFFFFF);
|
||||
|
||||
// Calculate look direction from yaw (normalized horizontal direction)
|
||||
float yaw = mc.renderViewEntity.rotationYaw;
|
||||
double yawRad = Math.toRadians(yaw);
|
||||
lookDirX = -Math.sin(yawRad);
|
||||
lookDirZ = Math.cos(yawRad);
|
||||
}
|
||||
}
|
||||
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
for (var lightSource : this.dynamicLightSources) {
|
||||
// Entity type filter
|
||||
if (!EntityLightConfig.isEntityTypeEnabled(lightSource)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Adaptive ticking filter
|
||||
if (AdaptiveTickingEnabled) {
|
||||
AdaptiveTickMode mode = AdaptiveTickCalculator.calculate(
|
||||
lightSource, cameraX, cameraY, cameraZ, lookDirX, lookDirZ);
|
||||
if (!mode.shouldTickThisFrame(worldTick, lightSource.hashCode())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (lightSource.angelica$updateDynamicLight(renderer))
|
||||
this.lastUpdateCount++;
|
||||
}
|
||||
this.lightSourcesLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public int getLastUpdateCount() {
|
||||
return this.lastUpdateCount;
|
||||
}
|
||||
|
||||
public void addLightSource(IDynamicLightSource lightSource) {
|
||||
this.lightSourcesLock.writeLock().lock();
|
||||
if (this.dynamicLightSources.add(lightSource)) {
|
||||
this.lightSourceCount = this.dynamicLightSources.size();
|
||||
}
|
||||
this.lightSourcesLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the light source from the tracked light sources.
|
||||
*
|
||||
* @param lightSource the light source to remove
|
||||
*/
|
||||
public void removeLightSource(@NotNull IDynamicLightSource lightSource) {
|
||||
this.lightSourcesLock.writeLock().lock();
|
||||
|
||||
var dynamicLightSources = this.dynamicLightSources.iterator();
|
||||
IDynamicLightSource it;
|
||||
while (dynamicLightSources.hasNext()) {
|
||||
it = dynamicLightSources.next();
|
||||
if (it.equals(lightSource)) {
|
||||
dynamicLightSources.remove();
|
||||
this.lightSourceCount = this.dynamicLightSources.size();
|
||||
if (activeRenderer != null)
|
||||
lightSource.angelica$scheduleTrackedChunksRebuild(activeRenderer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.lightSourcesLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes light sources if the filter matches.
|
||||
*
|
||||
* @param filter the removal filter
|
||||
*/
|
||||
public void removeLightSources(@NotNull Predicate<IDynamicLightSource> filter) {
|
||||
this.lightSourcesLock.writeLock().lock();
|
||||
|
||||
var dynamicLightSources = this.dynamicLightSources.iterator();
|
||||
IDynamicLightSource it;
|
||||
while (dynamicLightSources.hasNext()) {
|
||||
it = dynamicLightSources.next();
|
||||
if (filter.test(it)) {
|
||||
dynamicLightSources.remove();
|
||||
this.lightSourceCount = this.dynamicLightSources.size();
|
||||
if (activeRenderer != null) {
|
||||
if (it.angelica$getLuminance() > 0)
|
||||
it.angelica$resetDynamicLight();
|
||||
it.angelica$scheduleTrackedChunksRebuild(activeRenderer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.lightSourcesLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears light sources.
|
||||
*/
|
||||
public void clearLightSources() {
|
||||
this.lightSourcesLock.writeLock().lock();
|
||||
|
||||
var dynamicLightSources = this.dynamicLightSources.iterator();
|
||||
IDynamicLightSource it;
|
||||
while (dynamicLightSources.hasNext()) {
|
||||
it = dynamicLightSources.next();
|
||||
dynamicLightSources.remove();
|
||||
if (activeRenderer != null) {
|
||||
if (it.angelica$getLuminance() > 0)
|
||||
it.angelica$resetDynamicLight();
|
||||
it.angelica$scheduleTrackedChunksRebuild(activeRenderer);
|
||||
}
|
||||
}
|
||||
this.lightSourceCount = 0;
|
||||
|
||||
this.lightSourcesLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the light source is tracked or not.
|
||||
*
|
||||
* @param lightSource the light source to check
|
||||
* @return {@code true} if the light source is tracked, else {@code false}
|
||||
*/
|
||||
public boolean containsLightSource(@NotNull IDynamicLightSource lightSource) {
|
||||
boolean result;
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
result = this.dynamicLightSources.contains(lightSource);
|
||||
this.lightSourcesLock.readLock().unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of dynamic light sources that currently emit lights.
|
||||
*
|
||||
* @return the number of dynamic light sources emitting light
|
||||
*/
|
||||
public int getLightSourcesCount() {
|
||||
int result;
|
||||
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
result = this.dynamicLightSources.size();
|
||||
this.lightSourcesLock.readLock().unlock();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public double getDynamicLightLevel(int x, int y, int z) {
|
||||
double result = 0;
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
for (var lightSource : this.dynamicLightSources) {
|
||||
result = maxDynamicLightLevel(x, y, z, lightSource, result);
|
||||
}
|
||||
this.lightSourcesLock.readLock().unlock();
|
||||
|
||||
return MathHelper.clamp_double(result, 0, 15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets dynamic light level at an exact world position (for smooth per-vertex lighting).
|
||||
*/
|
||||
public double getDynamicLightLevel(double x, double y, double z) {
|
||||
double result = 0;
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
for (var lightSource : this.dynamicLightSources) {
|
||||
result = maxDynamicLightLevelExact(x, y, z, lightSource, result);
|
||||
}
|
||||
this.lightSourcesLock.readLock().unlock();
|
||||
|
||||
return MathHelper.clamp_double(result, 0, 15);
|
||||
}
|
||||
|
||||
public double getDynamicLightLevel(@NotNull BlockPos pos) {
|
||||
return this.getDynamicLightLevel(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
public static double maxDynamicLightLevel(int x, int y, int z, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
|
||||
int luminance = lightSource.angelica$getLuminance();
|
||||
if (luminance > 0) {
|
||||
// Can't use Entity#squaredDistanceTo because of eye Y coordinate.
|
||||
double dx = x - lightSource.angelica$getDynamicLightX() + 0.5;
|
||||
double dy = y - lightSource.angelica$getDynamicLightY() + 0.5;
|
||||
double dz = z - lightSource.angelica$getDynamicLightZ() + 0.5;
|
||||
|
||||
double distanceSquared = dx * dx + dy * dy + dz * dz;
|
||||
// 7.75 because else we would have to update more chunks and that's not a good idea.
|
||||
// 15 (max range for blocks) would be too much and a bit cheaty.
|
||||
if (distanceSquared <= MAX_RADIUS_SQUARED) {
|
||||
double multiplier = 1.0 - Math.sqrt(distanceSquared) / MAX_RADIUS;
|
||||
double lightLevel = multiplier * (double) luminance;
|
||||
if (lightLevel > currentLightLevel) {
|
||||
return lightLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
return currentLightLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates dynamic light level at an exact position (no block center offset).
|
||||
* Used for smooth per-vertex lighting.
|
||||
*/
|
||||
public static double maxDynamicLightLevelExact(double x, double y, double z, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
|
||||
int luminance = lightSource.angelica$getLuminance();
|
||||
if (luminance > 0) {
|
||||
double dx = x - lightSource.angelica$getDynamicLightX();
|
||||
double dy = y - lightSource.angelica$getDynamicLightY();
|
||||
double dz = z - lightSource.angelica$getDynamicLightZ();
|
||||
|
||||
double distanceSquared = dx * dx + dy * dy + dz * dz;
|
||||
if (distanceSquared <= MAX_RADIUS_SQUARED) {
|
||||
double multiplier = 1.0 - Math.sqrt(distanceSquared) / MAX_RADIUS;
|
||||
double lightLevel = multiplier * (double) luminance;
|
||||
if (lightLevel > currentLightLevel) {
|
||||
return lightLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
return currentLightLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dynamic light level generated by the light source at the specified position.
|
||||
*
|
||||
* @param pos the position
|
||||
* @param lightSource the light source
|
||||
* @param currentLightLevel the current surrounding dynamic light level
|
||||
* @return the dynamic light level at the specified position
|
||||
*/
|
||||
public static double maxDynamicLightLevel(@NotNull BlockPos pos, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
|
||||
return maxDynamicLightLevel(pos.getX(), pos.getY(), pos.getZ(), lightSource, currentLightLevel);
|
||||
}
|
||||
|
||||
public int getLightmapWithDynamicLight(int x, int y, int z, int lightmap) {
|
||||
return this.getLightmapWithDynamicLight(this.getDynamicLightLevel(x, y, z), lightmap);
|
||||
}
|
||||
|
||||
public List<IDynamicLightSource> getSourcesForChunk(int chunkMinX, int chunkMinY, int chunkMinZ) {
|
||||
// Chunk center
|
||||
final double centerX = chunkMinX + 8.0;
|
||||
final double centerY = chunkMinY + 8.0;
|
||||
final double centerZ = chunkMinZ + 8.0;
|
||||
|
||||
// Max distance from chunk center to any corner is sqrt(3) * 8 ≈ 13.86
|
||||
// Add MAX_RADIUS to get the search radius (13.86 + 7.75)^2 ≈ 467
|
||||
final double searchRadiusSq = 470.0; // Slightly padded for safety
|
||||
|
||||
List<IDynamicLightSource> result = null;
|
||||
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
try {
|
||||
for (IDynamicLightSource source : this.dynamicLightSources) {
|
||||
double dx = source.angelica$getDynamicLightX() - centerX;
|
||||
double dy = source.angelica$getDynamicLightY() - centerY;
|
||||
double dz = source.angelica$getDynamicLightZ() - centerZ;
|
||||
double distSq = dx * dx + dy * dy + dz * dz;
|
||||
|
||||
if (distSq <= searchRadiusSq) {
|
||||
if (result == null) {
|
||||
result = new ArrayList<>();
|
||||
}
|
||||
result.add(source);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.lightSourcesLock.readLock().unlock();
|
||||
}
|
||||
|
||||
return result != null ? result : Collections.emptyList();
|
||||
}
|
||||
|
||||
public double getDynamicLightLevelFromSources(double x, double y, double z, List<IDynamicLightSource> sources) {
|
||||
if (sources == null || sources.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
double result = 0;
|
||||
for (int i = 0, size = sources.size(); i < size; i++) {
|
||||
result = maxDynamicLightLevelExact(x, y, z, sources.get(i), result);
|
||||
}
|
||||
return MathHelper.clamp_double(result, 0, 15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lightmap with combined light levels.
|
||||
*
|
||||
* @param dynamicLightLevel the dynamic light level
|
||||
* @param lightmap the vanilla lightmap coordinates
|
||||
* @return the modified lightmap coordinates
|
||||
*/
|
||||
public int getLightmapWithDynamicLight(double dynamicLightLevel, int lightmap) {
|
||||
if (dynamicLightLevel > 0) {
|
||||
// lightmap is (skyLevel << 20 | blockLevel << 4)
|
||||
|
||||
// Get vanilla block light level.
|
||||
int blockLevel = (lightmap & 0xFFFF) >> 4;
|
||||
if (dynamicLightLevel > blockLevel) {
|
||||
// Equivalent to a << 4 bitshift with a little quirk: this one ensure more precision (more decimals are saved).
|
||||
int luminance = (int) (dynamicLightLevel * 16.0);
|
||||
lightmap &= 0xfff00000;
|
||||
lightmap |= luminance & 0x000fffff;
|
||||
}
|
||||
}
|
||||
|
||||
return lightmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a chunk rebuild at the specified chunk position.
|
||||
*
|
||||
* @param renderer the renderer
|
||||
* @param chunkPos the chunk position
|
||||
*/
|
||||
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, @NotNull IBlockPos chunkPos) {
|
||||
scheduleChunkRebuild(renderer, chunkPos.getX(), chunkPos.getY(), chunkPos.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a chunk rebuild at the specified chunk position.
|
||||
*
|
||||
* @param renderer the renderer
|
||||
* @param chunkPos the packed chunk position
|
||||
*/
|
||||
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, long chunkPos) {
|
||||
scheduleChunkRebuild(renderer, CoordinatePacker.unpackX(chunkPos), CoordinatePacker.unpackY(chunkPos), CoordinatePacker.unpackZ(chunkPos));
|
||||
}
|
||||
|
||||
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, int x, int y, int z) {
|
||||
if (FrustumCullingEnabled && instance != null) {
|
||||
instance.chunkRebuildManager.requestRebuild(x, y, z);
|
||||
} else {
|
||||
renderer.scheduleRebuildForChunk(x, y, z, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void scheduleChunkRebuildForRemoval(@NotNull IDynamicLightWorldRenderer renderer, int x, int y, int z) {
|
||||
renderer.scheduleRebuildForChunk(x, y, z, false);
|
||||
}
|
||||
|
||||
public static void scheduleChunkRebuildForRemoval(@NotNull IDynamicLightWorldRenderer renderer, long chunkPos) {
|
||||
scheduleChunkRebuildForRemoval(renderer, CoordinatePacker.unpackX(chunkPos), CoordinatePacker.unpackY(chunkPos), CoordinatePacker.unpackZ(chunkPos));
|
||||
}
|
||||
|
||||
public void processChunkRebuilds(@Nullable Viewport viewport) {
|
||||
if (activeRenderer == null) {
|
||||
return;
|
||||
}
|
||||
chunkRebuildManager.processVisible(viewport, activeRenderer);
|
||||
}
|
||||
|
||||
public ChunkRebuildManager getChunkRebuildManager() {
|
||||
return chunkRebuildManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the tracked chunk sets.
|
||||
*
|
||||
* @param chunkPos the packed chunk position
|
||||
* @param old the set of old chunk coordinates to remove this chunk from it
|
||||
* @param newPos the set of new chunk coordinates to add this chunk to it
|
||||
*/
|
||||
public static void updateTrackedChunks(@NotNull IBlockPos chunkPos, @Nullable LongOpenHashSet old, @Nullable LongOpenHashSet newPos) {
|
||||
if (old != null || newPos != null) {
|
||||
final long pos = chunkPos.asLong();
|
||||
if (old != null)
|
||||
old.remove(pos);
|
||||
if (newPos != null)
|
||||
newPos.add(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the dynamic lights tracking.
|
||||
*
|
||||
* @param lightSource the light source
|
||||
*/
|
||||
public static void updateTracking(@NotNull IDynamicLightSource lightSource) {
|
||||
boolean enabled = lightSource.angelica$isDynamicLightEnabled();
|
||||
int luminance = lightSource.angelica$getLuminance();
|
||||
|
||||
if (!enabled && luminance > 0) {
|
||||
lightSource.angelica$setDynamicLightEnabled(true);
|
||||
} else if (enabled && luminance < 1) {
|
||||
lightSource.angelica$setDynamicLightEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the luminance from an item stack.
|
||||
*
|
||||
* @param stack the item stack
|
||||
* @param submergedInWater {@code true} if the stack is submerged in water, else {@code false}
|
||||
* @return the luminance of the item
|
||||
*/
|
||||
public static int getLuminanceFromItemStack(@NotNull ItemStack stack) {
|
||||
Item item = stack.getItem();
|
||||
if (item instanceof ItemBlock itemBlock) {
|
||||
Block block = itemBlock.field_150939_a;
|
||||
if (block != null) {
|
||||
return block.getLightValue();
|
||||
}
|
||||
} else if (item instanceof IDynamicLightProducer lightProducer){
|
||||
return lightProducer.getLuminance();
|
||||
}
|
||||
|
||||
if (item == Items.lava_bucket) return Blocks.lava.getLightValue();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int getLuminanceFromEntity(@NotNull Entity entity) {
|
||||
|
||||
if (entity.isBurning()) return 15;
|
||||
|
||||
boolean inWater = entity.isInsideOfMaterial(Material.water);
|
||||
|
||||
// TODO only have certain items not glow in water?
|
||||
if (inWater) return 0;
|
||||
|
||||
if (entity instanceof EntityItem item) {
|
||||
return getLuminanceFromItemStack(item.getEntityItem());
|
||||
}
|
||||
|
||||
int luminance = 0;
|
||||
if (entity instanceof EntityLivingBase living) {
|
||||
|
||||
// check equipment + hand for light (should work for all entities)
|
||||
ItemStack itemStack;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if ((itemStack = living.getEquipmentInSlot(i)) != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(itemStack));
|
||||
}
|
||||
}
|
||||
|
||||
if (living instanceof EntityPlayer player) {
|
||||
if (ModStatus.isBattlegearLoaded &&
|
||||
player instanceof IBattlePlayer battlePlayer &&
|
||||
battlePlayer.battlegear2$isBattlemode()
|
||||
) {
|
||||
ItemStack offhand = Battlegear2Compat.getBattlegear2Offhand(player);
|
||||
if (offhand != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand));
|
||||
}
|
||||
}
|
||||
else if (ModStatus.isBackhandLoaded){
|
||||
ItemStack offhand = BackhandReflectionCompat.getOffhandItem(player);
|
||||
if (offhand != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand));
|
||||
}
|
||||
}
|
||||
|
||||
if (ModStatus.isBaublesLoaded){
|
||||
var playerBaubles = PlayerHandler.getPlayerBaubles(player);
|
||||
if (playerBaubles != null){
|
||||
for (int i = 0; i < playerBaubles.getSizeInventory(); i++){
|
||||
var stack = playerBaubles.getStackInSlot(i);
|
||||
if (stack != null){
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return luminance;
|
||||
}
|
||||
|
||||
// TODO: Creepers? TNT? Dusts?
|
||||
if (entity instanceof PrimedEntityAccessor primedEntity) {
|
||||
luminance = Math.max(luminance, primedEntity.angelica$getLuminance());
|
||||
}
|
||||
|
||||
return luminance;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1371 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user