Compare commits

231 Commits

Author SHA1 Message Date
octarine-noise
7065c9dd8c bump version to 2.6.5 2021-06-02 16:39:45 +02:00
octarine-noise
368b50a578 cherry-pick chinese translation
+lang file cleanup
2021-05-29 23:52:25 +02:00
octarine-noise
cd2d46f422 fix crash in resource pack selection GUI 2021-05-29 23:43:55 +02:00
octarine-noise
03367443b4 [WIP] fix log block detection 2021-05-16 20:08:44 +02:00
octarine-noise
81d3a2eba4 [WIP] beginning port to 1.16.5
+most of the stuff is working
2021-05-14 22:40:04 +02:00
octarine-noise
79ef6cfaa9 bump version 2021-05-13 21:13:45 +02:00
octarine-noise
40fd46b278 [WIP] adopt model replacement from Forge vesion
+ bunch of renames to bring the 2 version closer
+ at-least-not-crashing levels of Optifine support
2021-05-13 21:13:10 +02:00
octarine-noise
9dacdde761 [WIP] fix crash with falling blocks 2021-05-12 12:33:31 +02:00
octarine-noise
6a31adcdd8 [WIP] move AbstractParticles 2021-05-12 12:33:05 +02:00
octarine-noise
b46fdeaeef [WIP] start port to 1.15.2 2021-05-07 19:11:11 +02:00
octarine-noise
f1fa629c5c bring back the fade-out feature for falling leaf particles 2021-04-28 16:56:24 +02:00
octarine-noise
ad914ac03a fix round log x-ray bug 2021-04-28 15:53:02 +02:00
octarine-noise
d8ce8ecb06 bump version 2021-04-28 08:58:59 +02:00
octarine-noise
8d9214c190 add support for shader wind effects 2021-04-27 18:13:57 +02:00
octarine-noise
594db19bfb update block configs 2021-04-27 15:15:26 +02:00
octarine-noise
a89edd53a4 better block discovery logging 2021-04-27 15:12:04 +02:00
octarine-noise
d741338d46 minor cleanup 2020-02-17 15:55:37 +01:00
octarine-noise
01f697d2d3 un-bundle Mod Menu 2020-02-17 15:55:14 +01:00
octarine-noise
4c439dccd2 re-render chunks when configuration is changed 2020-02-17 12:40:21 +01:00
octarine-noise
df50f61b0d [WIP] initial Fabric port
major package refactoring
2020-02-05 13:43:54 +01:00
octarine-noise
2252fb3b42 [WIP] Optifine and Shaders fixes 2020-01-06 20:51:51 +01:00
octarine-noise
4efa831296 [WIP] more async texture loading
keep stitch() call in original method body, in case others want to mod it too
2020-01-06 18:15:17 +01:00
octarine-noise
a3d99c3076 [WIP] fix Mixin annotation processing 2020-01-05 16:33:12 +01:00
octarine-noise
c4ee768025 [WIP] async block texture reloading 2020-01-05 16:32:45 +01:00
octarine-noise
b4f18c1e1d switch to Gradle Kotlin DSL 2020-01-03 21:36:46 +01:00
octarine-noise
2a06c18884 [WIP] Roll all rendering parameters into a single context
+ split project into platform-dependent and -independent parts
2020-01-03 21:36:08 +01:00
octarine-noise
2ba99f40e7 Merge branch 'kotlin-1.12' into forge-1.14 2020-01-01 17:15:13 +01:00
octarine-noise
46cbe64328 [WIP] 1.14.4 port 2020-01-01 16:57:47 +01:00
octarine-noise
7b739c172f Bump version 2020-01-01 16:12:57 +01:00
octarine-noise
8319d721c7 Update to work with OptiFine_1.12.2_HD_U_F6_pre1 2020-01-01 15:15:44 +01:00
octarine-noise
1b0e93b050 Array bounds check for overlay layer 2020-01-01 15:14:19 +01:00
octarine-noise
1ea2b6b946 Model loading rework (1.14 prep)
remove unnecessary complexity
access sprites only at PostStitch
2019-12-30 17:35:52 +01:00
octarine-noise
558c9a2c34 Bump self and dependency versions 2019-12-21 15:10:32 +01:00
octarine-noise
f102bf4bda add Gradle wrapper 2019-12-21 15:10:09 +01:00
octarine-noise
f5cbf48dfa Try to cope with other mods wrapping IModel instances in a generic way 2019-12-21 15:09:51 +01:00
octarine-noise
d9cc03511a Cache round log neighborhood data for faster chunk re-rendering 2019-12-21 15:08:29 +01:00
octarine-noise
d0265483d2 Option to skip rendering of hidden extra leaves 2019-12-21 14:37:20 +01:00
octarine-noise
20da2a27bd Fully remove Optifine CTM code 2019-12-21 14:16:28 +01:00
octarine-noise
02509fa44d remove useless config option: distance limit 2019-09-04 11:29:50 +02:00
octarine-noise
6801304bd1 do not access TileEntities in unloaded chunks 2019-09-03 14:24:26 +02:00
octarine-noise
d96ac1c94c update Forge / Forgelin / Kotlin versions 2019-09-03 14:23:44 +02:00
octarine-noise
ac015b12df disable Optifine CTM support, fix custom colors 2019-04-13 09:37:57 +02:00
octarine-noise
71f0be0c93 fix shader mod integration 2019-03-02 13:27:39 +01:00
octarine-noise
bcc1b04e6b Optifine dev wrapper must be written in Java 2019-03-02 12:00:28 +01:00
octarine-noise
cbee4916ed Merge branch 'kotlin-1.10' into kotlin-1.12 2018-10-26 17:38:14 +02:00
octarine-noise
418d955f54 add Forgelin as mod dependency 2018-10-26 17:37:16 +02:00
octarine-noise
3121542c07 fix operand stack overflow in RenderChunk class transformation with recent Forge versions 2018-10-25 14:17:57 +02:00
octarine-noise
ab08457ae7 Merge branch 'kotlin-1.10' into kotlin-1.12 2018-10-25 14:15:06 +02:00
octarine-noise
5b1b35a891 update to latest recommended Forge 2018-10-25 14:14:30 +02:00
octarine-noise
787b3993b5 allow access to ClassWriter flags in class transformer context 2018-10-25 14:05:13 +02:00
octarine-noise
c593ff9bcb Merge branch 'kotlin-1.10' into kotlin-1.12 2018-10-25 13:46:29 +02:00
octarine-noise
afa619f838 use Forgelin 2018-10-25 11:11:14 +02:00
octarine-noise
e704a0af94 update to latest recommended Forge 2018-10-25 11:10:44 +02:00
octarine-noise
0997b83367 move values to properties file 2018-10-25 10:03:56 +02:00
octarine-noise
fe3030ef77 bump version to 2.1.11 2017-10-01 12:31:33 +02:00
octarine-noise
7aa510189a fix gray blocks with Optifine custom colors enabled 2017-09-30 13:37:43 +02:00
octarine-noise
36a3a38ff1 Merge branch 'kotlin-1.11.2' into kotlin-1.12 2017-09-10 01:18:47 +02:00
octarine-noise
7f4ee4b0a3 support MC versions 1.12 and 1.12.1 as well 2017-09-10 01:15:33 +02:00
octarine-noise
a4c6d1eecd Merge branch 'kotlin-1.10' into kotlin-1.11.2 2017-09-10 01:13:41 +02:00
octarine-noise
73223c15c3 bump version to 2.1.10 2017-09-10 01:12:32 +02:00
octarine-noise
b55d90802f fix broken serverside detection 2017-09-10 01:11:27 +02:00
octarine-noise
c5261216b2 Merge branch 'kotlin-1.11.2' into kotlin-1.12 2017-08-12 19:12:46 +02:00
octarine-noise
50399052b0 Merge branch 'kotlin-1.10' into kotlin-1.11.2 2017-08-12 18:57:10 +02:00
octarine-noise
e29d224df4 update to more recent Forge (GUI factory changes) 2017-08-12 18:19:25 +02:00
octarine-noise
3b7c44b062 bump version to 2.1.8 2017-08-12 18:15:18 +02:00
octarine-noise
32bf60492d add OptiFine custom color support 2017-08-12 17:58:24 +02:00
octarine-noise
9685971fd4 update lang file 2017-08-12 16:37:47 +02:00
octarine-noise
dacc63b11a minor code cleanup 2017-08-12 16:37:27 +02:00
octarine-noise
e20a3fbc54 move nVidia compatibility to a config option 2017-08-12 16:33:22 +02:00
octarine-noise
39df1951df add default model mappings for Lithos Core 2017-08-12 16:09:21 +02:00
octarine-noise
4761eb266f add support for block variants and multiple wood log bark textures 2017-08-12 15:59:22 +02:00
octarine-noise
25c302ecb6 add support for modded netherrack and mycelium
add support for NetherEx
2017-08-12 10:46:42 +02:00
octarine-noise
dec1f6ff03 Merge branch 'kotlin-1.11.2' into kotlin-1.12 2017-07-10 17:30:30 +02:00
octarine-noise
f145ff221e Merge branch 'kotlin-1.10' into kotlin-1.11.2 2017-07-10 17:27:34 +02:00
octarine-noise
c865c8e5ad bump version to 2.1.7 2017-07-10 17:20:32 +02:00
octarine-noise
47781cad91 fixed all rendering layer woes (hopefully) 2017-07-10 17:18:26 +02:00
octarine-noise
e329ce0270 remove unnecessary mirroring of round log endcap texture 2017-07-10 15:29:21 +02:00
octarine-noise
7cae04d7b4 fix round log texture glitch on non-nVidia cards 2017-07-10 15:28:52 +02:00
octarine-noise
0bbf206569 fix compatibility with OptiFine AA & AF 2017-07-10 15:14:07 +02:00
octarine-noise
55095e7252 Merge branch 'kotlin-1.11.2' into kotlin-1.12 2017-07-06 11:01:59 +02:00
octarine-noise
8eebb98c9d Merge branch 'kotlin-1.10' into kotlin-1.11.2 2017-07-06 11:00:32 +02:00
octarine-noise
2a8a9c2703 fix broken OptiFine integration 2017-07-06 10:59:52 +02:00
octarine-noise
7da89e24f1 update rubber integration for IC2 and TR 1.12 2017-07-06 10:12:46 +02:00
octarine-noise
fb078ab365 port to MC 1.12 2017-07-06 09:53:50 +02:00
octarine-noise
b5d87bb148 fix rubber tree integration to work with single resource loading pass 2017-07-06 09:10:38 +02:00
octarine-noise
6c98940d3e Merge branch 'kotlin-1.10' into kotlin-1.11.2 2017-07-05 15:09:28 +02:00
octarine-noise
813719c7f2 bump version to 2.1.6 2017-07-05 15:07:41 +02:00
octarine-noise
800fb4db9f add Better With Mods stumps to the log whitelist 2017-07-05 15:07:07 +02:00
octarine-noise
38b35c910b allow custom renderers to draw on multiple layers
fix extra leaves & fast graphics xray bug
2017-07-05 11:34:07 +02:00
octarine-noise
61076789db fix build errors after merge 2017-05-06 09:47:08 +02:00
octarine-noise
fde6c47ed3 Merge branch 'kotlin-1.10' into kotlin-1.11.2 2017-05-06 09:38:10 +02:00
octarine-noise
a9fba1a18e bump version to 2.1.5 2017-05-06 09:36:47 +02:00
octarine-noise
81ef954524 add support for multipart models 2017-05-06 09:36:32 +02:00
octarine-noise
674d22fdbb add AbyssalCraft grass blocks to default whitelist 2017-05-05 12:10:25 +02:00
octarine-noise
381b154413 change short grass override color to work better with dark textures 2017-05-05 12:08:55 +02:00
octarine-noise
568549e260 added population option to short grass as well (for non-full coverage) 2017-05-05 11:15:55 +02:00
octarine-noise
6d62cb9ac0 dev-only debug code for ASM transformer incompatibilities 2017-05-05 10:58:48 +02:00
octarine-noise
5bea5cde99 error reporting for unexpected registry misses 2017-05-05 10:57:34 +02:00
octarine-noise
28cead464e Merge branch 'kotlin-1.10' into kotlin-1.11.2 2017-04-09 12:07:25 +02:00
octarine-noise
8ffca417fb get rid of obfuscated namespace support, rely on FML deobfuscation instead 2017-04-09 11:30:35 +02:00
octarine-noise
8f9a35f40e bump version to 2.1.4 2017-04-09 10:26:56 +02:00
octarine-noise
ef90adf577 remove reeds and double plants from the crop list 2017-04-08 18:58:38 +02:00
octarine-noise
370e2bb38c don't render hidden faces on connected grass 2017-04-08 18:24:24 +02:00
octarine-noise
fefd5e5633 added compatibility with FoamFix anarchy parallel model baking 2017-04-08 17:58:54 +02:00
octarine-noise
bae81e8085 improve class transformer to work with SRG namespace as well 2017-04-08 17:57:52 +02:00
octarine-noise
479e4cadfa Switch to Kotlin 1.1.1, use typealias feature 2017-04-08 13:03:47 +02:00
octarine-noise
821d618395 update Forestry log support 2017-02-18 10:54:53 +01:00
octarine-noise
eb6058c4ee port to MC 1.11.2 2017-01-29 10:11:53 +01:00
octarine-noise
625a3bd543 change lang file names to lowercase 2016-12-22 00:34:42 +01:00
octarine-noise
37d091daed Merge branch 'kotlin-1.10' into kotlin-1.11 2016-12-22 00:28:58 +01:00
octarine-noise
d852faad96 bump version to 2.1.3 2016-12-22 00:01:14 +01:00
octarine-noise
da8d7ec237 add support for IC2 and TechReborn rubber logs and leaves 2016-12-21 23:57:28 +01:00
octarine-noise
31f64749b1 add some SideOnly annotations where appropriate 2016-12-21 19:54:49 +01:00
octarine-noise
66558932a9 move log block axis into the info object 2016-12-21 19:36:40 +01:00
octarine-noise
ceb3e5b116 port to MC 1.11 2016-12-11 17:02:54 +01:00
octarine-noise
1a1aa81c0f fixed leaf particle fallback type 2016-12-11 16:43:48 +01:00
octarine-noise
522fc1de33 create log directory if missing 2016-12-11 16:14:32 +01:00
octarine-noise
90c13a3a8e prefix log messages by mod name 2016-12-11 16:14:09 +01:00
octarine-noise
66b4df2850 bump version to 2.1.2 2016-12-05 00:32:15 +01:00
octarine-noise
7df142cf50 support for BOP flowering oak leaves
cleanup default models configs
2016-12-04 23:00:34 +01:00
octarine-noise
2b7582c5af fix modid mismatch between annotation and mcmod.info 2016-12-04 22:45:07 +01:00
octarine-noise
56e3dc5d24 fix color for connected grass blocks 2016-12-04 22:02:14 +01:00
octarine-noise
f7044e5225 update Aether II support 2016-12-04 20:19:05 +01:00
octarine-noise
59e4d0c602 fix saturation threshold not being respected for grass textures 2016-12-04 20:12:18 +01:00
octarine-noise
931dca6f3f add Agricultural Revolution log support
fixes #107
2016-12-04 19:21:15 +01:00
octarine-noise
c356c3ef57 defend against CME in Forestry leaf support module
fixes #106
2016-12-04 18:54:25 +01:00
octarine-noise
468d0f34b6 update Natura support
fixes #101
2016-12-04 18:50:02 +01:00
octarine-noise
6f42152cde get rid of some metadata warnings (future-proofing) 2016-12-04 18:47:25 +01:00
octarine-noise
44bfc93e1b preemptively create detail log file 2016-12-04 18:47:25 +01:00
octarine-noise
70591a484e null protection when fetching LeafInfo for leaf block 2016-12-04 18:47:24 +01:00
octarine-noise
fdc14595db add support for Forestry decorative leaves 2016-12-04 18:47:24 +01:00
Forstride
aa8226b46b Updated default block listings for Biomes O' Plenty support (#105)
Squashed 6 commits from branch kotlin-1.10 on Forstride/BetterFoliage

* Update CropDefault.cfg
* Update DirtDefault.cfg
* Update GrassBlocksDefault.cfg
* Update LeavesBlocksDefault.cfg
* Update LogBlocksDefault.cfg
* Update LilypadDefault.cfg
2016-12-04 18:46:25 +01:00
octarine-noise
ad78529d2a Merge branch 'kotlin-1.9.4' into kotlin-1.10 2016-09-17 08:33:33 +02:00
octarine-noise
8c922fd2e8 Merge branch 'kotlin-1.9' into kotlin-1.9.4 2016-09-13 21:12:29 +02:00
octarine-noise
9e9666f69f Merge branch 'kotlin-1.8' into kotlin-1.9 2016-09-13 21:08:36 +02:00
octarine-noise
2c0e95ba5b - revert to case-sensitive config
- fix handling of toggle lists
- ignore unknown options in config file
2016-09-13 20:36:35 +02:00
octarine-noise
85a4707494 automatically delete obsolete config properties 2016-09-05 22:45:00 +02:00
octarine-noise
f0a447bbbb switch to recent Forge and Kotlin versions 2016-09-05 22:43:30 +02:00
octarine-noise
4a4d39b523 Add support for Forestry logs
fixes #82
2016-09-05 22:40:13 +02:00
octarine-noise
e00ccd5919 bump version 2016-08-13 00:27:54 +02:00
octarine-noise
f032814d99 fix particle mapping
Forestry support
2016-08-11 11:56:52 +02:00
octarine-noise
62294bb2bb add support for Forestry leaves 2016-08-11 11:14:42 +02:00
octarine-noise
dec1ffd71c fix config change listener 2016-08-11 11:00:32 +02:00
octarine-noise
488078b50f rewrite model and texture detection
expose in mod configuration
2016-08-11 10:58:57 +02:00
octarine-noise
1bd353577f always push shader metadata for block models 2016-08-09 09:23:55 +02:00
octarine-noise
913496473d Merge branch 'kotlin-1.9.4' into kotlin-1.10 2016-07-28 12:18:58 +02:00
octarine-noise
a8e6c6c470 changed Kotlin version to 1.0.2, no more class relocation 2016-07-28 12:16:57 +02:00
octarine-noise
8bdb5ca8fd update modded block support 2016-07-28 12:16:09 +02:00
octarine-noise
5efc974133 Merge branch 'kotlin-1.9' into kotlin-1.9.4 2016-07-23 13:37:05 +02:00
octarine-noise
7f3617ef59 Merge branch 'kotlin-1.8' into kotlin-1.9 2016-07-23 13:32:31 +02:00
octarine-noise
400b965e02 use case insensitive names in config 2016-07-23 12:39:56 +02:00
octarine-noise
f96409f9a1 allow for custom column textures (preparation for rubber log support) 2016-07-23 12:01:28 +02:00
octarine-noise
2ca330fd29 added option to disable snow on extra leaves 2016-07-23 09:34:10 +02:00
octarine-noise
acf477d709 added korean localization 2016-07-23 09:32:11 +02:00
octarine-noise
97d5d6320c Merge pull request #62 from Ghostlyr/patch-1
Small inaccuracy in the russian localization
2016-06-28 00:43:51 +02:00
octarine-noise
6831050c77 port to 1.10 2016-06-27 23:38:37 +02:00
octarine-noise
70ec7e5289 use case insensitive names in config 2016-06-27 23:36:28 +02:00
octarine-noise
f66aabea67 port to 1.9.4 2016-06-21 01:28:43 +02:00
octarine-noise
0635f1e19e update to OptiFine H6 2016-06-15 20:54:58 +02:00
Ghostlyr
44a8abeb4b Small inaccuracy in the russian localization
Sorry, automatic translation =)
2016-06-07 00:32:10 +06:00
octarine-noise
b5af0fe1c5 upgrade to recent Forge and MCP mappings 2016-05-16 12:20:26 +02:00
octarine-noise
69db3d6608 Merge branch 'kotlin-1.8' into kotlin-1.9 2016-05-16 12:16:48 +02:00
octarine-noise
6903bd2de2 bump version 2016-05-16 12:12:43 +02:00
octarine-noise
9a544b1b53 support leaves that don't use the model minecraft:block/leaves 2016-05-16 12:05:54 +02:00
octarine-noise
bbd4df418c add support for blocks with submodels 2016-05-16 12:01:25 +02:00
octarine-noise
626bc69dad fix CTM textures
bump version to 2.0.10
2016-04-25 19:14:16 +02:00
octarine-noise
66ed1c098f do not use deprecated Block.canRenderInLayer() 2016-04-19 21:11:09 +02:00
octarine-noise
f37cb273f1 Merge branch 'kotlin-1.8' into kotlin-1.9 2016-04-19 21:09:52 +02:00
octarine-noise
a6cc354965 fix wrong height range used for algae 2016-04-19 20:56:28 +02:00
octarine-noise
8fe4346922 fix shallow water coral config option 2016-04-19 20:55:14 +02:00
octarine-noise
2f45abcd7c bump version to 2.0.9 2016-04-19 20:47:53 +02:00
octarine-noise
57cae957f8 keep round logs on the SOLID rendering layer 2016-04-19 20:23:07 +02:00
octarine-noise
befb64b8fc animated texture support for generated short grass 2016-04-19 20:21:17 +02:00
octarine-noise
a0aad5d608 fixed startup crash on null block in blockstate 2016-04-19 20:20:58 +02:00
octarine-noise
c0685d829b update to latest Forge and OptiFine
bump version to 2.0.8
2016-04-07 00:20:09 +02:00
octarine-noise
4209d1eea3 bump version to 2.0.7 2016-04-01 19:11:56 +02:00
octarine-noise
ac2001694b fix random display tick not firing 2016-04-01 19:11:28 +02:00
octarine-noise
50c4882855 fix config crash when accessing isOpaqueCube() before postInit 2016-04-01 19:08:06 +02:00
octarine-noise
07dc8888ef minor cosmetic tweaks in log renderer 2016-04-01 18:23:47 +02:00
octarine-noise
efebf29c64 bump version to 2.0.7 2016-04-01 18:21:49 +02:00
octarine-noise
983703133a Merge branch 'kotlin-1.8' into kotlin-1.9 2016-04-01 18:14:10 +02:00
octarine-noise
ee19120651 Copied from pull request #52 from Ghostlyr/kotlin-1.7.10
Add russian localization.
2016-04-01 18:12:27 +02:00
octarine-noise
cbaebfa26a fixed #50 2016-03-29 22:29:32 +02:00
octarine-noise
1ff5d45840 fixed X-ray glitch caused by Round Logs being reported as opaqe blocks 2016-03-29 22:28:49 +02:00
octarine-noise
7e667d483a fixed Log block endcap UV rotations 2016-03-29 22:14:59 +02:00
octarine-noise
6d5c03ba6a fix crash with OptiFine 1.9 (pre A series)
bump to 2.0.5
2016-03-26 09:28:54 +01:00
octarine-noise
f47aedf84d Merge branch 'kotlin-1.8' into kotlin-1.9 2016-03-26 08:08:58 +01:00
octarine-noise
6ee27c2a99 port to MC 1.9 2016-03-21 20:54:06 +01:00
octarine-noise
abf037d8a9 change the OptiFine dev tweaker to work with OF 1.8.x H5 and later 2016-03-21 20:51:37 +01:00
octarine-noise
c0be72bb37 get Optifine in the dev environment 2016-02-27 02:09:14 +01:00
octarine-noise
42c14790af Merge branch 'kotlin-1.8' into kotlin-1.8.8 2016-02-21 12:35:49 +01:00
octarine-noise
087e8d5685 fix Short Grass under snow 2016-02-21 12:34:00 +01:00
octarine-noise
aaca43fe2c Merge branch 'kotlin-1.8' into kotlin-1.8.8 2016-02-21 12:15:09 +01:00
octarine-noise
8e251dc038 make particle type definitions case insensitive 2016-02-21 12:12:55 +01:00
octarine-noise
1a6ffb251b bump version to 2.0.4 2016-02-21 11:57:59 +01:00
octarine-noise
efb49125b8 support Cooking Plus log blocks 2016-02-21 11:09:45 +01:00
octarine-noise
6032a120d8 support Plant Mega Pack log blocks 2016-02-21 11:08:44 +01:00
octarine-noise
f68f0e5edd Merge branch 'kotlin-1.8' into kotlin-1.8.8 2016-02-21 10:39:44 +01:00
octarine-noise
7393fed5fd Switch Kotlin version to 1.0.0 2016-02-21 10:37:37 +01:00
octarine-noise
41b080646a correctly apply smooth shading multipliers for AO data 2016-02-17 23:56:35 +01:00
octarine-noise
111f1b3907 fix custom texture search location 2016-02-13 11:56:48 +01:00
octarine-noise
662b4f50f0 Merge branch 'kotlin-1.8' into kotlin-1.8.8 2016-01-30 13:48:13 +01:00
octarine-noise
d9a042b356 protect against render buffer underruns 2016-01-30 13:44:33 +01:00
octarine-noise
ef574a13ac Merge branch 'kotlin-1.8' into kotlin-1.8.8 2016-01-30 13:05:30 +01:00
octarine-noise
b246c7acd0 simplified ModelDataInspector 2016-01-30 12:22:29 +01:00
octarine-noise
35ce80c602 added config option for rendering Round Logs with unknown axis
added Tangle Logs to the default whitelist
2016-01-30 12:20:13 +01:00
octarine-noise
4fdabbaf69 fixed lack of generic signature in 1.8 mappings 2016-01-30 11:57:34 +01:00
octarine-noise
0a6c433530 Merge branch 'kotlin-1.8' into kotlin-1.8.8 2016-01-30 00:58:41 +01:00
octarine-noise
3e6f98885f fixed & improved Round Log shading 2016-01-30 00:56:33 +01:00
octarine-noise
4720667f53 updated Thaumcraft Log block class 2016-01-30 00:52:35 +01:00
octarine-noise
5616a68f5a changed Kotlin version 2016-01-30 00:49:59 +01:00
octarine-noise
d1480ed3be improved Log block axis detection 2016-01-30 00:46:15 +01:00
octarine-noise
023e286fd4 Merge branch 'kotlin-1.8' into kotlin-1.8.8 2016-01-29 11:32:33 +01:00
octarine-noise
6c8f40f4e2 Optifine CTM support 2016-01-28 01:18:18 +01:00
octarine-noise
6306a5b03b fix accepted MC versions 2016-01-28 00:21:37 +01:00
octarine-noise
b0193bb108 update version 2016-01-18 14:31:22 +01:00
octarine-noise
eafd36b4b1 fixed OptiFine compatibility 2016-01-18 13:46:56 +01:00
octarine-noise
dac7033e18 Merge branch 'kotlin-1.8' into kotlin-1.8.8 2016-01-18 12:51:21 +01:00
octarine-noise
36c6b775db changed Extra Leaves brightness calculation 2016-01-18 12:50:08 +01:00
octarine-noise
0f985e996b port to MC 1.8.8 2016-01-18 12:47:23 +01:00
octarine-noise
e926f018e7 actually fix #36 2016-01-16 08:35:30 +01:00
octarine-noise
3716804ffb fix Round Log top and bottom texture rotation 2016-01-14 00:07:03 +01:00
octarine-noise
2d70de00e7 changed Round Log tapering rules 2016-01-13 23:12:04 +01:00
octarine-noise
9b717b549b fix for fix of #36 2016-01-13 23:00:19 +01:00
octarine-noise
81abad82e9 fixed #36 2016-01-13 22:55:07 +01:00
octarine-noise
601679f070 switch to new Forge and ForgeGradle version 2016-01-09 14:12:03 +01:00
octarine-noise
f0073b0f2a consolidate AccessTransformer 2016-01-09 14:11:28 +01:00
octarine-noise
5bdbb366eb remove nasty class visibility hack 2016-01-09 14:11:00 +01:00
octarine-noise
2e09f1f10a fixed language file typos 2016-01-09 13:36:55 +01:00
octarine-noise
8460103030 port to MC 1.8 2016-01-09 12:55:52 +01:00
octarine-noise
f44043bb0b first Kotlin version 2016-01-09 12:27:42 +01:00
211 changed files with 6869 additions and 3016 deletions

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="src" path="src/main/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.springsource.ide.eclipse.gradle.classpathcontainer"/>
<classpathentry kind="output" path="bin"/>
</classpath>

10
.gitignore vendored
View File

@@ -1,5 +1,9 @@
.idea/
*.iml
*.ipr
*.iws
run/
.gradle/ .gradle/
.settings/
bin/
build/ build/
libs/ classes/
temp/

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>BetterFoliage</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.springsource.ide.eclipse.gradle.core.nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -1,11 +1,4 @@
BetterFoliage BetterFoliage
============= =============
Minecraft mod that alters the appearance of leaves &amp; grass Minecraft mod that alters the appearance of leaves &amp; grass
More info: http://www.minecraftforum.net/topic/2776217-better-foliage/
Download
========
[BetterFoliage 0.9.4-beta] (http://goo.gl/pNBE23) (MC 1.7.2)
[BetterFoliage 0.9.4-beta] (http://goo.gl/ywT6Xg) (MC 1.7.10)

View File

@@ -1,45 +0,0 @@
buildscript {
repositories {
mavenCentral()
maven {
name = "forge"
url = "http://files.minecraftforge.net/maven"
}
maven {
name = "sonatype"
url = "https://oss.sonatype.org/content/repositories/snapshots/"
}
}
dependencies {
classpath 'net.minecraftforge.gradle:ForgeGradle:1.2-SNAPSHOT'
}
}
apply plugin: 'forge'
minecraft {
version = '1.7.2-10.12.2.1147'
}
jar.baseName = 'BetterFoliage-1.7.2'
group = 'com.github.octarine-noise'
version='0.9.5b'
processResources {
inputs.property "version", project.version
inputs.property "mcversion", project.minecraft.version
from(sourceSets.main.resources.srcDirs) {
include 'mcmod.info'
expand 'version':project.version, 'mcversion':project.minecraft.version
}
from(sourceSets.main.resources.srcDirs) {
exclude 'mcmod.info'
}
}
jar {
manifest {
attributes("FMLCorePlugin": "mods.betterfoliage.loader.BetterFoliageLoader", "FMLCorePluginContainsFMLMod": "mods.betterfoliage.BetterFoliage")
}
}

70
build.gradle.kts Normal file
View File

@@ -0,0 +1,70 @@
import net.fabricmc.loom.task.RemapJarTask
import org.ajoberstar.grgit.Grgit
plugins {
id("fabric-loom").version("0.6-SNAPSHOT")
kotlin("jvm").version("1.4.31")
id("org.ajoberstar.grgit").version("3.1.1")
}
apply(plugin = "org.ajoberstar.grgit")
val gitHash = (project.ext.get("grgit") as Grgit).head().abbreviatedId
val semVer = "${project.version}+$gitHash"
val jarName = "BetterFoliage-$semVer-Fabric-${properties["mcVersion"]}"
repositories {
maven("https://maven.fabricmc.net/")
maven("https://minecraft.curseforge.com/api/maven")
maven("https://maven.modmuss50.me/")
maven("https://maven.shedaniel.me/")
maven("https://grondag-repo.appspot.com").credentials { username = "guest"; password = "" }
maven("https://jitpack.io")
}
dependencies {
"minecraft"("com.mojang:minecraft:${properties["mcVersion"]}")
"mappings"("net.fabricmc:yarn:${properties["yarnMappings"]}:v2")
// basic Fabric stuff
"modImplementation"("net.fabricmc:fabric-loader:${properties["loaderVersion"]}")
"modImplementation"("net.fabricmc.fabric-api:fabric-api:${properties["fabricVersion"]}")
"modImplementation"("net.fabricmc:fabric-language-kotlin:${properties["fabricKotlinVersion"]}")
// configuration handling
"modImplementation"("io.github.prospector:modmenu:${properties["modMenuVersion"]}")
listOf("modImplementation", "include").forEach { configuration ->
configuration("me.shedaniel.cloth:cloth-config-fabric:${properties["clothConfigVersion"]}")
configuration("me.zeroeightsix:fiber:${properties["fiberVersion"]}")
}
// Canvas Renderer
// "modImplementation"("grondag:canvas:0.7.+")
// Optifabric
// "modImplementation"("com.github.modmuss50:OptiFabric:1.0.0")
"implementation"("org.zeroturnaround:zt-zip:1.13")
}
sourceSets {
get("main").ext["refMap"] = "betterfoliage.refmap.json"
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlin {
target.compilations.configureEach {
kotlinOptions.jvmTarget = "1.8"
kotlinOptions.freeCompilerArgs += listOf("-Xno-param-assertions", "-Xno-call-assertions")
}
}
tasks.getByName<ProcessResources>("processResources") {
filesMatching("fabric.mod.json") { expand(mutableMapOf("version" to semVer)) }
}
tasks.getByName<RemapJarTask>("remapJar") {
archiveName = "$jarName.jar"
}

20
gradle.properties Normal file
View File

@@ -0,0 +1,20 @@
org.gradle.jvmargs=-Xmx2G
org.gradle.daemon=false
group = com.github.octarine-noise
name = betterfoliage
jarName = BetterFoliage-Forge
version = 2.6.5
mcVersion = 1.16.5
yarnMappings=1.16.5+build.6
loaderVersion=0.11.3
fabricVersion=0.32.5+1.16
kotlinVersion=1.3.60
fabricKotlinVersion=1.5.0+kotlin.1.4.31
clothConfigVersion=4.11.24
modMenuVersion=1.16.9
fiberVersion=0.8.0-2

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

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

183
gradlew vendored Normal file
View File

@@ -0,0 +1,183 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
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.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

100
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,100 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
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.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
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.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

8
settings.gradle.kts Normal file
View File

@@ -0,0 +1,8 @@
pluginManagement {
repositories {
jcenter()
maven("https://maven.fabricmc.net/")
gradlePluginPortal()
}
}
rootProject.name = "betterfoliage"

View File

@@ -1,48 +0,0 @@
package mods.betterfoliage;
import java.io.File;
import java.util.Map;
import mods.betterfoliage.client.BetterFoliageClient;
import mods.betterfoliage.common.config.BetterFoliageConfig;
import org.apache.logging.log4j.Logger;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.network.NetworkCheckHandler;
import cpw.mods.fml.relauncher.Side;
@Mod(name=BetterFoliage.MOD_NAME, modid=BetterFoliage.MOD_ID, acceptedMinecraftVersions=BetterFoliage.MC_VERSIONS, guiFactory=BetterFoliage.GUI_FACTORY)
public class BetterFoliage {
public static final String MOD_ID = "BetterFoliage";
public static final String MOD_NAME = "Better Foliage";
public static final String MC_VERSIONS = "[1.7.2]";
public static final String GUI_FACTORY = "mods.betterfoliage.client.gui.ConfigGuiFactory";
@Mod.Instance
public static BetterFoliage instance;
public static BetterFoliageConfig config = new BetterFoliageConfig();
public static Logger log;
public static File configDir;
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
log = event.getModLog();
if (event.getSide() == Side.CLIENT) {
configDir = new File(event.getModConfigurationDirectory(), MOD_ID);
configDir.mkdir();
config.load(new File(configDir, "betterfoliage.cfg"));
BetterFoliageClient.preInit();
}
}
@NetworkCheckHandler
public boolean checkVersion(Map<String, String> mods, Side side) {
return true;
}
}

View File

@@ -0,0 +1,49 @@
package mods.betterfoliage;
import net.fabricmc.loader.api.FabricLoader;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.util.List;
import java.util.Set;
public class MixinConfigPlugin implements IMixinConfigPlugin {
Logger logger = LogManager.getLogger(this);
Boolean hasOptifine = null;
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
if (hasOptifine == null) {
hasOptifine = FabricLoader.getInstance().isModLoaded("optifabric");
if (hasOptifine) logger.log(Level.INFO, "[BetterFoliage] Optifabric detected, applying Optifine mixins");
else logger.log(Level.INFO, "[BetterFoliage] Optifabric not detected, applying Vanilla mixins");
}
if (mixinClassName.endsWith("Vanilla") && hasOptifine) return false;
if (mixinClassName.endsWith("Optifine") && !hasOptifine) return false;
return true;
}
@Override
public void onLoad(String mixinPackage) { }
@Override
public String getRefMapperConfig() { return null; }
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) { }
@Override
public List<String> getMixins() { return null; }
@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { }
@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { }
}

View File

@@ -1,128 +0,0 @@
package mods.betterfoliage.client;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.render.IRenderBlockDecorator;
import mods.betterfoliage.client.render.impl.RenderBlockBetterAlgae;
import mods.betterfoliage.client.render.impl.RenderBlockBetterCactus;
import mods.betterfoliage.client.render.impl.RenderBlockBetterGrass;
import mods.betterfoliage.client.render.impl.RenderBlockBetterLeaves;
import mods.betterfoliage.client.render.impl.RenderBlockBetterLilypad;
import mods.betterfoliage.client.render.impl.RenderBlockBetterReed;
import mods.betterfoliage.client.resource.BlockTextureGenerator;
import mods.betterfoliage.client.resource.HalfTextureResource;
import mods.betterfoliage.client.resource.ILeafTextureRecognizer;
import mods.betterfoliage.client.resource.LeafTextureGenerator;
import net.minecraft.block.Block;
import net.minecraft.block.BlockCarrot;
import net.minecraft.block.BlockCrops;
import net.minecraft.block.BlockDoublePlant;
import net.minecraft.block.BlockLeavesBase;
import net.minecraft.block.BlockPotato;
import net.minecraft.block.BlockReed;
import net.minecraft.block.BlockTallGrass;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.IResource;
import net.minecraft.init.Blocks;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.common.MinecraftForge;
import com.google.common.collect.Maps;
import cpw.mods.fml.client.registry.RenderingRegistry;
import cpw.mods.fml.common.FMLCommonHandler;
public class BetterFoliageClient implements ILeafTextureRecognizer {
public static Map<Integer, IRenderBlockDecorator> decorators = Maps.newHashMap();
public static LeafTextureGenerator leafGenerator;
public static BlockMatcher leaves;
public static BlockMatcher crops;
public static void preInit() {
FMLCommonHandler.instance().bus().register(new KeyHandler());
BetterFoliage.log.info("Registering renderers");
registerRenderer(new RenderBlockBetterLeaves());
registerRenderer(new RenderBlockBetterGrass());
registerRenderer(new RenderBlockBetterCactus());
registerRenderer(new RenderBlockBetterLilypad());
registerRenderer(new RenderBlockBetterReed());
registerRenderer(new RenderBlockBetterAlgae());
leaves = new BlockMatcher(BlockLeavesBase.class.getName(),
"forestry.arboriculture.gadgets.BlockLeaves",
"thaumcraft.common.blocks.BlockMagicalLeaves");
leaves.load(new File(BetterFoliage.configDir, "classesLeaves.cfg"));
crops = new BlockMatcher(BlockCrops.class.getName(),
"-" + BlockCarrot.class.getName(),
"-" + BlockPotato.class.getName(),
BlockTallGrass.class.getName(),
BlockDoublePlant.class.getName(),
BlockReed.class.getName(),
"biomesoplenty.common.blocks.BlockBOPFlower",
"biomesoplenty.common.blocks.BlockBOPFlower2",
"tconstruct.blocks.slime.SlimeTallGrass");
crops.load(new File(BetterFoliage.configDir, "classesCrops.cfg"));
BetterFoliage.log.info("Registering leaf texture generator");
leafGenerator = new LeafTextureGenerator();
MinecraftForge.EVENT_BUS.register(leafGenerator);
leafGenerator.recognizers.add(new BetterFoliageClient());
MinecraftForge.EVENT_BUS.register(new BlockTextureGenerator("bf_reed_bottom", new ResourceLocation("betterfoliage", "textures/blocks/missing_leaf.png")) {
@Override
public IResource getResource(ResourceLocation var1) throws IOException {
return new HalfTextureResource(unwrapResource(var1), true, getMissingResource());
}
});
MinecraftForge.EVENT_BUS.register(new BlockTextureGenerator("bf_reed_top", new ResourceLocation("betterfoliage", "textures/blocks/missing_leaf.png")) {
@Override
public IResource getResource(ResourceLocation var1) throws IOException {
return new HalfTextureResource(unwrapResource(var1), false, getMissingResource());
}
});
MinecraftForge.EVENT_BUS.register(new BetterFoliageClient());
}
public boolean isLeafTexture(TextureAtlasSprite icon) {
String resourceLocation = icon.getIconName();
if (resourceLocation.startsWith("forestry:leaves/")) return true;
return false;
}
public static int getRenderTypeOverride(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
// universal sign for DON'T RENDER ME!
if (original == -1) return original;
for (Map.Entry<Integer, IRenderBlockDecorator> entry : decorators.entrySet())
if (entry.getValue().isBlockAccepted(blockAccess, x, y, z, block, original))
return entry.getKey();
return original;
}
public static int getGLSLBlockIdOverride(int original, Block block) {
if (leaves.matchesID(original & 0xFFFF))
return Block.blockRegistry.getIDForObject(Blocks.leaves) & 0xFFFF | block.getRenderType() << 16;
if (crops.matchesID(original & 0xFFFF))
return Block.blockRegistry.getIDForObject(Blocks.tallgrass) & 0xFFFF | block.getRenderType() << 16;
return original;
}
public static void registerRenderer(IRenderBlockDecorator decorator) {
int renderId = RenderingRegistry.getNextAvailableRenderId();
decorators.put(renderId, decorator);
RenderingRegistry.registerBlockHandler(renderId, decorator);
MinecraftForge.EVENT_BUS.register(decorator);
decorator.init();
}
}

View File

@@ -1,116 +0,0 @@
package mods.betterfoliage.client;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Iterator;
import java.util.Set;
import mods.betterfoliage.BetterFoliage;
import net.minecraft.block.Block;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import com.google.common.collect.Sets;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
public class BlockMatcher {
public Set<String> whiteList = Sets.newHashSet();
public Set<String> blackList = Sets.newHashSet();
public Set<Integer> blockIDs = Sets.newHashSet();
public BlockMatcher(String... defaults) {
for (String clazz : defaults) addClass(clazz);
MinecraftForge.EVENT_BUS.register(this);
}
public void addClass(String className) {
if (className.startsWith("-"))
blackList.add(className.substring(1));
else
whiteList.add(className);
}
public boolean matchesClass(Block block) {
for (String className : blackList) {
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAssignableFrom(block.getClass())) return false;
} catch(ClassNotFoundException e) {}
}
for (String className : whiteList) {
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAssignableFrom(block.getClass())) return true;
} catch(ClassNotFoundException e) {}
}
return false;
}
public boolean matchesID(int blockId) {
return blockIDs.contains(blockId);
}
public boolean matchesID(Block block) {
return blockIDs.contains(Block.blockRegistry.getIDForObject(block));
}
public void load(File file) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
whiteList.clear();
blackList.clear();
String line = reader.readLine();
while(line != null) {
addClass(line.trim());
line = reader.readLine();
}
reader.close();
} catch (FileNotFoundException e) {
saveDefaults(file);
} catch (IOException e) {
BetterFoliage.log.warn(String.format("Error reading configuration: %s", file.getName()));
}
}
public void saveDefaults(File file) {
FileWriter writer = null;
try {
writer = new FileWriter(file);
for (String className : whiteList) {
writer.write(className);
writer.write("\n");
}
for (String className : blackList) {
writer.write("-");
writer.write(className);
writer.write("\n");
}
writer.close();
} catch (FileNotFoundException e) {
saveDefaults(file);
} catch (IOException e) {
BetterFoliage.log.warn(String.format("Error writing default configuration: %s", file.getName()));
}
}
/** Caches block IDs on world load for fast lookup
* @param event
*/
@SuppressWarnings("unchecked")
@SubscribeEvent
public void handleWorldLoad(WorldEvent.Load event) {
blockIDs.clear();
Iterator<Block> iter = Block.blockRegistry.iterator();
while (iter.hasNext()) {
Block block = iter.next();
if (matchesClass(block)) blockIDs.add(Block.blockRegistry.getIDForObject(block));
}
}
}

View File

@@ -1,27 +0,0 @@
package mods.betterfoliage.client;
import cpw.mods.fml.client.FMLClientHandler;
import cpw.mods.fml.client.registry.ClientRegistry;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.InputEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.gui.ConfigGuiMain;
import net.minecraft.client.settings.KeyBinding;
@SideOnly(Side.CLIENT)
public class KeyHandler {
public static KeyBinding guiBinding;
public KeyHandler() {
guiBinding = new KeyBinding("key.betterfoliage.gui", 66, BetterFoliage.MOD_NAME);
ClientRegistry.registerKeyBinding(guiBinding);
}
@SubscribeEvent
public void handleKeyPress(InputEvent.KeyInputEvent event) {
if (guiBinding.isPressed()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiMain(null));
}
}

View File

@@ -1,36 +0,0 @@
package mods.betterfoliage.client.gui;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.gui.widget.OptionDoubleWidget;
import mods.betterfoliage.client.gui.widget.OptionIntegerWidget;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import cpw.mods.fml.client.FMLClientHandler;
public class ConfigGuiAlgae extends ConfigGuiScreenBase {
public ConfigGuiAlgae(GuiScreen parent) {
super(parent);
int id = 10;
widgets.add(new OptionDoubleWidget(BetterFoliage.config.algaeHOffset, -100, -70, 200, 50, id++, id++, "message.betterfoliage.hOffset", "%.3f"));
widgets.add(new OptionDoubleWidget(BetterFoliage.config.algaeSize, -100, -40, 200, 50, id++, id++, "message.betterfoliage.size", "%.2f"));
widgets.add(new OptionDoubleWidget(BetterFoliage.config.algaeHeightMin, -100, -10, 200, 50, id++, id++, "message.betterfoliage.minHeight", "%.2f"));
widgets.add(new OptionDoubleWidget(BetterFoliage.config.algaeHeightMax, -100, 20, 200, 50, id++, id++, "message.betterfoliage.maxHeight", "%.2f"));
widgets.add(new OptionIntegerWidget(BetterFoliage.config.algaeChance, -100, 50, 200, 50, id++, id++, "message.betterfoliage.algaeChance"));
}
@SuppressWarnings("unchecked")
@Override
public void addButtons(int x, int y) {
buttonList.add(new GuiButton(0, x - 50, y + 100, 100, 20, I18n.format("message.betterfoliage.back")));
}
@Override
protected void onButtonPress(int id) {
if (id == 0) FMLClientHandler.instance().showGuiScreen(parent);
if (BetterFoliage.config.algaeHeightMin.value > BetterFoliage.config.algaeHeightMax.value) BetterFoliage.config.algaeHeightMin.value = BetterFoliage.config.algaeHeightMax.value;
}
}

View File

@@ -1,32 +0,0 @@
package mods.betterfoliage.client.gui;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import cpw.mods.fml.client.IModGuiFactory;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class ConfigGuiFactory implements IModGuiFactory {
public void initialize(Minecraft minecraftInstance) {
}
public Class<? extends GuiScreen> mainConfigGuiClass() {
return ConfigGuiMain.class;
}
public Set<RuntimeOptionCategoryElement> runtimeGuiCategories() {
return ImmutableSet.<RuntimeOptionCategoryElement>of();
}
public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) {
return null;
}
}

View File

@@ -1,34 +0,0 @@
package mods.betterfoliage.client.gui;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.gui.widget.OptionDoubleWidget;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import cpw.mods.fml.client.FMLClientHandler;
public class ConfigGuiGrass extends ConfigGuiScreenBase {
public ConfigGuiGrass(GuiScreen parent) {
super(parent);
int id = 10;
widgets.add(new OptionDoubleWidget(BetterFoliage.config.grassSize, -100, -70, 200, 50, id++, id++, "message.betterfoliage.size", "%.2f"));
widgets.add(new OptionDoubleWidget(BetterFoliage.config.grassHOffset, -100, -40, 200, 50, id++, id++, "message.betterfoliage.hOffset", "%.3f"));
widgets.add(new OptionDoubleWidget(BetterFoliage.config.grassHeightMin, -100, -10, 200, 50, id++, id++, "message.betterfoliage.minHeight", "%.2f"));
widgets.add(new OptionDoubleWidget(BetterFoliage.config.grassHeightMax, -100, 20, 200, 50, id++, id++, "message.betterfoliage.maxHeight", "%.2f"));
}
@SuppressWarnings("unchecked")
@Override
public void addButtons(int x, int y) {
buttonList.add(new GuiButton(0, x - 50, y + 50, 100, 20, I18n.format("message.betterfoliage.back")));
}
@Override
protected void onButtonPress(int id) {
if (id == 0) FMLClientHandler.instance().showGuiScreen(parent);
if (BetterFoliage.config.grassHeightMin.value > BetterFoliage.config.grassHeightMax.value) BetterFoliage.config.grassHeightMin.value = BetterFoliage.config.grassHeightMax.value;
}
}

View File

@@ -1,39 +0,0 @@
package mods.betterfoliage.client.gui;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.gui.widget.OptionDoubleWidget;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import cpw.mods.fml.client.FMLClientHandler;
public class ConfigGuiLeaves extends ConfigGuiScreenBase {
public enum Button {CLOSE, LEAVES_OFFSET_MODE}
public ConfigGuiLeaves(GuiScreen parent) {
super(parent);
int id = 10;
widgets.add(new OptionDoubleWidget(BetterFoliage.config.leavesSize, -100, -70, 200, 50, id++, id++, "message.betterfoliage.size", "%.2f"));
widgets.add(new OptionDoubleWidget(BetterFoliage.config.leavesHOffset, -100, -10, 200, 50, id++, id++, "message.betterfoliage.hOffset", "%.3f"));
widgets.add(new OptionDoubleWidget(BetterFoliage.config.leavesVOffset, -100, 20, 200, 50, id++, id++, "message.betterfoliage.vOffset", "%.3f"));
}
@SuppressWarnings("unchecked")
@Override
public void addButtons(int x, int y) {
buttonList.add(new GuiButton(Button.CLOSE.ordinal(), x - 50, y + 50, 100, 20, I18n.format("message.betterfoliage.back")));
buttonList.add(new GuiButton(Button.LEAVES_OFFSET_MODE.ordinal(), x - 100, y - 40, 200, 20, ""));
}
protected void updateButtons() {
setButtonOptionBoolean(Button.LEAVES_OFFSET_MODE.ordinal(), "message.betterfoliage.leavesMode", BetterFoliage.config.leavesSkew ? "message.betterfoliage.leavesSkew" : "message.betterfoliage.leavesTranslate");
}
@Override
protected void onButtonPress(int id) {
if (id == Button.CLOSE.ordinal()) FMLClientHandler.instance().showGuiScreen(parent);
if (id == Button.LEAVES_OFFSET_MODE.ordinal()) BetterFoliage.config.leavesSkew = !BetterFoliage.config.leavesSkew;
}
}

View File

@@ -1,31 +0,0 @@
package mods.betterfoliage.client.gui;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.gui.widget.OptionDoubleWidget;
import mods.betterfoliage.client.gui.widget.OptionIntegerWidget;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import cpw.mods.fml.client.FMLClientHandler;
public class ConfigGuiLilypad extends ConfigGuiScreenBase {
public ConfigGuiLilypad(GuiScreen parent) {
super(parent);
int id = 10;
widgets.add(new OptionDoubleWidget(BetterFoliage.config.lilypadHOffset, -100, -40, 200, 50, id++, id++, "message.betterfoliage.hOffset", "%.3f"));
widgets.add(new OptionIntegerWidget(BetterFoliage.config.lilypadChance, -100, -10, 200, 50, id++, id++, "message.betterfoliage.flowerChance"));
}
@SuppressWarnings("unchecked")
@Override
public void addButtons(int x, int y) {
buttonList.add(new GuiButton(0, x - 50, y + 50, 100, 20, I18n.format("message.betterfoliage.back")));
}
@Override
protected void onButtonPress(int id) {
if (id == 0) FMLClientHandler.instance().showGuiScreen(parent);
}
}

View File

@@ -1,83 +0,0 @@
package mods.betterfoliage.client.gui;
import mods.betterfoliage.BetterFoliage;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import cpw.mods.fml.client.FMLClientHandler;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class ConfigGuiMain extends ConfigGuiScreenBase {
public enum Button {CLOSE,
TOGGLE_LEAVES, CONFIG_LEAVES,
TOGGLE_GRASS, CONFIG_GRASS,
TOGGLE_CACTUS, CONFIG_CACTUS,
TOGGLE_LILYPAD, CONFIG_LILYPAD,
TOGGLE_REED, CONFIG_REED,
TOGGLE_ALGAE, CONFIG_ALGAE}
public ConfigGuiMain(GuiScreen parent) {
super(parent);
}
@SuppressWarnings("unchecked")
@Override
protected void addButtons(int x, int y) {
buttonList.add(new GuiButton(Button.CLOSE.ordinal(), x - 50, y + 80, 100, 20, I18n.format("message.betterfoliage.close")));
buttonList.add(new GuiButton(Button.TOGGLE_LEAVES.ordinal(), x - 100, y - 100, 150, 20, ""));
buttonList.add(new GuiButton(Button.CONFIG_LEAVES.ordinal(), x + 60, y - 100, 40, 20, I18n.format("message.betterfoliage.config")));
buttonList.add(new GuiButton(Button.TOGGLE_GRASS.ordinal(), x - 100, y - 70, 150, 20, ""));
buttonList.add(new GuiButton(Button.CONFIG_GRASS.ordinal(), x + 60, y - 70, 40, 20, I18n.format("message.betterfoliage.config")));
buttonList.add(new GuiButton(Button.TOGGLE_CACTUS.ordinal(), x - 100, y - 40, 150, 20, ""));
buttonList.add(new GuiButton(Button.CONFIG_CACTUS.ordinal(), x + 60, y - 40, 40, 20, I18n.format("message.betterfoliage.config")));
buttonList.add(new GuiButton(Button.TOGGLE_LILYPAD.ordinal(), x - 100, y - 10, 150, 20, ""));
buttonList.add(new GuiButton(Button.CONFIG_LILYPAD.ordinal(), x + 60, y - 10, 40, 20, I18n.format("message.betterfoliage.config")));
buttonList.add(new GuiButton(Button.TOGGLE_REED.ordinal(), x - 100, y + 20, 150, 20, ""));
buttonList.add(new GuiButton(Button.CONFIG_REED.ordinal(), x + 60, y + 20, 40, 20, I18n.format("message.betterfoliage.config")));
buttonList.add(new GuiButton(Button.TOGGLE_ALGAE.ordinal(), x - 100, y + 50, 150, 20, ""));
buttonList.add(new GuiButton(Button.CONFIG_ALGAE.ordinal(), x + 60, y + 50, 40, 20, I18n.format("message.betterfoliage.config")));
}
protected void updateButtons() {
setButtonOptionBoolean(Button.TOGGLE_LEAVES.ordinal(), "message.betterfoliage.betterLeaves", BetterFoliage.config.leavesEnabled);
setButtonOptionBoolean(Button.TOGGLE_GRASS.ordinal(), "message.betterfoliage.betterGrass", BetterFoliage.config.grassEnabled);
setButtonOptionBoolean(Button.TOGGLE_CACTUS.ordinal(), "message.betterfoliage.betterCactus", BetterFoliage.config.cactusEnabled);
setButtonOptionBoolean(Button.TOGGLE_LILYPAD.ordinal(), "message.betterfoliage.betterLilypad", BetterFoliage.config.lilypadEnabled);
setButtonOptionBoolean(Button.TOGGLE_REED.ordinal(), "message.betterfoliage.betterReed", BetterFoliage.config.reedEnabled);
setButtonOptionBoolean(Button.TOGGLE_ALGAE.ordinal(), "message.betterfoliage.betterAlgae", BetterFoliage.config.algaeEnabled);
((GuiButton) buttonList.get(Button.CONFIG_CACTUS.ordinal())).enabled = false;
}
@Override
protected void onButtonPress(int id) {
if (id == Button.CLOSE.ordinal()) {
BetterFoliage.config.save();
Minecraft.getMinecraft().renderGlobal.loadRenderers();
FMLClientHandler.instance().showGuiScreen(parent);
}
if (id == Button.TOGGLE_LEAVES.ordinal()) BetterFoliage.config.leavesEnabled = !BetterFoliage.config.leavesEnabled;
if (id == Button.TOGGLE_GRASS.ordinal()) BetterFoliage.config.grassEnabled = !BetterFoliage.config.grassEnabled;
if (id == Button.TOGGLE_CACTUS.ordinal()) BetterFoliage.config.cactusEnabled = !BetterFoliage.config.cactusEnabled;
if (id == Button.TOGGLE_LILYPAD.ordinal()) BetterFoliage.config.lilypadEnabled = !BetterFoliage.config.lilypadEnabled;
if (id == Button.TOGGLE_REED.ordinal()) BetterFoliage.config.reedEnabled = !BetterFoliage.config.reedEnabled;
if (id == Button.TOGGLE_ALGAE.ordinal()) BetterFoliage.config.algaeEnabled = !BetterFoliage.config.algaeEnabled;
if (id== Button.CONFIG_LEAVES.ordinal()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiLeaves(this));
if (id== Button.CONFIG_GRASS.ordinal()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiGrass(this));
if (id== Button.CONFIG_LILYPAD.ordinal()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiLilypad(this));
if (id== Button.CONFIG_REED.ordinal()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiReed(this));
if (id== Button.CONFIG_ALGAE.ordinal()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiAlgae(this));
}
}

View File

@@ -1,35 +0,0 @@
package mods.betterfoliage.client.gui;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.gui.widget.OptionDoubleWidget;
import mods.betterfoliage.client.gui.widget.OptionIntegerWidget;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import cpw.mods.fml.client.FMLClientHandler;
public class ConfigGuiReed extends ConfigGuiScreenBase {
public ConfigGuiReed(GuiScreen parent) {
super(parent);
int id = 10;
widgets.add(new OptionDoubleWidget(BetterFoliage.config.reedHOffset, -100, -70, 200, 50, id++, id++, "message.betterfoliage.hOffset", "%.3f"));
widgets.add(new OptionDoubleWidget(BetterFoliage.config.reedHeightMin, -100, -40, 200, 50, id++, id++, "message.betterfoliage.minHeight", "%.2f"));
widgets.add(new OptionDoubleWidget(BetterFoliage.config.reedHeightMax, -100, -10, 200, 50, id++, id++, "message.betterfoliage.maxHeight", "%.2f"));
widgets.add(new OptionIntegerWidget(BetterFoliage.config.reedChance, -100, 20, 200, 50, id++, id++, "message.betterfoliage.reedChance"));
}
@SuppressWarnings("unchecked")
@Override
public void addButtons(int x, int y) {
buttonList.add(new GuiButton(0, x - 50, y + 50, 100, 20, I18n.format("message.betterfoliage.back")));
}
@Override
protected void onButtonPress(int id) {
if (id == 0) FMLClientHandler.instance().showGuiScreen(parent);
if (BetterFoliage.config.reedHeightMin.value > BetterFoliage.config.reedHeightMax.value) BetterFoliage.config.reedHeightMin.value = BetterFoliage.config.reedHeightMax.value;
}
}

View File

@@ -1,75 +0,0 @@
package mods.betterfoliage.client.gui;
import java.util.List;
import mods.betterfoliage.client.gui.widget.IOptionWidget;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import com.google.common.collect.Lists;
public class ConfigGuiScreenBase extends GuiScreen {
protected GuiScreen parent;
protected List<IOptionWidget> widgets = Lists.newLinkedList();
public ConfigGuiScreenBase(GuiScreen parent) {
this.parent = parent;
}
@Override
public void drawScreen(int par1, int par2, float par3) {
this.drawDefaultBackground();
int x = width / 2;
int y = height / 2;
for (IOptionWidget widget : widgets) widget.drawStrings(this, fontRendererObj, x, y, 14737632, 16777120);
super.drawScreen(par1, par2, par3);
}
@SuppressWarnings("unchecked")
@Override
public void initGui() {
int x = width / 2;
int y = height / 2;
for (IOptionWidget widget : widgets) widget.addButtons(buttonList, x, y);
addButtons(x, y);
updateButtons();
}
protected void addButtons(int x, int y) {}
protected void updateButtons() {}
protected void onButtonPress(int id) {}
@Override
protected void actionPerformed(GuiButton button) {
super.actionPerformed(button);
for (IOptionWidget widget : widgets) widget.onAction(button.id);
onButtonPress(button.id);
updateButtons();
}
@SuppressWarnings("unchecked")
protected void setButtonOptionBoolean(int id, String msgKey, boolean option) {
for (GuiButton button : (List<GuiButton>) buttonList) {
if (button.id == id) {
String optionText = option ? (EnumChatFormatting.GREEN + I18n.format("message.betterfoliage.optionOn")) : (EnumChatFormatting.RED + I18n.format("message.betterfoliage.optionOff"));
button.displayString = I18n.format(msgKey, optionText);
break;
}
}
}
@SuppressWarnings("unchecked")
protected void setButtonOptionBoolean(int id, String msgKey, String optionKey) {
for (GuiButton button : (List<GuiButton>) buttonList) {
if (button.id == id) {
button.displayString = I18n.format(msgKey, I18n.format(optionKey));
break;
}
}
}
}

View File

@@ -1,15 +0,0 @@
package mods.betterfoliage.client.gui.widget;
import java.util.List;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
public interface IOptionWidget {
public void addButtons(List<GuiButton> buttonList, int xOffset, int yOffset);
public void drawStrings(GuiScreen screen, FontRenderer fontRenderer, int xOffset, int yOffset, int labelColor, int numColor);
public void onAction(int buttonId);
}

View File

@@ -1,53 +0,0 @@
package mods.betterfoliage.client.gui.widget;
import java.util.List;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import mods.betterfoliage.common.config.OptionDouble;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
@SideOnly(Side.CLIENT)
public class OptionDoubleWidget implements IOptionWidget {
public OptionDouble option;
public int x;
public int y;
public int width;
public int numWidth;
public int idDecrement;
public int idIncrement;
public String keyLabel;
public String formatString;
public OptionDoubleWidget(OptionDouble option, int x, int y, int width, int numWidth, int idDecrement, int idIncrement, String keyLabel, String formatString) {
this.option = option;
this.x = x;
this.y = y;
this.width = width;
this.numWidth = numWidth;
this.idDecrement = idDecrement;
this.idIncrement = idIncrement;
this.keyLabel = keyLabel;
this.formatString = formatString;
}
public void addButtons(List<GuiButton> buttonList, int xOffset, int yOffset) {
buttonList.add(new GuiButton(idDecrement, xOffset + x + width - numWidth - 40, yOffset + y, 20, 20, "-"));
buttonList.add(new GuiButton(idIncrement, xOffset + x + width - 20, yOffset + y, 20, 20, "+"));
}
public void drawStrings(GuiScreen screen, FontRenderer fontRenderer, int xOffset, int yOffset, int labelColor, int numColor) {
screen.drawString(fontRenderer, I18n.format(keyLabel), xOffset + x, yOffset + y + 5, labelColor);
screen.drawCenteredString(fontRenderer, String.format(formatString, option.value), xOffset + x + width - 20 - numWidth / 2, yOffset + y + 5, numColor);
}
public void onAction(int buttonId) {
if (buttonId == idDecrement) option.decrement();
if (buttonId == idIncrement) option.increment();
}
}

View File

@@ -1,50 +0,0 @@
package mods.betterfoliage.client.gui.widget;
import java.util.List;
import mods.betterfoliage.common.config.OptionInteger;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class OptionIntegerWidget implements IOptionWidget {
public OptionInteger option;
public int x;
public int y;
public int width;
public int numWidth;
public int idDecrement;
public int idIncrement;
public String keyLabel;
public OptionIntegerWidget(OptionInteger option, int x, int y, int width, int numWidth, int idDecrement, int idIncrement, String keyLabel) {
this.option = option;
this.x = x;
this.y = y;
this.width = width;
this.numWidth = numWidth;
this.idDecrement = idDecrement;
this.idIncrement = idIncrement;
this.keyLabel = keyLabel;
}
public void addButtons(List<GuiButton> buttonList, int xOffset, int yOffset) {
buttonList.add(new GuiButton(idDecrement, xOffset + x + width - numWidth - 40, yOffset + y, 20, 20, "-"));
buttonList.add(new GuiButton(idIncrement, xOffset + x + width - 20, yOffset + y, 20, 20, "+"));
}
public void drawStrings(GuiScreen screen, FontRenderer fontRenderer, int xOffset, int yOffset, int labelColor, int numColor) {
screen.drawString(fontRenderer, I18n.format(keyLabel), xOffset + x, yOffset + y + 5, labelColor);
screen.drawCenteredString(fontRenderer, Integer.toString(option.value), xOffset + x + width - 20 - numWidth / 2, yOffset + y + 5, numColor);
}
public void onAction(int buttonId) {
if (buttonId == idDecrement) option.decrement();
if (buttonId == idIncrement) option.increment();
}
}

View File

@@ -1,62 +0,0 @@
package mods.betterfoliage.client.render;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.util.IIcon;
/** Same as {@link RenderBlockAOBase}, but does not actually render anything.
* @author octarine-noise
*/
@SideOnly(Side.CLIENT)
public class FakeRenderBlockAOBase extends RenderBlockAOBase {
@Override
public void renderFaceZNeg(Block block, double x, double y, double z, IIcon icon) {
saveShadingTopLeft(aoZNXYPP);
saveShadingTopRight(aoZNXYNP);
saveShadingBottomLeft(aoZNXYPN);
saveShadingBottomRight(aoZNXYNN);
}
@Override
public void renderFaceZPos(Block block, double x, double y, double z, IIcon icon) {
saveShadingTopLeft(aoZPXYNP);
saveShadingTopRight(aoZPXYPP);
saveShadingBottomLeft(aoZPXYNN);
saveShadingBottomRight(aoZPXYPN);
}
@Override
public void renderFaceXNeg(Block block, double x, double y, double z, IIcon icon) {
saveShadingTopLeft(aoXNYZPN);
saveShadingTopRight(aoXNYZPP);
saveShadingBottomLeft(aoXNYZNN);
saveShadingBottomRight(aoXNYZNP);
}
@Override
public void renderFaceXPos(Block block, double x, double y, double z, IIcon icon) {
saveShadingTopLeft(aoXPYZPP);
saveShadingTopRight(aoXPYZPN);
saveShadingBottomLeft(aoXPYZNP);
saveShadingBottomRight(aoXPYZNN);
}
@Override
public void renderFaceYNeg(Block block, double x, double y, double z, IIcon icon) {
saveShadingTopLeft(aoYNXZNP);
saveShadingTopRight(aoYNXZPP);
saveShadingBottomLeft(aoYNXZNN);
saveShadingBottomRight(aoYNXZPN);
}
@Override
public void renderFaceYPos(Block block, double x, double y, double z, IIcon icon) {
saveShadingTopLeft(aoYPXZPP);
saveShadingTopRight(aoYPXZNP);
saveShadingBottomLeft(aoYPXZPN);
saveShadingBottomRight(aoYPXZNN);
}
}

View File

@@ -1,15 +0,0 @@
package mods.betterfoliage.client.render;
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.world.IBlockAccess;
@SideOnly(Side.CLIENT)
public interface IRenderBlockDecorator extends ISimpleBlockRenderingHandler {
public void init();
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original);
}

View File

@@ -1,52 +0,0 @@
package mods.betterfoliage.client.render;
import mods.betterfoliage.common.util.Utils;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
/** Loads an indexed set of textures
* @author octarine-noise
*/
@SideOnly(Side.CLIENT)
public class IconSet {
/** Icon array */
public IIcon[] icons = new IIcon[16];
/** Number of successfully loaded icons*/
public int numLoaded = 0;
/** Resource domain of icons */
String domain;
/** Format string of icon paths */
String path;
public IconSet(String domain, String path) {
this.domain = domain;
this.path = path;
}
public void registerIcons(IIconRegister register) {
numLoaded = 0;
for (int idx = 0; idx < 16; idx++) {
icons[idx] = null;
// if the path contains a domain, use that to check if the resource exists
String resolvedDomain = path.contains(":") ? new ResourceLocation(path).getResourceDomain() : domain;
String resolvedPath = String.format("textures/blocks/" + (path.contains(":") ? new ResourceLocation(path).getResourcePath() : path) + ".png", idx);
if (Utils.resourceExists(new ResourceLocation(resolvedDomain, resolvedPath)))
icons[numLoaded++] = register.registerIcon(domain + ":" + String.format(path, idx));
}
}
public IIcon get(int variation) {
return numLoaded == 0 ? null : icons[variation % numLoaded];
}
public boolean hasIcons() {
return numLoaded > 0;
}
}

View File

@@ -1,437 +0,0 @@
package mods.betterfoliage.client.render;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import mods.betterfoliage.common.util.Double3;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.init.Blocks;
import net.minecraft.util.IIcon;
import net.minecraft.util.MathHelper;
import net.minecraftforge.common.util.ForgeDirection;
import org.lwjgl.opengl.GL11;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
/** Block renderer base class. Stores calculated ambient occlusion light and color values when rendering
* block sides for later use.
* @author octarine-noise
*/
@SideOnly(Side.CLIENT)
public class RenderBlockAOBase extends RenderBlocks {
/** AO light and color values
* @author octarine-noise
*/
@SideOnly(Side.CLIENT)
public static class ShadingValues {
public int passCounter = 0;
public int brightness;
public float red;
public float green;
public float blue;
}
protected double[] uValues = new double[] {0.0, 16.0, 16.0, 0.0};
protected double[] vValues = new double[] {0.0, 0.0, 16.0, 16.0};
protected ForgeDirection[] faceDir1 = new ForgeDirection[] {ForgeDirection.WEST, ForgeDirection.WEST, ForgeDirection.WEST, ForgeDirection.EAST, ForgeDirection.SOUTH, ForgeDirection.NORTH};
protected ForgeDirection[] faceDir2 = new ForgeDirection[] {ForgeDirection.NORTH, ForgeDirection.SOUTH, ForgeDirection.UP, ForgeDirection.UP, ForgeDirection.UP, ForgeDirection.UP};
/** Random vector pool. Unit rotation vectors in the XZ plane, Y coord goes between [-1.0, 1.0].
* Filled at init time */
public Double3[] pRot = new Double3[64];
/** Pool of random double values. Filled at init time. */
public double[] pRand = new double[64];
public ShadingValues aoXPYZPP = new ShadingValues();
public ShadingValues aoXPYZPN = new ShadingValues();
public ShadingValues aoXPYZNP = new ShadingValues();
public ShadingValues aoXPYZNN = new ShadingValues();
public ShadingValues aoXNYZPP = new ShadingValues();
public ShadingValues aoXNYZPN = new ShadingValues();
public ShadingValues aoXNYZNP = new ShadingValues();
public ShadingValues aoXNYZNN = new ShadingValues();
public ShadingValues aoYPXZPP = new ShadingValues();
public ShadingValues aoYPXZPN = new ShadingValues();
public ShadingValues aoYPXZNP = new ShadingValues();
public ShadingValues aoYPXZNN = new ShadingValues();
public ShadingValues aoYNXZPP = new ShadingValues();
public ShadingValues aoYNXZPN = new ShadingValues();
public ShadingValues aoYNXZNP = new ShadingValues();
public ShadingValues aoYNXZNN = new ShadingValues();
public ShadingValues aoZPXYPP = new ShadingValues();
public ShadingValues aoZPXYPN = new ShadingValues();
public ShadingValues aoZPXYNP = new ShadingValues();
public ShadingValues aoZPXYNN = new ShadingValues();
public ShadingValues aoZNXYPP = new ShadingValues();
public ShadingValues aoZNXYPN = new ShadingValues();
public ShadingValues aoZNXYNP = new ShadingValues();
public ShadingValues aoZNXYNN = new ShadingValues();
// temporary shading values for a single face
public ShadingValues faceAOPP, faceAOPN, faceAONN, faceAONP;
/** Initialize random values */
public void init() {
List<Double3> perturbs = new ArrayList<Double3>(64);
for (int idx = 0; idx < 64; idx++) {
double angle = (double) idx * Math.PI * 2.0 / 64.0;
perturbs.add(new Double3(Math.cos(angle), Math.random() * 2.0 - 1.0, Math.sin(angle)));
pRand[idx] = Math.random();
}
Collections.shuffle(perturbs);
Iterator<Double3> iter = perturbs.iterator();
for (int idx = 0; idx < 64; idx++) pRot[idx] = iter.next();
}
/** Get a semi-random value depending on block position.
* @param x block X coord
* @param y block Y coord
* @param z block Z coord
* @param seed additional seed
* @return semirandom value
*/
protected int getSemiRandomFromPos(double x, double y, double z, int seed) {
long lx = MathHelper.floor_double(x);
long ly = MathHelper.floor_double(y);
long lz = MathHelper.floor_double(z);
long value = (lx * lx + ly * ly + lz * lz + lx * ly + ly * lz + lz * lx + seed * seed) & 63;
value = (3 * lx * value + 5 * ly * value + 7 * lz * value + 11 * seed) & 63;
return (int) value;
}
public void renderInventoryBlock(Block block, int metadata, int modelId, RenderBlocks renderer) {
renderStandardBlockAsItem(renderer, block, metadata, 1.0f);
}
public boolean shouldRender3DInInventory(int modelId) {
return true;
}
public int getRenderId() {
return 0;
}
protected void renderStandardBlockAsItem(RenderBlocks renderer, Block p_147800_1_, int p_147800_2_, float p_147800_3_) {
Tessellator tessellator = Tessellator.instance;
boolean flag = p_147800_1_ == Blocks.grass;
float f2;
float f3;
int k;
p_147800_1_.setBlockBoundsForItemRender();
renderer.setRenderBoundsFromBlock(p_147800_1_);
GL11.glRotatef(90.0F, 0.0F, 1.0F, 0.0F);
GL11.glTranslatef(-0.5F, -0.5F, -0.5F);
tessellator.startDrawingQuads();
tessellator.setNormal(0.0F, -1.0F, 0.0F);
renderer.renderFaceYNeg(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 0, p_147800_2_));
tessellator.draw();
if (flag && renderer.useInventoryTint)
{
k = p_147800_1_.getRenderColor(p_147800_2_);
f2 = (float)(k >> 16 & 255) / 255.0F;
f3 = (float)(k >> 8 & 255) / 255.0F;
float f4 = (float)(k & 255) / 255.0F;
GL11.glColor4f(f2 * p_147800_3_, f3 * p_147800_3_, f4 * p_147800_3_, 1.0F);
}
tessellator.startDrawingQuads();
tessellator.setNormal(0.0F, 1.0F, 0.0F);
renderer.renderFaceYPos(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 1, p_147800_2_));
tessellator.draw();
if (flag && renderer.useInventoryTint)
{
GL11.glColor4f(p_147800_3_, p_147800_3_, p_147800_3_, 1.0F);
}
tessellator.startDrawingQuads();
tessellator.setNormal(0.0F, 0.0F, -1.0F);
renderer.renderFaceZNeg(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 2, p_147800_2_));
tessellator.draw();
tessellator.startDrawingQuads();
tessellator.setNormal(0.0F, 0.0F, 1.0F);
renderer.renderFaceZPos(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 3, p_147800_2_));
tessellator.draw();
tessellator.startDrawingQuads();
tessellator.setNormal(-1.0F, 0.0F, 0.0F);
renderer.renderFaceXNeg(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 4, p_147800_2_));
tessellator.draw();
tessellator.startDrawingQuads();
tessellator.setNormal(1.0F, 0.0F, 0.0F);
renderer.renderFaceXPos(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 5, p_147800_2_));
tessellator.draw();
GL11.glTranslatef(0.5F, 0.5F, 0.5F);
}
protected void setShadingForFace(ForgeDirection dir) {
if (dir == ForgeDirection.DOWN) {
// dir1 WEST, dir2 NORTH
faceAOPP = aoYNXZPP; faceAOPN = aoYNXZPN; faceAONN = aoYNXZNN; faceAONP = aoYNXZNP;
} else if (dir == ForgeDirection.UP) {
// dir1 WEST, dir2 SOUTH
faceAOPP = aoYPXZPP; faceAOPN = aoYPXZPN; faceAONN = aoYPXZNN; faceAONP = aoYPXZNP;
} else if (dir == ForgeDirection.NORTH) {
// dir1 WEST, dir2 UP
faceAOPP = aoZNXYNP; faceAOPN = aoZNXYNN; faceAONN = aoZNXYPN; faceAONP = aoZNXYPP;
} else if (dir == ForgeDirection.SOUTH) {
// dir1 EAST, dir2 UP
faceAOPP = aoZPXYPP; faceAOPN = aoZPXYPN; faceAONN = aoZPXYNN; faceAONP = aoZPXYNP;
} else if (dir == ForgeDirection.WEST) {
// dir1 SOUTH, dir2 UP
faceAOPP = aoXNYZPP; faceAOPN = aoXNYZNP; faceAONN = aoXNYZNN; faceAONP = aoXNYZPN;
} else if (dir == ForgeDirection.EAST) {
// dir1 NORTH, dir2 UP
faceAOPP = aoXPYZPN; faceAOPN = aoXPYZNN; faceAONN = aoXPYZNP; faceAONP = aoXPYZPP;
}
}
public void renderCrossedSideQuads(Double3 drawBase, ForgeDirection dir, double scale, double halfHeight, Double3 rendomVec, double offset, IIcon renderIcon, int uvRot, boolean noShading) {
Double3 facePP, faceNP, faceNormal, drawCenter;
if (dir == ForgeDirection.UP) {
// special case for block top, we'll be rendering a LOT of those
facePP = new Double3(-scale, 0.0, scale);
faceNP = new Double3(scale, 0.0, scale);
faceNormal = new Double3(0.0, halfHeight, 0.0);
drawCenter = drawBase.add(faceNormal);
if (rendomVec != null) {
drawCenter = drawBase.add(faceNormal).add(rendomVec.scaleAxes(-offset, 0.0, offset));
}
} else {
facePP = new Double3(faceDir1[dir.ordinal()]).add(new Double3(faceDir2[dir.ordinal()])).scale(scale);
faceNP = new Double3(faceDir1[dir.ordinal()]).inverse().add(new Double3(faceDir2[dir.ordinal()])).scale(scale);
faceNormal = new Double3(dir).scale(halfHeight);
drawCenter = drawBase.add(faceNormal);
if (rendomVec != null) {
drawCenter = drawCenter.add(new Double3(faceDir1[dir.ordinal()]).scale(rendomVec.x).scale(offset))
.add(new Double3(faceDir2[dir.ordinal()]).scale(rendomVec.z).scale(offset));
}
}
if (Minecraft.isAmbientOcclusionEnabled() && !noShading) {
setShadingForFace(dir);
renderQuadWithShading(renderIcon, drawCenter, facePP, faceNormal, uvRot, faceAOPP, faceAONN, faceAONN, faceAOPP);
renderQuadWithShading(renderIcon, drawCenter, facePP.inverse(), faceNormal, uvRot, faceAONN, faceAOPP, faceAOPP, faceAONN);
renderQuadWithShading(renderIcon, drawCenter, faceNP, faceNormal, uvRot, faceAONP, faceAOPN, faceAOPN, faceAONP);
renderQuadWithShading(renderIcon, drawCenter, faceNP.inverse(), faceNormal, uvRot, faceAOPN, faceAONP, faceAONP, faceAOPN);
} else {
renderQuad(renderIcon, drawCenter, facePP, faceNormal, uvRot);
renderQuad(renderIcon, drawCenter, facePP.inverse(), faceNormal, uvRot);
renderQuad(renderIcon, drawCenter, faceNP, faceNormal, uvRot);
renderQuad(renderIcon, drawCenter, faceNP.inverse(), faceNormal, uvRot);
}
}
protected void renderCrossedBlockQuadsTranslate(Double3 blockCenter, double halfSize, Double3 offsetVec, IIcon crossLeafIcon, int uvRot, boolean isAirTop, boolean isAirBottom) {
Double3 drawCenter = blockCenter;
if (offsetVec != null) drawCenter = drawCenter.add(offsetVec);
Double3 horz1 = new Double3(halfSize, 0.0, halfSize);
Double3 horz2 = new Double3(halfSize, 0.0, -halfSize);
Double3 vert1 = new Double3(0.0, halfSize * 1.41, 0.0);
renderCrossedBlockQuadsInternal(drawCenter, horz1, horz2, vert1, crossLeafIcon, uvRot, isAirTop, isAirBottom);
}
protected void renderCrossedBlockQuadsSkew(Double3 blockCenter, double halfSize, Double3 offsetVec1, Double3 offsetVec2, IIcon crossLeafIcon, int uvRot, boolean isAirTop, boolean isAirBottom) {
Double3 horz1 = new Double3(halfSize, 0.0, halfSize).add(offsetVec1);
Double3 horz2 = new Double3(halfSize, 0.0, -halfSize).add(offsetVec2);
Double3 vert1 = new Double3(0.0, halfSize * 1.41, 0.0);
renderCrossedBlockQuadsInternal(blockCenter, horz1, horz2, vert1, crossLeafIcon, uvRot, isAirTop, isAirBottom);
}
private void renderCrossedBlockQuadsInternal(Double3 drawCenter, Double3 horz1, Double3 horz2, Double3 vert1, IIcon crossLeafIcon, int uvRot, boolean isAirTop, boolean isAirBottom) {
if (Minecraft.isAmbientOcclusionEnabled()) {
renderQuadWithShading(crossLeafIcon, drawCenter, horz1, vert1, uvRot,
isAirTop ? aoYPXZPP : aoZPXYPP, isAirTop ? aoYPXZNN : aoXNYZPN, isAirBottom ? aoYNXZNN : aoXNYZNN, isAirBottom ? aoYNXZPP : aoZPXYPN);
renderQuadWithShading(crossLeafIcon, drawCenter, horz1.inverse(), vert1, uvRot,
isAirTop ? aoYPXZNN : aoZNXYNP, isAirTop ? aoYPXZPP : aoXPYZPP, isAirBottom ? aoYNXZPP : aoXPYZNP, isAirBottom ? aoYNXZNN : aoZNXYNN);
renderQuadWithShading(crossLeafIcon, drawCenter, horz2, vert1, uvRot,
isAirTop ? aoYPXZPN : aoXPYZPN, isAirTop ? aoYPXZNP : aoZPXYNP, isAirBottom ? aoYNXZNP : aoZPXYNN, isAirBottom ? aoYNXZPN : aoXPYZNN);
renderQuadWithShading(crossLeafIcon, drawCenter, horz2.inverse(), vert1, uvRot,
isAirTop ? aoYPXZNP : aoXNYZPP, isAirTop ? aoYPXZPN : aoZNXYPP, isAirBottom ? aoYNXZPN : aoZNXYPN, isAirBottom ? aoYNXZNP : aoXNYZNP);
} else {
renderQuad(crossLeafIcon, drawCenter, horz1, vert1, uvRot);
renderQuad(crossLeafIcon, drawCenter, horz1.inverse(), vert1, uvRot);
renderQuad(crossLeafIcon, drawCenter, horz2, vert1, uvRot);
renderQuad(crossLeafIcon, drawCenter, horz2.inverse(), vert1, uvRot);
}
}
@Override
public void renderFaceZNeg(Block block, double x, double y, double z, IIcon icon) {
super.renderFaceZNeg(block, x, y, z, icon);
saveShadingTopLeft(aoZNXYPP);
saveShadingTopRight(aoZNXYNP);
saveShadingBottomLeft(aoZNXYPN);
saveShadingBottomRight(aoZNXYNN);
}
@Override
public void renderFaceZPos(Block block, double x, double y, double z, IIcon icon) {
super.renderFaceZPos(block, x, y, z, icon);
saveShadingTopLeft(aoZPXYNP);
saveShadingTopRight(aoZPXYPP);
saveShadingBottomLeft(aoZPXYNN);
saveShadingBottomRight(aoZPXYPN);
}
@Override
public void renderFaceXNeg(Block block, double x, double y, double z, IIcon icon) {
super.renderFaceXNeg(block, x, y, z, icon);
saveShadingTopLeft(aoXNYZPN);
saveShadingTopRight(aoXNYZPP);
saveShadingBottomLeft(aoXNYZNN);
saveShadingBottomRight(aoXNYZNP);
}
@Override
public void renderFaceXPos(Block block, double x, double y, double z, IIcon icon) {
super.renderFaceXPos(block, x, y, z, icon);
saveShadingTopLeft(aoXPYZPP);
saveShadingTopRight(aoXPYZPN);
saveShadingBottomLeft(aoXPYZNP);
saveShadingBottomRight(aoXPYZNN);
}
@Override
public void renderFaceYNeg(Block block, double x, double y, double z, IIcon icon) {
super.renderFaceYNeg(block, x, y, z, icon);
saveShadingTopLeft(aoYNXZNP);
saveShadingTopRight(aoYNXZPP);
saveShadingBottomLeft(aoYNXZNN);
saveShadingBottomRight(aoYNXZPN);
}
@Override
public void renderFaceYPos(Block block, double x, double y, double z, IIcon icon) {
super.renderFaceYPos(block, x, y, z, icon);
saveShadingTopLeft(aoYPXZPP);
saveShadingTopRight(aoYPXZNP);
saveShadingBottomLeft(aoYPXZPN);
saveShadingBottomRight(aoYPXZNN);
}
protected void saveShadingTopLeft(ShadingValues values) {
if (--values.passCounter != 0) return;
values.brightness = brightnessTopLeft;
values.red = colorRedTopLeft;
values.green = colorGreenTopLeft;
values.blue = colorBlueTopLeft;
}
protected void saveShadingTopRight(ShadingValues values) {
if (--values.passCounter != 0) return;
values.brightness = brightnessTopRight;
values.red = colorRedTopRight;
values.green = colorGreenTopRight;
values.blue = colorBlueTopRight;
}
protected void saveShadingBottomLeft(ShadingValues values) {
if (--values.passCounter != 0) return;
values.brightness = brightnessBottomLeft;
values.red = colorRedBottomLeft;
values.green = colorGreenBottomLeft;
values.blue = colorBlueBottomLeft;
}
protected void saveShadingBottomRight(ShadingValues values) {
if (--values.passCounter != 0) return;
values.brightness = brightnessBottomRight;
values.red = colorRedBottomRight;
values.green = colorGreenBottomRight;
values.blue = colorBlueBottomRight;
}
/** Set pass counter on all shading value objects.
* Used to collect AO values from a specific draw pass
* if the underlying renderer draws overlays
* @param value pass counter
*/
protected void setPassCounters(int value) {
aoXPYZPP.passCounter = value;
aoXPYZPN.passCounter = value;
aoXPYZNP.passCounter = value;
aoXPYZNN.passCounter = value;
aoXNYZPP.passCounter = value;
aoXNYZPN.passCounter = value;
aoXNYZNP.passCounter = value;
aoXNYZNN.passCounter = value;
aoYPXZPP.passCounter = value;
aoYPXZPN.passCounter = value;
aoYPXZNP.passCounter = value;
aoYPXZNN.passCounter = value;
aoYNXZPP.passCounter = value;
aoYNXZPN.passCounter = value;
aoYNXZNP.passCounter = value;
aoYNXZNN.passCounter = value;
aoZPXYPP.passCounter = value;
aoZPXYPN.passCounter = value;
aoZPXYNP.passCounter = value;
aoZPXYNN.passCounter = value;
aoZNXYPP.passCounter = value;
aoZNXYPN.passCounter = value;
aoZNXYNP.passCounter = value;
aoZNXYNN.passCounter = value;
}
/** Render textured quad
* @param icon texture to use
* @param center center of quad
* @param vec1 vector to the half-point of one of the sides
* @param vec2 vector to half-point of side next to vec1
* @param uvRot number of increments to rotate UV coordinates by
*/
protected void renderQuad(IIcon icon, Double3 center, Double3 vec1, Double3 vec2, int uvRot) {
Tessellator tessellator = Tessellator.instance;
tessellator.addVertexWithUV(center.x + vec1.x + vec2.x, center.y + vec1.y + vec2.y, center.z + vec1.z + vec2.z, icon.getInterpolatedU(uValues[uvRot & 3]), icon.getInterpolatedV(vValues[uvRot & 3]));
tessellator.addVertexWithUV(center.x - vec1.x + vec2.x, center.y - vec1.y + vec2.y, center.z - vec1.z + vec2.z, icon.getInterpolatedU(uValues[(uvRot + 1) & 3]), icon.getInterpolatedV(vValues[(uvRot + 1) & 3]));
tessellator.addVertexWithUV(center.x - vec1.x - vec2.x, center.y - vec1.y - vec2.y, center.z - vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 2) & 3]), icon.getInterpolatedV(vValues[(uvRot + 2) & 3]));
tessellator.addVertexWithUV(center.x + vec1.x - vec2.x, center.y + vec1.y - vec2.y, center.z + vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 3) & 3]), icon.getInterpolatedV(vValues[(uvRot + 3) & 3]));
}
/** Render textured quad using AO information
* @param icon texture to use
* @param center center of quad
* @param vec1 vector to the half-point of one of the sides
* @param vec2 vector to half-point of side next to vec1
* @param uvRot number of increments to rotate UV coordinates by
* @param aoPP AO values for vertex at (+vec1, +vec2)
* @param aoNP AO values for vertex at (-vec1, +vec2)
* @param aoNN AO values for vertex at (-vec1, -vec2)
* @param aoPN AO values for vertex at (+vec1, -vec2)
*/
protected void renderQuadWithShading(IIcon icon, Double3 center, Double3 vec1, Double3 vec2, int uvRot, ShadingValues aoPP, ShadingValues aoNP, ShadingValues aoNN, ShadingValues aoPN) {
Tessellator tessellator = Tessellator.instance;
tessellator.setBrightness(aoPP.brightness);
tessellator.setColorOpaque_F(aoPP.red, aoPP.green, aoPP.blue);
tessellator.addVertexWithUV(center.x + vec1.x + vec2.x, center.y + vec1.y + vec2.y, center.z + vec1.z + vec2.z, icon.getInterpolatedU(uValues[uvRot & 3]), icon.getInterpolatedV(vValues[uvRot & 3]));
tessellator.setBrightness(aoNP.brightness);
tessellator.setColorOpaque_F(aoNP.red, aoNP.green, aoNP.blue);
tessellator.addVertexWithUV(center.x - vec1.x + vec2.x, center.y - vec1.y + vec2.y, center.z - vec1.z + vec2.z, icon.getInterpolatedU(uValues[(uvRot + 1) & 3]), icon.getInterpolatedV(vValues[(uvRot + 1) & 3]));
tessellator.setBrightness(aoNN.brightness);
tessellator.setColorOpaque_F(aoNN.red, aoNN.green, aoNN.blue);
tessellator.addVertexWithUV(center.x - vec1.x - vec2.x, center.y - vec1.y - vec2.y, center.z - vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 2) & 3]), icon.getInterpolatedV(vValues[(uvRot + 2) & 3]));
tessellator.setBrightness(aoPN.brightness);
tessellator.setColorOpaque_F(aoPN.red, aoPN.green, aoPN.blue);
tessellator.addVertexWithUV(center.x + vec1.x - vec2.x, center.y + vec1.y - vec2.y, center.z + vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 3) & 3]), icon.getInterpolatedV(vValues[(uvRot + 3) & 3]));
}
protected int getBrightness(Block block, int x, int y, int z) {
return block.getMixedBrightnessForBlock(blockAccess, x, y, z);
}
}

View File

@@ -1,78 +0,0 @@
package mods.betterfoliage.client.render.impl;
import java.util.Random;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.render.IRenderBlockDecorator;
import mods.betterfoliage.client.render.IconSet;
import mods.betterfoliage.client.render.RenderBlockAOBase;
import mods.betterfoliage.common.util.Double3;
import net.minecraft.block.Block;
import net.minecraft.block.BlockDirt;
import net.minecraft.block.material.Material;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.IIcon;
import net.minecraft.util.MathHelper;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.gen.NoiseGeneratorSimplex;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.event.world.WorldEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class RenderBlockBetterAlgae extends RenderBlockAOBase implements IRenderBlockDecorator {
public IconSet algaeIcons = new IconSet("bettergrassandleaves", "better_algae_%d");
public NoiseGeneratorSimplex noise;
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
if (!BetterFoliage.config.algaeEnabled) return false;
if (y >= 254 || !(block instanceof BlockDirt)) return false;
if (blockAccess.getBlock(x, y + 1, z).getMaterial() != Material.water) return false;
if (blockAccess.getBlock(x, y + 2, z).getMaterial() != Material.water) return false;
if (blockAccess.getBiomeGenForCoords(x, z).temperature < 0.4f) return false;
int terrainVariation = MathHelper.floor_double((noise.func_151605_a(x, z) + 1.0) * 32.0);
return terrainVariation < BetterFoliage.config.algaeChance.value;
}
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
// store world for later use
blockAccess = world;
// render grass block
setPassCounters(1);
setRenderBoundsFromBlock(block);
renderStandardBlock(block, x, y, z);
int variation = getSemiRandomFromPos(x, y, z, 0);
int heightVariation = getSemiRandomFromPos(x, y, z, 1);
IIcon renderIcon = algaeIcons.get(variation);
if (renderIcon == null) return true;
double scale = BetterFoliage.config.algaeSize.value * 0.5;
double halfHeight = 0.5 * (BetterFoliage.config.algaeHeightMin.value + pRand[heightVariation] * (BetterFoliage.config.algaeHeightMax.value - BetterFoliage.config.algaeHeightMin.value));
Tessellator.instance.setBrightness(getBrightness(block, x, y + 1, z));
renderCrossedSideQuads(new Double3(x + 0.5, y + 1.0 - 0.125 * halfHeight, z + 0.5), ForgeDirection.UP, scale, halfHeight, pRot[variation], BetterFoliage.config.algaeHOffset.value, renderIcon, 0, false);
return true;
}
@SubscribeEvent
public void handleTextureReload(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() != 0) return;
algaeIcons.registerIcons(event.map);
BetterFoliage.log.info(String.format("Found %d algae textures", algaeIcons.numLoaded));
}
@SubscribeEvent
public void handleWorldLoad(WorldEvent.Load event) {
noise = new NoiseGeneratorSimplex(new Random(event.world.getWorldInfo().getSeed() + 1));
}
}

View File

@@ -1,87 +0,0 @@
package mods.betterfoliage.client.render.impl;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.render.FakeRenderBlockAOBase;
import mods.betterfoliage.client.render.IRenderBlockDecorator;
import mods.betterfoliage.client.render.IconSet;
import mods.betterfoliage.common.util.Double3;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.init.Blocks;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.common.util.ForgeDirection;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class RenderBlockBetterCactus extends FakeRenderBlockAOBase implements IRenderBlockDecorator {
public IIcon cactusRoundIcon;
public IconSet cactusSideIcons = new IconSet("bettergrassandleaves", "better_cactus_arm_%d");
public static ForgeDirection[] cactusDirections = new ForgeDirection[] { ForgeDirection.NORTH, ForgeDirection.SOUTH, ForgeDirection.EAST, ForgeDirection.WEST};
public static double cactusRadius = 0.4375;
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
return BetterFoliage.config.cactusEnabled && block == Blocks.cactus;
}
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
// store world for later use
blockAccess = world;
// render cactus center
setPassCounters(1);
setRenderBoundsFromBlock(block);
Double3 blockCenter = new Double3(x + 0.5, y + 0.5, z + 0.5);
renderStandardBlock(block, x, y, z);
Tessellator.instance.setBrightness(getBrightness(block,x, y, z));
renderCactusCore(block.getBlockTextureFromSide(ForgeDirection.UP.ordinal()),
block.getBlockTextureFromSide(ForgeDirection.NORTH.ordinal()),
blockCenter, 0);
// render side growth
ForgeDirection drawDirection = cactusDirections[getSemiRandomFromPos(x, y, z, 0) % 4];
int iconVariation = getSemiRandomFromPos(x, y, z, 1);
Double3 drawBase = blockCenter.add(new Double3(drawDirection).scale(cactusRadius));
Tessellator.instance.setBrightness(getBrightness(block, x, y, z));
if (cactusSideIcons.hasIcons()) renderCrossedSideQuads(drawBase, drawDirection, 0.5, 0.5, pRot[iconVariation], 0.2, cactusSideIcons.get(iconVariation), 0, false);
renderCrossedBlockQuadsSkew(blockCenter, 0.65,
pRot[iconVariation].scaleAxes(0.1, 0.0, 0.1),
pRot[(iconVariation + 1) & 63].scaleAxes(0.1, 0.0, 0.1),
cactusRoundIcon, iconVariation, false, false);
return true;
}
protected void renderCactusCore(IIcon topIcon, IIcon sideIcon, Double3 blockCenter, int sideUvRot) {
if (Minecraft.isAmbientOcclusionEnabled()) {
renderQuadWithShading(sideIcon, blockCenter.add(cactusRadius, 0.0, 0.0), new Double3(0.0, 0.0, -0.5), new Double3(0.0, 0.5, 0.0), sideUvRot, aoXPYZPN, aoXPYZPP, aoXPYZNP, aoXPYZNN);
renderQuadWithShading(sideIcon, blockCenter.add(-cactusRadius, 0.0, 0.0), new Double3(0.0, 0.0, 0.5), new Double3(0.0, 0.5, 0.0), sideUvRot, aoXNYZPP, aoXNYZPN, aoXNYZNN, aoXNYZNP);
renderQuadWithShading(sideIcon, blockCenter.add(0.0, 0.0, cactusRadius), new Double3(0.5, 0.0, 0.0), new Double3(0.0, 0.5, 0.0), sideUvRot, aoZPXYPP, aoZPXYNP, aoZPXYNN, aoZPXYPN);
renderQuadWithShading(sideIcon, blockCenter.add(0.0, 0.0, -cactusRadius), new Double3(-0.5, 0.0, 0.0), new Double3(0.0, 0.5, 0.0), sideUvRot, aoZNXYNP, aoZNXYPP, aoZNXYPN, aoZNXYNN);
renderQuadWithShading(topIcon, blockCenter.add(0.0, 0.5, 0.0), new Double3(-0.5, 0.0, 0.0), new Double3(0.0, 0.0, 0.5), 0, aoYPXZNP, aoYPXZPP, aoYPXZPN, aoYPXZNN);
} else {
renderQuad(sideIcon, blockCenter.add(cactusRadius, 0.0, 0.0), new Double3(0.0, 0.0, -0.5), new Double3(0.0, 0.5, 0.0), sideUvRot);
renderQuad(sideIcon, blockCenter.add(-cactusRadius, 0.0, 0.0), new Double3(0.0, 0.0, 0.5), new Double3(0.0, 0.5, 0.0), sideUvRot);
renderQuad(sideIcon, blockCenter.add(0.0, 0.0, cactusRadius), new Double3(0.5, 0.0, 0.0), new Double3(0.0, 0.5, 0.0), sideUvRot);
renderQuad(sideIcon, blockCenter.add(0.0, 0.0, -cactusRadius), new Double3(-0.5, 0.0, 0.0), new Double3(0.0, 0.5, 0.0), sideUvRot);
renderQuad(topIcon, blockCenter.add(0.0, 0.5, 0.0), new Double3(-0.5, 0.0, 0.0), new Double3(0.0, 0.0, 0.5), 0);
}
}
@SubscribeEvent
public void handleTextureReload(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() != 0) return;
cactusRoundIcon = event.map.registerIcon("bettergrassandleaves:better_cactus");
cactusSideIcons.registerIcons(event.map);
BetterFoliage.log.info(String.format("Found %d cactus arm textures", cactusSideIcons.numLoaded));
}
}

View File

@@ -1,68 +0,0 @@
package mods.betterfoliage.client.render.impl;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.render.IRenderBlockDecorator;
import mods.betterfoliage.client.render.IconSet;
import mods.betterfoliage.client.render.RenderBlockAOBase;
import mods.betterfoliage.common.util.Double3;
import net.minecraft.block.Block;
import net.minecraft.block.BlockGrass;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.init.Blocks;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.common.util.ForgeDirection;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class RenderBlockBetterGrass extends RenderBlockAOBase implements IRenderBlockDecorator {
public IconSet grassIcons = new IconSet("bettergrassandleaves", "better_grass_long_%d");
public IconSet myceliumIcons = new IconSet("bettergrassandleaves", "better_mycel_%d");
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
if (!BetterFoliage.config.grassEnabled) return false;
if (!((block instanceof BlockGrass || block == Blocks.mycelium))) return false;
if (y == 255 || !blockAccess.isAirBlock(x, y + 1, z)) return false;
return true;
}
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
// store world for later use
blockAccess = world;
// render grass block
setPassCounters(1);
setRenderBoundsFromBlock(block);
renderStandardBlock(block, x, y, z);
int variation = getSemiRandomFromPos(x, y, z, 0);
int heightVariation = getSemiRandomFromPos(x, y, z, 1);
IIcon renderIcon = (block == Blocks.mycelium) ? myceliumIcons.get(variation) : grassIcons.get(variation);
if (renderIcon == null) return true;
double scale = BetterFoliage.config.grassSize.value * 0.5;
double halfHeight = 0.5 * (BetterFoliage.config.grassHeightMin.value + pRand[heightVariation] * (BetterFoliage.config.grassHeightMax.value - BetterFoliage.config.grassHeightMin.value));
Tessellator.instance.setBrightness(getBrightness(block, x, y + 1, z));
renderCrossedSideQuads(new Double3(x + 0.5, y + 1.0 - 0.125 * halfHeight, z + 0.5), ForgeDirection.UP, scale, halfHeight, pRot[variation], BetterFoliage.config.grassHOffset.value, renderIcon, 0, false);
return true;
}
@SubscribeEvent
public void handleTextureReload(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() != 0) return;
grassIcons.registerIcons(event.map);
myceliumIcons.registerIcons(event.map);
BetterFoliage.log.info(String.format("Found %d short grass textures", grassIcons.numLoaded));
BetterFoliage.log.info(String.format("Found %d mycelium textures", myceliumIcons.numLoaded));
}
}

View File

@@ -1,86 +0,0 @@
package mods.betterfoliage.client.render.impl;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.BetterFoliageClient;
import mods.betterfoliage.client.render.IRenderBlockDecorator;
import mods.betterfoliage.client.render.RenderBlockAOBase;
import mods.betterfoliage.common.util.Double3;
import mods.betterfoliage.common.util.Utils;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.common.util.ForgeDirection;
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class RenderBlockBetterLeaves extends RenderBlockAOBase implements IRenderBlockDecorator {
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
if (!BetterFoliage.config.leavesEnabled) return false;
if (original > 0 && original < 42) return false;
return BetterFoliageClient.leaves.matchesID(block) && !isBlockSurrounded(blockAccess, x, y, z);
}
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
// store world for later use
blockAccess = world;
// render leaves center
setPassCounters(1);
setRenderBoundsFromBlock(block);
if (block.getRenderType() == 0) {
renderStandardBlock(block, x, y, z);
} else {
ISimpleBlockRenderingHandler handler = Utils.getRenderingHandler(block.getRenderType());
handler.renderWorldBlock(world, x, y, z, block, block.getRenderType(), this);
}
// find generated texture to render with, assume the
// "true" texture of the block is the one on the north size
TextureAtlasSprite blockLeafIcon = (TextureAtlasSprite) block.getIcon(world, x, y, z, ForgeDirection.NORTH.ordinal());
IIcon crossLeafIcon = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(BetterFoliageClient.leafGenerator.domainName + ":" + blockLeafIcon.getIconName());
if (crossLeafIcon == null) {
return true;
}
int offsetVariation = getSemiRandomFromPos(x, y, z, 0);
int uvVariation = getSemiRandomFromPos(x, y, z, 1);
double halfSize = 0.5 * BetterFoliage.config.leavesSize.value;
boolean isAirTop = y == 255 || blockAccess.isAirBlock(x, y + 1, z);
boolean isAirBottom = y == 0 || blockAccess.isAirBlock(x, y - 1, z);
Tessellator.instance.setBrightness(isAirTop ? getBrightness(block, x, y + 1, z) : (isAirBottom ? getBrightness(block, x, y - 1, z) : getBrightness(block, x, y, z)));
Tessellator.instance.setColorOpaque_I(block.colorMultiplier(blockAccess, x, y, z));
if (BetterFoliage.config.leavesSkew) {
renderCrossedBlockQuadsSkew(new Double3(x + 0.5, y + 0.5, z + 0.5), halfSize,
pRot[offsetVariation].scaleAxes(BetterFoliage.config.leavesHOffset.value, BetterFoliage.config.leavesVOffset.value, BetterFoliage.config.leavesHOffset.value),
pRot[(offsetVariation + 1) & 63].scaleAxes(BetterFoliage.config.leavesHOffset.value, BetterFoliage.config.leavesVOffset.value, BetterFoliage.config.leavesHOffset.value),
crossLeafIcon, uvVariation, isAirTop, isAirBottom);
} else {
renderCrossedBlockQuadsTranslate(new Double3(x + 0.5, y + 0.5, z + 0.5), halfSize,
pRot[offsetVariation].scaleAxes(BetterFoliage.config.leavesHOffset.value, BetterFoliage.config.leavesVOffset.value, BetterFoliage.config.leavesHOffset.value),
crossLeafIcon, uvVariation, isAirTop, isAirBottom);
}
return true;
}
protected boolean isBlockSurrounded(IBlockAccess blockAccess, int x, int y, int z) {
if (blockAccess.isAirBlock(x + 1, y, z)) return false;
if (blockAccess.isAirBlock(x - 1, y, z)) return false;
if (blockAccess.isAirBlock(x, y, z + 1)) return false;
if (blockAccess.isAirBlock(x, y, z - 1)) return false;
if (y == 255 || blockAccess.isAirBlock(x, y + 1, z)) return false;
if (y == 0 || blockAccess.isAirBlock(x, y - 1, z)) return false;
return true;
}
}

View File

@@ -1,67 +0,0 @@
package mods.betterfoliage.client.render.impl;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.render.FakeRenderBlockAOBase;
import mods.betterfoliage.client.render.IRenderBlockDecorator;
import mods.betterfoliage.client.render.IconSet;
import mods.betterfoliage.common.util.Double3;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.init.Blocks;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.common.util.ForgeDirection;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class RenderBlockBetterLilypad extends FakeRenderBlockAOBase implements IRenderBlockDecorator {
public IconSet lilypadFlowers = new IconSet("bettergrassandleaves", "better_lilypad_flower_%d");
public IconSet lilypadRoots = new IconSet("bettergrassandleaves", "better_lilypad_roots_%d");
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
return BetterFoliage.config.lilypadEnabled && block == Blocks.waterlily;
}
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
// store world for later use
blockAccess = world;
// render grass block
renderBlockLilyPad(block, x, y, z);
int chanceVariation = getSemiRandomFromPos(x, y, z, 0);
int iconVariation = getSemiRandomFromPos(x, y, z, 1);
int offsetVariation = getSemiRandomFromPos(x, y, z, 2);
Tessellator.instance.setBrightness(getBrightness(block, x, y, z));
Tessellator.instance.setColorOpaque(255, 255, 255);
if (lilypadRoots.hasIcons()) renderCrossedSideQuads(new Double3(x + 0.5, y + 0.015, z + 0.5), ForgeDirection.DOWN,
0.2, 0.3,
null, 0.0,
lilypadRoots.get(iconVariation), 2,
true);
if (chanceVariation < BetterFoliage.config.lilypadChance.value && lilypadFlowers.hasIcons())
renderCrossedSideQuads(new Double3(x + 0.5, y + 0.02, z + 0.5), ForgeDirection.UP,
0.2, 0.3,
pRot[offsetVariation], BetterFoliage.config.lilypadHOffset.value,
lilypadFlowers.get(iconVariation), 0,
true);
return true;
}
@SubscribeEvent
public void handleTextureReload(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() != 0) return;
lilypadFlowers.registerIcons(event.map);
lilypadRoots.registerIcons(event.map);
BetterFoliage.log.info(String.format("Found %d lilypad flower textures", lilypadFlowers.numLoaded));
BetterFoliage.log.info(String.format("Found %d lilypad root textures", lilypadRoots.numLoaded));
}
}

View File

@@ -1,82 +0,0 @@
package mods.betterfoliage.client.render.impl;
import java.util.Random;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.render.IRenderBlockDecorator;
import mods.betterfoliage.client.render.IconSet;
import mods.betterfoliage.client.render.RenderBlockAOBase;
import mods.betterfoliage.common.util.Double3;
import net.minecraft.block.Block;
import net.minecraft.block.BlockDirt;
import net.minecraft.block.material.Material;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.IIcon;
import net.minecraft.util.MathHelper;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.gen.NoiseGeneratorSimplex;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.event.world.WorldEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class RenderBlockBetterReed extends RenderBlockAOBase implements IRenderBlockDecorator {
public IconSet reedBottomIcons = new IconSet("bf_reed_bottom", "bettergrassandleaves:better_reed_%d");
public IconSet reedTopIcons = new IconSet("bf_reed_top", "bettergrassandleaves:better_reed_%d");
public NoiseGeneratorSimplex noise;
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
if (!BetterFoliage.config.reedEnabled) return false;
if (y >= 254 || !(block instanceof BlockDirt)) return false;
if (blockAccess.getBlock(x, y + 1, z).getMaterial() != Material.water) return false;
if (!blockAccess.isAirBlock(x, y + 2, z)) return false;
if (blockAccess.getBiomeGenForCoords(x, z).temperature < 0.4f || blockAccess.getBiomeGenForCoords(x, z).rainfall < 0.4f) return false;
int terrainVariation = MathHelper.floor_double((noise.func_151605_a(x, z) + 1.0) * 32.0);
return terrainVariation < BetterFoliage.config.reedChance.value;
}
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
// store world for later use
blockAccess = world;
// render grass block
setPassCounters(1);
setRenderBoundsFromBlock(block);
renderStandardBlock(block, x, y, z);
int iconVariation = getSemiRandomFromPos(x, y, z, 0);
int heightVariation = getSemiRandomFromPos(x, y, z, 1);
IIcon bottomIcon = reedBottomIcons.get(iconVariation);
IIcon topIcon = reedTopIcons.get(iconVariation);
if (bottomIcon == null || topIcon == null) return true;
double quarterHeight = 0.25 * (BetterFoliage.config.reedHeightMin.value + pRand[heightVariation] * (BetterFoliage.config.reedHeightMax.value - BetterFoliage.config.reedHeightMin.value));
Tessellator.instance.setBrightness(getBrightness(block, x, y + 2, z));
Tessellator.instance.setColorOpaque(255, 255, 255);
renderCrossedSideQuads(new Double3(x + 0.5, y + 1.0, z + 0.5), ForgeDirection.UP, 0.5, quarterHeight, pRot[iconVariation], BetterFoliage.config.reedHOffset.value, bottomIcon, 0, true);
renderCrossedSideQuads(new Double3(x + 0.5, y + 1.0 + 2.0 * quarterHeight, z + 0.5), ForgeDirection.UP, 0.5, quarterHeight, pRot[iconVariation], BetterFoliage.config.reedHOffset.value, topIcon, 0, true);
return true;
}
@SubscribeEvent
public void handleTextureReload(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() != 0) return;
reedBottomIcons.registerIcons(event.map);
reedTopIcons.registerIcons(event.map);
BetterFoliage.log.info(String.format("Found %d reed textures", reedBottomIcons.numLoaded));
}
@SubscribeEvent
public void handleWorldLoad(WorldEvent.Load event) {
noise = new NoiseGeneratorSimplex(new Random(event.world.getWorldInfo().getSeed()));
}
}

View File

@@ -1,91 +0,0 @@
package mods.betterfoliage.client.resource;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.common.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.TextureStitchEvent;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public abstract class BlockTextureGenerator implements IResourceManager {
/** Resource domain name of generated textures */
public String domainName;
/** Resource location for fallback texture (if the generation process fails) */
public ResourceLocation missingResource;
/** Texture atlas for block textures used in the current run */
public TextureMap blockTextures;
/** Number of textures generated in the current run */
int counter = 0;
public BlockTextureGenerator(String domainName, ResourceLocation missingResource) {
this.domainName = domainName;
this.missingResource = missingResource;
}
public void onStitchStart(TextureStitchEvent.Pre event) {}
public void onStitchEnd(TextureStitchEvent.Post event) {}
@SubscribeEvent
public void handleTextureReload(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() != 0) return;
blockTextures = event.map;
counter = 0;
Map<String, IResourceManager> domainManagers = Utils.getDomainResourceManagers();
if (domainManagers == null) {
BetterFoliage.log.warn("Failed to inject texture generator");
return;
}
domainManagers.put(domainName, this);
onStitchStart(event);
}
@SubscribeEvent
public void endTextureReload(TextureStitchEvent.Post event) {
blockTextures = null;
if (event.map.getTextureType() != 0) return;
// don't leave a mess
Map<String, IResourceManager> domainManagers = Utils.getDomainResourceManagers();
if (domainManagers != null) domainManagers.remove(domainName);
onStitchEnd(event);
}
public Set<String> getResourceDomains() {
return ImmutableSet.<String>of(domainName);
}
public List<IResource> getAllResources(ResourceLocation resource) throws IOException {
return ImmutableList.<IResource>of(getResource(resource));
}
public IResource getMissingResource() throws IOException {
return Minecraft.getMinecraft().getResourceManager().getResource(missingResource);
}
public ResourceLocation unwrapResource(ResourceLocation wrapped) {
return new ResourceLocation(wrapped.getResourcePath().substring(16));
}
}

View File

@@ -1,74 +0,0 @@
package mods.betterfoliage.client.resource;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import mods.betterfoliage.BetterFoliage;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.data.IMetadataSection;
import net.minecraft.util.ResourceLocation;
/** {@link IResource} of PNG containing one half (top or bottom) of a given texture resource
* @author octarine-noise
*/
@SideOnly(Side.CLIENT)
public class HalfTextureResource implements IResource {
/** Raw PNG data*/
public byte[] data = null;
/** Resource to return if generation fails */
public IResource fallbackResource;
public HalfTextureResource(ResourceLocation resource, boolean bottom, IResource fallbackResource) {
this.fallbackResource = fallbackResource;
IResourceManager resourceManager = Minecraft.getMinecraft().getResourceManager();
try {
// load full texture
ResourceLocation origResource = new ResourceLocation(resource.getResourceDomain(), "textures/blocks/" + resource.getResourcePath());
BufferedImage origImage = ImageIO.read(resourceManager.getResource(origResource).getInputStream());
// draw half texture
BufferedImage result = new BufferedImage(origImage.getWidth(), origImage.getHeight() / 2, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D graphics = result.createGraphics();
graphics.drawImage(origImage, 0, bottom ? -origImage.getHeight() / 2 : 0, null);
// create PNG image
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(result, "PNG", baos);
data = baos.toByteArray();
} catch (Exception e) {
// stop log spam with GLSL installed
if (e instanceof FileNotFoundException) return;
BetterFoliage.log.info(String.format("Could not load texture: %s, exception: %s", resource.toString(), e.getClass().getSimpleName()));
}
}
@Override
public InputStream getInputStream() {
return data != null ? new ByteArrayInputStream(data) : fallbackResource.getInputStream();
}
@Override
public boolean hasMetadata() {
return false;
}
@Override
public IMetadataSection getMetadata(String var1) {
return null;
}
}

View File

@@ -1,11 +0,0 @@
package mods.betterfoliage.client.resource;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
@SideOnly(Side.CLIENT)
public interface ILeafTextureRecognizer {
public boolean isLeafTexture(TextureAtlasSprite icon);
}

View File

@@ -1,118 +0,0 @@
package mods.betterfoliage.client.resource;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.client.BetterFoliageClient;
import mods.betterfoliage.common.util.DeobfNames;
import mods.betterfoliage.common.util.Utils;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.IResource;
import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.TextureStitchEvent.Post;
import net.minecraftforge.client.event.TextureStitchEvent.Pre;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
/** Generates rounded crossleaf textures for all registered normal leaf textures at stitch time.
* @author octarine-noise
*/
@SideOnly(Side.CLIENT)
public class LeafTextureGenerator extends BlockTextureGenerator implements IIconRegister {
public String nonGeneratedDomain = "betterfoliage";
public int nonGeneratedCounter = 0;
public LeafTextureGenerator() {
super("bf_leaves_autogen", new ResourceLocation("betterfoliage", "textures/blocks/missing_leaf.png"));
}
/** List of helpers which can identify leaf textures loaded by alternate means */
public List<ILeafTextureRecognizer> recognizers = Lists.newLinkedList();
public IResource getResource(ResourceLocation resourceLocation) throws IOException {
ResourceLocation original = unwrapResource(resourceLocation);
// check for provided texture
ResourceLocation handDrawnLocation = new ResourceLocation(nonGeneratedDomain, String.format("textures/blocks/%s/%s", original.getResourceDomain(), original.getResourcePath()));
if (Utils.resourceExists(handDrawnLocation)) {
nonGeneratedCounter++;
return Minecraft.getMinecraft().getResourceManager().getResource(handDrawnLocation);
}
// generate our own
LeafTextureResource result = new LeafTextureResource(original, getMissingResource());
if (result.data != null) counter++;
return result;
}
/** Leaf blocks register their textures here. An extra texture will be registered in the atlas
* for each, with the resource domain of this generator.
* @return the originally registered {@link IIcon} already in the atlas
*/
public IIcon registerIcon(String resourceLocation) {
IIcon original = blockTextures.getTextureExtry(resourceLocation);
blockTextures.registerIcon(new ResourceLocation(domainName, resourceLocation).toString());
BetterFoliage.log.debug(String.format("Found leaf texture: %s", resourceLocation));
return original;
}
/** Iterates through all leaf blocks in the registry and makes them register
* their textures to "sniff out" all leaf textures.
* @param event
*/
@SuppressWarnings("unchecked")
@Override
public void onStitchStart(Pre event) {
nonGeneratedCounter = 0;
BetterFoliage.log.info("Reloading leaf textures");
// register simple block textures
Iterator<Block> iter = Block.blockRegistry.iterator();
while(iter.hasNext()) {
Block block = iter.next();
if (BetterFoliageClient.leaves.matchesClass(block)) {
BetterFoliage.log.debug(String.format("Inspecting leaf block: %s", block.getClass().getName()));
block.registerBlockIcons(this);
}
}
// enumerate all registered textures, find leaf textures among them
Map<String, TextureAtlasSprite> mapAtlas = null;
mapAtlas = Utils.getField(blockTextures, DeobfNames.TM_MRS_SRG, Map.class);
if (mapAtlas == null) mapAtlas = Utils.getField(blockTextures, DeobfNames.TM_MRS_MCP, Map.class);
if (mapAtlas == null) {
BetterFoliage.log.warn("Failed to reflect texture atlas, textures may be missing");
} else {
Set<String> foundLeafTextures = Sets.newHashSet();
for (TextureAtlasSprite icon : mapAtlas.values())
for (ILeafTextureRecognizer recognizer : recognizers)
if (recognizer.isLeafTexture(icon))
foundLeafTextures.add(icon.getIconName());
for (String resourceLocation : foundLeafTextures) {
BetterFoliage.log.debug(String.format("Found non-block-registered leaf texture: %s", resourceLocation));
blockTextures.registerIcon(new ResourceLocation(domainName, resourceLocation).toString());
}
}
}
@Override
public void onStitchEnd(Post event) {
BetterFoliage.log.info(String.format("Found %d pre-drawn leaf textures", nonGeneratedCounter));
BetterFoliage.log.info(String.format("Generated %d leaf textures", counter));
}
}

View File

@@ -1,116 +0,0 @@
package mods.betterfoliage.client.resource;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import mods.betterfoliage.BetterFoliage;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.data.IMetadataSection;
import net.minecraft.util.ResourceLocation;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
/** {@link IResource} containing an autogenerated round crossleaf texture
* @author octarine-noise
*/
@SideOnly(Side.CLIENT)
public class LeafTextureResource implements IResource {
/** Raw PNG data*/
protected byte[] data = null;
/** Name of the default alpha mask to use */
public static String defaultMask = "rough";
/** Resource to return if generation fails */
public IResource fallbackResource;
public LeafTextureResource(ResourceLocation resLeaf, IResource fallbackResource) {
this.fallbackResource = fallbackResource;
IResourceManager resourceManager = Minecraft.getMinecraft().getResourceManager();
try {
// load normal leaf texture
ResourceLocation origResource = new ResourceLocation(resLeaf.getResourceDomain(), "textures/blocks/" + resLeaf.getResourcePath());
BufferedImage origImage = ImageIO.read(resourceManager.getResource(origResource).getInputStream());
if (origImage.getWidth() != origImage.getHeight()) return;
int size = origImage.getWidth();
// load alpha mask of appropriate size
BufferedImage maskImage = loadLeafMaskImage(defaultMask, size * 2);
int scale = size * 2 / maskImage.getWidth();
// tile leaf texture 2x2
BufferedImage overlayIcon = new BufferedImage(size * 2, size * 2, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D graphics = overlayIcon.createGraphics();
graphics.drawImage(origImage, 0, 0, null);
graphics.drawImage(origImage, 0, size, null);
graphics.drawImage(origImage, size, 0, null);
graphics.drawImage(origImage, size, size, null);
// overlay mask alpha on texture
for (int x = 0; x < overlayIcon.getWidth(); x++) {
for (int y = 0; y < overlayIcon.getHeight(); y++) {
long origPixel = overlayIcon.getRGB(x, y) & 0xFFFFFFFFl;
long maskPixel = maskImage.getRGB(x / scale, y / scale) & 0xFF000000l | 0x00FFFFFF;
overlayIcon.setRGB(x, y, (int) (origPixel & maskPixel));
}
}
// create PNG image
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(overlayIcon, "PNG", baos);
data = baos.toByteArray();
} catch (Exception e) {
// stop log spam with GLSL installed
if (e instanceof FileNotFoundException) return;
BetterFoliage.log.info(String.format("Could not create leaf texture: %s, exception: %s", resLeaf.toString(), e.getClass().getSimpleName()));
}
}
/** Loads the alpha mask of the given type and size. If a mask of the exact size can not be found,
* will try to load progressively smaller masks down to 16x16
* @param type mask type
* @param size texture size
* @return alpha mask
*/
protected BufferedImage loadLeafMaskImage(String type, int size) {
IResourceManager resourceManager = Minecraft.getMinecraft().getResourceManager();
IResource maskResource = null;
while (maskResource == null && size >= 16) {
try {
maskResource = resourceManager.getResource(new ResourceLocation(String.format("betterfoliage:textures/blocks/leafmask_%d_%s.png", size, type)));
} catch (Exception e) {}
size /= 2;
}
try {
return maskResource == null ? null : ImageIO.read(maskResource.getInputStream());
} catch (IOException e) {
return null;
}
}
public InputStream getInputStream() {
return data != null ? new ByteArrayInputStream(data) : fallbackResource.getInputStream();
}
public boolean hasMetadata() {
return false;
}
public IMetadataSection getMetadata(String var1) {
return null;
}
}

View File

@@ -1,82 +0,0 @@
package mods.betterfoliage.common.config;
public class BetterFoliageConfig extends ConfigBase {
@CfgElement(category="leaves", key="enabled")
public boolean leavesEnabled = true;
@CfgElement(category="leaves", key="skewMode")
public boolean leavesSkew = false;
@CfgElement(category="grass", key="enabled")
public boolean grassEnabled = true;
@CfgElement(category="cactus", key="enabled")
public boolean cactusEnabled = true;
@CfgElement(category="lilypad", key="enabled")
public boolean lilypadEnabled = true;
@CfgElement(category="reed", key="enabled")
public boolean reedEnabled = true;
@CfgElement(category="algae", key="enabled")
public boolean algaeEnabled = true;
@CfgElement(category="leaves", key="horizontalOffset")
public OptionDouble leavesHOffset = new OptionDouble(0.0, 0.4, 0.025, 0.2);
@CfgElement(category="leaves", key="verticalOffset")
public OptionDouble leavesVOffset = new OptionDouble(0.0, 0.4, 0.025, 0.1);
@CfgElement(category="leaves", key="size")
public OptionDouble leavesSize = new OptionDouble(0.75, 1.8, 0.05, 1.4);
@CfgElement(category="grass", key="horizontalOffset")
public OptionDouble grassHOffset = new OptionDouble(0.0, 0.4, 0.025, 0.2);
@CfgElement(category="grass", key="heightMin")
@Limit(max="grassHeightMax")
public OptionDouble grassHeightMin = new OptionDouble(0.1, 1.5, 0.05, 0.5);
@CfgElement(category="grass", key="heightMax")
public OptionDouble grassHeightMax = new OptionDouble(0.1, 1.5, 0.05, 1.0);
@CfgElement(category="grass", key="size")
public OptionDouble grassSize = new OptionDouble(0.5, 1.5, 0.05, 1.0);
@CfgElement(category="lilypad", key="horizontalOffset")
public OptionDouble lilypadHOffset = new OptionDouble(0.0, 0.25, 0.025, 0.1);
@CfgElement(category="lilypad", key="chance")
public OptionInteger lilypadChance = new OptionInteger(0, 64, 1, 16);
@CfgElement(category="reed", key="horizontalOffset")
public OptionDouble reedHOffset = new OptionDouble(0.0, 0.25, 0.025, 0.1);
@CfgElement(category="reed", key="heightMin")
@Limit(max="reedHeightMax")
public OptionDouble reedHeightMin = new OptionDouble(1.5, 3.5, 0.1, 2.0);
@CfgElement(category="reed", key="heightMax")
public OptionDouble reedHeightMax = new OptionDouble(1.5, 3.5, 0.1, 2.5);
@CfgElement(category="reed", key="chance")
public OptionInteger reedChance = new OptionInteger(0, 64, 1, 32);
@CfgElement(category="algae", key="horizontalOffset")
public OptionDouble algaeHOffset = new OptionDouble(0.0, 0.25, 0.025, 0.1);
@CfgElement(category="algae", key="size")
public OptionDouble algaeSize = new OptionDouble(0.5, 1.5, 0.05, 1.0);
@CfgElement(category="algae", key="heightMin")
@Limit(max="algaeHeightMax")
public OptionDouble algaeHeightMin = new OptionDouble(0.1, 1.5, 0.05, 0.5);
@CfgElement(category="algae", key="heightMax")
public OptionDouble algaeHeightMax = new OptionDouble(0.1, 1.5, 0.05, 1.0);
@CfgElement(category="algae", key="chance")
public OptionInteger algaeChance = new OptionInteger(0, 64, 1, 48);
}

View File

@@ -1,128 +0,0 @@
package mods.betterfoliage.common.config;
import java.io.File;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
public class ConfigBase {
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public static @interface CfgElement {
String category();
String key();
String comment() default "";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public static @interface Limit {
String min() default "";
String max() default "";
}
protected Configuration config;
public void load(File configFile) {
config = new Configuration(configFile);
config.load();
for (Field field : getClass().getDeclaredFields()) {
CfgElement annot = field.getAnnotation(CfgElement.class);
if (annot == null) continue;
field.setAccessible(true);
if (field.getType().equals(boolean.class) || field.getType().equals(Boolean.class)) {
try {
Property prop = config.get(annot.category(), annot.key(), field.getBoolean(this));
field.setBoolean(this, prop.getBoolean(field.getBoolean(this)));
} catch (Exception e) {
}
} else if (field.getType().equals(OptionInteger.class)) {
try {
OptionInteger option = (OptionInteger) field.get(this);
Property prop = config.get(annot.category(), annot.key(), option.value);
option.value = prop.getInt(option.value);
} catch (Exception e) {
}
} else if (field.getType().equals(OptionDouble.class)) {
try {
OptionDouble option = (OptionDouble) field.get(this);
Property prop = config.get(annot.category(), annot.key(), option.value);
option.value = prop.getDouble(option.value);
} catch (Exception e) {
}
}
}
validateLimits();
if (config.hasChanged()) config.save();
}
protected void validateLimits() {
for (Field fieldThis : getClass().getDeclaredFields()) {
Limit annot = fieldThis.getAnnotation(Limit.class);
if (annot == null) continue;
try {
Field fieldMin = annot.min().isEmpty() ? null : getClass().getDeclaredField(annot.min());
Field fieldMax = annot.max().isEmpty() ? null : getClass().getDeclaredField(annot.max());
fieldThis.setAccessible(true);
fieldMin.setAccessible(true);
fieldMax.setAccessible(true);
if (fieldThis.getType().equals(OptionInteger.class)) {
OptionInteger optionThis = (OptionInteger) fieldThis.get(this);
OptionInteger optionMin = fieldMin == null ? null : (OptionInteger) fieldMin.get(this);
OptionInteger optionMax = fieldMax == null ? null : (OptionInteger) fieldMax.get(this);
if (optionMin != null) optionThis.value = Math.max(optionThis.value, optionMin.value);
if (optionMax != null) optionThis.value = Math.min(optionThis.value, optionMax.value);
} else if (fieldThis.getType().equals(OptionDouble.class)) {
OptionDouble optionThis = (OptionDouble) fieldThis.get(this);
OptionDouble optionMin = fieldMin == null ? null : (OptionDouble) fieldMin.get(this);
OptionDouble optionMax = fieldMax == null ? null : (OptionDouble) fieldMax.get(this);
if (optionMin != null) optionThis.value = Math.max(optionThis.value, optionMin.value);
if (optionMax != null) optionThis.value = Math.min(optionThis.value, optionMax.value);
}
} catch (Exception e) {}
}
}
public void save() {
for (Field field : getClass().getDeclaredFields()) {
CfgElement annot = field.getAnnotation(CfgElement.class);
if (annot == null) continue;
field.setAccessible(true);
if (field.getType().equals(boolean.class) || field.getType().equals(Boolean.class)) {
try {
Property prop = config.get(annot.category(), annot.key(), field.getBoolean(this));
prop.set(field.getBoolean(this));
} catch (Exception e) {
}
} else if (field.getType().equals(OptionInteger.class)) {
try {
OptionInteger option = (OptionInteger) field.get(this);
Property prop = config.get(annot.category(), annot.key(), option.value);
prop.set(option.value);
} catch (Exception e) {
}
} else if (field.getType().equals(OptionDouble.class)) {
try {
OptionDouble option = (OptionDouble) field.get(this);
Property prop = config.get(annot.category(), annot.key(), option.value);
prop.set(option.value);
} catch (Exception e) {
}
}
}
config.save();
}
}

View File

@@ -1,26 +0,0 @@
package mods.betterfoliage.common.config;
public class OptionDouble {
public double min;
public double max;
public double step;
public double value;
public OptionDouble(double min, double max, double step, double value) {
this.min = min;
this.max = max;
this.step = step;
this.value = value;
}
public void increment() {
value += step;
if (value > max) value = max;
}
public void decrement() {
value -= step;
if (value < min) value = min;
}
}

View File

@@ -1,26 +0,0 @@
package mods.betterfoliage.common.config;
public class OptionInteger {
public int min;
public int max;
public int step;
public int value;
public OptionInteger(int min, int max, int step, int value) {
this.min = min;
this.max = max;
this.step = step;
this.value = value;
}
public void increment() {
value += step;
if (value > max) value = max;
}
public void decrement() {
value -= step;
if (value < min) value = min;
}
}

View File

@@ -1,71 +0,0 @@
package mods.betterfoliage.common.util;
public class DeobfNames {
private DeobfNames() {}
/** MCP name of RenderBlocks */
public static final String RB_NAME_MCP = "net/minecraft/client/renderer/RenderBlocks";
/** Obfuscated name of RenderBlocks */
public static final String RB_NAME_OBF = "ble";
/** MCP name of RenderBlocks.blockAccess */
public static final String RB_BA_NAME_MCP = "blockAccess";
/** Obfuscated name of RenderBlocks.blockAccess */
public static final String RB_BA_NAME_OBF = "a";
/** MCP signature of RenderBlocks.blockAccess */
public static final String RB_BA_SIG_MCP = "Lnet/minecraft/world/IBlockAccess;";
/** Obfuscated signature of RenderBlocks.blockAccess */
public static final String RB_BA_SIG_OBF = "Lafx;";
/** MCP name of RenderBlocks.renderBlockByRenderType() */
public static final String RB_RBBRT_NAME_MCP = "renderBlockByRenderType";
/** Obfuscated name of RenderBlocks.renderBlockByRenderType() */
public static final String RB_RBBRT_NAME_OBF = "b";
/** MCP signature of RenderBlocks.renderBlockByRenderType() */
public static final String RB_RBBRT_SIG_MCP = "(Lnet/minecraft/block/Block;III)Z";
/** Obfuscated signature of RenderBlocks.renderBlockByRenderType() */
public static final String RB_RBBRT_SIG_OBF = "(Lahu;III)Z";
/** MCP signature of BetterFoliageClient.getRenderTypeOverride() */
public static final String BFC_GRTO_SIG_MCP = "(Lnet/minecraft/world/IBlockAccess;IIILnet/minecraft/block/Block;I)I";
/** Obfuscated signature of BetterFoliageClient.getRenderTypeOverride() */
public static final String BFC_GRTO_SIG_OBF = "(Lafx;IIILahu;I)I";
/** MCP name of SimpleReloadableResourceManager.domainResourceManagers */
public static final String SRRM_DRM_MCP = "domainResourceManagers";
/** SRG name of SimpleReloadableResourceManager.domainResourceManagers */
public static final String SRRM_DRM_SRGNAME = "field_110548_a";
/** MCP name of TextureMap.mapRegisteredSprites */
public static final String TM_MRS_MCP = "mapRegisteredSprites";
/** Obfuscated name of TextureMap.mapRegisteredSprites */
public static final String TM_MRS_OBF = "bpr";
/** SRG name of TextureMap.mapRegisteredSprites */
public static final String TM_MRS_SRG = "field_110574_e";
/** MCP signature of Shaders.pushEntity() */
public static final String SHADERS_PE_SIG_MCP = "(Lnet/minecraft/client/renderer/RenderBlocks;Lnet/minecraft/block/Block;III)V";
/** Obfuscated signature of Shaders.pushEntity() */
public static final String SHADERS_PE_SIG_OBF = "(Lble;Lahu;III)V";
/** MCP signature of BetterFoliageClient.getGLSLBlockIdOverride() */
public static final String BFC_GLSLID_SIG_MCP = "(ILnet/minecraft/block/Block;)I";
/** Obfuscated signature of BetterFoliageClient.getGLSLBlockIdOverride() */
public static final String BFC_GLSLID_SIG_OBF = "(ILahu;)I";
}

View File

@@ -1,42 +0,0 @@
package mods.betterfoliage.common.util;
import net.minecraftforge.common.util.ForgeDirection;
public class Double3 {
public final double x;
public final double y;
public final double z;
public Double3(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
public Double3(ForgeDirection dir) {
this.x = dir.offsetX;
this.y = dir.offsetY;
this.z = dir.offsetZ;
}
public Double3 add(Double3 other) {
return new Double3(x + other.x, y + other.y, z + other.z);
}
public Double3 add(double x, double y, double z) {
return new Double3(this.x + x, this.y + y, this.z + z);
}
public Double3 scaleAxes(double sx, double sy, double sz) {
return new Double3(x * sx, y * sy, z * sz);
}
public Double3 scale(double s) {
return new Double3(x * s, y * s, z * s);
}
public Double3 inverse() {
return new Double3(-x, -y, -z);
}
}

View File

@@ -1,70 +0,0 @@
package mods.betterfoliage.common.util;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Map;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
import cpw.mods.fml.client.registry.RenderingRegistry;
public class Utils {
private Utils() {}
@SuppressWarnings("unchecked")
public static Map<String, IResourceManager> getDomainResourceManagers() {
IResourceManager manager = Minecraft.getMinecraft().getResourceManager();
Map<String, IResourceManager> result = getField(manager, DeobfNames.SRRM_DRM_MCP, Map.class);
if (result == null) result = getField(manager, DeobfNames.SRRM_DRM_SRGNAME, Map.class);
return result;
}
@SuppressWarnings("unchecked")
public static <T> T getField(Object target, String fieldName, Class<T> resultClass) {
try {
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(target);
} catch (Exception e) {
return null;
}
}
@SuppressWarnings("unchecked")
public static <T> T getStaticField(Class<?> clazz, String fieldName, Class<T> resultClass) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(null);
} catch (Exception e) {
return null;
}
}
@SuppressWarnings("unchecked")
public static ISimpleBlockRenderingHandler getRenderingHandler(int renderType) {
try {
Field field = RenderingRegistry.class.getDeclaredField("INSTANCE");
field.setAccessible(true);
RenderingRegistry inst = (RenderingRegistry) field.get(null);
field = RenderingRegistry.class.getDeclaredField("blockRenderers");
field.setAccessible(true);
return ((Map<Integer, ISimpleBlockRenderingHandler>) field.get(inst)).get(renderType);
} catch (Exception e) {
return null;
}
}
public static boolean resourceExists(ResourceLocation resourceLocation) {
try {
IResource resource = Minecraft.getMinecraft().getResourceManager().getResource(resourceLocation);
if (resource != null) return true;
} catch (IOException e) {
}
return false;
}
}

View File

@@ -1,30 +0,0 @@
package mods.betterfoliage.loader;
import java.util.Map;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
@IFMLLoadingPlugin.MCVersion("1.7.2")
@IFMLLoadingPlugin.TransformerExclusions({"mods.betterfoliage.loader"})
public class BetterFoliageLoader implements IFMLLoadingPlugin {
public String[] getASMTransformerClass() {
return new String[] {"mods.betterfoliage.loader.BetterFoliageTransformer"};
}
public String getModContainerClass() {
return null;
}
public String getSetupClass() {
return null;
}
public void injectData(Map<String, Object> data) {
}
public String getAccessTransformerClass() {
return null;
}
}

View File

@@ -1,48 +0,0 @@
package mods.betterfoliage.loader;
import mods.betterfoliage.common.util.DeobfNames;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
public class BetterFoliageTransformer extends EZTransformerBase {
@MethodTransform(className="net.minecraft.client.renderer.RenderBlocks",
obf=@MethodMatch(name=DeobfNames.RB_RBBRT_NAME_OBF, signature=DeobfNames.RB_RBBRT_SIG_OBF),
deobf=@MethodMatch(name=DeobfNames.RB_RBBRT_NAME_MCP, signature=DeobfNames.RB_RBBRT_SIG_MCP),
log="Applying RenderBlocks.renderBlockByRenderType() render type ovverride")
public void handleRenderBlockOverride(MethodNode method, boolean obf) {
AbstractInsnNode invokeGetRenderType = findNext(method.instructions.getFirst(), matchInvokeAny());
AbstractInsnNode storeRenderType = findNext(invokeGetRenderType, matchOpcode(Opcodes.ISTORE));
insertAfter(method.instructions, storeRenderType,
new VarInsnNode(Opcodes.ALOAD, 0),
obf ? new FieldInsnNode(Opcodes.GETFIELD, DeobfNames.RB_NAME_OBF, DeobfNames.RB_BA_NAME_OBF, DeobfNames.RB_BA_SIG_OBF) :
new FieldInsnNode(Opcodes.GETFIELD, DeobfNames.RB_NAME_MCP, DeobfNames.RB_BA_NAME_MCP, DeobfNames.RB_BA_SIG_MCP),
new VarInsnNode(Opcodes.ILOAD, 2),
new VarInsnNode(Opcodes.ILOAD, 3),
new VarInsnNode(Opcodes.ILOAD, 4),
new VarInsnNode(Opcodes.ALOAD, 1),
new VarInsnNode(Opcodes.ILOAD, 5),
obf ? new MethodInsnNode(Opcodes.INVOKESTATIC, "mods/betterfoliage/client/BetterFoliageClient", "getRenderTypeOverride", DeobfNames.BFC_GRTO_SIG_OBF) :
new MethodInsnNode(Opcodes.INVOKESTATIC, "mods/betterfoliage/client/BetterFoliageClient", "getRenderTypeOverride", DeobfNames.BFC_GRTO_SIG_MCP),
new VarInsnNode(Opcodes.ISTORE, 5)
);
}
@MethodTransform(className="shadersmodcore.client.Shaders",
obf=@MethodMatch(name="pushEntity", signature=DeobfNames.SHADERS_PE_SIG_OBF),
deobf=@MethodMatch(name="pushEntity", signature=DeobfNames.SHADERS_PE_SIG_MCP),
log="Applying Shaders.pushEntity() block id ovverride")
public void handleGLSLBlockIDOverride(MethodNode method, boolean obf) {
AbstractInsnNode arrayStore = findNext(method.instructions.getFirst(), matchOpcode(Opcodes.IASTORE));
insertAfter(method.instructions, arrayStore.getPrevious(),
new VarInsnNode(Opcodes.ALOAD, 1),
obf ? new MethodInsnNode(Opcodes.INVOKESTATIC, "mods/betterfoliage/client/BetterFoliageClient", "getGLSLBlockIdOverride", DeobfNames.BFC_GLSLID_SIG_OBF) :
new MethodInsnNode(Opcodes.INVOKESTATIC, "mods/betterfoliage/client/BetterFoliageClient", "getGLSLBlockIdOverride", DeobfNames.BFC_GLSLID_SIG_MCP)
);
}
}

View File

@@ -1,131 +0,0 @@
package mods.betterfoliage.loader;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import net.minecraft.launchwrapper.IClassTransformer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
public class EZTransformerBase implements IClassTransformer {
public static interface IInstructionMatch {
public boolean matches(AbstractInsnNode node);
}
@Retention(RetentionPolicy.RUNTIME)
public static @interface MethodMatch {
public String name();
public String signature();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public static @interface MethodTransform {
public String className();
public MethodMatch deobf();
public MethodMatch obf();
public String log();
}
protected Logger logger = LogManager.getLogger(getClass().getSimpleName());
public byte[] transform(String name, String transformedName, byte[] basicClass) {
// ???
if (basicClass == null) return null;
// read class
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(basicClass);
classReader.accept(classNode, 0);
boolean hasTransformed = false;
for (Method classMethod : getClass().getMethods()) {
// check for annotated method with correct signature
MethodTransform annot = classMethod.getAnnotation(MethodTransform.class);
if (annot == null) continue;
if (classMethod.getParameterTypes().length != 2) continue;
if (!classMethod.getParameterTypes()[0].equals(MethodNode.class)) continue;
if (!classMethod.getParameterTypes()[1].equals(boolean.class)) continue;
// try to find specified method in class
if (!transformedName.equals(annot.className())) continue;
for (MethodNode methodNode : classNode.methods) {
Boolean obf = null;
if (methodNode.name.equals(annot.obf().name()) && methodNode.desc.equals(annot.obf().signature())) {
obf = true;
} else if (methodNode.name.equals(annot.deobf().name()) && methodNode.desc.equals(annot.deobf().signature())) {
obf = false;
}
if (obf != null) {
// transform
hasTransformed = true;
try {
classMethod.invoke(this, new Object[] {methodNode, obf});
logger.info(String.format("%s: SUCCESS", annot.log()));
} catch (Exception e) {
logger.info(String.format("%s: FAILURE", annot.log()));
}
break;
}
}
}
// return result
ClassWriter writer = new ClassWriter(0);
if (hasTransformed) classNode.accept(writer);
return !hasTransformed ? basicClass : writer.toByteArray();
}
protected AbstractInsnNode findNext(AbstractInsnNode start, IInstructionMatch match) {
AbstractInsnNode current = start;
while(current != null) {
if (match.matches(current)) break;
current = current.getNext();
}
return current;
}
protected AbstractInsnNode findPrevious(AbstractInsnNode start, IInstructionMatch match) {
AbstractInsnNode current = start;
while(current != null) {
if (match.matches(current)) break;
current = current.getPrevious();
}
return current;
}
protected static IInstructionMatch matchInvokeAny() {
return new IInstructionMatch() {
public boolean matches(AbstractInsnNode node) {
return node instanceof MethodInsnNode;
}
};
}
protected static IInstructionMatch matchOpcode(final int opcode) {
return new IInstructionMatch() {
public boolean matches(AbstractInsnNode node) {
return node.getOpcode() == opcode;
}
};
}
protected static void insertAfter(InsnList insnList, AbstractInsnNode node, AbstractInsnNode... added) {
InsnList listAdd = new InsnList();
for (AbstractInsnNode inst : added) listAdd.add(inst);
insnList.insert(node, listAdd);
}
}

View File

@@ -0,0 +1,44 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.Hooks;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Random;
@Mixin(Block.class)
public class MixinBlock {
private static final String shouldSideBeRendered = "Lnet/minecraft/block/Block;shouldDrawSide(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Z";
private static final String getVoxelShape = "Lnet/minecraft/block/BlockState;getCullingFace(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Lnet/minecraft/util/shape/VoxelShape;";
private static final String randomDisplayTick = "randomDisplayTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V";
/**
* Override the {@link VoxelShape} used for the neighbor block in {@link Block}.shouldSideBeRendered().
*
* This way log blocks can be made to not block rendering, without altering any {@link Block} or
* {@link BlockState} properties with potential gameplay ramifications.
*/
@Redirect(method = shouldSideBeRendered, at = @At(value = "INVOKE", target = getVoxelShape, ordinal = 1))
private static VoxelShape getVoxelShapeOverride(BlockState state, BlockView reader, BlockPos pos, Direction dir) {
return Hooks.getVoxelShapeOverride(state, reader, pos, dir);
}
/**
* Inject a callback to call for every random display tick. Used for adding custom particle effects to blocks.
*/
@Inject(method = randomDisplayTick, at = @At("HEAD"))
void onRandomDisplayTick(BlockState state, World world, BlockPos pos, Random rnd, CallbackInfo ci) {
// Hooks.onRandomDisplayTick(state.getBlock(), state, world, pos, rnd);
}
}

View File

@@ -0,0 +1,17 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.BlockModelsReloadCallback;
import net.minecraft.client.render.block.BlockModels;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(BlockModels.class)
public class MixinBlockModels {
@Inject(method = "reload()V", at = @At("RETURN"))
void onReload(CallbackInfo ci) {
BlockModelsReloadCallback.EVENT.invoker().reloadBlockModels((BlockModels) (Object) this);
}
}

View File

@@ -0,0 +1,29 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.Hooks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
/**
* Mixin to override the result of {@link BlockState}.getAmbientOcclusionLightValue().
*
* Needed to avoid excessive darkening of Round Logs at the corners, now that they are not full blocks.
*/
@Mixin(AbstractBlock.AbstractBlockState.class)
@SuppressWarnings({"deprecation"})
public class MixinBlockState {
private static final String callFrom = "Lnet/minecraft/block/AbstractBlock$AbstractBlockState;getAmbientOcclusionLightLevel(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)F";
// why is the INVOKEVIRTUAL target class Block in the bytecode, not AbstractBlock?
private static final String callTo = "Lnet/minecraft/block/Block;getAmbientOcclusionLightLevel(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)F";
@Redirect(method = callFrom, at = @At(value = "INVOKE", target = callTo))
float getAmbientOcclusionValue(Block block, BlockState state, BlockView reader, BlockPos pos) {
return Hooks.getAmbientOcclusionLightValueOverride(block.getAmbientOcclusionLightLevel(state, reader, pos), state);
}
}

View File

@@ -0,0 +1,24 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.ClientChunkLoadCallback;
import net.minecraft.client.world.ClientChunkManager;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.world.biome.source.BiomeArray;
import net.minecraft.world.chunk.WorldChunk;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ClientChunkManager.class)
public class MixinClientChunkManager {
private static final String onLoadChunkFromPacket = "Lnet/minecraft/client/world/ClientChunkManager;loadChunkFromPacket(IILnet/minecraft/world/biome/source/BiomeArray;Lnet/minecraft/network/PacketByteBuf;Lnet/minecraft/nbt/CompoundTag;IZ)Lnet/minecraft/world/chunk/WorldChunk;";
@Inject(method = onLoadChunkFromPacket, at = @At(value = "RETURN", ordinal = 2))
void onLoadChunkFromPacket(int x, int z, @Nullable BiomeArray biomes, PacketByteBuf buf, CompoundTag tag, int verticalStripBitmask, boolean complete, CallbackInfoReturnable<WorldChunk> ci) {
ClientChunkLoadCallback.EVENT.invoker().loadChunk(ci.getReturnValue());
}
}

View File

@@ -0,0 +1,19 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.ClientChunkLoadCallback;
import net.minecraft.world.chunk.WorldChunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(targets = {"net.minecraft.client.world.ClientChunkManager$ClientChunkMap"})
public class MixinClientChunkManagerChunkMap {
private static final String onCompareAndSet = "Lnet/minecraft/client/world/ClientChunkManager$ClientChunkMap;compareAndSet(ILnet/minecraft/world/chunk/WorldChunk;Lnet/minecraft/world/chunk/WorldChunk;)Lnet/minecraft/world/chunk/WorldChunk;";
@Inject(method = onCompareAndSet, at = @At("HEAD"))
void onSetAndCompare(int i, WorldChunk oldChunk, WorldChunk newChunk, CallbackInfoReturnable<WorldChunk> ci) {
ClientChunkLoadCallback.EVENT.invoker().unloadChunk(oldChunk);
}
}

View File

@@ -0,0 +1,53 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.ClientWorldLoadCallback;
import mods.betterfoliage.Hooks;
import net.minecraft.block.BlockState;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionType;
import net.minecraft.world.level.LevelInfo;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Random;
import java.util.function.Supplier;
@Mixin(ClientWorld.class)
public class MixinClientWorld {
private static final String ctor = "Lnet/minecraft/client/world/ClientWorld;<init>(Lnet/minecraft/client/network/ClientPlayNetworkHandler;Lnet/minecraft/client/world/ClientWorld$Properties;Lnet/minecraft/util/registry/RegistryKey;Lnet/minecraft/world/dimension/DimensionType;ILjava/util/function/Supplier;Lnet/minecraft/client/render/WorldRenderer;ZJ)V";
private static final String scheduleBlockRerenderIfNeeded = "Lnet/minecraft/client/world/ClientWorld;scheduleBlockRerenderIfNeeded(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;)V";
private static final String rendererNotify = "Lnet/minecraft/client/render/WorldRenderer;method_21596(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;)V";
private static final String worldDisplayTick = "randomBlockDisplayTick(IIIILjava/util/Random;ZLnet/minecraft/util/math/BlockPos$Mutable;)V";
private static final String blockDisplayTick = "Lnet/minecraft/block/Block;randomDisplayTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V";
/**
* Inject callback to get notified of client-side blockstate changes.
* Used to invalidate caches in the {@link mods.betterfoliage.chunk.ChunkOverlayManager}
*/
@Inject(method = scheduleBlockRerenderIfNeeded, at = @At(value = "HEAD"))
void onClientBlockChanged(BlockPos pos, BlockState oldState, BlockState newState, CallbackInfo ci) {
Hooks.onClientBlockChanged((ClientWorld) (Object) this, pos, oldState, newState);
}
@Inject(method = ctor, at = @At("RETURN"))
void onClientWorldCreated(ClientPlayNetworkHandler networkHandler, ClientWorld.Properties properties, RegistryKey<World> registryRef, DimensionType dimensionType, int loadDistance, Supplier<Profiler> profiler, WorldRenderer worldRenderer, boolean debugWorld, long seed, CallbackInfo ci) {
ClientWorldLoadCallback.EVENT.invoker().loadWorld((ClientWorld) (Object) this);
}
/**
* Inject a callback to call for every random display tick. Used for adding custom particle effects to blocks.
*/
@Inject(method = worldDisplayTick, at = @At(value = "INVOKE", target = blockDisplayTick))
void onRandomDisplayTick(int xCenter, int yCenter, int zCenter, int radius, Random random, boolean spawnBarrierParticles, BlockPos.Mutable mutable, CallbackInfo ci) {
Hooks.onRandomDisplayTick((ClientWorld) (Object) this, mutable);
}
}

View File

@@ -0,0 +1,37 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.ModelLoadingCallback;
import mods.betterfoliage.resource.discovery.BakeWrapperManager;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.function.Function;
@Mixin(ModelLoader.class)
public class MixinModelLoader {
@Shadow @Final private ResourceManager resourceManager;
// use the same trick fabric-api does to get around the no-mixins-in-constructors policy
@Inject(at = @At("HEAD"), method = "addModel")
private void addModelHook(ModelIdentifier id, CallbackInfo info) {
if (id.getPath().equals("trident_in_hand")) {
// last step before stitching
ModelLoadingCallback.EVENT.invoker().beginLoadModels((ModelLoader) (Object) this, resourceManager);
}
}
}

View File

@@ -0,0 +1,34 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.resource.discovery.BakeWrapperManager;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.function.Function;
@Mixin(ModelLoader.class)
public class MixinModelLoaderOptifine {
private static final String loaderBake = "Lnet/minecraft/class_1088;getBakedModel(Lnet/minecraft/class_2960;Lnet/minecraft/class_3665;Ljava/util/function/Function;)Lnet/minecraft/class_1087;";
private static final String modelBake = "Lnet/minecraft/class_1100;method_4753(Lnet/minecraft/class_1088;Ljava/util/function/Function;Lnet/minecraft/class_3665;Lnet/minecraft/class_2960;)Lnet/minecraft/class_1087;";
@SuppressWarnings("UnresolvedMixinReference")
@Redirect(method = loaderBake, at = @At(value = "INVOKE", target = modelBake), remap = false)
BakedModel onBakeModel(
UnbakedModel unbaked,
ModelLoader loader,
Function<SpriteIdentifier, Sprite> textureGetter,
ModelBakeSettings rotationContainer,
Identifier modelId
) {
return BakeWrapperManager.INSTANCE.onBake(unbaked, loader, textureGetter, rotationContainer, modelId);
}
}

View File

@@ -0,0 +1,34 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.resource.discovery.BakeWrapperManager;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.function.Function;
@Mixin(ModelLoader.class)
public class MixinModelLoaderVanilla {
private static final String loaderBake = "Lnet/minecraft/client/render/model/ModelLoader;bake(Lnet/minecraft/util/Identifier;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;";
private static final String modelBake = "Lnet/minecraft/client/render/model/UnbakedModel;bake(Lnet/minecraft/client/render/model/ModelLoader;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/model/BakedModel;";
@Redirect(method = loaderBake, at = @At(value = "INVOKE", target = modelBake))
BakedModel onBakeModel(
UnbakedModel unbaked,
ModelLoader loader,
Function<SpriteIdentifier, Sprite> textureGetter,
ModelBakeSettings rotationContainer,
Identifier modelId
) {
return BakeWrapperManager.INSTANCE.onBake(unbaked, loader, textureGetter, rotationContainer, modelId);
}
}

View File

@@ -0,0 +1,94 @@
package mods.betterfoliage
import me.zeroeightsix.fiber.JanksonSettings
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.MainConfig
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.render.block.vanilla.*
import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.particle.RisingSoulParticle
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.BlockTypeCache
import mods.betterfoliage.resource.generated.GeneratedBlockTexturePack
import net.fabricmc.api.ClientModInitializer
import net.fabricmc.fabric.api.resource.ResourceManagerHelper
import net.fabricmc.fabric.mixin.resource.loader.ResourcePackManagerAccessor
import net.fabricmc.loader.api.FabricLoader
import net.minecraft.block.BlockState
import net.minecraft.client.MinecraftClient
import net.minecraft.resource.ResourceType
import net.minecraft.util.Identifier
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.util.PropertiesUtil
import java.io.File
import java.io.PrintStream
import java.util.*
object BetterFoliage : ClientModInitializer {
const val MOD_ID = "betterfoliage"
val detailLogStream = PrintStream(File("logs/betterfoliage.log").apply {
parentFile.mkdirs()
if (!exists()) createNewFile()
})
fun logger(obj: Any) = LogManager.getLogger(obj)
fun detailLogger(obj: Any) = SimpleLogger(
obj::class.java.simpleName, Level.DEBUG, false, true, true, false, "yyyy-MM-dd HH:mm:ss", null, PropertiesUtil(Properties()), detailLogStream
)
val configFile get() = File(FabricLoader.getInstance().configDirectory, "BetterFoliage.json")
val config = MainConfig().apply {
if (configFile.exists()) JanksonSettings().deserialize(fiberNode, configFile.inputStream())
else JanksonSettings().serialize(fiberNode, configFile.outputStream(), false)
}
val blockConfig = BlockConfig()
val generatedPack = GeneratedBlockTexturePack(Identifier(MOD_ID, "generated"), "betterfoliage-generated", "Better Foliage", "Generated leaf textures")
/** List of recognized [BlockState]s */
var blockTypes = BlockTypeCache()
override fun onInitializeClient() {
// Register generated resource pack
ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(generatedPack.reloader)
(MinecraftClient.getInstance().resourcePackManager as ResourcePackManagerAccessor)
.providers.add(generatedPack.finder)
ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(blockConfig)
// Add standard block support
BakeWrapperManager.discoverers.add(StandardCactusDiscovery)
BakeWrapperManager.discoverers.add(StandardDirtDiscovery)
BakeWrapperManager.discoverers.add(StandardGrassDiscovery)
BakeWrapperManager.discoverers.add(StandardLeafDiscovery)
BakeWrapperManager.discoverers.add(StandardLilypadDiscovery)
BakeWrapperManager.discoverers.add(StandardMyceliumDiscovery)
BakeWrapperManager.discoverers.add(StandardNetherrackDiscovery)
BakeWrapperManager.discoverers.add(StandardRoundLogDiscovery)
BakeWrapperManager.discoverers.add(StandardSandDiscovery)
// Init overlay layers
ChunkOverlayManager.layers.add(RoundLogOverlayLayer)
// Init singletons
LeafParticleRegistry
StandardLeafModel.Companion
StandardGrassModel.Companion
StandardRoundLogModel.Companion
StandardCactusModel.Companion
StandardLilypadModel.Companion
DirtModel.Companion
StandardSandModel.Companion
StandardMyceliumModel.Companion
StandardNetherrackModel.Companion
RisingSoulParticle.Companion
ShadersModIntegration
}
}

View File

@@ -0,0 +1,11 @@
package mods.betterfoliage
import it.unimi.dsi.fastutil.ints.IntList
import mods.betterfoliage.util.YarnHelper
import net.minecraft.client.texture.Sprite
import net.minecraft.world.World
val VertexFormat_offsets = YarnHelper.requiredField<IntList>("net.minecraft.class_293", "field_1597", "Lit/unimi/dsi/fastutil/ints/IntList;")
val BakedQuad_sprite = YarnHelper.requiredField<Sprite>("net.minecraft.class_777", "field_4176", "Lnet/minecraft/class_1058;")
val WorldChunk_world = YarnHelper.requiredField<World>("net.minecraft.class_2818", "field_12858", "Lnet/minecraft/class_1937;")
val ChunkRendererRegion_world = YarnHelper.requiredField<World>("net.minecraft.class_853", "field_4490", "Lnet/minecraft/class_1937;")

View File

@@ -0,0 +1,72 @@
package mods.betterfoliage
import net.fabricmc.fabric.api.event.Event
import net.fabricmc.fabric.api.event.EventFactory
import net.minecraft.client.render.block.BlockModels
import net.minecraft.client.render.model.ModelLoader
import net.minecraft.client.world.ClientWorld
import net.minecraft.resource.ResourceManager
import net.minecraft.world.chunk.WorldChunk
interface ClientChunkLoadCallback {
fun loadChunk(chunk: WorldChunk)
fun unloadChunk(chunk: WorldChunk)
companion object {
@JvmField val EVENT: Event<ClientChunkLoadCallback> = EventFactory.createArrayBacked(ClientChunkLoadCallback::class.java) { listeners ->
object : ClientChunkLoadCallback {
override fun loadChunk(chunk: WorldChunk) { listeners.forEach { it.loadChunk(chunk) } }
override fun unloadChunk(chunk: WorldChunk) { listeners.forEach { it.unloadChunk(chunk) } }
}
}
}
}
interface ClientWorldLoadCallback {
fun loadWorld(world: ClientWorld)
companion object {
@JvmField val EVENT : Event<ClientWorldLoadCallback> = EventFactory.createArrayBacked(ClientWorldLoadCallback::class.java) { listeners ->
object : ClientWorldLoadCallback {
override fun loadWorld(world: ClientWorld) { listeners.forEach { it.loadWorld(world) } }
}
}
}
}
/**
* Event fired after [BlockModels.reload] finishes.
*/
interface BlockModelsReloadCallback {
fun reloadBlockModels(blockModels: BlockModels)
companion object {
@JvmField val EVENT: Event<BlockModelsReloadCallback> = EventFactory.createArrayBacked(BlockModelsReloadCallback::class.java) { listeners ->
object : BlockModelsReloadCallback {
override fun reloadBlockModels(blockModels: BlockModels) {
listeners.forEach { it.reloadBlockModels(blockModels) }
}
}
}
}
}
/**
* Event fired when the [ModelLoader] first starts loading models.
*
* This happens during the constructor, so BEWARE!
* Try to avoid any interaction until the block texture atlas starts stitching.
*/
interface ModelLoadingCallback {
fun beginLoadModels(loader: ModelLoader, manager: ResourceManager)
companion object {
@JvmField val EVENT: Event<ModelLoadingCallback> = EventFactory.createArrayBacked(ModelLoadingCallback::class.java) { listeners ->
object : ModelLoadingCallback {
override fun beginLoadModels(loader: ModelLoader, manager: ResourceManager) {
listeners.forEach { it.beginLoadModels(loader, manager) }
}
}
}
}
}

View File

@@ -0,0 +1,67 @@
@file:JvmName("Hooks")
package mods.betterfoliage
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.render.block.vanilla.LeafParticleKey
import mods.betterfoliage.render.block.vanilla.RoundLogKey
import mods.betterfoliage.render.particle.FallingLeafParticle
import mods.betterfoliage.render.particle.RisingSoulParticle
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.random
import mods.betterfoliage.util.randomD
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.MinecraftClient
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView
fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float {
if (BetterFoliage.config.enabled &&
BetterFoliage.config.roundLogs.enabled &&
BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state)
) return BetterFoliage.config.roundLogs.dimming.toFloat()
return original
}
fun getUseNeighborBrightnessOverride(original: Boolean, state: BlockState): Boolean {
return original || (BetterFoliage.config.enabled && BetterFoliage.config.roundLogs.enabled && BetterFoliage.blockConfig.logBlocks.matchesClass(state.block));
}
fun onClientBlockChanged(worldClient: ClientWorld, pos: BlockPos, oldState: BlockState, newState: BlockState) {
ChunkOverlayManager.onBlockChange(worldClient, pos)
}
fun onRandomDisplayTick(world: ClientWorld, pos: BlockPos) {
val state = world.getBlockState(pos)
if (BetterFoliage.config.enabled &&
BetterFoliage.config.risingSoul.enabled &&
state.block == Blocks.SOUL_SAND &&
world.isAir(pos + Direction.UP.offset) &&
Math.random() < BetterFoliage.config.risingSoul.chance) {
RisingSoulParticle(world, pos).addIfValid()
}
if (BetterFoliage.config.enabled &&
BetterFoliage.config.fallingLeaves.enabled &&
world.isAir(pos + Direction.DOWN.offset) &&
randomD() < BetterFoliage.config.fallingLeaves.chance) {
BetterFoliage.blockTypes.getTyped<LeafParticleKey>(state)?.let { key ->
val blockColor = MinecraftClient.getInstance().blockColors.getColor(state, world, pos, 0)
FallingLeafParticle(world, pos, key, blockColor, random).addIfValid()
}
}
}
fun getVoxelShapeOverride(state: BlockState, reader: BlockView, pos: BlockPos, dir: Direction): VoxelShape {
if (BetterFoliage.blockTypes.hasTyped<RoundLogKey>(state)) {
return VoxelShapes.empty()
}
// TODO ?
return state.getCullingFace(reader, pos, dir)
}

View File

@@ -0,0 +1,60 @@
package mods.betterfoliage.chunk
import mods.betterfoliage.ChunkRendererRegion_world
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.chunk.ChunkRendererRegion
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockRenderView
import net.minecraft.world.WorldView
import net.minecraft.world.biome.Biome
/**
* Represents the block being rendered. Has properties and methods to query the neighborhood of the block in
* block-relative coordinates.
*/
interface BlockCtx {
val world: BlockRenderView
val pos: BlockPos
fun offset(dir: Direction) = offset(dir.offset)
fun offset(offset: Int3): BlockCtx
val state: BlockState get() = world.getBlockState(pos)
fun state(dir: Direction) = world.getBlockState(pos + dir.offset)
fun state(offset: Int3) = world.getBlockState(pos + offset)
val biome: Biome? get() =
(world as? WorldView)?.getBiome(pos) ?:
(world as? ChunkRendererRegion)?.let { ChunkRendererRegion_world[it]?.getBiome(pos) }
val isNormalCube: Boolean get() = state.isOpaqueFullCube(world, pos)
fun shouldSideBeRendered(side: Direction) = Block.shouldDrawSide(state, world, pos, side)
fun isNeighborSolid(dir: Direction) = offset(dir).let { it.state.isSideSolidFullSquare(it.world, it.pos, dir.opposite) }
fun model(dir: Direction) = state(dir).let { MinecraftClient.getInstance().blockRenderManager.getModel(it)!! }
fun model(offset: Int3) = state(offset).let { MinecraftClient.getInstance().blockRenderManager.getModel(it)!! }
}
open class BasicBlockCtx(
override val world: BlockRenderView,
override val pos: BlockPos
) : BlockCtx {
override val state = world.getBlockState(pos)
override fun offset(offset: Int3) = BasicBlockCtx(world, pos + offset)
fun cache() = CachedBlockCtx(world, pos)
}
open class CachedBlockCtx(world: BlockRenderView, pos: BlockPos) : BasicBlockCtx(world, pos) {
var neighbors = Array<BlockState>(6) { world.getBlockState(pos + allDirections[it].offset) }
override var biome: Biome? = super.biome
override fun state(dir: Direction) = neighbors[dir.ordinal]
}

View File

@@ -0,0 +1,50 @@
package mods.betterfoliage.chunk
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockView
import net.minecraft.world.LightType
import net.minecraft.world.WorldView
/**
* Delegating [IBlockAccess] that fakes a _modified_ location to return values from a _target_ location.
* All other locations are handled normally.
*
* @param[original] the [IBlockAccess] that is delegated to
*/
@Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
open class OffsetBlockView(open val original: BlockView, val modded: BlockPos, val target: BlockPos) : BlockView {
inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos
override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos))
override fun getBlockEntity(pos: BlockPos) = original.getBlockEntity(actualPos(pos))
override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos))
}
@Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
class OffsetExtBlockView(val original: WorldView, val modded: BlockPos, val target: BlockPos) : WorldView by original {
inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos
override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos))
override fun getBlockEntity(pos: BlockPos) = original.getBlockEntity(actualPos(pos))
override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos))
override fun getLightLevel(type: LightType, pos: BlockPos) = original.getLightLevel(type, actualPos(pos))
override fun getBaseLightLevel(pos: BlockPos, light: Int) = original.getBaseLightLevel(actualPos(pos), light)
override fun getBiome(pos: BlockPos) = original.getBiome(actualPos(pos))
}
/**
* Temporarily replaces the [IBlockReader] used by this [BlockContext] and the corresponding [ExtendedRenderBlocks]
* to use an [OffsetEnvBlockReader] while executing this lambda.
*
* @param[modded] the _modified_ location
* @param[target] the _target_ location
* @param[func] the lambda to execute
*/
//inline fun <reified T> BlockContext.withOffset(modded: Int3, target: Int3, func: () -> T): T {
// val original = reader!!
// reader = OffsetEnvBlockReader(original, pos + modded, pos + target)
// val result = func()
// reader = original
// return result
//}

View File

@@ -0,0 +1,119 @@
package mods.betterfoliage.chunk
import mods.betterfoliage.*
import mods.betterfoliage.util.YarnHelper
import mods.betterfoliage.util.get
import net.minecraft.client.render.chunk.ChunkRendererRegion
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.ChunkPos
import net.minecraft.world.BlockRenderView
import net.minecraft.world.World
import net.minecraft.world.WorldView
import net.minecraft.world.chunk.WorldChunk
import net.minecraft.world.dimension.DimensionType
import java.util.*
import kotlin.collections.List
import kotlin.collections.MutableMap
import kotlin.collections.associateWith
import kotlin.collections.forEach
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.set
val BlockRenderView.dimType: DimensionType get() = when {
this is WorldView -> dimension
this is ChunkRendererRegion -> this[ChunkRendererRegion_world]!!.dimension
// this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache]!!.dimType
else -> throw IllegalArgumentException("DimensionType of world with class ${this::class.qualifiedName} cannot be determined!")
}
/**
* Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position
*/
interface ChunkOverlayLayer<T> {
fun calculate(ctx: BlockCtx): T
fun onBlockUpdate(world: WorldView, pos: BlockPos)
}
/**
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
*/
object ChunkOverlayManager : ClientChunkLoadCallback, ClientWorldLoadCallback {
init {
ClientWorldLoadCallback.EVENT.register(this)
ClientChunkLoadCallback.EVENT.register(this)
}
val chunkData = IdentityHashMap<DimensionType, MutableMap<ChunkPos, ChunkOverlayData>>()
val layers = mutableListOf<ChunkOverlayLayer<*>>()
/**
* Get the overlay data for a given layer and position
*
* @param layer Overlay layer to query
* @param reader World to use if calculation of overlay value is necessary
* @param pos Block position
*/
fun <T> get(layer: ChunkOverlayLayer<T>, ctx: BlockCtx): T? {
val data = chunkData[ctx.world.dimType]?.get(ChunkPos(ctx.pos)) ?: return null
data.get(layer, ctx.pos).let { value ->
if (value !== ChunkOverlayData.UNCALCULATED) return value
val newValue = layer.calculate(ctx)
data.set(layer, ctx.pos, newValue)
return newValue
}
}
/**
* Clear the overlay data for a given layer and position
*
* @param layer Overlay layer to clear
* @param pos Block position
*/
fun <T> clear(dimension: DimensionType, layer: ChunkOverlayLayer<T>, pos: BlockPos) {
chunkData[dimension]?.get(ChunkPos(pos))?.clear(layer, pos)
}
fun onBlockChange(world: ClientWorld, pos: BlockPos) {
if (chunkData[world.dimType]?.containsKey(ChunkPos(pos)) == true) {
layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
}
}
override fun loadChunk(chunk: WorldChunk) {
chunk[WorldChunk_world]!!.dimType.let { dim ->
val data = chunkData[dim] ?: mutableMapOf<ChunkPos, ChunkOverlayData>().apply { chunkData[dim] = this }
data.let { chunks ->
// check for existence first because Optifine fires a TON of these
if (chunk.pos !in chunks.keys) chunks[chunk.pos] = ChunkOverlayData(layers)
}
}
}
override fun unloadChunk(chunk: WorldChunk) {
chunk[WorldChunk_world]!!.dimType.let { dim ->
chunkData[dim]?.remove(chunk.pos)
}
}
override fun loadWorld(world: ClientWorld) {
val dim = world.dimType
// chunkData.keys.forEach { if (it == dim) chunkData[dim] = mutableMapOf() else chunkData.remove(dim)}
}
}
class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) {
val BlockPos.isValid: Boolean get() = y in validYRange
val rawData = layers.associateWith { emptyOverlay() }
fun <T> get(layer: ChunkOverlayLayer<T>, pos: BlockPos): T? = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.get(pos.y) as T? else null
fun <T> set(layer: ChunkOverlayLayer<T>, pos: BlockPos, data: T) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, data) else null
fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, UNCALCULATED) else null
companion object {
val UNCALCULATED = object {}
fun emptyOverlay() = Array(16) { Array(16) { Array<Any?>(256) { UNCALCULATED }}}
val validYRange = 0 until 256
}
}

View File

@@ -0,0 +1,39 @@
package mods.betterfoliage.config
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.resource.VeryEarlyReloadListener
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ModelTextureListConfiguration
import net.minecraft.resource.ResourceManager
import net.minecraft.util.Identifier
class BlockConfig : VeryEarlyReloadListener {
private val list = mutableListOf<Any>()
val leafBlocks = blocks("leaves_blocks_default.cfg")
val leafModels = models("leaves_models_default.cfg")
val grassBlocks = blocks("grass_blocks_default.cfg")
val grassModels = models("grass_models_default.cfg")
// val mycelium = blocks("mycelium_blocks_default.cfg")
// val dirt = blocks("dirt_default.cfg")
// val crops = blocks("crop_default.cfg")
val logBlocks = blocks("log_blocks_default.cfg")
val logModels = models("log_models_default.cfg")
// val sand = blocks("sand_default.cfg")
// val lilypad = blocks("lilypad_default.cfg")
// val cactus = blocks("cactus_default.cfg")
// val netherrack = blocks("netherrack_blocks_default.cfg")
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(Identifier(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) }
private fun models(cfgName: String) = ModelTextureListConfiguration(Identifier(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) }
override fun getFabricId() = Identifier(BetterFoliage.MOD_ID, "block-config")
override fun onReloadStarted(manager: ResourceManager) {
list.forEach { when(it) {
is ConfigurableBlockMatcher -> it.readDefaults(manager)
is ModelTextureListConfiguration -> it.readDefaults(manager)
} }
}
}

View File

@@ -0,0 +1,141 @@
package mods.betterfoliage.config
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder
import me.shedaniel.clothconfig2.gui.entries.SubCategoryListEntry
import me.zeroeightsix.fiber.builder.ConfigValueBuilder
import me.zeroeightsix.fiber.tree.ConfigLeaf
import me.zeroeightsix.fiber.tree.ConfigNode
import me.zeroeightsix.fiber.tree.ConfigValue
import net.minecraft.client.resource.language.I18n
import net.minecraft.text.LiteralText
import java.util.*
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
const val MAX_LINE_LEN = 30
fun textify(string: String) = LiteralText(string)
fun textify(strings: Array<String>) = strings.map(::LiteralText).toTypedArray()
sealed class DelegatingConfigNode<N: ConfigLeaf>(val fiberNode: N) {
abstract fun createClothNode(names: List<String>): AbstractConfigListEntry<*>
}
abstract class DelegatingConfigValue<T>(fiberNode: ConfigValue<T>) : DelegatingConfigNode<ConfigValue<T>>(fiberNode), ReadOnlyProperty<DelegatingConfigGroup, T>
open class DelegatingConfigGroup(fiberNode: ConfigNode) : DelegatingConfigNode<ConfigNode>(fiberNode) {
val children = mutableListOf<DelegatingConfigNode<*>>()
override fun createClothNode(names: List<String>): SubCategoryListEntry {
val builder = ConfigEntryBuilder.create()
.startSubCategory(textify(names.joinToString(".").translate()))
.setTooltip(*textify(names.joinToString(".").translateTooltip()))
.setExpanded(false)
children.forEach { builder.add(it.createClothNode(names + it.fiberNode.name!!)) }
return builder.build()
}
operator fun get(name: String) = children.find { it.fiberNode.name == name }
}
interface DelegatingConfigGroupFactory<T> {
operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T>
}
fun <T: DelegatingConfigGroup> subNode(factory: (ConfigNode)->T) = object : DelegatingConfigGroupFactory<T> {
override operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> {
val childNode = ConfigNode(property.name, null)
val configGroup = factory(childNode)
parent.fiberNode.items.add(childNode)
parent.children.add(configGroup)
return object : ReadOnlyProperty<DelegatingConfigGroup, T> {
override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) = configGroup
}
}
}
interface DelegatingConfigValueFactory<T> {
fun createFiberNode(parent: ConfigNode, name: String): ConfigValue<T>
fun createClothNode(node: ConfigValue<T>, names: List<String>): AbstractConfigListEntry<T>
operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> {
return object : DelegatingConfigValue<T>(createFiberNode(parent.fiberNode, property.name)) {
override fun createClothNode(names: List<String>) = createClothNode(fiberNode, names)
override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) = fiberNode.value!!
}.apply { parent.children.add(this) }
}
}
fun String.translate() = I18n.translate(this)
fun String.translateTooltip(lineLength: Int = MAX_LINE_LEN) = ("$this.tooltip").translate().let { tooltip ->
tooltip.splitToSequence(" ").fold(mutableListOf("")) { tooltips, word ->
if (tooltips.last().length + word.length < lineLength) {
tooltips[tooltips.lastIndex] += "$word "
} else {
tooltips.add("$word ")
}
tooltips
}.map { it.trim() }.toTypedArray()
}
fun boolean(
default: Boolean,
langKey: (List<String>)->String = { it.joinToString(".") },
valueOverride: (Boolean)->Boolean = { it }
) = object : DelegatingConfigValueFactory<Boolean> {
override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Boolean::class.java)
.withName(name)
.withParent(parent)
.withDefaultValue(default)
.build()
override fun createClothNode(node: ConfigValue<Boolean>, names: List<String>) = ConfigEntryBuilder.create()
.startBooleanToggle(textify(langKey(names).translate()), node.value!!)
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(textify(it.translateTooltip())) else Optional.empty() })
.setSaveConsumer { node.value = valueOverride(it) }
.build()
}
fun integer(
default: Int, min: Int, max: Int,
langKey: (List<String>)->String = { it.joinToString(".") },
valueOverride: (Int)->Int = { it }
) = object : DelegatingConfigValueFactory<Int> {
override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Int::class.java)
.withName(name)
.withParent(parent)
.withDefaultValue(default)
.constraints().minNumerical(min).maxNumerical(max).finish()
.build()
override fun createClothNode(node: ConfigValue<Int>, names: List<String>) = ConfigEntryBuilder.create()
.startIntField(textify(langKey(names).translate()), node.value!!)
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(textify(it.translateTooltip())) else Optional.empty() })
.setMin(min).setMax(max)
.setSaveConsumer { node.value = valueOverride(it) }
.build()
}
fun double(
default: Double, min: Double, max: Double,
langKey: (List<String>)->String = { it.joinToString(".") },
valueOverride: (Double)->Double = { it }
) = object : DelegatingConfigValueFactory<Double> {
override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Double::class.java)
.withName(name)
.withParent(parent)
.withDefaultValue(default)
.constraints().minNumerical(min).maxNumerical(max).finish()
.build()
override fun createClothNode(node: ConfigValue<Double>, names: List<String>) = ConfigEntryBuilder.create()
.startDoubleField(textify(langKey(names).translate()), node.value!!)
.setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(textify(it.translateTooltip())) else Optional.empty() })
.setMin(min).setMax(max)
.setSaveConsumer { node.value = valueOverride(it) }
.build()
}
val recurring = { names: List<String> -> "${names.first()}.${names.last()}" }
fun fakeCategory(name: String) = { names: List<String> ->
(listOf(names.first(), name) + names.drop(1)).joinToString(".")
}

View File

@@ -0,0 +1,158 @@
package mods.betterfoliage.config
import me.zeroeightsix.fiber.tree.ConfigNode
import java.util.*
interface PopulationConfigData {
val enabled: Boolean
val population: Int
fun enabled(random: Random) = random.nextInt(64) < population && enabled
}
fun population(default: Int) = integer(default, min = 0, max = 64, langKey = recurring)
class MainConfig : DelegatingConfigGroup(ConfigNode("root", null)) {
val enabled by boolean(true, langKey = fakeCategory("global"))
val nVidia by boolean(true, langKey = fakeCategory("global"))
val leaves by subNode { LeavesConfig(it) }
val shortGrass by subNode { ShortGrassConfig(it) }
val connectedGrass by subNode { ConnectedGrassConfig(it) }
val roundLogs by subNode { RoundLogConfig(it) }
val cactus by subNode { CactusConfig(it) }
val lilypad by subNode { LilypadConfig(it) }
val reed by subNode { ReedConfig(it) }
val algae by subNode { AlgaeConfig(it) }
val coral by subNode { CoralConfig(it) }
val netherrack by subNode { NetherrackConfig(it) }
val fallingLeaves by subNode { FallingLeavesConfig(it) }
val risingSoul by subNode { RisingSoulConfig(it) }
}
class LeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by boolean(true, langKey = recurring)
val snowEnabled by boolean(true)
val dense by boolean(false)
val hideInternal by boolean(true)
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
val vOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring)
val size by double(1.4, min = 0.75, max = 2.5, langKey = recurring)
val shaderWind by boolean(true, langKey = recurring)
val saturationThreshold by double(0.1, min = 0.0, max = 1.0, langKey = recurring)
}
class ShortGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
override val enabled by boolean(true, langKey = recurring)
val myceliumEnabled by boolean(true)
val snowEnabled by boolean(true)
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
val heightMin by double(0.6, min = 0.1, max = 2.5, langKey = recurring)
val heightMax by double(0.6, min = 0.1, max = 2.5, langKey = recurring) { it.coerceAtLeast(heightMin) }
val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring)
override val population by population(64)
val useGenerated by boolean(false)
val shaderWind by boolean(true, langKey = recurring)
val saturationThreshold by double(0.1, min = 0.0, max = 1.0, langKey = recurring)
}
class ConnectedGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by boolean(true, langKey = recurring)
val snowEnabled by boolean(true)
}
class RoundLogConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by boolean(true, langKey = recurring)
val defaultY by boolean(false)
val connectSolids by boolean(false)
val lenientConnect by boolean(true)
val connectPerpendicular by boolean(true)
val connectGrass by boolean(true)
val radiusSmall by double(0.25, min = 0.0, max = 0.5)
val radiusLarge by double(0.44, min = 0.0, max = 0.5) { it.coerceAtLeast(radiusSmall) }
val dimming by double(0.7, min = 0.0, max = 1.0)
val zProtection by double(0.99, min = 0.9, max = 1.0)
}
class CactusConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by boolean(true, langKey = recurring)
val size by double(1.3, min = 1.0, max = 2.0, langKey = recurring)
val sizeVariation by double(0.1, min = 0.0, max = 0.5)
val hOffset by double(0.1, min = 0.0, max = 0.5, langKey = recurring)
}
class LilypadConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
override val enabled by boolean(true, langKey = recurring)
val hOffset by double(0.1, min = 0.0, max = 0.25, langKey = recurring)
override val population by population(16)
val shaderWind by boolean(true, langKey = recurring)
}
class ReedConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
override val enabled by boolean(true, langKey = recurring)
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
val heightMin by double(1.7, min = 1.5, max = 3.0, langKey = recurring)
val heightMax by double(2.2, min = 1.5, max = 3.0, langKey = recurring) { it.coerceAtLeast(heightMin) }
override val population by population(32)
val minBiomeTemp by double(0.4, min = 0.0, max = 2.0)
val minBiomeRainfall by double(0.4, min = 0.0, max = 1.0)
val shaderWind by boolean(true, langKey = recurring)
}
class AlgaeConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
override val enabled by boolean(true, langKey = recurring)
val hOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring)
val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring)
val heightMin by double(0.5, min = 0.1, max = 1.0, langKey = recurring)
val heightMax by double(0.5, min = 0.1, max = 1.0, langKey = recurring) { it.coerceAtLeast(heightMin) }
override val population by population(48)
val shaderWind by boolean(true, langKey = recurring)
}
class CoralConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData {
override val enabled by boolean(true, langKey = recurring)
val shallowWater by boolean(false)
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
val vOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring)
val size by double(0.7, min = 0.5, max = 1.5, langKey = recurring)
val crustSize by double(1.4, min = 0.5, max = 1.5)
val chance by integer(32, min = 0, max = 64)
override val population by population(48)
}
class NetherrackConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by boolean(true, langKey = recurring)
val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring)
val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring)
val heightMin by double(0.6, min = 0.5, max = 1.5, langKey = recurring)
val heightMax by double(0.8, min = 0.5, max = 1.5, langKey = recurring) { it.coerceAtLeast(heightMin) }
}
class FallingLeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by boolean(true, langKey = recurring)
val opacityHack by boolean(false)
val speed by double(0.05, min = 0.01, max = 0.15)
val windStrength by double(0.5, min = 0.1, max = 2.0)
val stormStrength by double(0.8, min = 0.1, max = 2.0) { it.coerceAtLeast(windStrength) }
val size by double(0.75, min = 0.25, max = 1.5)
val chance by double(0.05, min = 0.001, max = 1.0)
val perturb by double(0.25, min = 0.01, max = 1.0)
val lifetime by double(7.5, min = 1.0, max = 15.0)
}
class RisingSoulConfig(node: ConfigNode) : DelegatingConfigGroup(node) {
val enabled by boolean(true, langKey = recurring)
val chance by double(0.02, min = 0.001, max = 1.0)
val perturb by double(0.05, min = 0.01, max = 0.25)
val headSize by double(1.0, min = 0.25, max = 1.5)
val trailSize by double(0.75, min = 0.25, max = 1.5)
val opacity by double(0.5, min = 0.05, max = 1.0)
val sizeDecay by double(0.97, min = 0.5, max = 1.0)
val opacityDecay by double(0.97, min = 0.5, max = 1.0)
val lifetime by double(4.0, min = 1.0, max = 15.0)
val trailLength by integer(48, min = 2, max = 128)
val trailDensity by integer(3, min = 1, max = 16)
}

View File

@@ -0,0 +1,13 @@
package mods.betterfoliage.config
import net.minecraft.block.Blocks
import net.minecraft.block.Material
import net.minecraft.world.biome.Biome
val CACTUS_BLOCKS = listOf(Blocks.CACTUS)
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.PODZOL)
val SAND_BLOCKS = listOf(Blocks.SAND, Blocks.RED_SAND)
val NETHERRACK_BLOCKS = listOf(Blocks.NETHERRACK)
val SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN)
val SNOW_MATERIALS = listOf(Material.SNOW_BLOCK)

View File

@@ -0,0 +1,31 @@
package mods.betterfoliage.integration
import io.github.prospector.modmenu.api.ModMenuApi
import me.shedaniel.clothconfig2.api.ConfigBuilder
import me.zeroeightsix.fiber.JanksonSettings
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.resource.language.I18n
import net.minecraft.text.LiteralText
import java.util.function.Function
object ModMenu : ModMenuApi {
override fun getModId() = BetterFoliage.MOD_ID
override fun getConfigScreenFactory() = Function { screen: Screen ->
val builder = ConfigBuilder.create()
.setParentScreen(screen)
.setTitle(LiteralText(I18n.translate("betterfoliage.title")))
BetterFoliage.config.createClothNode(listOf("betterfoliage")).value.forEach { rootOption ->
builder.getOrCreateCategory(LiteralText("main")).addEntry(rootOption)
}
builder.savingRunnable = Runnable {
JanksonSettings().serialize(BetterFoliage.config.fiberNode, BetterFoliage.configFile.outputStream(), false)
BakeWrapperManager.invalidate()
MinecraftClient.getInstance().worldRenderer.reload()
}
builder.build()
}
}

View File

@@ -0,0 +1,139 @@
package mods.betterfoliage.integration
object IC2RubberIntegration {
// val BlockRubWood = ClassRefOld<Any>("ic2.core.block.BlockRubWood")
init {
// if (ModList.get().isLoaded("ic2") && allAvailable(BlockRubWood)) {
// BetterFoliage.log(Level.INFO, "IC2 rubber support initialized")
// LogRegistry.registries.add(IC2LogDiscovery)
// BetterFoliage.blockSprites.providers.add(IC2LogDiscovery)
// }
}
}
object TechRebornRubberIntegration {
// val BlockRubberLog = ClassRefOld<Any>("techreborn.blocks.BlockRubberLog")
init {
// if (ModList.get().isLoaded("techreborn") && allAvailable(BlockRubberLog)) {
// BetterFoliage.log(Level.INFO, "TechReborn rubber support initialized")
// LogRegistry.registries.add(TechRebornLogDiscovery)
// BetterFoliage.blockSprites.providers.add(TechRebornLogDiscovery)
// }
}
}
/*
class RubberLogInfo(
axis: Axis?,
val spotDir: Direction,
topTexture: Sprite,
bottomTexture: Sprite,
val spotTexture: Sprite,
sideTextures: List<Sprite>
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
override val side: QuadIconResolver = { ctx: CombinedContext, idx: Int, quad: Quad ->
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation)
if (worldFace == spotDir) spotTexture else {
val sideIdx = if (this.sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0
this.sideTextures[sideIdx]
}
}
}
object IC2LogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock, and "state" blockstate property
if (!IC2RubberIntegration.BlockRubWood.isInstance(ctx.state.block)) return null
val blockLoc = ctx.models.firstOrNull() as Pair<JsonUnbakedModel, Identifier> ?: return null
val type = ctx.state.entries.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
// logs with no rubber spot
if (blockLoc.derivesFrom(Identifier("block/cube_column"))) {
val axis = when(type) {
"plain_y" -> Axis.Y
"plain_x" -> Axis.X
"plain_z" -> Axis.Z
else -> null
}
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTexture(it) }
if (textureNames.any { it == "missingno" }) return null
log("IC2LogSupport: block state ${ctx.state.toString()}")
log("IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[1]}")
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
return atlas.mapAfter {
SimpleColumnInfo(axis, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
}
}
// logs with rubber spot
val spotDir = when(type) {
"dry_north", "wet_north" -> NORTH
"dry_south", "wet_south" -> SOUTH
"dry_west", "wet_west" -> WEST
"dry_east", "wet_east" -> EAST
else -> null
}
val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTexture(it) }
if (textureNames.any { it == "missingno" }) return null
log("IC2LogSupport: block state ${ctx.state.toString()}")
log("IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
val upSprite = atlas.sprite(textureNames[0])
val downSprite = atlas.sprite(textureNames[1])
val sideSprite = atlas.sprite(textureNames[2])
val spotSprite = atlas.sprite(textureNames[3])
return if (spotDir != null) atlas.mapAfter {
RubberLogInfo(Axis.Y, spotDir, upSprite.get(), downSprite.get(), spotSprite.get(), listOf(sideSprite.get()))
} else atlas.mapAfter {
SimpleColumnInfo(Axis.Y, upSprite.get(), downSprite.get(), listOf(sideSprite.get()))
}
}
}
object TechRebornLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(ctx.state.block)) return null
val blockLoc = ctx.models.map { it as? Pair<JsonUnbakedModel, Identifier> }.firstOrNull() ?: return null
val hasSap = ctx.state.entries.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
val sapSide = ctx.state.entries.entries.find { it.key.getName() == "sapside" }?.value as? Direction ?: return null
log("$logName: block state ${ctx.state}")
if (hasSap) {
val textureNames = listOf("end", "side", "sapside").map { blockLoc.first.resolveTexture(it) }
log("$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) {
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
val sapSprite = atlas.sprite(textureNames[2])
return atlas.mapAfter {
RubberLogInfo(Axis.Y, sapSide, endSprite.get(), endSprite.get(), sapSprite.get(), listOf(sideSprite.get()))
}
}
} else {
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTexture(it) }
log("$logName: end=${textureNames[0]}, side=${textureNames[1]}")
if (textureNames.all { it != "missingno" }) {
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
return atlas.mapAfter {
SimpleColumnInfo(Axis.Y, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
}
}
}
return null
}
}
*/

View File

@@ -0,0 +1,117 @@
package mods.betterfoliage.model
import mods.betterfoliage.BakedQuad_sprite
import mods.betterfoliage.VertexFormat_offsets
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.findFirst
import mods.betterfoliage.util.get
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.minecraft.block.BlockState
import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormatElement
import net.minecraft.client.render.VertexFormatElement.Format.FLOAT
import net.minecraft.client.render.VertexFormatElement.Format.UBYTE
import net.minecraft.client.render.VertexFormatElement.Type.*
import net.minecraft.client.render.VertexFormatElement.Type.UV
import net.minecraft.client.render.VertexFormats
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BakedQuad
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.util.math.Direction
import java.util.*
interface BakedModelConverter {
/**
* Convert baked model. Returns null if conversion unsuccessful (wrong input type).
* @param model Input model
* @param converter Converter to use for converting nested models.
*/
fun convert(model: BakedModel): BakedModel?
companion object {
fun of(func: (BakedModel)->BakedModel?) = object : BakedModelConverter {
override fun convert(model: BakedModel) = func(model)
}
val identity = of { model -> model }
}
}
/**
* Convert [BakedModel] using the provided list of [BakedModelConverter]s (in order).
* If all converters fail, gives the original model back.
*/
fun List<BakedModelConverter>.convert(model: BakedModel) = object : BakedModelConverter {
val converters = this@convert + BakedModelConverter.identity
override fun convert(model: BakedModel) = converters.findFirst { it.convert(model) }
}.let { converterStack ->
// we are guaranteed a result here because of the identity converter
converterStack.convert(model)!!
}
/**
* Convert [BasicBakedModel] into one using fabric-rendering-api [Mesh] instead of the vanilla pipeline.
* @param blendMode Use the given [BlockRenderLayer] for the [Mesh]
* instead of the one declared by the corresponding [Block]
*/
fun meshifyStandard(model: BasicBakedModel, state: BlockState? = null, blendMode: BlendMode? = null) =
WrappedMeshModel.converter(state, blendModeOverride = blendMode).convert(model)!!
fun meshifySolid(model: BasicBakedModel) = meshifyStandard(model, null, BlendMode.SOLID)
fun meshifyCutoutMipped(model: BasicBakedModel) = meshifyStandard(model, null, BlendMode.CUTOUT_MIPPED)
/**
* Convert a vanilla [BakedModel] into intermediate [Quad]s
* Vertex normals not supported (yet)
* Vertex data elements not aligned to 32 bit boundaries not supported
*/
fun unbakeQuads(model: BakedModel, state: BlockState?, random: Random, unshade: Boolean): List<Quad> {
return (allDirections.toList() + null as Direction?).flatMap { face ->
model.getQuads(state, face, random).mapIndexed { qIdx, bakedQuad ->
var quad = Quad(Vertex(), Vertex(), Vertex(), Vertex(), face = face, colorIndex = bakedQuad.colorIndex, sprite = bakedQuad[BakedQuad_sprite])
val format = quadVertexFormat(bakedQuad)
val stride = format.vertexSizeInteger
format.getIntOffset(POSITION, FLOAT, 3)?.let { posOffset ->
quad = quad.transformVI { vertex, vIdx -> vertex.copy(xyz = Double3(
x = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 0]).toDouble(),
y = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 1]).toDouble(),
z = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 2]).toDouble()
)) }
}
format.getIntOffset(COLOR, UBYTE, 4)?.let { colorOffset ->
quad = quad.transformVI { vertex, vIdx -> vertex.copy(
color = Color(bakedQuad.vertexData[vIdx * stride + colorOffset])
) }
}
format.getIntOffset(UV, FLOAT, 2, 0)?.let { uvOffset ->
quad = quad.transformVI { vertex, vIdx -> vertex.copy(uv = UV(
u = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 0]).toDouble(),
v = java.lang.Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 1]).toDouble()
)) }
}
quad = quad.transformV { it.copy(uv = it.uv.unbake(quad.sprite!!)) }.move(Double3(-0.5, -0.5, -0.5))
if (unshade) quad = quad.transformV { it.copy(color = it.color * (1.0f / Color.bakeShade(quad.face))) }
quad
}
}
}
/** Get the byte offset of the [VertexFormatElement] matching the given criteria */
fun VertexFormat.getByteOffset(type: VertexFormatElement.Type, format: VertexFormatElement.Format, count: Int, index: Int = 0): Int? {
elements.forEachIndexed { idx, element ->
if (element == VertexFormatElement(index, format, type, count))
return VertexFormat_offsets[this]!!.getInt(idx)
}
return null
}
/**
* Get the int (32 bit) offset of the [VertexFormatElement] matching the given criteria
* Returns null if the element is not properly aligned
*/
fun VertexFormat.getIntOffset(type: VertexFormatElement.Type, format: VertexFormatElement.Format, count: Int, index: Int = 0) =
getByteOffset(type, format, count, index)?.let { if (it % 4 == 0) it / 4 else null }
/** Function to determine [VertexFormat] used by [BakedQuad] */
var quadVertexFormat: (BakedQuad)->VertexFormat = { VertexFormats.POSITION_COLOR_TEXTURE_LIGHT_NORMAL }

View File

@@ -0,0 +1,230 @@
package mods.betterfoliage.model
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.*
import mods.betterfoliage.util.minmax
import net.fabricmc.fabric.api.renderer.v1.RendererAccess
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.minecraft.client.texture.MissingSprite
import net.minecraft.client.texture.Sprite
import net.minecraft.util.math.Direction
import net.minecraft.util.math.Direction.*
import java.lang.Math.max
import java.lang.Math.min
import java.util.Random
import kotlin.math.cos
import kotlin.math.sin
/**
* Vertex UV coordinates
*
* Zero-centered: sprite coordinates fall between (-0.5, 0.5)
*/
data class UV(val u: Double, val v: Double) {
companion object {
val topLeft = UV(-0.5, -0.5)
val topRight = UV(0.5, -0.5)
val bottomLeft = UV(-0.5, 0.5)
val bottomRight = UV(0.5, 0.5)
}
val rotate: UV get() = UV(v, -u)
fun rotate(n: Int) = when(n % 4) {
0 -> copy()
1 -> UV(v, -u)
2 -> UV(-u, -v)
else -> UV(-v, u)
}
fun clamp(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) =
UV(u.minmax(minU, maxU), v.minmax(minV, maxV))
fun mirror(mirrorU: Boolean, mirrorV: Boolean) = UV(if (mirrorU) -u else u, if (mirrorV) -v else v)
fun unbake(sprite: Sprite) = UV(
(u - sprite.minU.toDouble()) / (sprite.maxU - sprite.minU).toDouble() - 0.5,
(v - sprite.minV.toDouble()) / (sprite.maxV - sprite.minV).toDouble() - 0.5
)
}
data class Color(val alpha: Int, val red: Int, val green: Int, val blue: Int) {
constructor(combined: Int) : this(combined shr 24 and 255, combined shr 16 and 255, combined shr 8 and 255, combined and 255)
val asInt get() = (alpha shl 24) or (red shl 16) or (green shl 8) or blue
operator fun times(f: Float) = Color(
alpha,
(f * red.toFloat()).toInt().coerceIn(0 until 256),
(f * green.toFloat()).toInt().coerceIn(0 until 256),
(f * blue.toFloat()).toInt().coerceIn(0 until 256)
)
companion object {
val white get() = Color(255, 255, 255, 255)
/** Amount of vanilla diffuse lighting applied to face quads */
fun bakeShade(dir: Direction?) = when(dir) {
DOWN -> 0.5f
NORTH, SOUTH -> 0.8f
EAST, WEST -> 0.6f
else -> 1.0f
}
}
}
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
companion object {
fun fromColor(color: Int): HSB {
val hsbVals = java.awt.Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
}
val asColor: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
}
/**
* Model vertex
*
* @param[xyz] x, y, z coordinates
* @param[uv] u, v coordinates
* @param[color] vertex color RGB components
* @param[alpha] vertex color alpha component
*/
data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0),
val uv: UV = UV(0.0, 0.0),
val color: Color = Color.white,
val alpha: Int = 255,
val normal: Double3? = null
)
/**
* Intermediate (fabric-renderer-api independent) representation of model quad
* Immutable, double-precision
* Zero-centered (both XYZ and UV) coordinates for simpler rotation/mirroring
*/
data class Quad(
val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex,
val sprite: Sprite? = null,
val colorIndex: Int = -1,
val face: Direction? = null
) {
val verts = arrayOf(v1, v2, v3, v4)
inline fun transformV(trans: (Vertex)-> Vertex): Quad = transformVI { vertex, idx -> trans(vertex) }
inline fun transformVI(trans: (Vertex, Int)-> Vertex): Quad = copy(
v1 = trans(v1, 0), v2 = trans(v2, 1), v3 = trans(v3, 2), v4 = trans(v4, 3)
)
val normal: Double3 get() = (v2.xyz - v1.xyz).cross(v4.xyz - v1.xyz).normalize
fun move(trans: Double3) = transformV { it.copy(xyz = it.xyz + trans) }
fun move(trans: Pair<Double, Direction>) = move(Double3(trans.second) * trans.first)
fun scale (scale: Double) = transformV { it.copy(xyz = it.xyz * scale) }
fun scale (scale: Double3) = transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) }
fun rotate(rot: Rotation) = transformV { it.copy(xyz = it.xyz.rotate(rot), normal = it.normal?.rotate(rot)) }.copy(face = face?.rotate(rot))
fun rotateZ(angle: Double) = transformV { it.copy(
xyz = Double3(it.xyz.x * cos(angle) + it.xyz.z * sin(angle), it.xyz.y, it.xyz.z * cos(angle) - it.xyz.x * sin(angle)),
normal = it.normal?.let { normal-> Double3(normal.x * cos(angle) + normal.z * sin(angle), normal.y, normal.z * cos(angle) - normal.x * sin(angle)) }
) }
fun scaleUV (scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) }
fun rotateUV(n: Int) = transformV { it.copy(uv = it.uv.rotate(n)) }
fun clampUV(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) =
transformV { it.copy(uv = it.uv.clamp(minU, maxU, minV, maxV)) }
fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) = transformV { it.copy(uv = it.uv.mirror(mirrorU, mirrorV)) }
fun scrambleUV(random: Random, canFlipU: Boolean, canFlipV: Boolean, canRotate: Boolean) = this
.mirrorUV(canFlipU && random.nextBoolean(), canFlipV && random.nextBoolean())
.let { if (canRotate) it.rotateUV(random.nextInt(4)) else it }
fun sprite(sprite: Sprite) = copy(sprite = sprite)
fun color(color: Color) = transformV { it.copy(color = color) }
fun color(color: Int) = transformV { it.copy(color = Color(color)) }
fun colorIndex(colorIndex: Int) = copy(colorIndex = colorIndex)
fun colorAndIndex(color: Int?) = color(color ?: Color.white.asInt).colorIndex(if (color == null) 0 else -1)
val flipped: Quad get() = Quad(v4, v3, v2, v1, sprite, colorIndex)
fun cycleVertices(n: Int) = when(n % 4) {
1 -> Quad(v2, v3, v4, v1)
2 -> Quad(v3, v4, v1, v2)
3 -> Quad(v4, v1, v2, v3)
else -> this.copy()
}
companion object {
fun mix(first: Quad, second: Quad, vertexFactory: (Vertex, Vertex)-> Vertex) = Quad(
v1 = vertexFactory(first.v1, second.v1),
v2 = vertexFactory(first.v2, second.v2),
v3 = vertexFactory(first.v3, second.v3),
v4 = vertexFactory(first.v4, second.v4)
)
}
}
fun List<Quad>.transform(trans: Quad.()-> Quad) = map { it.trans() }
fun Array<List<Quad>>.transform(trans: Quad.(Int)-> Quad) = mapIndexed { idx, qList -> qList.map { it.trans(idx) } }.toTypedArray()
fun List<Quad>.withOpposites() = flatMap { listOf(it, it.flipped) }
fun Array<List<Quad>>.withOpposites() = map { it.withOpposites() }.toTypedArray()
/**
* Pour quad data into a fabric-renderer-api Mesh
*/
fun List<Quad>.build(blendMode: BlendMode, noDiffuse: Boolean = false, flatLighting: Boolean = false): Mesh {
val renderer = RendererAccess.INSTANCE.renderer!!
val material = renderer.materialFinder().blendMode(0, blendMode).disableAo(0, flatLighting).disableDiffuse(0, noDiffuse).find()
val builder = renderer.meshBuilder()
builder.emitter.apply {
forEach { quad ->
val sprite = quad.sprite ?: Atlas.BLOCKS[MissingSprite.getMissingSpriteId()]!!
quad.verts.forEachIndexed { idx, vertex ->
pos(idx, (vertex.xyz + Double3(0.5, 0.5, 0.5)).asVec3f)
sprite(idx, 0,
(sprite.maxU - sprite.minU) * (vertex.uv.u.toFloat() + 0.5f) + sprite.minU,
(sprite.maxV - sprite.minV) * (vertex.uv.v.toFloat() + 0.5f) + sprite.minV
)
spriteColor(idx, 0, vertex.color.asInt)
}
cullFace(quad.face)
colorIndex(quad.colorIndex)
material(material)
emit()
}
}
return builder.build()
}
fun Array<List<Quad>>.build(blendMode: BlendMode, noDiffuse: Boolean = false, flatLighting: Boolean = false) = map { it.build(blendMode, noDiffuse, flatLighting) }.toTypedArray()
/**
* The model should be positioned so that (0,0,0) is the block center.
* The block extends to (-0.5, 0.5) in all directions (inclusive).
*/
fun verticalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, yBottom: Double, yTop: Double) = Quad(
Vertex(Double3(x1, yBottom, z1), UV.bottomLeft),
Vertex(Double3(x2, yBottom, z2), UV.bottomRight),
Vertex(Double3(x2, yTop, z2), UV.topRight),
Vertex(Double3(x1, yTop, z1), UV.topLeft)
)
fun horizontalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, y: Double): Quad {
val xMin = min(x1, x2); val xMax = max(x1, x2)
val zMin = min(z1, z2); val zMax = max(z1, z2)
return Quad(
Vertex(Double3(xMin, y, zMin), UV.topLeft),
Vertex(Double3(xMin, y, zMax), UV.bottomLeft),
Vertex(Double3(xMax, y, zMax), UV.bottomRight),
Vertex(Double3(xMax, y, zMin), UV.topRight)
)
}
fun faceQuad(face: Direction): Quad {
val base = face.vec * 0.5
val top = boxFaces[face].top * 0.5
val left = boxFaces[face].left * 0.5
return Quad(
Vertex(base + top + left, UV.topLeft),
Vertex(base - top + left, UV.bottomLeft),
Vertex(base - top - left, UV.bottomRight),
Vertex(base + top - left, UV.topRight),
face = face
)
}
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(cos(it), 0.0, sin(it)) }

View File

@@ -0,0 +1,75 @@
package mods.betterfoliage.model
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.get
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback
import net.minecraft.client.MinecraftClient
import net.minecraft.client.texture.MissingSprite
import net.minecraft.client.texture.Sprite
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.util.Identifier
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
interface SpriteSet {
val num: Int
operator fun get(idx: Int): Sprite
}
class FixedSpriteSet(val sprites: List<Sprite>) : SpriteSet {
override val num = sprites.size
override fun get(idx: Int) = sprites[idx % num]
constructor(atlas: Atlas, ids: List<Identifier>) : this(
ids.mapNotNull { atlas[it] }.let { sprites ->
if (sprites.isNotEmpty()) sprites else listOf(atlas[MissingSprite.getMissingSpriteId()]!!)
}
)
}
class SpriteDelegate(val atlas: Atlas, val idFunc: ()->Identifier) : ReadOnlyProperty<Any, Sprite>, ClientSpriteRegistryCallback {
private var id: Identifier? = null
private var value: Sprite? = null
init { ClientSpriteRegistryCallback.event(atlas.resourceId).register(this) }
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
id = idFunc(); value = null
registry.register(id)
}
override fun getValue(thisRef: Any, property: KProperty<*>): Sprite {
value?.let { return it }
synchronized(this) {
value?.let { return it }
atlas[id!!]!!.let { value = it; return it }
}
}
}
class SpriteSetDelegate(
val atlas: Atlas,
val idRegister: (Identifier)->Identifier = { it },
val idFunc: (Int)->Identifier
) : ReadOnlyProperty<Any, SpriteSet>, ClientSpriteRegistryCallback {
private var idList: List<Identifier> = emptyList()
private var spriteSet: SpriteSet? = null
init { ClientSpriteRegistryCallback.event(atlas.resourceId).register(this) }
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
spriteSet = null
val manager = MinecraftClient.getInstance().resourceManager
idList = (0 until 16).map(idFunc).filter { manager.containsResource(atlas.file(it)) }.map(idRegister)
idList.forEach { registry.register(it) }
}
override fun getValue(thisRef: Any, property: KProperty<*>): SpriteSet {
spriteSet?.let { return it }
synchronized(this) {
spriteSet?.let { return it }
spriteSet = FixedSpriteSet(atlas, idList)
return spriteSet!!
}
}
}

View File

@@ -0,0 +1,80 @@
package mods.betterfoliage.model
import mods.betterfoliage.util.*
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.minecraft.client.texture.Sprite
import net.minecraft.util.Identifier
import net.minecraft.util.math.Direction.UP
data class TuftShapeKey(
val size: Double,
val height: Double,
val offset: Double3,
val flipU1: Boolean,
val flipU2: Boolean
)
fun tuftShapeSet(size: Double, heightMin: Double, heightMax: Double, hOffset: Double): Array<TuftShapeKey> {
return Array(64) { idx ->
TuftShapeKey(
size,
randomD(heightMin, heightMax),
xzDisk(idx) * randomD(hOffset / 2.0, hOffset),
randomB(),
randomB()
)
}
}
fun tuftQuadSingle(size: Double, height: Double, flipU: Boolean) =
verticalRectangle(x1 = -0.5 * size, z1 = 0.5 * size, x2 = 0.5 * size, z2 = -0.5 * size, yBottom = 0.5, yTop = 0.5 + height)
.mirrorUV(flipU, false)
fun tuftModelSet(shapes: Array<TuftShapeKey>, tintIndex: Int, spriteGetter: (Int)->Sprite) = shapes.mapIndexed { idx, shape ->
listOf(
tuftQuadSingle(shape.size, shape.height, shape.flipU1),
tuftQuadSingle(shape.size, shape.height, shape.flipU2).rotate(rot(UP))
).map { it.move(shape.offset) }
.map { it.colorIndex(tintIndex) }
.map { it.sprite(spriteGetter(idx)) }
}.toTypedArray()
fun fullCubeTextured(spriteId: Identifier, tintIndex: Int, scrambleUV: Boolean = true): Mesh {
val sprite = Atlas.BLOCKS[spriteId]!!
return allDirections.map { faceQuad(it) }
.map { if (!scrambleUV) it else it.rotateUV(randomI(max = 4)) }
.map { it.sprite(sprite) }
.map { it.colorIndex(tintIndex) }
.build(BlendMode.SOLID, noDiffuse = true)
}
fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Array<List<Quad>> {
return Array(num) { idx ->
listOf(
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41),
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
.rotate(rot(UP))
).map { it.scale(size) }
.map { it.move(xzDisk(idx) * hOffset) }
.map { it.move(UP.vec * randomD(-1.0, 1.0) * vOffset) }
}
}
fun crossModelSingle(base: List<Quad>, sprite: Sprite, tintIndex: Int,scrambleUV: Boolean) =
base.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it }
.map { it.colorIndex(tintIndex) }
.mapIndexed { idx, quad -> quad.sprite(sprite) }
.withOpposites()
.build(BlendMode.CUTOUT_MIPPED)
fun crossModelsTextured(
leafBase: Array<List<Quad>>,
tintIndex: Int,
scrambleUV: Boolean,
spriteGetter: (Int) -> Identifier
) = leafBase.mapIndexed { idx, leaf ->
crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], tintIndex, scrambleUV)
}.toTypedArray()
fun Array<List<Quad>>.buildTufts() = withOpposites().build(BlendMode.CUTOUT_MIPPED)

View File

@@ -0,0 +1,87 @@
package mods.betterfoliage.model
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.util.HasLogger
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.client.render.RenderLayers
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.item.ItemStack
import net.minecraft.util.collection.WeightedPicker
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockRenderView
import java.util.*
import java.util.function.Supplier
abstract class ModelWrapKey : ModelBakingKey, HasLogger() {
override fun bake(ctx: ModelBakingContext): BakedModel? {
val baseModel = super.bake(ctx)
if (baseModel is BasicBakedModel)
return bake(ctx, baseModel)
else
return baseModel
}
abstract fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel): BakedModel
}
abstract class WrappedBakedModel(val wrapped: BakedModel) : BakedModel by wrapped, FabricBakedModel {
override fun isVanillaAdapter() = false
override fun emitItemQuads(stack: ItemStack, randomSupplier: Supplier<Random>, context: RenderContext) {
(wrapped as FabricBakedModel).emitItemQuads(stack, randomSupplier, context)
}
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
(wrapped as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
}
class WrappedMeshModel(wrapped: BasicBakedModel, val mesh: Mesh) : WrappedBakedModel(wrapped) {
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
context.meshConsumer().accept(mesh)
}
companion object {
/**
* Converter for [BasicBakedModel] instances.
* @param state [BlockState] to use when querying [BakedModel]
* @param unshade undo vanilla diffuse lighting when unbaking the [BakedModel]
* @param noDiffuse disable diffuse lighting when baking the [Mesh]
* @param blendModeOverride [BlockRenderLayer] to use instead of the one declared by the corresponding [Block]
*/
fun converter(state: BlockState?, unshade: Boolean = false, noDiffuse: Boolean = true, blendModeOverride: BlendMode? = null) = BakedModelConverter.of { model ->
if (model is BasicBakedModel) {
val mesh = unbakeQuads(model, state, Random(42L), unshade).build(
blendMode = blendModeOverride ?: BlendMode.fromRenderLayer(RenderLayers.getBlockLayer(state)),
noDiffuse = noDiffuse,
flatLighting = !model.useAmbientOcclusion()
)
WrappedMeshModel(model, mesh)
} else null
}
}
}
class WeightedModelWrapper(
val models: List<WeightedModel>, baseModel: BakedModel
): WrappedBakedModel(baseModel), FabricBakedModel {
class WeightedModel(val model: BakedModel, val weight: Int) : WeightedPicker.Entry(weight)
fun getModel(random: Random) = WeightedPicker.getRandom(random, models).model
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
(getModel(randomSupplier.get()) as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
}
fun getUnderlyingModel(model: BakedModel, random: Random): BakedModel = when(model) {
is WeightedModelWrapper -> getUnderlyingModel(model.getModel(random), random)
is WrappedBakedModel -> model.wrapped
else -> model
}

View File

@@ -0,0 +1,54 @@
package mods.betterfoliage.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.lighting.getBufferBuilder
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.getAllMethods
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.render.BufferBuilder
import net.minecraft.client.render.RenderLayer
/**
* Integration for ShadersMod.
*/
object ShadersModIntegration : HasLogger() {
val BufferBuilder_SVertexBuilder = BufferBuilder::class.java.fields.find { it.name == "sVertexBuilder" }
val SVertexBuilder_pushState = getAllMethods("net.optifine.shaders.SVertexBuilder", "pushEntity").find { it.parameterCount == 1 }
val SVertexBuilder_popState = getAllMethods("net.optifine.shaders.SVertexBuilder", "popEntity").find { it.parameterCount == 0 }
val BlockAliases_getAliasBlockId = getAllMethods("net.optifine.shaders.BlockAliases", "getAliasBlockId").firstOrNull()
@JvmStatic val isAvailable =
listOf(BufferBuilder_SVertexBuilder).all { it != null } &&
listOf(SVertexBuilder_pushState, SVertexBuilder_popState, BlockAliases_getAliasBlockId).all { it != null }
val defaultLeaves = Blocks.OAK_LEAVES.defaultState
val defaultGrass = Blocks.TALL_GRASS.defaultState
init {
logger.info("[BetterFoliage] ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
}
/** Quads rendered inside this block will use the given block entity data in shader programs. */
inline fun renderAs(ctx: RenderContext, state: BlockState, layer: RenderLayer, enabled: Boolean = true, func: ()->Unit) {
if (isAvailable && enabled) {
val sVertexBuilder = BufferBuilder_SVertexBuilder!!.get(ctx.getBufferBuilder(layer))
val aliasBlockId = BlockAliases_getAliasBlockId!!.invoke(null, state)
SVertexBuilder_pushState!!.invoke(sVertexBuilder, aliasBlockId)
func()
SVertexBuilder_popState!!.invoke(sVertexBuilder)
} else {
func()
}
}
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
inline fun grass(ctx: RenderContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, defaultGrass, RenderLayer.getCutoutMipped(), enabled, func)
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
inline fun leaves(ctx: RenderContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, defaultLeaves, RenderLayer.getCutoutMipped(), enabled, func)
}

View File

@@ -0,0 +1,103 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.CACTUS_BLOCKS
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteDelegate
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.crossModelsRaw
import mods.betterfoliage.model.crossModelsTextured
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.render.lighting.roundLeafLighting
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.get
import mods.betterfoliage.util.horizontalDirections
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardCactusDiscovery : AbstractModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked()
if (model is JsonUnbakedModel && ctx.blockState.block in CACTUS_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardCactusKey)
ctx.sprites.add(StandardCactusModel.cactusCrossSprite)
}
super.processModel(ctx)
}
}
object StandardCactusKey : ModelWrapKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardCactusModel(meshifyCutoutMipped(wrapped))
}
class StandardCactusModel(wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel {
val armLighting = horizontalDirections.map { grassTuftLighting(it) }
val crossLighting = roundLeafLighting()
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
(wrapped as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
if (!BetterFoliage.config.enabled || !BetterFoliage.config.cactus.enabled) return
val random = randomSupplier.get()
val armSide = random.nextInt() and 3
context.withLighting(armLighting[armSide]) {
it.accept(cactusArmModels[armSide][random])
}
context.withLighting(crossLighting) {
it.accept(cactusCrossModels[random])
}
}
companion object {
val cactusCrossSprite = Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus")
val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus_arm_$idx")
}
val cactusArmModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.cactus.let { tuftShapeSet(0.8, 0.8, 0.8, 0.2) }
val models = tuftModelSet(shapes, Color.white.asInt) { cactusArmSprites[randomI()] }
horizontalDirections.map { side ->
models.transform { move(0.0625 to DOWN).rotate(Rotation.fromUp[side.ordinal]) }.buildTufts()
}.toTypedArray()
}
val cactusCrossModels by LazyInvalidatable(BakeWrapperManager) {
val models = BetterFoliage.config.cactus.let { config ->
crossModelsRaw(64, config.size, 0.0, 0.0)
.transform { rotateZ(randomD(-config.sizeVariation, config.sizeVariation)) }
}
crossModelsTextured(models, Color.white.asInt, true) { cactusCrossSprite }
}
}
}

View File

@@ -0,0 +1,142 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.config.DIRT_BLOCKS
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.build
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.model.withOpposites
import mods.betterfoliage.config.SALTWATER_BIOMES
import mods.betterfoliage.model.getUnderlyingModel
import mods.betterfoliage.model.meshifySolid
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.render.lighting.reedLighting
import mods.betterfoliage.render.lighting.renderMasquerade
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.generated.CenteredSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.Material
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardDirtDiscovery : AbstractModelDiscovery() {
fun canRenderInLayer(layer: RenderLayer) = when {
!BetterFoliage.config.enabled -> layer == RenderLayer.getSolid()
(!BetterFoliage.config.connectedGrass.enabled &&
!BetterFoliage.config.algae.enabled &&
!BetterFoliage.config.reed.enabled
) -> layer == RenderLayer.getSolid()
else -> layer == RenderLayer.getCutoutMipped()
}
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in DIRT_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardDirtKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
}
super.processModel(ctx)
}
}
object StandardDirtKey : ModelWrapKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = DirtModel(meshifySolid(wrapped))
}
class DirtModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val algaeLighting = grassTuftLighting(UP)
val reedLighting = reedLighting()
override fun emitBlockQuads(
blockView: BlockRenderView,
state: BlockState,
pos: BlockPos,
randomSupplier: Supplier<Random>,
context: RenderContext
) {
if (!BetterFoliage.config.enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val ctx = BasicBlockCtx(blockView, pos)
val stateUp = ctx.offset(UP).state
val isGrassUp = stateUp in BetterFoliage.blockTypes.grass
val isWater = stateUp.material == Material.WATER
val isDeepWater = isWater && ctx.offset(Int3(2 to UP)).state.material == Material.WATER
val isShallowWater = isWater && ctx.offset(Int3(2 to UP)).state.isAir
val isSaltWater = isWater && ctx.biome?.category in SALTWATER_BIOMES
val random = randomSupplier.get()
if (BetterFoliage.config.connectedGrass.enabled && isGrassUp) {
val grassBaseModel = getUnderlyingModel(ctx.model(UP), random)
context.renderMasquerade(grassBaseModel, blockView, stateUp, pos, randomSupplier, context)
} else {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
if (BetterFoliage.config.algae.enabled(random) && isDeepWater) {
ShadersModIntegration.grass(context, BetterFoliage.config.algae.shaderWind) {
context.withLighting(algaeLighting) {
it.accept(algaeModels[random])
}
}
} else if (BetterFoliage.config.reed.enabled(random) && isShallowWater && !isSaltWater) {
ShadersModIntegration.grass(context, BetterFoliage.config.reed.shaderWind) {
context.withLighting(reedLighting) {
it.accept(reedModels[random])
}
}
}
}
companion object {
val algaeSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_algae_$idx")
}
val reedSprites by SpriteSetDelegate(Atlas.BLOCKS,
idFunc = { idx -> Identifier(BetterFoliage.MOD_ID, "blocks/better_reed_$idx") },
idRegister = { id -> CenteredSprite(id, aspectHeight = 2).register(BetterFoliage.generatedPack) }
)
val algaeModels by LazyInvalidatable(BakeWrapperManager) {
val shapes =
BetterFoliage.config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white.asInt) { algaeSprites[randomI()] }
.withOpposites()
.build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
}
val reedModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white.asInt) { reedSprites[randomI()] }
.withOpposites()
.build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
}
}
}

View File

@@ -0,0 +1,115 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.SNOW_MATERIALS
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.*
import mods.betterfoliage.model.*
import mods.betterfoliage.util.*
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.*
import net.minecraft.world.BlockRenderView
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
object StandardGrassDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.grassBlocks
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.grassModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<Identifier>) {
ctx.addReplacement(StandardGrassKey(textureMatch[0], null))
BetterFoliage.blockTypes.grass.add(ctx.blockState)
}
}
data class StandardGrassKey(
val grassLocation: Identifier,
val overrideColor: Color?
) : ModelWrapKey() {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel): BakedModel {
val grassSpriteColor = Atlas.BLOCKS[grassLocation].averageColor.let { hsb ->
logColorOverride(detailLogger, BetterFoliage.config.shortGrass.saturationThreshold, hsb)
hsb.colorOverride(BetterFoliage.config.shortGrass.saturationThreshold)
}
return StandardGrassModel(meshifyCutoutMipped(wrapped), this.copy(overrideColor = grassSpriteColor))
}
}
class StandardGrassModel(wrapped: BakedModel, val key: StandardGrassKey) : WrappedBakedModel(wrapped) {
val tuftNormal by grassTuftMeshesNormal.delegate(key)
val tuftSnowed by grassTuftMeshesSnowed.delegate(key)
val fullBlock by grassFullBlockMeshes.delegate(key)
val tuftLighting = grassTuftLighting(UP)
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
if (!BetterFoliage.config.enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val ctx = BasicBlockCtx(blockView, pos)
val stateBelow = ctx.state(DOWN)
val stateAbove = ctx.state(UP)
val isSnowed = stateAbove.material in SNOW_MATERIALS
val connected = BetterFoliage.config.connectedGrass.enabled &&
(!isSnowed || BetterFoliage.config.connectedGrass.snowEnabled) &&
(stateBelow in BetterFoliage.blockTypes.dirt || stateBelow in BetterFoliage.blockTypes.grass)
val random = randomSupplier.get()
if (connected) {
context.meshConsumer().accept(if (isSnowed) snowFullBlockMeshes[random] else fullBlock[random])
} else {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
if (BetterFoliage.config.shortGrass.enabled(random) && !ctx.isNeighborSolid(UP)) {
ShadersModIntegration.grass(context, BetterFoliage.config.shortGrass.shaderWind) {
context.withLighting(tuftLighting) {
it.accept(if (isSnowed) tuftSnowed[random] else tuftNormal[random])
}
}
}
}
companion object {
val grassTuftSpritesNormal by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx")
}
val grassTuftSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx")
}
val grassTuftShapes = LazyMap(BakeWrapperManager) { key: StandardGrassKey ->
BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
}
val grassTuftMeshesNormal = LazyMap(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes[key], key.tintIndex) { idx -> grassTuftSpritesNormal[randomI()] }
.withOpposites()
.build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
}
val grassTuftMeshesSnowed = LazyMap(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes[key], Color.white.asInt) { idx -> grassTuftSpritesSnowed[randomI()] }
.withOpposites()
.build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
}
val grassFullBlockMeshes = LazyMap(BakeWrapperManager) { key: StandardGrassKey ->
Array(64) { fullCubeTextured(key.grassLocation, key.tintIndex) }
}
val snowFullBlockMeshes by LazyInvalidatable(BakeWrapperManager) {
Array(64) { fullCubeTextured(Identifier("block/snow"), -1) }
}
}
}

View File

@@ -0,0 +1,122 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.config.SNOW_MATERIALS
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.crossModelsRaw
import mods.betterfoliage.model.crossModelsTextured
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.render.lighting.roundLeafLighting
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.resource.generated.GeneratedLeafSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyMap
import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.colorOverride
import mods.betterfoliage.util.get
import mods.betterfoliage.util.logColorOverride
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP
import net.minecraft.world.BlockRenderView
import org.apache.logging.log4j.Level
import java.util.Random
import java.util.function.Supplier
interface LeafBlockModel {
val key: LeafParticleKey
}
interface LeafParticleKey {
val leafType: String
val overrideColor: Color?
}
object StandardLeafDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.leafBlocks
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.leafModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<Identifier>) {
val leafType = LeafParticleRegistry.typeMappings.getType(textureMatch[0]) ?: "default"
val generated = GeneratedLeafSprite(textureMatch[0], leafType)
.register(BetterFoliage.generatedPack)
.apply { ctx.sprites.add(this) }
detailLogger.log(Level.INFO, " particle $leafType")
ctx.addReplacement(StandardLeafKey(generated, leafType, null))
}
}
data class StandardLeafKey(
val roundLeafTexture: Identifier,
override val leafType: String,
override val overrideColor: Color?
) : ModelWrapKey(), LeafParticleKey {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel): BakedModel {
val leafSpriteColor = Atlas.BLOCKS[roundLeafTexture].averageColor.let { hsb ->
logColorOverride(detailLogger, BetterFoliage.config.leaves.saturationThreshold, hsb)
hsb.colorOverride(BetterFoliage.config.leaves.saturationThreshold)
}
return StandardLeafModel(meshifyCutoutMipped(wrapped), this.copy(overrideColor = leafSpriteColor))
}
}
class StandardLeafModel(
wrapped: BakedModel,
override val key: StandardLeafKey
) : WrappedBakedModel(wrapped), LeafBlockModel {
val leafNormal by leafModelsNormal.delegate(key)
val leafSnowed by leafModelsSnowed.delegate(key)
val leafLighting = roundLeafLighting()
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
ShadersModIntegration.leaves(context, BetterFoliage.config.leaves.shaderWind) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
if (!BetterFoliage.config.enabled || !BetterFoliage.config.leaves.enabled) return
val ctx = BasicBlockCtx(blockView, pos)
val stateAbove = ctx.state(UP)
val isSnowed = stateAbove.material in SNOW_MATERIALS
val random = randomSupplier.get()
context.withLighting(leafLighting) {
it.accept(leafNormal[random])
if (isSnowed) it.accept(leafSnowed[random])
}
}
}
companion object {
val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_leaves_snowed_$idx")
}
val leafModelsBase = LazyMap(BakeWrapperManager) { key: StandardLeafKey ->
BetterFoliage.config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) }
}
val leafModelsNormal = LazyMap(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], key.tintIndex, true) { key.roundLeafTexture }
}
val leafModelsSnowed = LazyMap(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], Color.white.asInt, false) { leafSpritesSnowed[it].id }
}
}
}

View File

@@ -0,0 +1,84 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardLilypadDiscovery : AbstractModelDiscovery() {
val LILYPAD_BLOCKS = listOf(Blocks.LILY_PAD)
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in LILYPAD_BLOCKS) {
ctx.addReplacement(StandardLilypadKey)
}
super.processModel(ctx)
}
}
object StandardLilypadKey : ModelWrapKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardLilypadModel(meshifyCutoutMipped(wrapped))
}
class StandardLilypadModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
if (!BetterFoliage.config.enabled || !BetterFoliage.config.lilypad.enabled) return
val random = randomSupplier.get()
ShadersModIntegration.grass(context, BetterFoliage.config.lilypad.shaderWind) {
context.meshConsumer().accept(lilypadRootModels[random])
}
if (random.nextInt(64) < BetterFoliage.config.lilypad.population) {
context.meshConsumer().accept(lilypadFlowerModels[random])
}
}
companion object {
val lilypadRootSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_roots_$idx")
}
val lilypadFlowerSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_flower_$idx")
}
val lilypadRootModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = tuftShapeSet(1.0, 1.0, 1.0, BetterFoliage.config.lilypad.hOffset)
tuftModelSet(shapes, Color.white.asInt) { lilypadRootSprites[it] }
.transform { move(2.0 to DOWN) }
.buildTufts()
}
val lilypadFlowerModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = tuftShapeSet(0.5, 0.5, 0.5, BetterFoliage.config.lilypad.hOffset)
tuftModelSet(shapes, Color.white.asInt) { lilypadFlowerSprites[it] }
.transform { move(1.0 to DOWN) }
.buildTufts()
}
}
}

View File

@@ -0,0 +1,84 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.ShadersModIntegration
import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardMyceliumDiscovery : AbstractModelDiscovery() {
val MYCELIUM_BLOCKS = listOf(Blocks.MYCELIUM)
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in MYCELIUM_BLOCKS) {
ctx.addReplacement(StandardMyceliumKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.getCutout())
}
super.processModel(ctx)
}
}
object StandardMyceliumKey : ModelWrapKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardMyceliumModel(meshifyCutoutMipped(wrapped))
}
class StandardMyceliumModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val tuftLighting = grassTuftLighting(UP)
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val random = randomSupplier.get()
if (BetterFoliage.config.enabled &&
BetterFoliage.config.shortGrass.let { it.myceliumEnabled && random.nextInt(64) < it.population } &&
blockView.getBlockState(pos + UP.offset).isAir
) {
ShadersModIntegration.grass(context, BetterFoliage.config.shortGrass.shaderWind) {
context.withLighting(tuftLighting) {
it.accept(myceliumTuftModels[random])
}
}
}
}
companion object {
val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_mycel_$idx")
}
val myceliumTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white.asInt) { idx -> myceliumTuftSprites[randomI()] }.buildTufts()
}
}
}

View File

@@ -0,0 +1,94 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.NETHERRACK_BLOCKS
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.build
import mods.betterfoliage.model.meshifyCutoutMipped
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.model.withOpposites
import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.DOWN
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardNetherrackDiscovery : AbstractModelDiscovery() {
fun canRenderInLayer(layer: RenderLayer) = when {
!BetterFoliage.config.enabled -> layer == RenderLayer.getSolid()
!BetterFoliage.config.netherrack.enabled -> layer == RenderLayer.getSolid()
else -> layer == RenderLayer.getCutoutMipped()
}
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in NETHERRACK_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardNetherrackKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
}
super.processModel(ctx)
}
}
object StandardNetherrackKey : ModelWrapKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardNetherrackModel(meshifyCutoutMipped(wrapped))
}
class StandardNetherrackModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val tuftLighting = grassTuftLighting(DOWN)
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
if (BetterFoliage.config.enabled &&
BetterFoliage.config.netherrack.enabled &&
blockView.getBlockState(pos + DOWN.offset).isAir
) {
val random = randomSupplier.get()
context.withLighting(tuftLighting) {
it.accept(netherrackTuftModels[random])
}
}
}
companion object {
val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_netherrack_$idx")
}
val netherrackTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, Color.white.asInt) { netherrackTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) }
.withOpposites()
.build(BlendMode.CUTOUT_MIPPED, flatLighting = false)
}
}
}

View File

@@ -0,0 +1,92 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.meshifySolid
import mods.betterfoliage.render.column.ColumnBlockKey
import mods.betterfoliage.render.column.ColumnMeshSet
import mods.betterfoliage.render.column.ColumnModelBase
import mods.betterfoliage.render.column.ColumnRenderLayer
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyMap
import mods.betterfoliage.util.tryDefault
import net.minecraft.block.BlockState
import net.minecraft.block.PillarBlock
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.Direction.Axis
import org.apache.logging.log4j.Level
interface RoundLogKey : ColumnBlockKey, ModelBakingKey {
val barkSprite: Identifier
val endSprite: Identifier
}
object RoundLogOverlayLayer : ColumnRenderLayer() {
override fun getColumnKey(state: BlockState) = BetterFoliage.blockTypes.getTyped<ColumnBlockKey>(state)
override val connectSolids: Boolean get() = BetterFoliage.config.roundLogs.connectSolids
override val lenientConnect: Boolean get() = BetterFoliage.config.roundLogs.lenientConnect
override val defaultToY: Boolean get() = BetterFoliage.config.roundLogs.defaultY
}
object StandardRoundLogDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.logBlocks
override val modelTextures: List<ModelTextureList> get() = BetterFoliage.blockConfig.logModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<Identifier>) {
val axis = getAxis(ctx.blockState)
detailLogger.log(Level.INFO, " axis $axis")
ctx.addReplacement(StandardRoundLogKey(axis, textureMatch[0], textureMatch[1]))
}
fun getAxis(state: BlockState): Axis? {
val axis = tryDefault(null) { state.get(PillarBlock.AXIS).toString() } ?:
state.entries.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
return when (axis) {
"x" -> Axis.X
"y" -> Axis.Y
"z" -> Axis.Z
else -> null
}
}
}
data class StandardRoundLogKey(
override val axis: Axis?,
override val barkSprite: Identifier,
override val endSprite: Identifier
) : RoundLogKey, ModelWrapKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardRoundLogModel(meshifySolid(wrapped), this)
}
class StandardRoundLogModel(wrapped: BakedModel, val key: StandardRoundLogKey) : ColumnModelBase(wrapped) {
override val enabled: Boolean get() = BetterFoliage.config.enabled && BetterFoliage.config.roundLogs.enabled
override val overlayLayer: ColumnRenderLayer get() = RoundLogOverlayLayer
override val connectPerpendicular: Boolean get() = BetterFoliage.config.roundLogs.connectPerpendicular
val modelSet by modelSets.delegate(key)
override fun getMeshSet(axis: Axis, quadrant: Int) = modelSet
companion object {
val modelSets = LazyMap(BakeWrapperManager) { key: StandardRoundLogKey ->
val barkSprite = Atlas.BLOCKS[key.barkSprite]!!
val endSprite = Atlas.BLOCKS[key.endSprite]!!
BetterFoliage.config.roundLogs.let { config ->
ColumnMeshSet(
config.radiusSmall, config.radiusLarge, config.zProtection,
key.axis ?: Axis.Y,
barkSprite, barkSprite,
endSprite, endSprite
)
}
}
}
}

View File

@@ -0,0 +1,122 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.CachedBlockCtx
import mods.betterfoliage.config.SALTWATER_BIOMES
import mods.betterfoliage.config.SAND_BLOCKS
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.ModelWrapKey
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.WrappedBakedModel
import mods.betterfoliage.model.build
import mods.betterfoliage.model.horizontalRectangle
import mods.betterfoliage.model.meshifySolid
import mods.betterfoliage.model.meshifyStandard
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.model.withOpposites
import mods.betterfoliage.render.lighting.grassTuftLighting
import mods.betterfoliage.render.lighting.withLighting
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.get
import mods.betterfoliage.util.randomB
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.Material
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.json.JsonUnbakedModel
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.UP
import net.minecraft.world.BlockRenderView
import java.util.Random
import java.util.function.Supplier
object StandardSandDiscovery : AbstractModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is JsonUnbakedModel && ctx.blockState.block in SAND_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardSandKey)
// RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.getCutoutMipped())
}
super.processModel(ctx)
}
}
object StandardSandKey : ModelWrapKey() {
override fun bake(ctx: ModelBakingContext, wrapped: BasicBakedModel) = StandardSandModel(meshifySolid(wrapped))
}
class StandardSandModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
val coralLighting = allDirections.map { grassTuftLighting(it) }.toTypedArray()
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val ctx = CachedBlockCtx(blockView, pos)
val random = randomSupplier.get()
if (!BetterFoliage.config.enabled || !BetterFoliage.config.coral.enabled(random)) return
if (ctx.biome?.category !in SALTWATER_BIOMES) return
allDirections.filter { random.nextInt(64) < BetterFoliage.config.coral.chance }.forEach { face ->
val isWater = ctx.state(face).material == Material.WATER
val isDeepWater = isWater && ctx.offset(face).state(UP).material == Material.WATER
if (isDeepWater) context.withLighting(coralLighting[face]) {
it.accept(coralCrustModels[face][random])
it.accept(coralTuftModels[face][random])
}
}
}
companion object {
// val sandModel by LazyInvalidatable(BetterFoliage.modelReplacer) {
// Array(64) { fullCubeTextured(Identifier("block/sand"), Color.white.asInt) }
// }
val coralTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_coral_$idx")
}
val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
Identifier(BetterFoliage.MOD_ID, "blocks/better_crust_$idx")
}
val coralTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = BetterFoliage.config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) }
allDirections.map { face ->
tuftModelSet(shapes, Color.white.asInt) { coralTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[face]) }
.withOpposites()
.build(BlendMode.CUTOUT_MIPPED)
}.toTypedArray()
}
val coralCrustModels by LazyInvalidatable(BakeWrapperManager) {
allDirections.map { face ->
Array(64) { idx ->
listOf(horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0)
.scale(BetterFoliage.config.coral.crustSize)
.move(0.5 + randomD(0.01, BetterFoliage.config.coral.vOffset) to UP)
.rotate(Rotation.fromUp[face])
.mirrorUV(randomB(), randomB()).rotateUV(randomI(max = 4))
.sprite(coralCrustSprites[idx]).colorAndIndex(null)
).build(BlendMode.CUTOUT_MIPPED)
}
}.toTypedArray()
}
}
}

View File

@@ -0,0 +1,170 @@
package mods.betterfoliage.render.column
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.Quad
import mods.betterfoliage.model.UV
import mods.betterfoliage.model.Vertex
import mods.betterfoliage.model.build
import mods.betterfoliage.model.horizontalRectangle
import mods.betterfoliage.model.verticalRectangle
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode.SOLID
import net.minecraft.client.texture.Sprite
import net.minecraft.util.math.Direction.Axis
import net.minecraft.util.math.Direction.EAST
import net.minecraft.util.math.Direction.SOUTH
import net.minecraft.util.math.Direction.UP
/**
* Collection of dynamically generated meshes used to render rounded columns.
*/
class ColumnMeshSet(
radiusSmall: Double,
radiusLarge: Double,
zProtection: Double,
val axis: Axis,
val spriteLeft: Sprite,
val spriteRight: Sprite,
val spriteTop: Sprite,
val spriteBottom: Sprite
) {
protected fun sideRounded(radius: Double, yBottom: Double, yTop: Double): List<Quad> {
val halfRadius = radius * 0.5
return listOf(
// left side of the diagonal
verticalRectangle(0.0, 0.5, 0.5 - radius, 0.5, yBottom, yTop).clampUV(minU = 0.0, maxU = 0.5 - radius),
verticalRectangle(0.5 - radius, 0.5, 0.5 - halfRadius, 0.5 - halfRadius, yBottom, yTop).clampUV(minU = 0.5 - radius),
// right side of the diagonal
verticalRectangle(0.5 - halfRadius, 0.5 - halfRadius, 0.5, 0.5 - radius, yBottom, yTop).clampUV(maxU = radius - 0.5),
verticalRectangle(0.5, 0.5 - radius, 0.5, 0.0, yBottom, yTop).clampUV(minU = radius - 0.5, maxU = 0.0)
)
}
protected fun sideRoundedTransition(radiusBottom: Double, radiusTop: Double, yBottom: Double, yTop: Double): List<Quad> {
val ySplit = 0.5 * (yBottom + yTop)
val modelTop = sideRounded(radiusTop, yBottom, yTop)
val modelBottom = sideRounded(radiusBottom, yBottom, yTop)
return (modelBottom zip modelTop).map { (quadBottom, quadTop) ->
Quad.mix(quadBottom, quadTop) { vBottom, vTop -> if (vBottom.xyz.y < ySplit) vBottom.copy() else vTop.copy() }
}
}
protected fun sideSquare(yBottom: Double, yTop: Double) = listOf(
verticalRectangle(0.0, 0.5, 0.5, 0.5, yBottom, yTop).clampUV(minU = 0.0),
verticalRectangle(0.5, 0.5, 0.5, 0.0, yBottom, yTop).clampUV(maxU = 0.0)
)
protected fun lidRounded(radius: Double, y: Double, isBottom: Boolean) = Array(4) { quadrant ->
val rotation = baseRotation(axis) + quadrantRotations[quadrant]
val v1 = Vertex(Double3(0.0, y, 0.0), UV(0.0, 0.0))
val v2 = Vertex(Double3(0.0, y, 0.5), UV(0.0, 0.5))
val v3 = Vertex(Double3(0.5 - radius, y, 0.5), UV(0.5 - radius, 0.5))
val v4 = Vertex(Double3(0.5 - radius * 0.5, y, 0.5 - radius * 0.5), UV(0.5, 0.5))
val v5 = Vertex(Double3(0.5, y, 0.5 - radius), UV(0.5, 0.5 - radius))
val v6 = Vertex(Double3(0.5, y, 0.0), UV(0.5, 0.0))
listOf(Quad(v1, v2, v3, v4), Quad(v1, v4, v5, v6))
.map { it.cycleVertices(if (isBottom xor BetterFoliage.config.nVidia) 0 else 1) }
.map { it.rotate(rotation).rotateUV(quadrant) }
.map { it.sprite(if (isBottom) spriteBottom else spriteTop).colorAndIndex(Color.white.asInt) }
.map { if (isBottom) it.flipped else it }
}
protected fun lidSquare(y: Double, isBottom: Boolean) = Array(4) { quadrant ->
val rotation = baseRotation(axis) + quadrantRotations[quadrant]
listOf(
horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = y).clampUV(minU = 0.0, minV = 0.0)
.rotate(rotation).rotateUV(quadrant)
.sprite(if (isBottom) spriteBottom else spriteTop).colorAndIndex(Color.white.asInt)
.let { if (isBottom) it.flipped else it }
)
}
protected val zProtectionScale = zProtection.let { Double3(it, 1.0, it) }
protected fun List<Quad>.extendTop(size: Double) = map { q -> q.clampUV(minV = 0.5 - size).transformV { v ->
if (v.xyz.y > 0.501) v.copy(xyz = v.xyz * zProtectionScale) else v }
}
protected fun List<Quad>.extendBottom(size: Double) = map { q -> q.clampUV(maxV = -0.5 + size).transformV { v ->
if (v.xyz.y < -0.501) v.copy(xyz = v.xyz * zProtectionScale) else v }
}
protected fun List<Quad>.buildSides(quadsPerSprite: Int) = Array(4) { quadrant ->
val rotation = baseRotation(axis) + quadrantRotations[quadrant]
this.map { it.rotate(rotation).colorAndIndex(Color.white.asInt) }
.mapIndexed { idx, q -> if (idx % (2 * quadsPerSprite) >= quadsPerSprite) q.sprite(spriteRight) else q.sprite(spriteLeft) }
.build(SOLID, flatLighting = false)
}
companion object {
fun baseRotation(axis: Axis) = when(axis) {
Axis.X -> Rotation.fromUp[EAST.ordinal]
Axis.Y -> Rotation.fromUp[UP.ordinal]
Axis.Z -> Rotation.fromUp[SOUTH.ordinal]
}
val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it }
}
//
// Mesh definitions
// 4-element arrays hold prebuild meshes for each of the rotations around the axis
//
val sideSquare = sideSquare(-0.5, 0.5).buildSides(quadsPerSprite = 1)
val sideRoundSmall = sideRounded(radiusSmall, -0.5, 0.5).buildSides(quadsPerSprite = 2)
val sideRoundLarge = sideRounded(radiusLarge, -0.5, 0.5).buildSides(quadsPerSprite = 2)
val sideExtendTopSquare = sideSquare(0.5, 0.5 + radiusLarge).extendTop(radiusLarge).buildSides(quadsPerSprite = 1)
val sideExtendTopRoundSmall = sideRounded(radiusSmall, 0.5, 0.5 + radiusLarge).extendTop(radiusLarge).buildSides(quadsPerSprite = 2)
val sideExtendTopRoundLarge = sideRounded(radiusLarge, 0.5, 0.5 + radiusLarge).extendTop(radiusLarge).buildSides(quadsPerSprite = 2)
val sideExtendBottomSquare = sideSquare(-0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 1)
val sideExtendBottomRoundSmall = sideRounded(radiusSmall, -0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 2)
val sideExtendBottomRoundLarge = sideRounded(radiusLarge, -0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 2)
val lidTopSquare = lidSquare(0.5, false).build(SOLID, flatLighting = false)
val lidTopRoundSmall = lidRounded(radiusSmall, 0.5, false).build(SOLID, flatLighting = false)
val lidTopRoundLarge = lidRounded(radiusLarge, 0.5, false).build(SOLID, flatLighting = false)
val lidBottomSquare = lidSquare(-0.5, true).build(SOLID, flatLighting = false)
val lidBottomRoundSmall = lidRounded(radiusSmall, -0.5, true).build(SOLID, flatLighting = false)
val lidBottomRoundLarge = lidRounded(radiusLarge, -0.5, true).build(SOLID, flatLighting = false)
val transitionTop = sideRoundedTransition(radiusLarge, radiusSmall, -0.5, 0.5).buildSides(quadsPerSprite = 2)
val transitionBottom = sideRoundedTransition(radiusSmall, radiusLarge, -0.5, 0.5).buildSides(quadsPerSprite = 2)
//
// Helper fuctions for lids (block ends)
//
fun flatTop(quadrantTypes: Array<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
SMALL_RADIUS -> lidTopRoundSmall[quadrant]
LARGE_RADIUS -> lidTopRoundLarge[quadrant]
SQUARE -> lidTopSquare[quadrant]
INVISIBLE -> lidTopSquare[quadrant]
}
fun flatBottom(quadrantTypes: Array<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
SMALL_RADIUS -> lidBottomRoundSmall[quadrant]
LARGE_RADIUS -> lidBottomRoundLarge[quadrant]
SQUARE -> lidBottomSquare[quadrant]
INVISIBLE -> lidBottomSquare[quadrant]
}
fun extendTop(quadrantTypes: Array<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
SMALL_RADIUS -> sideExtendTopRoundSmall[quadrant]
LARGE_RADIUS -> sideExtendTopRoundLarge[quadrant]
SQUARE -> sideExtendTopSquare[quadrant]
INVISIBLE -> sideExtendTopSquare[quadrant]
}
fun extendBottom(quadrantTypes: Array<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
SMALL_RADIUS -> sideExtendBottomRoundSmall[quadrant]
LARGE_RADIUS -> sideExtendBottomRoundLarge[quadrant]
SQUARE -> sideExtendBottomSquare[quadrant]
INVISIBLE -> sideExtendBottomSquare[quadrant]
}
}

View File

@@ -0,0 +1,109 @@
package mods.betterfoliage.render.column
import mods.betterfoliage.chunk.CachedBlockCtx
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.render.column.ColumnLayerData.NormalRender
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.betterfoliage.model.WrappedBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockState
import net.minecraft.client.render.model.BakedModel
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.Axis
import net.minecraft.world.BlockRenderView
import java.util.*
import java.util.function.Supplier
abstract class ColumnModelBase(wrapped: BakedModel) : WrappedBakedModel(wrapped) {
abstract val enabled: Boolean
abstract val overlayLayer: ColumnRenderLayer
abstract val connectPerpendicular: Boolean
abstract fun getMeshSet(axis: Axis, quadrant: Int): ColumnMeshSet
override fun emitBlockQuads(blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
if (!enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
val ctx = CachedBlockCtx(blockView, pos)
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)
when(roundLog) {
ColumnLayerData.SkipRender -> return
NormalRender -> return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
ColumnLayerData.ResolveError, null -> {
return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
}
// if log axis is not defined and "Default to vertical" config option is not set, render normally
if ((roundLog as ColumnLayerData.SpecialRender).column.axis == null && !overlayLayer.defaultToY) {
return super.emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
val axis = roundLog.column.axis ?: Axis.Y
val baseRotation = ColumnMeshSet.baseRotation(axis)
ColumnMeshSet.quadrantRotations.forEachIndexed { idx, quadrantRotation ->
// set rotation for the current quadrant
val rotation = baseRotation + quadrantRotation
val meshSet = getMeshSet(axis, idx)
// disallow sharp discontinuities in the chamfer radius, or tapering-in where inappropriate
if (roundLog.quadrants[idx] == LARGE_RADIUS &&
roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] != LARGE_RADIUS &&
roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] != LARGE_RADIUS) {
roundLog.quadrants[idx] = SMALL_RADIUS
}
// select meshes for current quadrant based on connectivity rules
val sideMesh = when (roundLog.quadrants[idx]) {
SMALL_RADIUS -> meshSet.sideRoundSmall[idx]
LARGE_RADIUS -> if (roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] == SMALL_RADIUS) meshSet.transitionTop[idx]
else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) meshSet.transitionBottom[idx]
else meshSet.sideRoundLarge[idx]
SQUARE -> meshSet.sideSquare[idx]
else -> null
}
val upMesh = when(roundLog.upType) {
NONSOLID -> meshSet.flatTop(roundLog.quadrants, idx)
PERPENDICULAR -> {
if (!connectPerpendicular) {
meshSet.flatTop(roundLog.quadrants, idx)
} else {
meshSet.extendTop(roundLog.quadrants, idx)
}
}
PARALLEL -> {
if (roundLog.quadrants[idx] discontinuousWith roundLog.quadrantsTop[idx] &&
roundLog.quadrants[idx].let { it == SQUARE || it == INVISIBLE } )
meshSet.flatTop(roundLog.quadrants, idx)
else null
}
else -> null
}
val downMesh = when(roundLog.downType) {
NONSOLID -> meshSet.flatBottom(roundLog.quadrants, idx)
PERPENDICULAR -> {
if (!connectPerpendicular) {
meshSet.flatBottom(roundLog.quadrants, idx)
} else {
meshSet.extendBottom(roundLog.quadrants, idx)
}
}
PARALLEL -> {
if (roundLog.quadrants[idx] discontinuousWith roundLog.quadrantsBottom[idx] &&
roundLog.quadrants[idx].let { it == SQUARE || it == INVISIBLE } )
meshSet.flatBottom(roundLog.quadrants, idx)
else null
}
else -> null
}
// render
sideMesh?.let { context.meshConsumer().accept(it) }
upMesh?.let { context.meshConsumer().accept(it) }
downMesh?.let { context.meshConsumer().accept(it) }
}
}
}

View File

@@ -0,0 +1,199 @@
package mods.betterfoliage.render.column
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.chunk.ChunkOverlayLayer
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.chunk.dimType
import mods.betterfoliage.render.block.vanilla.RoundLogKey
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.NONSOLID
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PARALLEL
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PERPENDICULAR
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.SOLID
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.face
import mods.betterfoliage.util.get
import mods.betterfoliage.util.plus
import net.minecraft.block.BlockState
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction.Axis
import net.minecraft.util.math.Direction.AxisDirection
import net.minecraft.world.WorldView
/** Index of SOUTH-EAST quadrant. */
const val SE = 0
/** Index of NORTH-EAST quadrant. */
const val NE = 1
/** Index of NORTH-WEST quadrant. */
const val NW = 2
/** Index of SOUTH-WEST quadrant. */
const val SW = 3
interface ColumnBlockKey {
val axis: Axis?
}
/**
* Sealed class hierarchy for all possible render outcomes
*/
sealed class ColumnLayerData {
/**
* Data structure to cache texture and world neighborhood data relevant to column rendering
*/
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
data class SpecialRender(
val column: ColumnBlockKey,
val upType: BlockType,
val downType: BlockType,
val quadrants: Array<QuadrantType>,
val quadrantsTop: Array<QuadrantType>,
val quadrantsBottom: Array<QuadrantType>
) : ColumnLayerData() {
enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR }
enum class QuadrantType {
SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE;
infix fun continuousWith(other: QuadrantType) =
this == other || ((this == SQUARE || this == INVISIBLE) && (other == SQUARE || other == INVISIBLE))
infix fun discontinuousWith(other: QuadrantType) = !continuousWith(other)
}
}
/** Column block should not be rendered at all */
object SkipRender : ColumnLayerData()
/** Column block must be rendered normally */
object NormalRender : ColumnLayerData()
/** Error while resolving render data, column block must be rendered normally */
object ResolveError : ColumnLayerData()
}
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
abstract val connectSolids: Boolean
abstract val lenientConnect: Boolean
abstract val defaultToY: Boolean
abstract fun getColumnKey(state: BlockState): ColumnBlockKey?
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
override fun onBlockUpdate(world: WorldView, pos: BlockPos) {
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
}
override fun calculate(ctx: BlockCtx): ColumnLayerData {
if (allDirections.all { dir ->
ctx.offset(dir).let { it.isNormalCube && !BetterFoliage.blockTypes.hasTyped<RoundLogKey>(it.state) }
}) return ColumnLayerData.SkipRender
val columnTextures = getColumnKey(ctx.state) ?: return ColumnLayerData.ResolveError
// if log axis is not defined and "Default to vertical" config option is not set, render normally
val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender
// check log neighborhood
val baseRotation = Rotation.fromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal]
val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0))
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
val quadrants = Array(4) { SMALL_RADIUS }.checkNeighbors(ctx, baseRotation, logAxis, 0)
val quadrantsTop = Array(4) { SMALL_RADIUS }
if (upType == PARALLEL) quadrantsTop.checkNeighbors(ctx, baseRotation, logAxis, 1)
val quadrantsBottom = Array(4) { SMALL_RADIUS }
if (downType == PARALLEL) quadrantsBottom.checkNeighbors(ctx, baseRotation, logAxis, -1)
return ColumnLayerData.SpecialRender(columnTextures, upType, downType, quadrants, quadrantsTop, quadrantsBottom)
}
/** Sets the type of the given quadrant only if the new value is "stronger" (larger ordinal). */
inline fun Array<QuadrantType>.upgrade(idx: Int, value: QuadrantType) {
if (this[idx].ordinal < value.ordinal) this[idx] = value
}
/** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */
fun Array<QuadrantType>.checkNeighbors(ctx: BlockCtx, rotation: Rotation, logAxis: Axis, yOff: Int): Array<QuadrantType> {
val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1))
val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0))
val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1))
val blkW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 0))
// a solid block on one side will make the 2 neighboring quadrants SQUARE
// if there are solid blocks to both sides of a quadrant, it is INVISIBLE
if (connectSolids) {
if (blkS == SOLID) {
upgrade(SW, SQUARE); upgrade(SE, SQUARE)
}
if (blkE == SOLID) {
upgrade(SE, SQUARE); upgrade(NE, SQUARE)
}
if (blkN == SOLID) {
upgrade(NE, SQUARE); upgrade(NW, SQUARE)
}
if (blkW == SOLID) {
upgrade(NW, SQUARE); upgrade(SW, SQUARE)
}
if (blkS == SOLID && blkE == SOLID) upgrade(SE, INVISIBLE)
if (blkN == SOLID && blkE == SOLID) upgrade(NE, INVISIBLE)
if (blkN == SOLID && blkW == SOLID) upgrade(NW, INVISIBLE)
if (blkS == SOLID && blkW == SOLID) upgrade(SW, INVISIBLE)
}
val blkSE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 1))
val blkNE = ctx.blockType(rotation, logAxis, Int3(1, yOff, -1))
val blkNW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, -1))
val blkSW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 1))
if (lenientConnect) {
// if the block forms the tip of an L-shape, connect to its neighbor with SQUARE quadrants
if (blkE == PARALLEL && (blkSE == PARALLEL || blkNE == PARALLEL)) {
upgrade(SE, SQUARE); upgrade(NE, SQUARE)
}
if (blkN == PARALLEL && (blkNE == PARALLEL || blkNW == PARALLEL)) {
upgrade(NE, SQUARE); upgrade(NW, SQUARE)
}
if (blkW == PARALLEL && (blkNW == PARALLEL || blkSW == PARALLEL)) {
upgrade(NW, SQUARE); upgrade(SW, SQUARE)
}
if (blkS == PARALLEL && (blkSE == PARALLEL || blkSW == PARALLEL)) {
upgrade(SW, SQUARE); upgrade(SE, SQUARE)
}
}
// if the block forms the middle of an L-shape, or is part of a 2x2 configuration,
// connect to its neighbors with SQUARE quadrants, INVISIBLE on the inner corner, and LARGE_RADIUS on the outer corner
if (blkN == PARALLEL && blkW == PARALLEL && (lenientConnect || blkNW == PARALLEL)) {
upgrade(SE, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(NW, INVISIBLE)
}
if (blkS == PARALLEL && blkW == PARALLEL && (lenientConnect || blkSW == PARALLEL)) {
upgrade(NE, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(SW, INVISIBLE)
}
if (blkS == PARALLEL && blkE == PARALLEL && (lenientConnect || blkSE == PARALLEL)) {
upgrade(NW, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(SE, INVISIBLE)
}
if (blkN == PARALLEL && blkE == PARALLEL && (lenientConnect || blkNE == PARALLEL)) {
upgrade(SW, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(NE, INVISIBLE)
}
return this
}
/**
* Get the type of the block at the given offset in a rotated reference frame.
*/
fun BlockCtx.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType {
val offsetRot = offset.rotate(rotation)
val key = getColumnKey(state(offsetRot))
return if (key == null) {
if (offset(offsetRot).isNormalCube) SOLID else NONSOLID
} else {
(key.axis ?: if (BetterFoliage.config.roundLogs.defaultY) Axis.Y else null)?.let {
if (it == axis) PARALLEL else PERPENDICULAR
} ?: SOLID
}
}
}

View File

@@ -0,0 +1,140 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.util.*
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView
import net.minecraft.util.math.Direction
import net.minecraft.util.math.Direction.*
import kotlin.math.abs
val EPSILON = 0.05
interface CustomLighting {
fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean)
}
interface CustomLightingMeshConsumer {
/** Clear cached block brightness and AO values */
fun clearLighting()
/** Fill AO/light cache for given face */
fun fillAoData(lightFace: Direction)
/** Set AO/light values for quad vertex */
fun setLighting(vIdx: Int, ao: Float, light: Int)
/** Get neighbor block brightness */
fun brNeighbor(dir: Direction): Int
/** Block brightness value */
val brSelf: Int
/** Cached AO values for all box face corners */
val aoFull: FloatArray
/** Cached light values for all box face corners */
val lightFull: IntArray
}
/** Custom lighting used for protruding tuft quads (short grass, algae, cactus arms, etc.) */
fun grassTuftLighting(lightFace: Direction) = object : CustomLighting {
override fun applyLighting(lighting: CustomLightingMeshConsumer, quad : QuadView, flat: Boolean, emissive: Boolean) {
if (flat) lighting.flatForceNeighbor(quad, lightFace) else lighting.smoothWithFaceOverride(quad, lightFace)
}
}
/** Custom lighting used for round leaves */
fun roundLeafLighting() = object : CustomLighting {
override fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean) {
if (flat) lighting.flatMax(quad) else lighting.smooth45PreferUp(quad)
}
}
/** Custom lighting used for reeds */
fun reedLighting() = object : CustomLighting {
override fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean) {
lighting.flatForceNeighbor(quad, UP)
}
}
/** Flat lighting, use neighbor brightness in the given direction */
fun CustomLightingMeshConsumer.flatForceNeighbor(quad: QuadView, lightFace: Direction) {
for (vIdx in 0 until 4) {
setLighting(vIdx, 1.0f, brNeighbor(lightFace))
}
}
/** Smooth lighting, use *only* AO/light values on the given face (closest corner) */
fun CustomLightingMeshConsumer.smoothWithFaceOverride(quad: QuadView, lightFace: Direction) {
fillAoData(lightFace)
forEachVertex(quad) { vIdx, x, y, z ->
val cornerUndir = getCornerUndir(x, y, z)
cornerDirFromUndir[lightFace.ordinal][cornerUndir]?.let { aoCorner ->
setLighting(vIdx, aoFull[aoCorner], lightFull[aoCorner])
}
}
}
/**
* Smooth lighting scheme for 45-degree quads bisecting the box along 2 opposing face diagonals.
*
* Determine 2 *primary faces* based on the normal direction.
* Take AO/light values *only* from the 2 primary faces *or* the UP direction,
* based on which box corner is closest. Prefer taking values from the top face.
*/
fun CustomLightingMeshConsumer.smooth45PreferUp(quad: QuadView) {
getAngles45(quad)?.let { normalFaces ->
fillAoData(normalFaces.first)
fillAoData(normalFaces.second)
if (normalFaces.first != UP && normalFaces.second != UP) fillAoData(UP)
forEachVertex(quad) { vIdx, x, y, z ->
val isUp = y > 0.5f
val cornerUndir = getCornerUndir(x, y, z)
val preferredFace = if (isUp) UP else normalFaces.minBy { faceDistance(it, x, y, z) }
val aoCorner = cornerDirFromUndir[preferredFace.ordinal][cornerUndir]!!
setLighting(vIdx, aoFull[aoCorner], lightFull[aoCorner])
}
}
}
/** Flat lighting, use maximum neighbor brightness at the nearest box corner */
fun CustomLightingMeshConsumer.flatMax(quad: QuadView) {
forEachVertex(quad) { vIdx, x, y, z ->
val maxBrightness = cornersUndir[getCornerUndir(x, y, z)].maxValueBy { brNeighbor(it) }
setLighting(vIdx, 1.0f, maxBrightness)
}
}
/**
* If the quad normal approximately bisects 2 axes at a 45 degree angle,
* and is approximately perpendicular to the third, returns the 2 directions
* the quad normal points towards.
* Returns null otherwise.
*/
fun getAngles45(quad: QuadView): Pair<Direction, Direction>? {
val normal = quad.faceNormal()
// one of the components must be close to zero
val zeroAxis = when {
abs(normal.x) < EPSILON -> Axis.X
abs(normal.y) < EPSILON -> Axis.Y
abs(normal.z) < EPSILON -> Axis.Z
else -> return null
}
// the other two must be of similar magnitude
val diff = when(zeroAxis) {
Axis.X -> abs(abs(normal.y) - abs(normal.z))
Axis.Y -> abs(abs(normal.x) - abs(normal.z))
Axis.Z -> abs(abs(normal.x) - abs(normal.y))
}
if (diff > EPSILON) return null
return when(zeroAxis) {
Axis.X -> Pair(if (normal.y > 0.0f) UP else DOWN, if (normal.z > 0.0f) SOUTH else NORTH)
Axis.Y -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.z > 0.0f) SOUTH else NORTH)
Axis.Z -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.y > 0.0f) UP else DOWN)
}
}
fun faceDistance(face: Direction, x: Float, y: Float, z: Float) = when(face) {
WEST -> x; EAST -> 1.0f - x
DOWN -> y; UP -> 1.0f - y
NORTH -> z; SOUTH -> 1.0f - z
}
inline fun forEachVertex(quad: QuadView, func: (vIdx: Int, x: Float, y: Float, z: Float)->Unit) {
for (vIdx in 0..3) {
func(vIdx, quad.x(vIdx), quad.y(vIdx), quad.z(vIdx))
}
}

View File

@@ -0,0 +1,78 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.util.YarnHelper
import mods.betterfoliage.util.get
import mods.betterfoliage.util.reflectField
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator
import net.fabricmc.fabric.impl.client.indigo.renderer.render.*
import net.minecraft.block.BlockState
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.render.model.BakedModel
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockRenderView
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
val AbstractQuadRenderer_blockInfo2 = YarnHelper.requiredField<TerrainBlockRenderInfo>(
"net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractQuadRenderer",
"blockInfo", "Lnet/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainBlockRenderInfo;"
)
val AbstractQuadRenderer_bufferFunc2 = YarnHelper.requiredField<java.util.function.Function<RenderLayer, VertexConsumer>>(
"net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractQuadRenderer",
"bufferFunc", "Ljava/util/function/Function;"
)
val AbstractQuadRenderer_aoCalc = YarnHelper.requiredField<AoCalculator>(
"net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractQuadRenderer",
"aoCalc", "Lnet/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator;"
)
val AbstractQuadRenderer_transform = YarnHelper.requiredField<RenderContext.QuadTransform>(
"net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractQuadRenderer",
"transform", "Lnet/fabricmc/fabric/api/renderer/v1/render/RenderContext\$QuadTransform;"
)
val MODIFIED_CONSUMER_POOL = ThreadLocal<ModifiedTerrainMeshConsumer>()
fun AbstractMeshConsumer.modified() = MODIFIED_CONSUMER_POOL.get() ?: let {
ModifiedTerrainMeshConsumer(this)
}.apply { MODIFIED_CONSUMER_POOL.set(this) }
/**
* Render the given model at the given position.
* Mutates the state of the [RenderContext]!!
*/
fun RenderContext.renderMasquerade(model: BakedModel, blockView: BlockRenderView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) = when(this) {
is TerrainRenderContext -> {
val blockInfo = meshConsumer()[AbstractQuadRenderer_blockInfo2]!!
blockInfo.prepareForBlock(state, pos, model.useAmbientOcclusion())
(model as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
else -> {
(model as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
}
/** Execute the provided block with a mesh consumer using the given custom lighting. */
fun RenderContext.withLighting(lighter: CustomLighting, func: (Consumer<Mesh>)->Unit) = when(this) {
is TerrainRenderContext -> {
val consumer = (meshConsumer() as AbstractMeshConsumer).modified()
consumer.clearLighting()
consumer.lighter = lighter
func(consumer)
consumer.lighter = null
}
else -> func(meshConsumer())
}
/** Get the [BufferBuilder] responsible for a given [BlockRenderLayer] */
fun RenderContext.getBufferBuilder(layer: RenderLayer) = when(this) {
is TerrainRenderContext -> {
val bufferFunc = meshConsumer()[AbstractQuadRenderer_bufferFunc2]!!
bufferFunc.apply(layer)
}
else -> null
}

View File

@@ -0,0 +1,102 @@
package mods.betterfoliage.render.particle
import mods.betterfoliage.util.Double3
import net.minecraft.client.MinecraftClient
import net.minecraft.client.particle.SpriteBillboardParticle
import net.minecraft.client.render.Camera
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.texture.Sprite
import net.minecraft.client.util.math.Vector3f
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
abstract class AbstractParticle(world: ClientWorld, x: Double, y: Double, z: Double) : SpriteBillboardParticle(world, x, y, z) {
companion object {
// @JvmStatic val sin = Array(64) { idx -> Math.sin(PI2 / 64.0 * idx) }
// @JvmStatic val cos = Array(64) { idx -> Math.cos(PI2 / 64.0 * idx) }
}
val billboardRot = Pair(Double3.zero, Double3.zero)
val currentPos = Double3.zero
val prevPos = Double3.zero
val velocity = Double3.zero
override fun tick() {
super.tick()
currentPos.setTo(x, y, z)
prevPos.setTo(prevPosX, prevPosY, prevPosZ)
velocity.setTo(velocityX, velocityY, velocityZ)
update()
x = currentPos.x; y = currentPos.y; z = currentPos.z;
velocityX = velocity.x; velocityY = velocity.y; velocityZ = velocity.z;
}
/** Update particle on world tick. */
abstract fun update()
/** True if the particle is renderable. */
abstract val isValid: Boolean
/** Add the particle to the effect renderer if it is valid. */
fun addIfValid() { if (isValid) MinecraftClient.getInstance().particleManager.addParticle(this) }
override fun buildGeometry(vertexConsumer: VertexConsumer, camera: Camera, tickDelta: Float) {
renderParticleQuad(vertexConsumer, camera, tickDelta)
}
/**
* Render a particle quad.
*
* @param[tessellator] the [Tessellator] instance to use
* @param[tickDelta] partial tick time
* @param[currentPos] render position
* @param[prevPos] previous tick position for interpolation
* @param[size] particle size
* @param[currentAngle] viewpoint-dependent particle rotation (64 steps)
* @param[sprite] particle texture
* @param[isMirrored] mirror particle texture along V-axis
* @param[alpha] aplha blending
*/
fun renderParticleQuad(vertexConsumer: VertexConsumer,
camera: Camera,
tickDelta: Float,
currentPos: Double3 = this.currentPos,
prevPos: Double3 = this.prevPos,
size: Double = scale.toDouble(),
currentAngle: Float = this.angle,
prevAngle: Float = this.prevAngle,
sprite: Sprite = this.sprite,
alpha: Float = this.colorAlpha) {
val center = Double3.lerp(tickDelta.toDouble(), prevPos, currentPos)
val angle = MathHelper.lerp(tickDelta, prevAngle, currentAngle)
val rotation = camera.rotation.copy().apply { hamiltonProduct(Vector3f.POSITIVE_Z.getRadialQuaternion(angle)) }
val lightmapCoord = getColorMultiplier(tickDelta)
val coords = arrayOf(
Double3(-1.0, -1.0, 0.0),
Double3(-1.0, 1.0, 0.0),
Double3(1.0, 1.0, 0.0),
Double3(1.0, -1.0, 0.0)
).map { it.rotate(rotation).mul(size).add(center).sub(camera.pos.x, camera.pos.y, camera.pos.z) }
fun renderVertex(vertex: Double3, u: Float, v: Float) = vertexConsumer
.vertex(vertex.x, vertex.y, vertex.z).texture(u, v)
.color(colorRed, colorGreen, colorBlue, alpha).light(lightmapCoord)
.next()
renderVertex(coords[0], sprite.maxU, sprite.maxV)
renderVertex(coords[1], sprite.maxU, sprite.minV)
renderVertex(coords[2], sprite.minU, sprite.minV)
renderVertex(coords[3], sprite.minU, sprite.maxV)
}
fun setColor(color: Int) {
colorBlue = (color and 255) / 256.0f
colorGreen = ((color shr 8) and 255) / 256.0f
colorRed = ((color shr 16) and 255) / 256.0f
}
}

View File

@@ -0,0 +1,118 @@
package mods.betterfoliage.render.particle
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.ClientWorldLoadCallback
import mods.betterfoliage.render.block.vanilla.LeafParticleKey
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.minmax
import mods.betterfoliage.util.randomB
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomF
import mods.betterfoliage.util.randomI
import net.fabricmc.fabric.api.event.world.WorldTickCallback
import net.minecraft.client.particle.ParticleTextureSheet
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
import java.util.Random
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
class FallingLeafParticle(
world: ClientWorld, pos: BlockPos, leaf: LeafParticleKey, blockColor: Int, random: Random
) : AbstractParticle(
world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5
) {
companion object {
@JvmStatic val biomeBrightnessMultiplier = 0.5f
}
var rotationSpeed = random.randomF(min = PI2 / 80.0, max = PI2 / 50.0)
val isMirrored = randomB()
var wasCollided = false
init {
angle = random.randomF(max = PI2)
prevAngle = angle - rotationSpeed
maxAge = MathHelper.floor(randomD(0.6, 1.0) * BetterFoliage.config.fallingLeaves.lifetime * 20.0)
velocityY = -BetterFoliage.config.fallingLeaves.speed
scale = BetterFoliage.config.fallingLeaves.size.toFloat() * 0.1f
val state = world.getBlockState(pos)
setColor(leaf.overrideColor?.asInt ?: blockColor)
sprite = LeafParticleRegistry[leaf.leafType][randomI(max = 1024)]
}
override val isValid: Boolean get() = (sprite != null)
override fun update() {
if (randomF() > 0.95f) rotationSpeed = -rotationSpeed
if (age > maxAge - 20) colorAlpha = 0.05f * (maxAge - age)
if (onGround || wasCollided) {
velocity.setTo(0.0, 0.0, 0.0)
prevAngle = angle
if (!wasCollided) {
age = age.coerceAtLeast(maxAge - 20)
wasCollided = true
}
} else {
val cosRotation = cos(angle).toDouble(); val sinRotation = sin(angle).toDouble()
velocity.setTo(cosRotation, 0.0, sinRotation).mul(BetterFoliage.config.fallingLeaves.perturb)
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(BetterFoliage.config.fallingLeaves.speed)
prevAngle = angle
angle += rotationSpeed
}
}
fun setParticleColor(overrideColor: Int?, blockColor: Int) {
val color = overrideColor ?: blockColor
setColor(color)
}
override fun getType() =
if (BetterFoliage.config.fallingLeaves.opacityHack) ParticleTextureSheet.PARTICLE_SHEET_OPAQUE
else ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT
}
object LeafWindTracker : WorldTickCallback, ClientWorldLoadCallback {
val random = Random()
val target = Double3.zero
val current = Double3.zero
var nextChange: Long = 0
fun changeWindTarget(world: World) {
nextChange = world.time + 120 + random.nextInt(80)
val direction = PI2 * random.nextDouble()
val speed = abs(random.nextGaussian()) * BetterFoliage.config.fallingLeaves.windStrength +
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * BetterFoliage.config.fallingLeaves.stormStrength)
target.setTo(cos(direction) * speed, 0.0, sin(direction) * speed)
}
override fun tick(world: World) {
if (world.isClient) {
// change target wind speed
if (world.time >= nextChange) changeWindTarget(world)
// change current wind speed
val changeRate = if (world.isRaining) 0.015 else 0.005
current.add(
(target.x - current.x).minmax(-changeRate, changeRate),
0.0,
(target.z - current.z).minmax(-changeRate, changeRate)
)
}
}
override fun loadWorld(world: ClientWorld) {
changeWindTarget(world)
}
}

View File

@@ -0,0 +1,104 @@
package mods.betterfoliage.render.particle
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.model.FixedSpriteSet
import mods.betterfoliage.model.SpriteSet
import mods.betterfoliage.resource.VeryEarlyReloadListener
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.get
import mods.betterfoliage.util.getLines
import mods.betterfoliage.util.resourceManager
import mods.betterfoliage.util.stripStart
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback
import net.minecraft.client.texture.MissingSprite
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.resource.ResourceManager
import net.minecraft.util.Identifier
import org.apache.logging.log4j.Level
import kotlin.collections.MutableList
import kotlin.collections.distinct
import kotlin.collections.filter
import kotlin.collections.firstOrNull
import kotlin.collections.forEach
import kotlin.collections.isNotEmpty
import kotlin.collections.joinToString
import kotlin.collections.listOf
import kotlin.collections.map
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.plus
import kotlin.collections.set
object LeafParticleRegistry : HasLogger(), ClientSpriteRegistryCallback, VeryEarlyReloadListener {
val typeMappings = TextureMatcher()
val allTypes get() = (typeMappings.mappings.map { it.type } + "default").distinct()
val spriteSets = mutableMapOf<String, SpriteSet>()
override fun getFabricId() = Identifier(BetterFoliage.MOD_ID, "leaf-particles")
override fun onReloadStarted(resourceManager: ResourceManager) {
typeMappings.loadMappings(Identifier(BetterFoliage.MOD_ID, "leaf_texture_mappings.cfg"))
detailLogger.log(Level.INFO, "Loaded leaf particle mappings, types = [${allTypes.joinToString(", ")}]")
}
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
spriteSets.clear()
allTypes.forEach { leafType ->
val validIds = (0 until 16).map { idx -> Identifier(BetterFoliage.MOD_ID, "particle/falling_leaf_${leafType}_$idx") }
.filter { resourceManager.containsResource(Atlas.PARTICLES.file(it)) }
validIds.forEach { registry.register(it) }
}
}
operator fun get(leafType: String): SpriteSet {
spriteSets[leafType]?.let { return it }
val sprites = (0 until 16)
.map { idx -> Identifier(BetterFoliage.MOD_ID, "particle/falling_leaf_${leafType}_$idx") }
.map { Atlas.PARTICLES[it] }
.filter { it !is MissingSprite }
detailLogger.log(Level.INFO, "Leaf particle type [$leafType], ${sprites.size} sprites in atlas")
if (sprites.isNotEmpty()) return FixedSpriteSet(sprites).apply { spriteSets[leafType] = this }
return if (leafType == "default")
FixedSpriteSet(listOf(Atlas.PARTICLES[MissingSprite.getMissingSpriteId()])).apply { spriteSets[leafType] = this }
else
get("default").apply { spriteSets[leafType] = this }
}
init {
ClientSpriteRegistryCallback.event(SpriteAtlasTexture.PARTICLE_ATLAS_TEXTURE).register(this)
}
}
class TextureMatcher {
data class Mapping(val domain: String?, val path: String, val type: String) {
fun matches(iconLocation: Identifier): Boolean {
return (domain == null || domain == iconLocation.namespace) &&
iconLocation.path.stripStart("blocks/").contains(path, ignoreCase = true)
}
}
val mappings: MutableList<Mapping> = mutableListOf()
fun getType(resource: Identifier) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull()
fun getType(iconName: String) = Identifier(iconName).let { getType(it) }
fun loadMappings(mappingLocation: Identifier) {
mappings.clear()
resourceManager[mappingLocation]?.getLines()?.let { lines ->
lines.filter { !it.startsWith("//") }.filter { it.isNotEmpty() }.forEach { line ->
val line2 = line.trim().split('=')
if (line2.size == 2) {
val mapping = line2[0].trim().split(':')
if (mapping.size == 1) mappings.add(Mapping(null, mapping[0].trim(), line2[1].trim()))
else if (mapping.size == 2) mappings.add(Mapping(mapping[0].trim(), mapping[1].trim(), line2[1].trim()))
}
}
}
}
}

View File

@@ -0,0 +1,88 @@
package mods.betterfoliage.render.particle
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.model.SpriteDelegate
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.forEachPairIndexed
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.minecraft.client.particle.ParticleTextureSheet
import net.minecraft.client.render.Camera
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import java.util.Deque
import java.util.LinkedList
import kotlin.math.cos
import kotlin.math.sin
class RisingSoulParticle(
world: ClientWorld, pos: BlockPos
) : AbstractParticle(
world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5
) {
val particleTrail: Deque<Double3> = LinkedList()
val initialPhase = randomD(max = PI2)
init {
velocityY = 0.1
gravityStrength = 0.0f
sprite = headIcons[randomI(max = 1024)]
maxAge = MathHelper.floor((0.6 + 0.4 * randomD()) * BetterFoliage.config.risingSoul.lifetime * 20.0)
}
override val isValid: Boolean get() = true
override fun update() {
val phase = initialPhase + (age.toDouble() * PI2 / 64.0)
val cosPhase = cos(phase);
val sinPhase = sin(phase)
velocity.setTo(BetterFoliage.config.risingSoul.perturb.let { Double3(cosPhase * it, 0.1, sinPhase * it) })
particleTrail.addFirst(currentPos.copy())
while (particleTrail.size > BetterFoliage.config.risingSoul.trailLength) particleTrail.removeLast()
if (!BetterFoliage.config.enabled) markDead()
}
override fun buildGeometry(vertexConsumer: VertexConsumer, camera: Camera, tickDelta: Float) {
var alpha = BetterFoliage.config.risingSoul.opacity.toFloat()
if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f
renderParticleQuad(
vertexConsumer, camera, tickDelta,
size = BetterFoliage.config.risingSoul.headSize * 0.25,
alpha = alpha
)
var scale = BetterFoliage.config.risingSoul.trailSize * 0.25
particleTrail.forEachPairIndexed { idx, current, previous ->
scale *= BetterFoliage.config.risingSoul.sizeDecay
alpha *= BetterFoliage.config.risingSoul.opacityDecay.toFloat()
if (idx % BetterFoliage.config.risingSoul.trailDensity == 0)
renderParticleQuad(
vertexConsumer, camera, tickDelta,
currentPos = current,
prevPos = previous,
size = scale,
alpha = alpha,
sprite = trackIcon
)
}
}
override fun getType() = ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT
companion object {
val headIcons by SpriteSetDelegate(Atlas.PARTICLES) { idx ->
Identifier(BetterFoliage.MOD_ID, "particle/rising_soul_$idx")
}
val trackIcon by SpriteDelegate(Atlas.PARTICLES) { Identifier(BetterFoliage.MOD_ID, "particle/soul_track") }
}
}

View File

@@ -0,0 +1,28 @@
package mods.betterfoliage.resource
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener
import net.minecraft.resource.ResourceManager
import net.minecraft.resource.ResourceReloadListener
import net.minecraft.util.profiler.Profiler
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
/**
* Catch resource reload extremely early, before any of the reloaders
* have started working.
*/
interface VeryEarlyReloadListener : ResourceReloadListener, IdentifiableResourceReloadListener {
override fun reload(
synchronizer: ResourceReloadListener.Synchronizer,
resourceManager: ResourceManager,
preparationsProfiler: Profiler,
reloadProfiler: Profiler,
backgroundExecutor: Executor,
gameExecutor: Executor
): CompletableFuture<Void> {
onReloadStarted(resourceManager)
return synchronizer.whenPrepared(null)
}
fun onReloadStarted(resourceManager: ResourceManager) {}
}

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