Compare commits

211 Commits

Author SHA1 Message Date
5aa33d7c70 Update README: Add fix details for texture rendering adjustments 2026-04-09 03:17:17 +02:00
64146a0f98 Ensure grass rendering respects air block checks and update project Java home in gradle properties. 2026-04-09 03:09:28 +02:00
octarine-noise
47c134049c Merge branch 'Snownee-1.12' into kotlin-1.12
# Conflicts:
#	src/main/resources/assets/betterfoliage/crop_default.cfg
#	src/main/resources/assets/betterfoliage/leaves_blocks_default.cfg
2021-04-26 11:53:34 +02:00
thedarkcolour
b1ad58c089 Fix an NPE with music discs
Fixes an NPE I found while playing around with music discs. Changes the "soundIn" parameter in the playRecord function in IBlockUpdateListener nullable which is safe because the parameter never gets used. This fixes the NPE because somehow taking a music disc out of the jukebox passes null for the playRecord function, which causes a crash when it shouldn't.
2021-04-26 11:34:02 +02:00
Jordan Rey
85e63b9161 "Plants" mod Crop compatibility 2021-04-26 11:33:21 +02:00
Jordan Rey
59ddaa0335 "Plants" mod Log compatibility 2021-04-26 11:33:21 +02:00
Jordan Rey
ae84741622 "Plants" mod Leaves compatibility 2021-04-26 11:33:21 +02: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
Snownee
369348f6aa Add Cuisine classes to defaults 2019-01-20 20:30:03 +08: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
167 changed files with 7814 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.idea/
*.iml
*.ipr
*.iws
run/
.gradle/
build/
classes/
temp/

View File

@@ -2,3 +2,6 @@ BetterFoliage
=============
Minecraft mod that alters the appearance of leaves & grass
fixed by @CatmanGames for StateMC<br>
(don't render certain textures when there is a block from another mod above)

62
build.gradle Normal file
View File

@@ -0,0 +1,62 @@
apply plugin: "net.minecraftforge.gradle.forge"
apply plugin: 'kotlin'
archivesBaseName = jarName
buildscript {
repositories {
mavenCentral()
maven {
name = "forge"
url = "http://files.minecraftforge.net/maven"
}
}
dependencies {
classpath "net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
repositories {
mavenCentral()
jcenter()
maven {
name = "shadowfacts"
url = "https://maven.shadowfacts.net/"
}
}
dependencies {
compile "net.shadowfacts:Forgelin:$forgelin_version"
}
minecraft {
version = mc_version + "-" + forge_version
mappings = mcp_mappings
runDir = 'run'
}
processResources {
from(sourceSets.main.resources) { exclude 'mcmod.info' }
from(sourceSets.main.resources) { include 'mcmod.info' expand 'version':version, 'mcversion':minecraft.version }
into "${buildDir}/classes/main"
}
def manifestCfg = {
attributes "FMLCorePlugin": "mods.betterfoliage.loader.BetterFoliageLoader"
attributes "FMLCorePluginContainsFMLMod": "mods.betterfoliage.BetterFoliageMod"
attributes "FMLAT": "BetterFoliage_at.cfg"
}
jar {
manifest manifestCfg
exclude "optifine"
}
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
manifest manifestCfg
from(sourceSets.main.kotlin)
from(sourceSets.main.resources) { exclude 'mcmod.info' }
from(sourceSets.main.resources) { include 'mcmod.info' expand 'version':version, 'mcversion':minecraft.version }
}

13
gradle.properties Normal file
View File

@@ -0,0 +1,13 @@
group = com.github.octarine-noise
jarName = BetterFoliage-MC1.12
version = 2.3.1
mc_version = 1.12.2
forge_version = 14.23.5.2847
mcp_mappings = stable_39
kotlin_version = 1.3.40
forgelin_version = 1.8.4
org.gradle.java.home=C:\\Users\\catma\\.jdks\\corretto-1.8.0_482

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-4.9-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

172
gradlew vendored Normal file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## 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=""
# 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, switch paths to Windows format before running java
if $cygwin ; 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=$((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"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,84 @@
@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=
@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

2
settings.gradle Normal file
View File

@@ -0,0 +1,2 @@
rootProject.name = 'BetterFoliage'

View File

@@ -0,0 +1,38 @@
package mods.betterfoliage.loader;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
import java.util.Map;
@IFMLLoadingPlugin.TransformerExclusions({
"mods.betterfoliage.loader",
"mods.octarinecore.metaprog",
"kotlin"
})
@IFMLLoadingPlugin.MCVersion("1.12.2")
@IFMLLoadingPlugin.SortingIndex(1400)
public class BetterFoliageLoader implements IFMLLoadingPlugin {
@Override
public String[] getASMTransformerClass() {
return new String[] { "mods.betterfoliage.loader.BetterFoliageTransformer" };
}
@Override
public String getModContainerClass() {
return null;
}
@Override
public String getSetupClass() {
return null;
}
@Override
public void injectData(Map<String, Object> data) {
}
@Override
public String getAccessTransformerClass() {
return null;
}
}

View File

@@ -0,0 +1,65 @@
package optifine;
import net.minecraft.launchwrapper.IClassTransformer;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class OptifineTransformerDevWrapper implements IClassTransformer {
public static String OPTIFINE_CLASSNAME = "optifine/OptiFineClassTransformer.class";
private ZipFile ofZip = null;
public OptifineTransformerDevWrapper() {
Stream<URL> loaderSources = Arrays.stream(((URLClassLoader) getClass().getClassLoader()).getURLs());
Optional<URL> optifineURL = loaderSources.filter(this::isOptifineJar).findFirst();
optifineURL.ifPresent(url -> ofZip = getZip(url));
}
private ZipFile getZip(URL url) {
try {
return new ZipFile(new File(url.toURI()));
} catch (Exception e) {
return null;
}
}
private boolean isOptifineJar(URL url) {
ZipFile zip = getZip(url);
return zip != null && zip.getEntry(OPTIFINE_CLASSNAME) != null;
}
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
if (ofZip == null) return basicClass;
ZipEntry replacement = ofZip.getEntry(name.replace(".", "/") + ".class");
if (replacement == null) return basicClass;
try {
return readAll(ofZip.getInputStream(replacement));
} catch (IOException e) {
return basicClass;
}
}
private byte[] readAll(InputStream is) throws IOException {
byte[] buf = new byte[4096];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len;
do {
len = is.read(buf, 0, 4096);
if (len > 0) bos.write(buf, 0, len);
} while (len > -1);
is.close();
return bos.toByteArray();
}
}

View File

@@ -0,0 +1,28 @@
package optifine;
import net.minecraft.launchwrapper.ITweaker;
import net.minecraft.launchwrapper.LaunchClassLoader;
import java.io.File;
import java.util.List;
public class OptifineTweakerDevWrapper implements ITweaker {
@Override
public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) {
}
@Override
public void injectIntoClassLoader(LaunchClassLoader classLoader) {
classLoader.registerTransformer("optifine.OptifineTransformerDevWrapper");
}
@Override
public String getLaunchTarget() {
return "net.minecraft.client.main.Main";
}
@Override
public String[] getLaunchArguments() {
return new String[0];
}
}

View File

@@ -0,0 +1,78 @@
package mods.betterfoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.isAfterPostInit
import net.minecraftforge.common.config.Configuration
import net.minecraftforge.fml.common.FMLCommonHandler
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent
import net.minecraftforge.fml.common.network.NetworkCheckHandler
import net.minecraftforge.fml.relauncher.Side
import org.apache.logging.log4j.Level.DEBUG
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Logger
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.*
@Mod(
modid = BetterFoliageMod.MOD_ID,
name = BetterFoliageMod.MOD_NAME,
acceptedMinecraftVersions = BetterFoliageMod.MC_VERSIONS,
guiFactory = BetterFoliageMod.GUI_FACTORY,
dependencies = "after:forgelin",
modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter",
clientSideOnly = true
)
object BetterFoliageMod {
const val MOD_ID = "betterfoliage"
const val MOD_NAME = "Better Foliage"
const val DOMAIN = "betterfoliage"
const val LEGACY_DOMAIN = "bettergrassandleaves"
const val MC_VERSIONS = "[1.12]"
const val GUI_FACTORY = "mods.betterfoliage.client.gui.ConfigGuiFactory"
lateinit var log: Logger
lateinit var logDetail: Logger
var config: Configuration? = null
init {
// inject pack into default list at construction time to get domains enumerated
// there's no 2nd resource reload pass anymore
Client.generatorPack.inject()
}
@Mod.EventHandler
fun preInit(event: FMLPreInitializationEvent) {
log = event.modLog
val logDetailFile = File(event.modConfigurationDirectory.parentFile, "logs/betterfoliage.log").apply {
parentFile.mkdirs()
if (!exists()) createNewFile()
}
logDetail = SimpleLogger(
"BetterFoliage",
DEBUG,
false, false, true, false,
"yyyy-MM-dd HH:mm:ss",
null,
PropertiesUtil(Properties()),
PrintStream(logDetailFile)
)
config = Configuration(event.suggestedConfigurationFile, null, true)
Config.attach(config!!)
Client.init()
Client.log(INFO, "BetterFoliage initialized")
isAfterPostInit = true
}
/** Mod is cosmetic only, always allow connection. */
@NetworkCheckHandler
fun checkVersion(mods: Map<String, String>, side: Side) = true
}

View File

@@ -0,0 +1,121 @@
package mods.betterfoliage.client
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.gui.ConfigGuiFactory
import mods.betterfoliage.client.integration.*
import mods.betterfoliage.client.render.*
import mods.betterfoliage.client.texture.*
import mods.octarinecore.client.KeyHandler
import mods.octarinecore.client.gui.textComponent
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
import mods.octarinecore.client.resource.CenteringTextureGenerator
import mods.octarinecore.client.resource.GeneratorPack
import net.minecraft.block.Block
import net.minecraft.block.state.IBlockState
import net.minecraft.client.Minecraft
import net.minecraft.util.math.BlockPos
import net.minecraft.util.text.TextComponentTranslation
import net.minecraft.util.text.TextFormatting
import net.minecraftforge.fml.client.FMLClientHandler
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
/**
* Object responsible for initializing (and holding a reference to) all the infrastructure of the mod
* except for the call hooks.
*
* This and all other singletons are annotated [SideOnly] to avoid someone accidentally partially
* initializing the mod on a server environment.
*/
@SideOnly(Side.CLIENT)
object Client {
lateinit var renderers: List<AbstractBlockRenderingHandler>
val suppressRenderErrors = mutableSetOf<IBlockState>()
// texture generation stuff
val genGrass = GrassGenerator("bf_gen_grass")
val genLeaves = LeafGenerator("bf_gen_leaves")
val genReeds = CenteringTextureGenerator("bf_gen_reeds", 1, 2)
val generatorPack = GeneratorPack(
"Better Foliage generated",
genGrass,
genLeaves,
genReeds
)
fun init() {
// init renderers
renderers = listOf(
RenderGrass(),
RenderMycelium(),
RenderLeaves(),
RenderCactus(),
RenderLilypad(),
RenderReeds(),
RenderAlgae(),
RenderCoral(),
RenderLog(),
RenderNetherrack(),
RenderConnectedGrass(),
RenderConnectedGrassLog()
)
// init other singletons
val singletons = listOf(
StandardCactusRegistry,
LeafParticleRegistry,
ChunkOverlayManager,
LeafWindTracker,
RisingSoulTextures
)
// init mod integrations
val integrations = listOf(
ShadersModIntegration,
OptifineCustomColors,
ForestryIntegration,
IC2RubberIntegration,
TechRebornRubberIntegration
)
// add basic block support instances as last
GrassRegistry.addRegistry(StandardGrassRegistry)
LeafRegistry.addRegistry(StandardLeafRegistry)
LogRegistry.addRegistry(StandardLogRegistry)
// init config hotkey
val configKey = KeyHandler(BetterFoliageMod.MOD_NAME, 66, "key.betterfoliage.gui") {
FMLClientHandler.instance().showGuiScreen(
ConfigGuiFactory.createBFConfigGui(Minecraft.getMinecraft().currentScreen)
)
}
}
fun log(level: Level, msg: String) {
BetterFoliageMod.log.log(level, "[BetterFoliage] $msg")
BetterFoliageMod.logDetail.log(level, msg)
}
fun logDetail(msg: String) {
BetterFoliageMod.logDetail.log(Level.DEBUG, msg)
}
fun logRenderError(state: IBlockState, location: BlockPos) {
if (state in suppressRenderErrors) return
suppressRenderErrors.add(state)
val blockName = Block.REGISTRY.getNameForObject(state.block).toString()
val blockLoc = "${location.x},${location.y},${location.z}"
Minecraft.getMinecraft().ingameGUI.chatGUI.printChatMessage(TextComponentTranslation(
"betterfoliage.rendererror",
textComponent(blockName, TextFormatting.GOLD),
textComponent(blockLoc, TextFormatting.GOLD)
))
logDetail("Error rendering block $state at $blockLoc")
}
}

View File

@@ -0,0 +1,101 @@
@file:JvmName("Hooks")
@file:SideOnly(Side.CLIENT)
package mods.betterfoliage.client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.*
import mods.betterfoliage.loader.Refs
import mods.octarinecore.client.render.blockContext
import mods.octarinecore.client.resource.LoadModelDataEvent
import mods.octarinecore.common.plus
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.Block
import net.minecraft.block.state.IBlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.init.Blocks
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.BlockRenderLayer.CUTOUT
import net.minecraft.util.BlockRenderLayer.CUTOUT_MIPPED
import net.minecraft.util.EnumFacing
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraft.world.World
import net.minecraftforge.client.model.ModelLoader
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
var isAfterPostInit = false
val isOptifinePresent = allAvailable(Refs.OptifineClassTransformer)
fun doesSideBlockRenderingOverride(original: Boolean, blockAccess: IBlockAccess, pos: BlockPos, side: EnumFacing): Boolean {
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(blockAccess.getBlockState(pos).block));
}
fun isOpaqueCubeOverride(original: Boolean, state: IBlockState): Boolean {
// caution: blocks are initialized and the method called during startup
if (!isAfterPostInit) return original
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block))
}
fun getAmbientOcclusionLightValueOverride(original: Float, state: IBlockState): Float {
if (Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block)) return Config.roundLogs.dimming;
return original;
}
fun getUseNeighborBrightnessOverride(original: Boolean, state: IBlockState): Boolean {
return original || (Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block));
}
fun onRandomDisplayTick(world: World, state: IBlockState, pos: BlockPos) {
if (Config.enabled &&
Config.risingSoul.enabled &&
state.block == Blocks.SOUL_SAND &&
world.isAirBlock(pos + up1) &&
Math.random() < Config.risingSoul.chance) {
EntityRisingSoulFX(world, pos).addIfValid()
}
if (Config.enabled &&
Config.fallingLeaves.enabled &&
Config.blocks.leavesClasses.matchesClass(state.block) &&
world.isAirBlock(pos + down1) &&
Math.random() < Config.fallingLeaves.chance) {
EntityFallingLeavesFX(world, pos).addIfValid()
}
}
fun onAfterLoadModelDefinitions(loader: ModelLoader) {
MinecraftForge.EVENT_BUS.post(LoadModelDataEvent(loader))
}
fun renderWorldBlock(dispatcher: BlockRendererDispatcher,
state: IBlockState,
pos: BlockPos,
blockAccess: IBlockAccess,
worldRenderer: BufferBuilder,
layer: BlockRenderLayer
): Boolean {
val doBaseRender = state.canRenderInLayer(layer) || (layer == targetCutoutLayer && state.canRenderInLayer(otherCutoutLayer))
blockContext.let { ctx ->
ctx.set(blockAccess, pos)
Client.renderers.forEach { renderer ->
if (renderer.isEligible(ctx)) {
// render on the block's default layer
// also render on the cutout layer if the renderer requires it
if (doBaseRender || (renderer.addToCutout && layer == targetCutoutLayer)) {
return renderer.render(ctx, dispatcher, worldRenderer, layer)
}
}
}
}
return if (doBaseRender) dispatcher.renderBlock(state, pos, blockAccess, worldRenderer) else false
}
fun canRenderBlockInLayer(block: Block, state: IBlockState, layer: BlockRenderLayer) = block.canRenderInLayer(state, layer) || layer == targetCutoutLayer
val targetCutoutLayer: BlockRenderLayer get() = if (Minecraft.getMinecraft().gameSettings.mipmapLevels > 0) CUTOUT_MIPPED else CUTOUT
val otherCutoutLayer: BlockRenderLayer get() = if (Minecraft.getMinecraft().gameSettings.mipmapLevels > 0) CUTOUT else CUTOUT_MIPPED

View File

@@ -0,0 +1,124 @@
package mods.betterfoliage.client.chunk
import mods.betterfoliage.client.Client
import net.minecraft.block.state.IBlockState
import net.minecraft.client.multiplayer.WorldClient
import net.minecraft.entity.Entity
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.util.SoundCategory
import net.minecraft.util.SoundEvent
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.ChunkPos
import net.minecraft.world.IBlockAccess
import net.minecraft.world.IWorldEventListener
import net.minecraft.world.World
import net.minecraft.world.chunk.EmptyChunk
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.world.ChunkEvent
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import org.apache.logging.log4j.Level
/**
* Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position
*/
interface ChunkOverlayLayer<T> {
abstract fun calculate(world: IBlockAccess, pos: BlockPos): T
abstract fun onBlockUpdate(world: IBlockAccess, pos: BlockPos)
}
/**
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
*/
object ChunkOverlayManager : IBlockUpdateListener {
init {
Client.log(Level.INFO, "Initializing client overlay manager")
MinecraftForge.EVENT_BUS.register(this)
}
val chunkData = mutableMapOf<ChunkPos, ChunkOverlayData>()
val layers = mutableListOf<ChunkOverlayLayer<*>>()
/**
* Get the overlay data for a given layer and position
*
* @param layer Overlay layer to query
* @param world World to use if calculation of overlay value is necessary
* @param pos Block position
*/
fun <T> get(layer: ChunkOverlayLayer<T>, world: IBlockAccess, pos: BlockPos): T? {
val data = chunkData[ChunkPos(pos)] ?: return null
data.get(layer, pos).let { value ->
if (value !== ChunkOverlayData.UNCALCULATED) return value
val newValue = layer.calculate(world, pos)
data.set(layer, 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(layer: ChunkOverlayLayer<T>, pos: BlockPos) {
chunkData[ChunkPos(pos)]?.clear(layer, pos)
}
override fun notifyBlockUpdate(world: World, pos: BlockPos, oldState: IBlockState, newState: IBlockState, flags: Int) {
if (chunkData.containsKey(ChunkPos(pos))) layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
}
@SubscribeEvent
fun handleLoadWorld(event: WorldEvent.Load) {
if (event.world is WorldClient) {
event.world.addEventListener(this)
}
}
@SubscribeEvent
fun handleLoadChunk(event: ChunkEvent.Load) {
if (event.world is WorldClient && event.chunk !is EmptyChunk) {
chunkData[event.chunk.pos] = ChunkOverlayData(layers)
}
}
@SubscribeEvent
fun handleUnloadChunk(event: ChunkEvent.Unload) {
if (event.world is WorldClient) {
chunkData.remove(event.chunk.pos)
}
}
}
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
}
}
/**
* IWorldEventListener helper subclass
* No-op for everything except notifyBlockUpdate()
*/
interface IBlockUpdateListener : IWorldEventListener {
override fun playSoundToAllNearExcept(player: EntityPlayer?, soundIn: SoundEvent, category: SoundCategory, x: Double, y: Double, z: Double, volume: Float, pitch: Float) {}
override fun onEntityAdded(entityIn: Entity) {}
override fun broadcastSound(soundID: Int, pos: BlockPos, data: Int) {}
override fun playEvent(player: EntityPlayer?, type: Int, blockPosIn: BlockPos, data: Int) {}
override fun onEntityRemoved(entityIn: Entity) {}
override fun notifyLightSet(pos: BlockPos) {}
override fun spawnParticle(particleID: Int, ignoreRange: Boolean, xCoord: Double, yCoord: Double, zCoord: Double, xSpeed: Double, ySpeed: Double, zSpeed: Double, vararg parameters: Int) {}
override fun spawnParticle(id: Int, ignoreRange: Boolean, minimiseParticleLevel: Boolean, x: Double, y: Double, z: Double, xSpeed: Double, ySpeed: Double, zSpeed: Double, vararg parameters: Int) {}
override fun playRecord(soundIn: SoundEvent?, pos: BlockPos) {}
override fun sendBlockBreakProgress(breakerId: Int, pos: BlockPos, progress: Int) {}
override fun markBlockRangeForRenderUpdate(x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int) {}
}

View File

@@ -0,0 +1,207 @@
package mods.betterfoliage.client.config
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.gui.BiomeListConfigEntry
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.common.config.*
import net.minecraft.client.Minecraft
import net.minecraft.world.biome.Biome
import net.minecraftforge.fml.client.event.ConfigChangedEvent
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.lwjgl.opengl.GL11
// BetterFoliage-specific property delegates
private val OBSOLETE = ObsoleteConfigProperty()
private fun featureEnable() = boolean(true).lang("enabled")
fun biomeList(defaults: (Biome) -> Boolean) = intList {
Biome.REGISTRY
.filter { it != null && defaults(it) }
.map { Biome.REGISTRY.getIDForObject(it) }
.toTypedArray()
}.apply { guiClass = BiomeListConfigEntry::class.java }
// Biome filter methods
private fun Biome.filterTemp(min: Float?, max: Float?) = (min == null || min <= defaultTemperature) && (max == null || max >= defaultTemperature)
private fun Biome.filterRain(min: Float?, max: Float?) = (min == null || min <= rainfall) && (max == null || max >= rainfall)
private fun Biome.filterClass(vararg name: String) = name.any { it in this.javaClass.name.toLowerCase() }
// Config singleton
@SideOnly(Side.CLIENT)
object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAIN) {
var enabled by boolean(true)
var nVidia by boolean(GL11.glGetString(GL11.GL_VENDOR).toLowerCase().contains("nvidia"))
object shaders {
val leavesId by long(min = 1, max = 65535, default = ShadersModIntegration.leavesDefaultBlockId.toInt())
val grassId by long(min = 1, max = 65535, default = ShadersModIntegration.grassDefaultBlockId.toInt())
}
object blocks {
val leavesClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "leaves_blocks_default.cfg")
val leavesModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "leaves_models_default.cfg", 1)
val grassClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "grass_blocks_default.cfg")
val grassModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "grass_models_default.cfg", 1)
val mycelium = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "mycelium_blocks_default.cfg")
val dirt = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "dirt_default.cfg")
val crops = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "crop_default.cfg")
val logClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "log_blocks_default.cfg")
val logModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "log_models_default.cfg", 3)
val sand = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "sand_default.cfg")
val lilypad = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "lilypad_default.cfg")
val cactus = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "cactus_default.cfg")
val netherrack = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "netherrack_blocks_default.cfg")
val leavesWhitelist = OBSOLETE
val leavesBlacklist = OBSOLETE
val grassWhitelist = OBSOLETE
val grassBlacklist = OBSOLETE
val logsWhitelist = OBSOLETE
val logsBlacklist = OBSOLETE
}
object leaves {
val enabled by featureEnable()
val snowEnabled by boolean(true)
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val vOffset by double(max=0.4, default=0.1).lang("vOffset")
val size by double(min=0.75, max=2.5, default=1.4).lang("size")
val dense by boolean(false)
val hideInternal by boolean(true)
}
object shortGrass {
val grassEnabled by boolean(true)
val myceliumEnabled by boolean(true)
val snowEnabled by boolean(true)
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val heightMin by double(min=0.1, max=2.5, default=0.6).lang("heightMin")
val heightMax by double(min=0.1, max=2.5, default=0.8).lang("heightMax")
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
val population by int(max=64, default=64).lang("population")
val useGenerated by boolean(false)
val shaderWind by boolean(true).lang("shaderWind")
val saturationThreshold by double(default=0.1)
}
// object hangingGrass {
// var enabled by featureEnable()
// var distance by distanceLimit()
// var size by double(min=0.25, max=1.5, default=0.75).lang("size")
// var separation by double(max=0.5, default=0.25)
// }
object connectedGrass {
val enabled by boolean(true)
val snowEnabled by boolean(false)
}
object roundLogs {
val enabled by featureEnable()
val radiusSmall by double(max=0.5, default=0.25)
val radiusLarge by double(max=0.5, default=0.44)
val dimming by float(default = 0.7)
val connectSolids by boolean(false)
val lenientConnect by boolean(true)
val connectPerpendicular by boolean(true)
val connectGrass by boolean(true)
val defaultY by boolean(false)
val zProtection by double(min = 0.9, default = 0.99)
}
object cactus {
val enabled by featureEnable()
val size by double(min=0.5, max=1.5, default=0.8).lang("size")
val sizeVariation by double(max=0.5, default=0.1)
val hOffset by double(max=0.5, default=0.1).lang("hOffset")
}
object lilypad {
val enabled by featureEnable()
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
val flowerChance by int(max=64, default=16, min=0)
}
object reed {
val enabled by featureEnable()
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val heightMin by double(min=1.5, max=3.5, default=1.7).lang("heightMin")
val heightMax by double(min=1.5, max=3.5, default=2.2).lang("heightMax")
val population by int(max=64, default=32).lang("population")
val biomes by biomeList { it.filterTemp(0.4f, null) && it.filterRain(0.4f, null) }
val shaderWind by boolean(true).lang("shaderWind")
}
object algae {
val enabled by featureEnable()
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
val heightMin by double(min=0.1, max=1.5, default=0.5).lang("heightMin")
val heightMax by double(min=0.1, max=1.5, default=1.0).lang("heightMax")
val population by int(max=64, default=48).lang("population")
val biomes by biomeList { it.filterClass("river", "ocean") }
val shaderWind by boolean(true).lang("shaderWind")
}
object coral {
val enabled by featureEnable()
val shallowWater by boolean(false)
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val vOffset by double(max=0.4, default=0.1).lang("vOffset")
val size by double(min=0.5, max=1.5, default=0.7).lang("size")
val crustSize by double(min=0.5, max=1.5, default=1.4)
val chance by int(max=64, default=32)
val population by int(max=64, default=48).lang("population")
val biomes by biomeList { it.filterClass("river", "ocean", "beach") }
}
object netherrack {
val enabled by featureEnable()
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val heightMin by double(min=0.1, max=1.5, default=0.6).lang("heightMin")
val heightMax by double(min=0.1, max=1.5, default=0.8).lang("heightMax")
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
}
object fallingLeaves {
val enabled by featureEnable()
val speed by double(min=0.01, max=0.15, default=0.05)
val windStrength by double(min=0.1, max=2.0, default=0.5)
val stormStrength by double(min=0.1, max=2.0, default=0.8)
val size by double(min=0.25, max=1.5, default=0.75).lang("size")
val chance by double(min=0.001, max=1.0, default=0.05)
val perturb by double(min=0.01, max=1.0, default=0.25)
val lifetime by double(min=1.0, max=15.0, default=5.0)
val opacityHack by boolean(true)
}
object risingSoul {
val enabled by featureEnable()
val chance by double(min=0.001, max=1.0, default=0.02)
val perturb by double(min=0.01, max=0.25, default=0.05)
val headSize by double(min=0.25, max=1.5, default=1.0)
val trailSize by double(min=0.25, max=1.5, default=0.75)
val opacity by float(min=0.05, max=1.0, default=0.5)
val sizeDecay by double(min=0.5, max=1.0, default=0.97)
val opacityDecay by float(min=0.5, max=1.0, default=0.97)
val lifetime by double(min=1.0, max=15.0, default=4.0)
val trailLength by int(min=2, max=128, default=48)
val trailDensity by int(min=1, max=16, default=3)
}
val forceReloadOptions = listOf(
blocks.leavesClasses,
blocks.leavesModels,
blocks.grassClasses,
blocks.grassModels,
shortGrass["saturationThreshold"]!!
)
override fun onChange(event: ConfigChangedEvent.PostConfigChangedEvent) {
if (hasChanged(forceReloadOptions))
Minecraft.getMinecraft().refreshResources()
else
Minecraft.getMinecraft().renderGlobal.loadRenderers()
}
}

View File

@@ -0,0 +1,19 @@
package mods.betterfoliage.client.gui
import mods.octarinecore.client.gui.IdListConfigEntry
import net.minecraft.world.biome.Biome
import net.minecraftforge.fml.client.config.GuiConfig
import net.minecraftforge.fml.client.config.GuiConfigEntries
import net.minecraftforge.fml.client.config.IConfigElement
/** Toggleable list of all defined biomes. */
class BiomeListConfigEntry(
owningScreen: GuiConfig,
owningEntryList: GuiConfigEntries,
configElement: IConfigElement)
: IdListConfigEntry<Biome>(owningScreen, owningEntryList, configElement) {
override val baseSet: List<Biome> get() = Biome.REGISTRY.filterNotNull()
override val Biome.itemId: Int get() = Biome.REGISTRY.getIDForObject(this)
override val Biome.itemName: String get() = this.biomeName
}

View File

@@ -0,0 +1,29 @@
package mods.betterfoliage.client.gui
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiScreen
import net.minecraftforge.fml.client.IModGuiFactory
import net.minecraftforge.fml.client.config.GuiConfig
class ConfigGuiFactory : IModGuiFactory {
override fun initialize(minecraftInstance: Minecraft?) { }
override fun hasConfigGui() = true
override fun runtimeGuiCategories() = hashSetOf<IModGuiFactory.RuntimeOptionCategoryElement>()
override fun createConfigGui(parentScreen: GuiScreen?) = createBFConfigGui(parentScreen)
companion object {
@JvmStatic
fun createBFConfigGui(parentScreen: GuiScreen?) = GuiConfig(
parentScreen,
Config.rootGuiElements,
BetterFoliageMod.MOD_ID,
null,
false,
false,
BetterFoliageMod.MOD_NAME
)
}
}

View File

@@ -0,0 +1,154 @@
package mods.betterfoliage.client.integration
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.LogRegistry
import mods.betterfoliage.client.render.StandardLogRegistry
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.betterfoliage.client.texture.LeafInfo
import mods.betterfoliage.client.texture.LeafRegistry
import mods.betterfoliage.client.texture.StandardLeafKey
import mods.betterfoliage.loader.Refs
import mods.octarinecore.client.resource.ModelRenderKey
import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.client.resource.ModelRenderRegistryBase
import mods.octarinecore.getTileEntitySafe
import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.FieldRef
import mods.octarinecore.metaprog.MethodRef
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.state.IBlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.block.model.ModelResourceLocation
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.client.model.IModel
import net.minecraftforge.fml.common.Loader
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
import kotlin.collections.Map
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.emptyMap
import kotlin.collections.find
import kotlin.collections.forEach
import kotlin.collections.get
import kotlin.collections.listOf
import kotlin.collections.mapValues
import kotlin.collections.mutableMapOf
import kotlin.collections.set
@SideOnly(Side.CLIENT)
object ForestryIntegration {
val TextureLeaves = ClassRef("forestry.arboriculture.models.TextureLeaves")
val TeLleafTextures = FieldRef(TextureLeaves, "leafTextures", Refs.Map)
val TeLplain = FieldRef(TextureLeaves, "plain", Refs.ResourceLocation)
val TeLfancy = FieldRef(TextureLeaves, "fancy", Refs.ResourceLocation)
val TeLpollplain = FieldRef(TextureLeaves, "pollinatedPlain", Refs.ResourceLocation)
val TeLpollfancy = FieldRef(TextureLeaves, "pollinatedFancy", Refs.ResourceLocation)
val TileLeaves = ClassRef("forestry.arboriculture.tiles.TileLeaves")
val TiLgetLeaveSprite = MethodRef(TileLeaves, "getLeaveSprite", Refs.ResourceLocation, ClassRef.boolean)
val PropertyWoodType = ClassRef("forestry.arboriculture.blocks.PropertyWoodType")
val IWoodType = ClassRef("forestry.api.arboriculture.IWoodType")
val barkTex = MethodRef(IWoodType, "getBarkTexture", Refs.String)
val heartTex = MethodRef(IWoodType, "getHeartTexture", Refs.String)
val PropertyTreeType = ClassRef("forestry.arboriculture.blocks.PropertyTreeType")
val TreeDefinition = ClassRef("forestry.arboriculture.genetics.TreeDefinition")
val IAlleleTreeSpecies = ClassRef("forestry.api.arboriculture.IAlleleTreeSpecies")
val ILeafSpriteProvider = ClassRef("forestry.api.arboriculture.ILeafSpriteProvider")
val TdSpecies = FieldRef(TreeDefinition, "species", IAlleleTreeSpecies)
val getLeafSpriteProvider = MethodRef(IAlleleTreeSpecies, "getLeafSpriteProvider", ILeafSpriteProvider)
val getSprite = MethodRef(ILeafSpriteProvider, "getSprite", Refs.ResourceLocation, ClassRef.boolean, ClassRef.boolean)
init {
if (Loader.isModLoaded("forestry") && allAvailable(TiLgetLeaveSprite, getLeafSpriteProvider, getSprite)) {
Client.log(Level.INFO, "Forestry support initialized")
LeafRegistry.addRegistry(ForestryLeafRegistry)
LogRegistry.addRegistry(ForestryLogRegistry)
}
}
}
object ForestryLeafRegistry : ModelRenderRegistry<LeafInfo> {
val logger = BetterFoliageMod.logDetail
val textureToKey = mutableMapOf<ResourceLocation, ModelRenderKey<LeafInfo>>()
var textureToValue = emptyMap<ResourceLocation, LeafInfo>()
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos): LeafInfo? {
// check variant property (used in decorative leaves)
state.properties.entries.find {
ForestryIntegration.PropertyTreeType.isInstance(it.key) && ForestryIntegration.TreeDefinition.isInstance(it.value)
} ?.let {
val species = ForestryIntegration.TdSpecies.get(it.value)
val spriteProvider = ForestryIntegration.getLeafSpriteProvider.invoke(species!!)
val textureLoc = ForestryIntegration.getSprite.invoke(spriteProvider!!, false, Minecraft.isFancyGraphicsEnabled())
return textureToValue[textureLoc]
}
// extract leaf texture information from TileEntity
val tile = world.getTileEntitySafe(pos) ?: return null
if (!ForestryIntegration.TileLeaves.isInstance(tile)) return null
val textureLoc = ForestryIntegration.TiLgetLeaveSprite.invoke(tile, Minecraft.isFancyGraphicsEnabled()) ?: return null
return textureToValue[textureLoc]
}
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
textureToValue = emptyMap()
val allLeaves = ForestryIntegration.TeLleafTextures.getStatic() as Map<*, *>
allLeaves.entries.forEach {
logger.log(Level.DEBUG, "ForestryLeavesSupport: base leaf type ${it.key.toString()}")
listOf(
ForestryIntegration.TeLplain.get(it.value) as ResourceLocation,
ForestryIntegration.TeLfancy.get(it.value) as ResourceLocation,
ForestryIntegration.TeLpollplain.get(it.value) as ResourceLocation,
ForestryIntegration.TeLpollfancy.get(it.value) as ResourceLocation
).forEach { textureLocation ->
val key = StandardLeafKey(logger, textureLocation.toString()).apply { onPreStitch(event.map) }
textureToKey[textureLocation] = key
}
}
}
@SubscribeEvent(priority = EventPriority.LOW)
fun handlePostStitch(event: TextureStitchEvent.Post) {
textureToValue = textureToKey.mapValues { (_, key) -> key.resolveSprites(event.map) }
textureToKey.clear()
}
}
object ForestryLogRegistry : ModelRenderRegistryBase<ColumnTextureInfo>() {
override val logger = BetterFoliageMod.logDetail
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
// respect class list to avoid triggering on fences, stairs, etc.
if (!Config.blocks.logClasses.matchesClass(state.block)) return null
// find wood type property
val woodType = state.properties.entries.find {
ForestryIntegration.PropertyWoodType.isInstance(it.key) && ForestryIntegration.IWoodType.isInstance(it.value)
} ?: return null
logger.log(Level.DEBUG, "ForestryLogRegistry: block state $state")
logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}")
// get texture names for wood type
val bark = ForestryIntegration.barkTex.invoke(woodType.value) as String?
val heart = ForestryIntegration.heartTex.invoke(woodType.value) as String?
logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]")
if (bark != null && heart != null) return SimpleColumnInfo.Key(logger, StandardLogRegistry.getAxis(state), listOf(heart, heart, bark))
return null
}
}

View File

@@ -0,0 +1,60 @@
package mods.betterfoliage.client.integration
import mods.betterfoliage.client.Client
import mods.betterfoliage.loader.Refs
import mods.octarinecore.ThreadLocalDelegate
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.common.Int3
import mods.octarinecore.metaprog.allAvailable
import mods.octarinecore.metaprog.reflectField
import net.minecraft.block.state.IBlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.block.model.BakedQuad
import net.minecraft.client.renderer.vertex.DefaultVertexFormats
import net.minecraft.util.EnumFacing
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
/**
* Integration for OptiFine custom block colors.
*/
@Suppress("UNCHECKED_CAST")
@SideOnly(Side.CLIENT)
object OptifineCustomColors {
val isColorAvailable = allAvailable(
Refs.CustomColors, Refs.getColorMultiplier
)
init {
Client.log(Level.INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
}
val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() }
val fakeQuad = BakedQuad(IntArray(0), 1, EnumFacing.UP, null, true, DefaultVertexFormats.BLOCK)
fun getBlockColor(ctx: BlockContext): Int {
val ofColor = if (isColorAvailable && Minecraft.getMinecraft().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
renderEnv.reset(ctx.blockState(Int3.zero), ctx.pos)
Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.blockState(Int3.zero), ctx.world!!, ctx.pos, renderEnv.wrapped) as? Int
} else null
return if (ofColor == null || ofColor == -1) ctx.blockData(Int3.zero).color else ofColor
}
}
@SideOnly(Side.CLIENT)
class OptifineRenderEnv {
val wrapped: Any = Refs.RenderEnv.element!!.getDeclaredConstructor(
Refs.IBlockState.element, Refs.BlockPos.element
).let {
it.isAccessible = true
it.newInstance(null, null)
}
fun reset(state: IBlockState, pos: BlockPos) {
Refs.RenderEnv_reset.invoke(wrapped, state, pos)
}
}

View File

@@ -0,0 +1,147 @@
package mods.betterfoliage.client.integration
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.render.LogRegistry
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.octarinecore.client.render.Quad
import mods.octarinecore.client.render.QuadIconResolver
import mods.octarinecore.client.render.ShadingContext
import mods.octarinecore.client.render.blockContext
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.rotate
import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.block.model.ModelResourceLocation
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.IModel
import net.minecraftforge.fml.common.Loader
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
@SideOnly(Side.CLIENT)
object IC2RubberIntegration {
val BlockRubWood = ClassRef("ic2.core.block.BlockRubWood")
init {
if (Loader.isModLoaded("ic2") && allAvailable(BlockRubWood)) {
Client.log(Level.INFO, "IC2 rubber support initialized")
LogRegistry.addRegistry(IC2LogSupport)
}
}
}
@SideOnly(Side.CLIENT)
object TechRebornRubberIntegration {
val BlockRubberLog = ClassRef("techreborn.blocks.BlockRubberLog")
init {
if (Loader.isModLoaded("techreborn") && allAvailable(BlockRubberLog)) {
Client.log(Level.INFO, "TechReborn rubber support initialized")
LogRegistry.addRegistry(TechRebornLogSupport)
}
}
}
class RubberLogInfo(
axis: EnumFacing.Axis?,
val spotDir: EnumFacing,
topTexture: TextureAtlasSprite,
bottomTexture: TextureAtlasSprite,
val spotTexture: TextureAtlasSprite,
sideTextures: List<TextureAtlasSprite>
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
override val side: QuadIconResolver = { ctx: ShadingContext, idx: Int, quad: Quad ->
val worldFace = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation)
if (worldFace == spotDir) spotTexture else {
val sideIdx = if (this.sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0
this.sideTextures[sideIdx]
}
}
class Key(override val logger: Logger, val axis: EnumFacing.Axis?, val spotDir: EnumFacing, val textures: List<String>): ModelRenderKey<ColumnTextureInfo> {
override fun resolveSprites(atlas: TextureMap) = RubberLogInfo(
axis,
spotDir,
atlas[textures[0]] ?: atlas.missingSprite,
atlas[textures[1]] ?: atlas.missingSprite,
atlas[textures[2]] ?: atlas.missingSprite,
textures.drop(3).map { atlas[it] ?: atlas.missingSprite }
)
}
}
object IC2LogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
override val logger = BetterFoliageMod.logDetail
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock, and "state" blockstate property
if (!IC2RubberIntegration.BlockRubWood.isInstance(state.block)) return null
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
val type = state.properties.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
// logs with no rubber spot
if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) {
val axis = when(type) {
"plain_y" -> EnumFacing.Axis.Y
"plain_x" -> EnumFacing.Axis.X
"plain_z" -> EnumFacing.Axis.Z
else -> null
}
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
if (textureNames.any { it == "missingno" }) return null
logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}")
logger.log(Level.DEBUG, "IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[2]}")
return SimpleColumnInfo.Key(logger, axis, textureNames)
}
// logs with rubber spot
val spotDir = when(type) {
"dry_north", "wet_north" -> EnumFacing.NORTH
"dry_south", "wet_south" -> EnumFacing.SOUTH
"dry_west", "wet_west" -> EnumFacing.WEST
"dry_east", "wet_east" -> EnumFacing.EAST
else -> null
}
val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTextureName(it) }
if (textureNames.any { it == "missingno" }) return null
logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}")
logger.log(Level.DEBUG, "IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
return if (spotDir != null) RubberLogInfo.Key(logger, EnumFacing.Axis.Y, spotDir, textureNames) else SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
}
}
object TechRebornLogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
override val logger = BetterFoliageMod.logDetail
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(state.block)) return null
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
val hasSap = state.properties.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
val sapSide = state.properties.entries.find { it.key.getName() == "sapside" }?.value as? EnumFacing ?: return null
logger.log(Level.DEBUG, "$logName: block state $state")
if (hasSap) {
val textureNames = listOf("end", "end", "sapside", "side").map { blockLoc.first.resolveTextureName(it) }
logger.log(Level.DEBUG, "$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) return RubberLogInfo.Key(logger, EnumFacing.Axis.Y, sapSide, textureNames)
} else {
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
logger.log(Level.DEBUG, "$logName: end=${textureNames[0]}, side=${textureNames[2]}")
if (textureNames.all { it != "missingno" })return SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
}
return null
}
}

View File

@@ -0,0 +1,81 @@
package mods.betterfoliage.client.integration
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.loader.Refs
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.Block
import net.minecraft.block.BlockTallGrass
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.init.Blocks
import net.minecraft.util.EnumBlockRenderType
import net.minecraft.util.EnumBlockRenderType.MODEL
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
/**
* Integration for ShadersMod.
*/
@SideOnly(Side.CLIENT)
object ShadersModIntegration {
@JvmStatic val isAvailable = allAvailable(Refs.sVertexBuilder, Refs.pushEntity_state, Refs.pushEntity_num, Refs.popEntity)
val grassDefaultBlockId = blockIdFor(Blocks.TALLGRASS.defaultState.withProperty(BlockTallGrass.TYPE, BlockTallGrass.EnumType.GRASS))
val leavesDefaultBlockId = blockIdFor(Blocks.LEAVES.defaultState)
fun blockIdFor(blockState: IBlockState) = Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535
// fun entityDataFor(blockState: IBlockState) =
// (Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535) //or
// ((blockState.renderType.ordinal.toLong() and 65535) shl 16) or
// (blockState.block.getMetaFromState(blockState).toLong() shl 32)
fun logEntityData(name: String, blockState: IBlockState) {
val blockId = Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535
val meta = blockState.renderType.ordinal.toLong() and 65535
val renderType = blockState.renderType.ordinal.toLong() and 65535
Client.log(INFO, "ShadersMod integration for $name")
Client.log(INFO, " blockState=$blockState")
Client.log(INFO, " blockId=$blockId, meta=$meta, type=$renderType")
}
/**
* Called from transformed ShadersMod code.
* @see mods.betterfoliage.loader.BetterFoliageTransformer
*/
@JvmStatic fun getBlockIdOverride(original: Long, blockState: IBlockState): Long {
if (Config.blocks.leavesClasses.matchesClass(blockState.block)) return Config.shaders.leavesId
if (Config.blocks.crops.matchesClass(blockState.block)) return Config.shaders.grassId
return original
}
init {
Client.log(INFO, "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(blockId: Long, renderType: EnumBlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) {
val blockData = blockId or (renderType.ordinal shl 16).toLong()
if ((isAvailable && enabled)) {
val vertexBuilder = Refs.sVertexBuilder.get(renderer)!!
Refs.pushEntity_num.invoke(vertexBuilder, blockId)
func()
Refs.popEntity.invoke(vertexBuilder)
} else {
func()
}
}
/** Quads rendered inside this block will use the given block entity data in shader programs. */
inline fun renderAs(state: IBlockState, renderType: EnumBlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
renderAs(blockIdFor(state), renderType, renderer, enabled, func)
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
inline fun grass(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
renderAs(Config.shaders.grassId, MODEL, renderer, enabled, func)
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
inline fun leaves(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
renderAs(Config.shaders.leavesId, MODEL, renderer, enabled, func)
}

View File

@@ -0,0 +1,136 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.texture.LeafParticleRegistry
import mods.betterfoliage.client.texture.LeafRegistry
import mods.octarinecore.PI2
import mods.octarinecore.client.render.AbstractEntityFX
import mods.octarinecore.client.render.HSB
import mods.octarinecore.common.Double3
import mods.octarinecore.minmax
import mods.octarinecore.random
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.common.gameevent.TickEvent
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.lwjgl.opengl.GL11
import java.lang.Math.*
import java.util.*
@SideOnly(Side.CLIENT)
class EntityFallingLeavesFX(world: World, pos: BlockPos) :
AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5) {
companion object {
@JvmStatic val biomeBrightnessMultiplier = 0.5f
}
var particleRot = rand.nextInt(64)
var rotPositive = true
val isMirrored = (rand.nextInt() and 1) == 1
var wasCollided = false
init {
particleMaxAge = MathHelper.floor(random(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0)
motionY = -Config.fallingLeaves.speed
particleScale = Config.fallingLeaves.size.toFloat() * 0.1f
val state = world.getBlockState(pos)
val blockColor = Minecraft.getMinecraft().blockColors.colorMultiplier(state, world, pos, 0)
val leafInfo = LeafRegistry[state, world, pos]
if (leafInfo != null) {
particleTexture = leafInfo.particleTextures[rand.nextInt(1024)]
calculateParticleColor(leafInfo.averageColor, blockColor)
} else {
particleTexture = LeafParticleRegistry["default"][rand.nextInt(1024)]
setColor(blockColor)
}
}
override val isValid: Boolean get() = (particleTexture != null)
override fun update() {
if (rand.nextFloat() > 0.95f) rotPositive = !rotPositive
if (particleAge > particleMaxAge - 20) particleAlpha = 0.05f * (particleMaxAge - particleAge)
if (onGround || wasCollided) {
velocity.setTo(0.0, 0.0, 0.0)
if (!wasCollided) {
particleAge = Math.max(particleAge, particleMaxAge - 20)
wasCollided = true
}
} else {
velocity.setTo(cos[particleRot], 0.0, sin[particleRot]).mul(Config.fallingLeaves.perturb)
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(Config.fallingLeaves.speed)
particleRot = (particleRot + (if (rotPositive) 1 else -1)) and 63
}
}
override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) {
if (Config.fallingLeaves.opacityHack) GL11.glDepthMask(true)
renderParticleQuad(worldRenderer, partialTickTime, rotation = particleRot, isMirrored = isMirrored)
}
fun calculateParticleColor(textureAvgColor: Int, blockColor: Int) {
val texture = HSB.fromColor(textureAvgColor)
val block = HSB.fromColor(blockColor)
val weightTex = texture.saturation / (texture.saturation + block.saturation)
val weightBlock = 1.0f - weightTex
// avoid circular average for hue for performance reasons
// one of the color components should dominate anyway
val particle = HSB(
weightTex * texture.hue + weightBlock * block.hue,
weightTex * texture.saturation + weightBlock * block.saturation,
weightTex * texture.brightness + weightBlock * block.brightness * biomeBrightnessMultiplier
)
setColor(particle.asColor)
}
}
@SideOnly(Side.CLIENT)
object LeafWindTracker {
var random = Random()
val target = Double3.zero
val current = Double3.zero
var nextChange: Long = 0
init {
MinecraftForge.EVENT_BUS.register(this)
}
fun changeWind(world: World) {
nextChange = world.worldInfo.worldTime + 120 + random.nextInt(80)
val direction = PI2 * random.nextDouble()
val speed = abs(random.nextGaussian()) * Config.fallingLeaves.windStrength +
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * Config.fallingLeaves.stormStrength)
target.setTo(cos(direction) * speed, 0.0, sin(direction) * speed)
}
@SubscribeEvent
fun handleWorldTick(event: TickEvent.ClientTickEvent) {
if (event.phase == TickEvent.Phase.START) Minecraft.getMinecraft().world?.let { world ->
// change target wind speed
if (world.worldInfo.worldTime >= nextChange) changeWind(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)
)
}
}
@SubscribeEvent
fun handleWorldLoad(event: WorldEvent.Load) { if (event.world.isRemote) changeWind(event.world) }
}

View File

@@ -0,0 +1,77 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.AbstractEntityFX
import mods.octarinecore.client.resource.ResourceHandler
import mods.octarinecore.common.Double3
import mods.octarinecore.forEachPairIndexed
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
import java.util.*
@SideOnly(Side.CLIENT)
class EntityRisingSoulFX(world: World, pos: BlockPos) :
AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5) {
val particleTrail: Deque<Double3> = LinkedList<Double3>()
val initialPhase = rand.nextInt(64)
init {
motionY = 0.1
particleGravity = 0.0f
particleTexture = RisingSoulTextures.headIcons[rand.nextInt(256)]
particleMaxAge = MathHelper.floor((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0)
}
override val isValid: Boolean get() = true
override fun update() {
val phase = (initialPhase + particleAge) % 64
velocity.setTo(cos[phase] * Config.risingSoul.perturb, 0.1, sin[phase] * Config.risingSoul.perturb)
particleTrail.addFirst(currentPos.copy())
while (particleTrail.size > Config.risingSoul.trailLength) particleTrail.removeLast()
if (!Config.enabled) setExpired()
}
override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) {
var alpha = Config.risingSoul.opacity
if (particleAge > particleMaxAge - 40) alpha *= (particleMaxAge - particleAge) / 40.0f
renderParticleQuad(worldRenderer, partialTickTime,
size = Config.risingSoul.headSize * 0.25,
alpha = alpha
)
var scale = Config.risingSoul.trailSize * 0.25
particleTrail.forEachPairIndexed { idx, current, previous ->
scale *= Config.risingSoul.sizeDecay
alpha *= Config.risingSoul.opacityDecay
if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime,
currentPos = current,
prevPos = previous,
size = scale,
alpha = alpha,
icon = RisingSoulTextures.trackIcon.icon!!
)
}
}
}
@SideOnly(Side.CLIENT)
object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID) {
val headIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/rising_soul_%d")
val trackIcon = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/soul_track")
override fun afterPreStitch() {
Client.log(INFO, "Registered ${headIcons.num} soul particle textures")
}
}

View File

@@ -0,0 +1,146 @@
@file:JvmName("ModelColumn")
package mods.betterfoliage.client.render
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import mods.octarinecore.common.Double3
import mods.octarinecore.exchange
import net.minecraft.util.EnumFacing.*
import org.lwjgl.opengl.GL11
/** Weight of the same-side AO values on the outer edges of the 45deg chamfered column faces. */
const val chamferAffinity = 0.9f
/** Amount to shrink column extension bits to stop Z-fighting. */
val zProtectionScale: Double3 get() = Double3(Config.roundLogs.zProtection, 1.0, Config.roundLogs.zProtection)
fun Model.columnSide(radius: Double, yBottom: Double, yTop: Double, transform: (Quad) -> Quad = { it }) {
val halfRadius = radius * 0.5
listOf(
verticalRectangle(x1 = 0.0, z1 = 0.5, x2 = 0.5 - radius, z2 = 0.5, yBottom = yBottom, yTop = yTop)
.clampUV(minU = 0.0, maxU = 0.5 - radius)
.setAoShader(faceOrientedInterpolate(overrideFace = SOUTH))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 1 || vi == 2}),
verticalRectangle(x1 = 0.5 - radius, z1 = 0.5, x2 = 0.5 - halfRadius, z2 = 0.5 - halfRadius, yBottom = yBottom, yTop = yTop)
.clampUV(minU = 0.5 - radius)
.setAoShader(
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming))
)
.setAoShader(
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming)),
predicate = { v, vi -> vi == 1 || vi == 2}
)
).forEach { transform(it.setFlatShader(FaceFlat(SOUTH))).add() }
listOf(
verticalRectangle(x1 = 0.5 - halfRadius, z1 = 0.5 - halfRadius, x2 = 0.5, z2 = 0.5 - radius, yBottom = yBottom, yTop = yTop)
.clampUV(maxU = radius - 0.5)
.setAoShader(
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming)))
.setAoShader(
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming)),
predicate = { v, vi -> vi == 0 || vi == 3}
),
verticalRectangle(x1 = 0.5, z1 = 0.5 - radius, x2 = 0.5, z2 = 0.0, yBottom = yBottom, yTop = yTop)
.clampUV(minU = radius - 0.5, maxU = 0.0)
.setAoShader(faceOrientedInterpolate(overrideFace = EAST))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 0 || vi == 3})
).forEach { transform(it.setFlatShader(FaceFlat(EAST))).add() }
quads.exchange(1, 2)
}
/**
* Create a model of the side of a square column quadrant.
*
* @param[transform] transformation to apply to the model
*/
fun Model.columnSideSquare(yBottom: Double, yTop: Double, transform: (Quad) -> Quad = { it }) {
listOf(
verticalRectangle(x1 = 0.0, z1 = 0.5, x2 = 0.5, z2 = 0.5, yBottom = yBottom, yTop = yTop)
.clampUV(minU = 0.0)
.setAoShader(faceOrientedInterpolate(overrideFace = SOUTH))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 1 || vi == 2}),
verticalRectangle(x1 = 0.5, z1 = 0.5, x2 = 0.5, z2 = 0.0, yBottom = yBottom, yTop = yTop)
.clampUV(maxU = 0.0)
.setAoShader(faceOrientedInterpolate(overrideFace = EAST))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 0 || vi == 3})
).forEach {
transform(it.setFlatShader(faceOrientedAuto(corner = cornerFlat))).add()
}
}
/**
* Create a model of the top lid of a chamfered column quadrant.
*
* @param[radius] the chamfer radius
* @param[transform] transformation to apply to the model
*/
fun Model.columnLid(radius: Double, transform: (Quad)->Quad = { it }) {
val v1 = Vertex(Double3(0.0, 0.5, 0.0), UV(0.0, 0.0))
val v2 = Vertex(Double3(0.0, 0.5, 0.5), UV(0.0, 0.5))
val v3 = Vertex(Double3(0.5 - radius, 0.5, 0.5), UV(0.5 - radius, 0.5))
val v4 = Vertex(Double3(0.5 - radius * 0.5, 0.5, 0.5 - radius * 0.5), UV(0.5, 0.5))
val v5 = Vertex(Double3(0.5, 0.5, 0.5 - radius), UV(0.5, 0.5 - radius))
val v6 = Vertex(Double3(0.5, 0.5, 0.0), UV(0.5, 0.0))
val q1 = Quad(v1, v2, v3, v4).setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.transformVI { vertex, idx -> vertex.copy(aoShader = when(idx) {
0 -> FaceCenter(UP)
1 -> EdgeInterpolateFallback(UP, SOUTH, 0.0)
else -> vertex.aoShader
})}
.cycleVertices(if (Config.nVidia) 0 else 1)
val q2 = Quad(v1, v4, v5, v6).setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.transformVI { vertex, idx -> vertex.copy(aoShader = when(idx) {
0 -> FaceCenter(UP)
3 -> EdgeInterpolateFallback(UP, EAST, 0.0)
else -> vertex.aoShader
})}
.cycleVertices(if (Config.nVidia) 0 else 1)
listOf(q1, q2).forEach { transform(it.setFlatShader(FaceFlat(UP))).add() }
}
/**
* Create a model of the top lid of a square column quadrant.
*
* @param[transform] transformation to apply to the model
*/
fun Model.columnLidSquare(transform: (Quad)-> Quad = { it }) {
transform(
horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = 0.5)
.transformVI { vertex, idx -> vertex.copy(uv = UV(vertex.xyz.x, vertex.xyz.z), aoShader = when(idx) {
0 -> FaceCenter(UP)
1 -> EdgeInterpolateFallback(UP, SOUTH, 0.0)
2 -> CornerSingleFallback(UP, SOUTH, EAST, UP)
else -> EdgeInterpolateFallback(UP, EAST, 0.0)
}) }
.setFlatShader(FaceFlat(UP))
).add()
}
/**
* Transform a chamfered side quadrant model of a column that extends from the top of the block.
* (clamp UV coordinates, apply some scaling to avoid Z-fighting).
*
* @param[size] amount that the model extends from the top
*/
fun topExtension(size: Double) = { q: Quad ->
q.clampUV(minV = 0.5 - size).transformVI { vertex, idx ->
if (idx < 2) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale)
}
}
/**
* Transform a chamfered side quadrant model of a column that extends from the bottom of the block.
* (clamp UV coordinates, apply some scaling to avoid Z-fighting).
*
* @param[size] amount that the model extends from the bottom
*/
fun bottomExtension(size: Double) = { q: Quad ->
q.clampUV(maxV = -0.5 + size).transformVI { vertex, idx ->
if (idx > 1) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale)
}
}

View File

@@ -0,0 +1,57 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.client.render.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
@SideOnly(Side.CLIENT)
class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val noise = simplexNoise()
val algaeIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_algae_%d")
val algaeModels = modelSet(64, RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax))
override fun afterPreStitch() {
Client.log(INFO, "Registered ${algaeIcons.num} algae textures")
}
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.algae.enabled &&
ctx.blockState(up2).material == Material.WATER &&
ctx.blockState(up1).material == Material.WATER &&
Config.blocks.dirt.matchesClass(ctx.block) &&
ctx.biomeId in Config.algae.biomes &&
noise[ctx.pos] < Config.algae.population
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
if (!layer.isCutout) return baseRender
modelRenderer.updateShading(Int3.zero, allFaces)
val rand = ctx.semiRandomArray(3)
ShadersModIntegration.grass(renderer, Config.algae.shaderWind) {
modelRenderer.render(
renderer,
algaeModels[rand[2]],
Rotation.identity,
icon = { _, qi, _ -> algaeIcons[rand[qi and 1]]!! },
postProcess = noPost
)
}
return true
}
}

View File

@@ -0,0 +1,111 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.octarinecore.client.render.*
import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.common.config.SimpleBlockMatcher
import net.minecraft.block.BlockCactus
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.*
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
object StandardCactusRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
override val logger = BetterFoliageMod.logDetail
override val matchClasses = SimpleBlockMatcher(BlockCactus::class.java)
override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side"))
override fun processModel(state: IBlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, Axis.Y, textures)
init { MinecraftForge.EVENT_BUS.register(this) }
}
@SideOnly(Side.CLIENT)
class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val cactusStemRadius = 0.4375
val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] }
val iconCross = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus")
val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus_arm_%d")
val modelStem = model {
horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5)
.scaleUV(cactusStemRadius * 2.0)
.let { listOf(it.flipped.move(1.0 to DOWN), it) }
.forEach { it.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y), edge = null)).add() }
verticalRectangle(x1 = -0.5, z1 = cactusStemRadius, x2 = 0.5, z2 = cactusStemRadius, yBottom = -0.5, yTop = 0.5)
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y), edge = null))
.toCross(UP).addAll()
}
val modelCross = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
.setAoShader(edgeOrientedAuto(corner = cornerAoMaxGreen))
.scale(1.4)
.transformV { v ->
val perturb = xzDisk(modelIdx) * Config.cactus.sizeVariation
Vertex(v.xyz + (if (v.uv.u < 0.0) perturb else -perturb), v.uv, v.aoShader)
}
.toCross(UP).addAll()
}
val modelArm = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
.scale(Config.cactus.size).move(0.5 to UP)
.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y), edge = null))
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.cactus.hOffset) }.addAll()
}
override fun afterPreStitch() {
Client.log(Level.INFO, "Registered ${iconArm.num} cactus arm textures")
}
override fun isEligible(ctx: BlockContext): Boolean =
Config.enabled && Config.cactus.enabled &&
Config.blocks.cactus.matchesClass(ctx.block)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer
if (!layer.isCutout) return false
// get AO data
modelRenderer.updateShading(Int3.zero, allFaces)
val icons = StandardCactusRegistry[ctx] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, null)
modelRenderer.render(
renderer,
modelStem.model,
Rotation.identity,
icon = { ctx, qi, q -> when(qi) {
0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q)
} },
postProcess = noPost
)
modelRenderer.render(
renderer,
modelCross[ctx.random(0)],
Rotation.identity,
icon = { _, _, _ -> iconCross.icon!!},
postProcess = noPost
)
modelRenderer.render(
renderer,
modelArm[ctx.random(1)],
cactusArmRotation[ctx.random(2) % 4],
icon = { _, _, _ -> iconArm[ctx.random(3)]!!},
postProcess = noPost
)
return true
}
}

View File

@@ -0,0 +1,36 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.withOffset
import mods.octarinecore.common.Int3
import mods.octarinecore.common.forgeDirsHorizontal
import mods.octarinecore.common.offset
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
@SideOnly(Side.CLIENT)
class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.connectedGrass.enabled &&
Config.blocks.dirt.matchesClass(ctx.block) &&
Config.blocks.grassClasses.matchesClass(ctx.block(up1)) &&
(Config.connectedGrass.snowEnabled || !ctx.blockState(up2).isSnow)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
// if the block sides are not visible anyway, render normally
if (forgeDirsHorizontal.all { ctx.blockState(it.offset).isOpaqueCube }) return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
if (ctx.isSurroundedBy { it.isOpaqueCube } ) return false
return ctx.withOffset(Int3.zero, up1) {
ctx.withOffset(up1, up2) {
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
}
}
}
}

View File

@@ -0,0 +1,36 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.withOffset
import mods.octarinecore.common.Int3
import mods.octarinecore.common.offset
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.*
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
@SideOnly(Side.CLIENT)
class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val grassCheckDirs = listOf(EAST, WEST, NORTH, SOUTH)
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&
Config.blocks.dirt.matchesClass(ctx.block) &&
Config.blocks.logClasses.matchesClass(ctx.block(up1))
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val grassDir = grassCheckDirs.find {
Config.blocks.grassClasses.matchesClass(ctx.block(it.offset))
} ?: return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
return ctx.withOffset(Int3.zero, grassDir.offset) {
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
}
}
}

View File

@@ -0,0 +1,77 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.forgeDirOffsets
import mods.octarinecore.common.forgeDirs
import mods.octarinecore.random
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.Axis
import net.minecraft.util.EnumFacing.UP
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
@SideOnly(Side.CLIENT)
class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val noise = simplexNoise()
val coralIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_coral_%d")
val crustIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_crust_%d")
val coralModels = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
.scale(Config.coral.size).move(0.5 to UP)
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.coral.hOffset) }.addAll()
val separation = random(0.01, Config.coral.vOffset)
horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0)
.scale(Config.coral.crustSize).move(0.5 + separation to UP).add()
transformQ {
it.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.setFlatShader(faceOrientedAuto(overrideFace = UP, corner = cornerFlat))
}
}
override fun afterPreStitch() {
Client.log(INFO, "Registered ${coralIcons.num} coral textures")
Client.log(INFO, "Registered ${crustIcons.num} coral crust textures")
}
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.coral.enabled &&
(ctx.blockState(up2).material == Material.WATER || Config.coral.shallowWater) &&
ctx.blockState(up1).material == Material.WATER &&
Config.blocks.sand.matchesClass(ctx.block) &&
ctx.biomeId in Config.coral.biomes &&
noise[ctx.pos] < Config.coral.population
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
if (!layer.isCutout) return baseRender
modelRenderer.updateShading(Int3.zero, allFaces)
forgeDirs.forEachIndexed { idx, face ->
if (!ctx.blockState(forgeDirOffsets[idx]).isOpaqueCube && blockContext.random(idx) < Config.coral.chance) {
var variation = blockContext.random(6)
modelRenderer.render(
renderer,
coralModels[variation++],
rotationFromUp[idx],
icon = { _, qi, _ -> if (qi == 4) crustIcons[variation]!! else coralIcons[variation + (qi and 1)]!!},
postProcess = noPost
)
}
}
return true
}
}

View File

@@ -0,0 +1,131 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCustomColors
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.*
import mods.octarinecore.common.*
import mods.octarinecore.random
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumBlockRenderType
import net.minecraft.util.EnumBlockRenderType.MODEL
import net.minecraft.util.EnumFacing.Axis
import net.minecraft.util.EnumFacing.UP
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
@SideOnly(Side.CLIENT)
class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
companion object {
@JvmStatic fun grassTopQuads(heightMin: Double, heightMax: Double): Model.(Int)->Unit = { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5,
yTop = 0.5 + random(heightMin, heightMax)
)
.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.setFlatShader(faceOrientedAuto(overrideFace = UP, corner = cornerFlat))
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.shortGrass.hOffset) }.addAll()
}
}
val noise = simplexNoise()
val normalIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_grass_long_%d")
val snowedIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_grass_snowed_%d")
val normalGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:blocks/tallgrass", "snowed" to false))
val snowedGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:blocks/tallgrass", "snowed" to true))
val grassModels = modelSet(64, grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax))
override fun afterPreStitch() {
Client.log(INFO, "Registered ${normalIcons.num} grass textures")
Client.log(INFO, "Registered ${snowedIcons.num} snowed grass textures")
}
override fun isEligible(ctx: BlockContext) =
Config.enabled &&
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
GrassRegistry[ctx] != null
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer
if (!layer.isCutout) return false
val isConnected = ctx.block(down1).let {
Config.blocks.dirt.matchesClass(it) ||
Config.blocks.grassClasses.matchesClass(it)
}
val isSnowed = ctx.blockState(up1).isSnow
val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled)
val grass = GrassRegistry[ctx]
if (grass == null) {
// shouldn't happen
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
return renderWorldBlockBase(ctx, dispatcher, renderer, null)
}
val blockColor = OptifineCustomColors.getBlockColor(ctx)
if (connectedGrass) {
// get full AO data
modelRenderer.updateShading(Int3.zero, allFaces)
// check occlusion
val isHidden = forgeDirs.map { ctx.blockState(it.offset).isOpaqueCube }
// render full grass block
ShadersModIntegration.renderAs(ctx.blockState(Int3.zero), MODEL, renderer) {
modelRenderer.render(
renderer,
fullCube,
quadFilter = { qi, _ -> !isHidden[qi] },
icon = { _, _, _ -> grass.grassTopTexture },
postProcess = { ctx, _, _, _, _ ->
rotateUV(2)
if (isSnowed) {
if (!ctx.aoEnabled) setGrey(1.4f)
} else if (ctx.aoEnabled && grass.overrideColor == null) multiplyColor(blockColor)
}
)
}
} else {
renderWorldBlockBase(ctx, dispatcher, renderer, null)
// get AO data only for block top
modelRenderer.updateShading(Int3.zero, topOnly)
}
if (!Config.shortGrass.grassEnabled) return true
val stateAbove = ctx.blockState(up1)
if (!stateAbove.block.isAir(stateAbove, ctx.world!!, ctx.pos.up())) return true
if (isSnowed && !Config.shortGrass.snowEnabled) return true
if (ctx.blockState(up1).isOpaqueCube) return true
if (Config.shortGrass.population < 64 && noise[ctx.pos] >= Config.shortGrass.population) return true
// render grass quads
val iconset = if (isSnowed) snowedIcons else normalIcons
val iconGen = if (isSnowed) snowedGenIcon else normalGenIcon
val rand = ctx.semiRandomArray(2)
ShadersModIntegration.grass(renderer, Config.shortGrass.shaderWind) {
modelRenderer.render(
renderer,
grassModels[rand[0]],
Rotation.identity,
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen.icon!! else iconset[rand[qi and 1]]!! },
postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grass.overrideColor ?: blockColor) }
)
}
return true
}
}

View File

@@ -0,0 +1,94 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCustomColors
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.texture.LeafRegistry
import mods.octarinecore.PI2
import mods.octarinecore.client.render.*
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.vec
import mods.octarinecore.random
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.DOWN
import net.minecraft.util.EnumFacing.UP
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import java.lang.Math.cos
import java.lang.Math.sin
@SideOnly(Side.CLIENT)
class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val leavesModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
.setAoShader(edgeOrientedAuto(corner = cornerAoMaxGreen))
.setFlatShader(FlatOffset(Int3.zero))
.scale(Config.leaves.size)
.toCross(UP).addAll()
}
val snowedIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_leaves_snowed_%d")
val perturbs = vectorSet(64) { idx ->
val angle = PI2 * idx / 64.0
Double3(cos(angle), 0.0, sin(angle)) * Config.leaves.hOffset +
UP.vec * random(-1.0, 1.0) * Config.leaves.vOffset
}
override fun isEligible(ctx: BlockContext) =
Config.enabled &&
Config.leaves.enabled &&
LeafRegistry[ctx] != null &&
!(Config.leaves.hideInternal && ctx.isSurroundedBy { it.isFullCube || it.material == Material.LEAVES } )
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val isSnowed = ctx.blockState(up1).material.let {
it == Material.SNOW || it == Material.CRAFTED_SNOW
}
val leafInfo = LeafRegistry[ctx]
if (leafInfo == null) {
// shouldn't happen
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
}
val blockColor = OptifineCustomColors.getBlockColor(ctx)
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
if (!layer.isCutout) return true
modelRenderer.updateShading(Int3.zero, allFaces)
ShadersModIntegration.leaves(renderer) {
val rand = ctx.semiRandomArray(2)
(if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation ->
modelRenderer.render(
renderer,
leavesModel.model,
rotation,
ctx.blockCenter + perturbs[rand[0]],
icon = { _, _, _ -> leafInfo.roundLeafTexture },
postProcess = { _, _, _, _, _ ->
rotateUV(rand[1])
multiplyColor(blockColor)
}
)
}
if (isSnowed && Config.leaves.snowEnabled) modelRenderer.render(
renderer,
leavesModel.model,
Rotation.identity,
ctx.blockCenter + perturbs[rand[0]],
icon = { _, _, _ -> snowedIcon[rand[1]]!! },
postProcess = whitewash
)
}
return true
}
}

View File

@@ -0,0 +1,79 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.client.render.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.DOWN
import net.minecraft.util.EnumFacing.UP
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
@SideOnly(Side.CLIENT)
class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val rootModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5)
.setFlatShader(FlatOffsetNoColor(Int3.zero))
.toCross(UP).addAll()
}
val flowerModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
.scale(0.5).move(0.5 to DOWN)
.setFlatShader(FlatOffsetNoColor(Int3.zero))
.toCross(UP).addAll()
}
val rootIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_roots_%d")
val flowerIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_flower_%d")
val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset }
override fun afterPreStitch() {
Client.log(Level.INFO, "Registered ${rootIcon.num} lilypad root textures")
Client.log(Level.INFO, "Registered ${flowerIcon.num} lilypad flower textures")
}
override fun isEligible(ctx: BlockContext): Boolean =
Config.enabled && Config.lilypad.enabled &&
Config.blocks.lilypad.matchesClass(ctx.block)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer
if (!layer.isCutout) return false
renderWorldBlockBase(ctx, dispatcher, renderer, null)
modelRenderer.updateShading(Int3.zero, allFaces)
val rand = ctx.semiRandomArray(5)
ShadersModIntegration.grass(renderer) {
modelRenderer.render(
renderer,
rootModel.model,
Rotation.identity,
ctx.blockCenter.add(perturbs[rand[2]]),
forceFlat = true,
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! },
postProcess = noPost
)
}
if (rand[3] < Config.lilypad.flowerChance) modelRenderer.render(
renderer,
flowerModel.model,
Rotation.identity,
ctx.blockCenter.add(perturbs[rand[4]]),
forceFlat = true,
icon = { _, _, _ -> flowerIcon[rand[0]]!! },
postProcess = noPost
)
return true
}
}

View File

@@ -0,0 +1,67 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.column.AbstractRenderColumn
import mods.betterfoliage.client.render.column.ColumnRenderLayer
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.tryDefault
import net.minecraft.block.BlockLog
import net.minecraft.block.state.IBlockState
import net.minecraft.util.EnumFacing.Axis
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
override val addToCutout: Boolean get() = false
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.roundLogs.enabled &&
Config.blocks.logClasses.matchesClass(ctx.block)
override val overlayLayer = RoundLogOverlayLayer()
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
override val radiusSmall: Double get() = Config.roundLogs.radiusSmall
override val radiusLarge: Double get() = Config.roundLogs.radiusLarge
init {
ChunkOverlayManager.layers.add(overlayLayer)
}
}
class RoundLogOverlayLayer : ColumnRenderLayer() {
override val registry: ModelRenderRegistry<ColumnTextureInfo> get() = LogRegistry
override val blockPredicate = { state: IBlockState -> Config.blocks.logClasses.matchesClass(state.block) }
override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !Config.blocks.logClasses.matchesClass(state.block) }
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect
override val defaultToY: Boolean get() = Config.roundLogs.defaultY
}
@SideOnly(Side.CLIENT)
object LogRegistry : ModelRenderRegistryRoot<ColumnTextureInfo>()
object StandardLogRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
override val logger = BetterFoliageMod.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.logClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.logModels.list
override fun processModel(state: IBlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, getAxis(state), textures)
fun getAxis(state: IBlockState): Axis? {
val axis = tryDefault(null) { state.getValue(BlockLog.LOG_AXIS).toString() } ?:
state.properties.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
return when (axis) {
"x" -> Axis.X
"y" -> Axis.Y
"z" -> Axis.Z
else -> null
}
}
}

View File

@@ -0,0 +1,57 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.modelRenderer
import mods.octarinecore.client.render.noPost
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Rotation
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
@SideOnly(Side.CLIENT)
class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val myceliumIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_mycel_%d")
val myceliumModel = modelSet(64, RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax))
override fun afterPreStitch() {
Client.log(INFO, "Registered ${myceliumIcon.num} mycelium textures")
}
override fun isEligible(ctx: BlockContext): Boolean {
if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false
return Config.blocks.mycelium.matchesClass(ctx.block)
}
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer
if (!layer.isCutout) return false
val isSnowed = ctx.blockState(up1).isSnow
renderWorldBlockBase(ctx, dispatcher, renderer, null)
if (isSnowed && !Config.shortGrass.snowEnabled) return true
if (ctx.blockState(up1).isOpaqueCube) return true
val rand = ctx.semiRandomArray(2)
modelRenderer.render(
renderer,
myceliumModel[rand[0]],
Rotation.identity,
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]]!! },
postProcess = if (isSnowed) whitewash else noPost
)
return true
}
}

View File

@@ -0,0 +1,59 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.random
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.*
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
@SideOnly(Side.CLIENT)
class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val netherrackIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_netherrack_%d")
val netherrackModel = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yTop = -0.5,
yBottom = -0.5 - random(Config.netherrack.heightMin, Config.netherrack.heightMax))
.setAoShader(faceOrientedAuto(overrideFace = DOWN, corner = cornerAo(Axis.Y)))
.setFlatShader(faceOrientedAuto(overrideFace = DOWN, corner = cornerFlat))
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.shortGrass.hOffset) }.addAll()
}
override fun afterPreStitch() {
Client.log(INFO, "Registered ${netherrackIcon.num} netherrack textures")
}
override fun isEligible(ctx: BlockContext): Boolean {
if (!Config.enabled || !Config.netherrack.enabled) return false
return Config.blocks.netherrack.matchesClass(ctx.block)
}
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
if (!layer.isCutout) return baseRender
if (ctx.blockState(down1).isOpaqueCube) return baseRender
modelRenderer.updateShading(Int3.zero, allFaces)
val rand = ctx.semiRandomArray(2)
modelRenderer.render(
renderer,
netherrackModel[rand[0]],
Rotation.identity,
icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]]!! },
postProcess = noPost
)
return true
}
}

View File

@@ -0,0 +1,74 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.client.render.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.random
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.UP
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
@SideOnly(Side.CLIENT)
class RenderReeds : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val noise = simplexNoise()
val reedIcons = iconSet(Client.genReeds.generatedResource("${BetterFoliageMod.LEGACY_DOMAIN}:blocks/better_reed_%d"))
val reedModels = modelSet(64) { modelIdx ->
val height = random(Config.reed.heightMin, Config.reed.heightMax)
val waterline = 0.875f
val vCutLine = 0.5 - waterline / height
listOf(
// below waterline
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5, yTop = 0.5 + waterline)
.setFlatShader(FlatOffsetNoColor(up1)).clampUV(minV = vCutLine),
// above waterline
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5 + waterline, yTop = 0.5 + height)
.setFlatShader(FlatOffsetNoColor(up2)).clampUV(maxV = vCutLine)
).forEach {
it.clampUV(minU = -0.25, maxU = 0.25)
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.reed.hOffset) }.addAll()
}
}
override fun afterPreStitch() {
Client.log(Level.INFO, "Registered ${reedIcons.num} reed textures")
}
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.reed.enabled &&
ctx.blockState(up2).material == Material.AIR &&
ctx.blockState(up1).material == Material.WATER &&
Config.blocks.dirt.matchesClass(ctx.block) &&
ctx.biomeId in Config.reed.biomes &&
noise[ctx.pos] < Config.reed.population
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
if (!layer.isCutout) return baseRender
modelRenderer.updateShading(Int3.zero, allFaces)
val iconVar = ctx.random(1)
ShadersModIntegration.grass(renderer, Config.reed.shaderWind) {
modelRenderer.render(
renderer,
reedModels[ctx.random(0)],
Rotation.identity,
forceFlat = true,
icon = { _, _, _ -> reedIcons[iconVar]!! },
postProcess = noPost
)
}
return true
}
}

View File

@@ -0,0 +1,62 @@
@file:JvmName("Utils")
package mods.betterfoliage.client.render
import mods.octarinecore.PI2
import mods.octarinecore.client.render.*
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.times
import net.minecraft.block.Block
import net.minecraft.block.material.Material
import net.minecraft.block.state.IBlockState
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing
import net.minecraft.util.EnumFacing.*
val up1 = Int3(1 to UP)
val up2 = Int3(2 to UP)
val down1 = Int3(1 to DOWN)
val snowOffset = UP * 0.0625
val normalLeavesRot = arrayOf(Rotation.identity)
val denseLeavesRot = arrayOf(Rotation.identity, Rotation.rot90[EAST.ordinal], Rotation.rot90[SOUTH.ordinal])
val whitewash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.4f) }
val greywash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.0f) }
val IBlockState.isSnow: Boolean get() = material.let { it == Material.SNOW || it == Material.CRAFTED_SNOW }
fun Quad.toCross(rotAxis: EnumFacing, trans: (Quad)->Quad) =
(0..3).map { rotIdx ->
trans(rotate(Rotation.rot90[rotAxis.ordinal] * rotIdx).mirrorUV(rotIdx > 1, false))
}
fun Quad.toCross(rotAxis: EnumFacing) = toCross(rotAxis) { it }
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(Math.cos(it), 0.0, Math.sin(it)) }
val rotationFromUp = arrayOf(
Rotation.rot90[EAST.ordinal] * 2,
Rotation.identity,
Rotation.rot90[WEST.ordinal],
Rotation.rot90[EAST.ordinal],
Rotation.rot90[SOUTH.ordinal],
Rotation.rot90[NORTH.ordinal]
)
fun Model.mix(first: Model, second: Model, predicate: (Int)->Boolean) {
first.quads.forEachIndexed { qi, quad ->
val otherQuad = second.quads[qi]
Quad(
if (predicate(0)) otherQuad.v1.copy() else quad.v1.copy(),
if (predicate(1)) otherQuad.v2.copy() else quad.v2.copy(),
if (predicate(2)) otherQuad.v3.copy() else quad.v3.copy(),
if (predicate(3)) otherQuad.v4.copy() else quad.v4.copy()
).add()
}
}
val BlockRenderLayer.isCutout: Boolean get() = (this == BlockRenderLayer.CUTOUT) || (this == BlockRenderLayer.CUTOUT_MIPPED)
fun IBlockState.canRenderInLayer(layer: BlockRenderLayer) = this.block.canRenderInLayer(this, layer)
fun IBlockState.canRenderInCutout() = this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT) || this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT_MIPPED)

View File

@@ -0,0 +1,224 @@
package mods.betterfoliage.client.render.column
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.chunk.ChunkOverlayLayer
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs
import mods.betterfoliage.client.render.*
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.octarinecore.client.render.*
import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.common.*
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumBlockRenderType
import net.minecraft.util.EnumBlockRenderType.MODEL
import net.minecraft.util.EnumFacing.*
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
@SideOnly(Side.CLIENT)
@Suppress("NOTHING_TO_INLINE")
abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) {
/** The rotations necessary to bring the models in position for the 4 quadrants */
val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it }
// ============================
// Configuration
// ============================
abstract val overlayLayer: ColumnRenderLayer
abstract val connectPerpendicular: Boolean
abstract val radiusSmall: Double
abstract val radiusLarge: Double
// ============================
// Models
// ============================
val sideSquare = model { columnSideSquare(-0.5, 0.5) }
val sideRoundSmall = model { columnSide(radiusSmall, -0.5, 0.5) }
val sideRoundLarge = model { columnSide(radiusLarge, -0.5, 0.5) }
val extendTopSquare = model { columnSideSquare(0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
val extendTopRoundSmall = model { columnSide(radiusSmall, 0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
val extendTopRoundLarge = model { columnSide(radiusLarge, 0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
inline fun extendTop(type: QuadrantType) = when(type) {
SMALL_RADIUS -> extendTopRoundSmall.model
LARGE_RADIUS -> extendTopRoundLarge.model
SQUARE -> extendTopSquare.model
INVISIBLE -> extendTopSquare.model
else -> null
}
val extendBottomSquare = model { columnSideSquare(-0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
val extendBottomRoundSmall = model { columnSide(radiusSmall, -0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
val extendBottomRoundLarge = model { columnSide(radiusLarge, -0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
inline fun extendBottom(type: QuadrantType) = when (type) {
SMALL_RADIUS -> extendBottomRoundSmall.model
LARGE_RADIUS -> extendBottomRoundLarge.model
SQUARE -> extendBottomSquare.model
INVISIBLE -> extendBottomSquare.model
else -> null
}
val topSquare = model { columnLidSquare() }
val topRoundSmall = model { columnLid(radiusSmall) }
val topRoundLarge = model { columnLid(radiusLarge) }
inline fun flatTop(type: QuadrantType) = when(type) {
SMALL_RADIUS -> topRoundSmall.model
LARGE_RADIUS -> topRoundLarge.model
SQUARE -> topSquare.model
INVISIBLE -> topSquare.model
else -> null
}
val bottomSquare = model { columnLidSquare() { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } }
val bottomRoundSmall = model { columnLid(radiusSmall) { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } }
val bottomRoundLarge = model { columnLid(radiusLarge) { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } }
inline fun flatBottom(type: QuadrantType) = when(type) {
SMALL_RADIUS -> bottomRoundSmall.model
LARGE_RADIUS -> bottomRoundLarge.model
SQUARE -> bottomSquare.model
INVISIBLE -> bottomSquare.model
else -> null
}
val transitionTop = model { mix(sideRoundLarge.model, sideRoundSmall.model) { it > 1 } }
val transitionBottom = model { mix(sideRoundSmall.model, sideRoundLarge.model) { it > 1 } }
inline fun continuous(q1: QuadrantType, q2: QuadrantType) =
q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE))
@Suppress("NON_EXHAUSTIVE_WHEN")
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx.world!!, ctx.pos)
when(roundLog) {
ColumnLayerData.SkipRender -> return true
ColumnLayerData.NormalRender -> return renderWorldBlockBase(ctx, dispatcher, renderer, null)
ColumnLayerData.ResolveError, null -> {
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
return renderWorldBlockBase(ctx, dispatcher, renderer, null)
}
}
// 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 renderWorldBlockBase(ctx, dispatcher, renderer, null)
}
// get AO data
modelRenderer.updateShading(Int3.zero, allFaces)
val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal]
renderAs(ctx.blockState(Int3.zero), MODEL, renderer) {
quadrantRotations.forEachIndexed { idx, quadrantRotation ->
// set rotation for the current quadrant
val rotation = baseRotation + quadrantRotation
// 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
}
// render side of current quadrant
val sideModel = when (roundLog.quadrants[idx]) {
SMALL_RADIUS -> sideRoundSmall.model
LARGE_RADIUS -> if (roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] == SMALL_RADIUS) transitionTop.model
else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) transitionBottom.model
else sideRoundLarge.model
SQUARE -> sideSquare.model
else -> null
}
if (sideModel != null) modelRenderer.render(
renderer,
sideModel,
rotation,
icon = roundLog.column.side,
postProcess = noPost
)
// render top and bottom end of current quadrant
var upModel: Model? = null
var downModel: Model? = null
var upIcon = roundLog.column.top
var downIcon = roundLog.column.bottom
var isLidUp = true
var isLidDown = true
when (roundLog.upType) {
NONSOLID -> upModel = flatTop(roundLog.quadrants[idx])
PERPENDICULAR -> {
if (!connectPerpendicular) {
upModel = flatTop(roundLog.quadrants[idx])
} else {
upIcon = roundLog.column.side
upModel = extendTop(roundLog.quadrants[idx])
isLidUp = false
}
}
PARALLEL -> {
if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsTop[idx])) {
if (roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE) {
upModel = topSquare.model
}
}
}
}
when (roundLog.downType) {
NONSOLID -> downModel = flatBottom(roundLog.quadrants[idx])
PERPENDICULAR -> {
if (!connectPerpendicular) {
downModel = flatBottom(roundLog.quadrants[idx])
} else {
downIcon = roundLog.column.side
downModel = extendBottom(roundLog.quadrants[idx])
isLidDown = false
}
}
PARALLEL -> {
if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsBottom[idx]) &&
(roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE)) {
downModel = bottomSquare.model
}
}
}
if (upModel != null) modelRenderer.render(
renderer,
upModel,
rotation,
icon = upIcon,
postProcess = { _, _, _, _, _ ->
if (isLidUp) {
rotateUV(idx + if (roundLog.column.axis == Axis.X) 1 else 0)
}
}
)
if (downModel != null) modelRenderer.render(
renderer,
downModel,
rotation,
icon = downIcon,
postProcess = { _, _, _, _, _ ->
if (isLidDown) {
rotateUV((if (roundLog.column.axis == Axis.X) 0 else 3) - idx)
}
}
)
}
}
return true
}
}

View File

@@ -0,0 +1,190 @@
package mods.betterfoliage.client.render.column
import mods.betterfoliage.client.chunk.ChunkOverlayLayer
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.betterfoliage.client.render.rotationFromUp
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.face
import mods.octarinecore.common.plus
import net.minecraft.block.state.IBlockState
import net.minecraft.util.EnumFacing
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
/** 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
/**
* Sealed class hierarchy for all possible render outcomes
*/
@SideOnly(Side.CLIENT)
sealed class ColumnLayerData {
/**
* Data structure to cache texture and world neighborhood data relevant to column rendering
*/
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
@SideOnly(Side.CLIENT)
data class SpecialRender(
val column: ColumnTextureInfo,
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 }
}
/** Column block should not be rendered at all */
@SideOnly(Side.CLIENT)
object SkipRender : ColumnLayerData()
/** Column block must be rendered normally */
@SideOnly(Side.CLIENT)
object NormalRender : ColumnLayerData()
/** Error while resolving render data, column block must be rendered normally */
@SideOnly(Side.CLIENT)
object ResolveError : ColumnLayerData()
}
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
abstract val registry: ModelRenderRegistry<ColumnTextureInfo>
abstract val blockPredicate: (IBlockState)->Boolean
abstract val surroundPredicate: (IBlockState) -> Boolean
abstract val connectSolids: Boolean
abstract val lenientConnect: Boolean
abstract val defaultToY: Boolean
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
override fun onBlockUpdate(world: IBlockAccess, pos: BlockPos) {
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(this, pos + offset) }
}
override fun calculate(world: IBlockAccess, pos: BlockPos) = calculate(BlockContext(world, pos))
fun calculate(ctx: BlockContext): ColumnLayerData {
if (ctx.isSurroundedBy(surroundPredicate)) return ColumnLayerData.SkipRender
val columnTextures = registry[ctx] ?: 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) EnumFacing.Axis.Y else return ColumnLayerData.NormalRender
// check log neighborhood
val baseRotation = rotationFromUp[(logAxis to EnumFacing.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: BlockContext, rotation: Rotation, logAxis: EnumFacing.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 BlockContext.blockType(rotation: Rotation, axis: EnumFacing.Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType {
val offsetRot = offset.rotate(rotation)
val state = blockState(offsetRot)
return if (!blockPredicate(state)) {
if (state.isOpaqueCube) SOLID else NONSOLID
} else {
(registry[state, world!!, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) EnumFacing.Axis.Y else null)?.let {
if (it == axis) PARALLEL else PERPENDICULAR
} ?: SOLID
}
}
}

View File

@@ -0,0 +1,50 @@
package mods.betterfoliage.client.render.column
import mods.octarinecore.client.render.QuadIconResolver
import mods.octarinecore.client.render.blockContext
import mods.octarinecore.client.resource.ModelRenderKey
import mods.octarinecore.client.resource.get
import mods.octarinecore.common.rotate
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Logger
@SideOnly(Side.CLIENT)
interface ColumnTextureInfo {
val axis: EnumFacing.Axis?
val top: QuadIconResolver
val bottom: QuadIconResolver
val side: QuadIconResolver
}
@SideOnly(Side.CLIENT)
open class SimpleColumnInfo(
override val axis: EnumFacing.Axis?,
val topTexture: TextureAtlasSprite,
val bottomTexture: TextureAtlasSprite,
val sideTextures: List<TextureAtlasSprite>
) : ColumnTextureInfo {
// index offsets for EnumFacings, to make it less likely for neighboring faces to get the same bark texture
val dirToIdx = arrayOf(0, 1, 2, 4, 3, 5)
override val top: QuadIconResolver = { _, _, _ -> topTexture }
override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture }
override val side: QuadIconResolver = { ctx, idx, _ ->
val worldFace = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation)
val sideIdx = if (sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0
sideTextures[sideIdx]
}
class Key(override val logger: Logger, val axis: EnumFacing.Axis?, val textures: List<String>) : ModelRenderKey<ColumnTextureInfo> {
override fun resolveSprites(atlas: TextureMap) = SimpleColumnInfo(
axis,
atlas[textures[0]] ?: atlas.missingSprite,
atlas[textures[1]] ?: atlas.missingSprite,
textures.drop(2).map { atlas[it] ?: atlas.missingSprite }
)
}
}

View File

@@ -0,0 +1,50 @@
package mods.betterfoliage.client.texture
import mods.octarinecore.client.resource.*
import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage
/**
* Generate Short Grass textures from [Blocks.tallgrass] block textures.
* The bottom 3/8 of the base texture is chopped off.
*
* @param[domain] Resource domain of generator
*/
class GrassGenerator(domain: String) : TextureGenerator(domain) {
override fun generate(params: ParameterList): BufferedImage? {
val target = targetResource(params)!!
val isSnowed = params["snowed"]?.toBoolean() ?: false
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = result.createGraphics()
val size = baseTexture.width
val frames = baseTexture.height / size
// iterate all frames
for (frame in 0 .. frames - 1) {
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
val grassFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
// draw bottom half of texture
grassFrame.createGraphics().apply {
drawImage(baseFrame, 0, 3 * size / 8, null)
}
// add to animated png
graphics.drawImage(grassFrame, 0, size * frame, null)
}
// blend with white if snowed
if (isSnowed && target.first == ResourceType.COLOR) {
for (x in 0..result.width - 1) for (y in 0..result.height - 1) {
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
}
}
return result
}
}

View File

@@ -0,0 +1,68 @@
package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.HSB
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import java.lang.Math.min
const val defaultGrassColor = 0
/** Rendering-related information for a grass block. */
class GrassInfo(
/** Top texture of the grass block. */
val grassTopTexture: TextureAtlasSprite,
/**
* Color to use for Short Grass rendering instead of the biome color.
*
* Value is null if the texture is mostly grey (the saturation of its average color is under a configurable limit),
* the average color of the texture otherwise.
*/
val overrideColor: Int?
)
object GrassRegistry : ModelRenderRegistryRoot<GrassInfo>()
object StandardGrassRegistry : ModelRenderRegistryConfigurable<GrassInfo>() {
override val logger = BetterFoliageMod.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.grassClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.grassModels.list
override fun processModel(state: IBlockState, textures: List<String>) = StandardGrassKey(logger, textures[0])
}
class StandardGrassKey(override val logger: Logger, val textureName: String) : ModelRenderKey<GrassInfo> {
override fun resolveSprites(atlas: TextureMap): GrassInfo {
val logName = "StandardGrassKey"
val texture = atlas[textureName] ?: atlas.missingSprite
logger.log(Level.DEBUG, "$logName: texture $textureName")
val hsb = HSB.fromColor(texture.averageColor ?: defaultGrassColor)
val overrideColor = if (hsb.saturation >= Config.shortGrass.saturationThreshold) {
logger.log(Level.DEBUG, "$logName: brightness ${hsb.brightness}")
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} >= ${Config.shortGrass.saturationThreshold}, using texture color")
hsb.copy(brightness = min(0.9f, hsb.brightness * 2.0f)).asColor
} else {
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} < ${Config.shortGrass.saturationThreshold}, using block color")
null
}
return GrassInfo(texture, overrideColor)
}
}

View File

@@ -0,0 +1,77 @@
package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliageMod
import mods.octarinecore.client.resource.*
import mods.octarinecore.stripStart
import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage
/**
* Generate round leaf textures from leaf block textures.
* The base texture is tiled 2x2, then parts of it are made transparent by applying a mask to the alpha channel.
*
* Generator parameter _type_: Leaf type (configurable by user). Different leaf types may have their own alpha mask.
*
* @param[domain] Resource domain of generator
*/
class LeafGenerator(domain: String) : TextureGenerator(domain) {
override fun generate(params: ParameterList): BufferedImage? {
val target = targetResource(params)!!
val leafType = params["type"] ?: "default"
val handDrawnLoc = target.second.stripStart("textures/").stripStart("blocks/").let {
ResourceLocation(BetterFoliageMod.DOMAIN, "${it.namespace}/textures/blocks/${it.path}")
}
resourceManager[handDrawnLoc]?.loadImage()?.let { return it }
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
val size = baseTexture.width
val frames = baseTexture.height / size
val maskTexture = (getLeafMask(leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage()
fun scale(i: Int) = i * maskTexture!!.width / (size * 2)
val leafTexture = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = leafTexture.createGraphics()
// iterate all frames
for (frame in 0 .. frames - 1) {
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
val leafFrame = BufferedImage(size * 2, size * 2, BufferedImage.TYPE_4BYTE_ABGR)
// tile leaf texture 2x2
leafFrame.createGraphics().apply {
drawImage(baseFrame, 0, 0, null)
drawImage(baseFrame, 0, size, null)
drawImage(baseFrame, size, 0, null)
drawImage(baseFrame, size, size, null)
}
// overlay alpha mask
if (target.first == ResourceType.COLOR && maskTexture != null) {
for (x in 0 .. size * 2 - 1) for (y in 0 .. size * 2 - 1) {
val basePixel = leafFrame[x, y].toLong() and 0xFFFFFFFFL
val maskPixel = maskTexture[scale(x), scale(y)].toLong() and 0xFF000000L or 0xFFFFFFL
leafFrame[x, y] = (basePixel and maskPixel).toInt()
}
}
// add to animated png
graphics.drawImage(leafFrame, 0, size * frame * 2, null)
}
return leafTexture
}
/**
* Get the alpha mask to use
*
* @param[type] Alpha mask type.
* @param[maxSize] Preferred mask size.
*/
fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size ->
ResourceLocation(BetterFoliageMod.DOMAIN, "textures/blocks/leafmask_${size}_${type}.png")
}
}

View File

@@ -0,0 +1,69 @@
package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliageMod
import mods.octarinecore.client.resource.*
import mods.octarinecore.stripStart
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
object LeafParticleRegistry {
val typeMappings = TextureMatcher()
val particles = hashMapOf<String, IconSet>()
operator fun get(type: String) = particles[type] ?: particles["default"]!!
init { MinecraftForge.EVENT_BUS.register(this) }
@SubscribeEvent(priority = EventPriority.HIGH)
fun handleLoadModelData(event: LoadModelDataEvent) {
particles.clear()
typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.DOMAIN, "leaf_texture_mappings.cfg"))
}
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
val allTypes = (typeMappings.mappings.map { it.type } + "default").distinct()
allTypes.forEach { leafType ->
val particleSet = IconSet("betterfoliage", "blocks/falling_leaf_${leafType}_%d").apply { onPreStitch(event.map) }
if (leafType == "default" || particleSet.num > 0) particles[leafType] = particleSet
}
}
@SubscribeEvent
fun handlePostStitch(event: TextureStitchEvent.Post) {
particles.forEach { (_, particleSet) -> particleSet.onPostStitch(event.map) }
}
}
class TextureMatcher {
data class Mapping(val domain: String?, val path: String, val type: String) {
fun matches(iconLocation: ResourceLocation): Boolean {
return (domain == null || domain == iconLocation.namespace) &&
iconLocation.path.stripStart("blocks/").contains(path, ignoreCase = true)
}
}
val mappings: MutableList<Mapping> = mutableListOf()
fun getType(resource: ResourceLocation) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull()
fun getType(iconName: String) = ResourceLocation(iconName).let { getType(it) }
fun loadMappings(mappingLocation: ResourceLocation) {
mappings.clear()
resourceManager[mappingLocation]?.getLines()?.let { lines ->
lines.filter { !it.startsWith("//") }.filter { !it.isEmpty() }.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,70 @@
package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
const val defaultLeafColor = 0
/** Rendering-related information for a leaf block. */
class LeafInfo(
/** The generated round leaf texture. */
val roundLeafTexture: TextureAtlasSprite,
/** Type of the leaf block (configurable by user). */
val leafType: String,
/** Average color of the round leaf texture. */
val averageColor: Int = roundLeafTexture.averageColor ?: defaultLeafColor
) {
/** [IconSet] of the textures to use for leaf particles emitted from this block. */
val particleTextures: IconSet get() = LeafParticleRegistry[leafType]
}
object LeafRegistry : ModelRenderRegistryRoot<LeafInfo>()
object StandardLeafRegistry : ModelRenderRegistryConfigurable<LeafInfo>() {
override val logger = BetterFoliageMod.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.leavesClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.leavesModels.list
override fun processModel(state: IBlockState, textures: List<String>) = StandardLeafKey(logger, textures[0])
}
class StandardLeafKey(override val logger: Logger, val textureName: String) : ModelRenderKey<LeafInfo> {
lateinit var leafType: String
lateinit var generated: ResourceLocation
override fun onPreStitch(atlas: TextureMap) {
val logName = "StandardLeafKey"
leafType = LeafParticleRegistry.typeMappings.getType(textureName) ?: "default"
generated = Client.genLeaves.generatedResource(textureName, "type" to leafType)
atlas.registerSprite(generated)
logger.log(Level.DEBUG, "$logName: leaf texture $textureName")
logger.log(Level.DEBUG, "$logName: particle $leafType")
}
override fun resolveSprites(atlas: TextureMap) = LeafInfo(atlas[generated] ?: atlas.missingSprite, leafType)
}

View File

@@ -0,0 +1,11 @@
@file:JvmName("Utils")
package mods.betterfoliage.client.texture
fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
val r = (((rgb1 shr 16) and 255) * weight1 + ((rgb2 shr 16) and 255) * weight2) / (weight1 + weight2)
val g = (((rgb1 shr 8) and 255) * weight1 + ((rgb2 shr 8) and 255) * weight2) / (weight1 + weight2)
val b = ((rgb1 and 255) * weight1 + (rgb2 and 255) * weight2) / (weight1 + weight2)
val a = (rgb1 shr 24) and 255
val result = ((a shl 24) or (r shl 16) or (g shl 8) or b).toInt()
return result
}

View File

@@ -0,0 +1,138 @@
package mods.betterfoliage.loader
import mods.octarinecore.metaprog.Transformer
import mods.octarinecore.metaprog.allAvailable
import net.minecraftforge.fml.relauncher.FMLLaunchHandler
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import org.objectweb.asm.Opcodes.*
class BetterFoliageTransformer : Transformer() {
val isOptifinePresent = allAvailable(Refs.OptifineClassTransformer)
init {
if (FMLLaunchHandler.side().isClient) setupClient()
}
fun setupClient() {
// where: WorldClient.showBarrierParticles(), right after invoking Block.randomDisplayTick
// what: invoke BF code for every random display tick
// why: allows us to catch random display ticks, without touching block code
transformMethod(Refs.showBarrierParticles) {
find(invokeRef(Refs.randomDisplayTick))?.insertAfter {
log.info("[BetterFoliageLoader] Applying random display tick call hook")
varinsn(ALOAD, 0)
varinsn(ALOAD, 11)
varinsn(ALOAD, 7)
invokeStatic(Refs.onRandomDisplayTick)
} ?: log.warn("[BetterFoliageLoader] Failed to apply random display tick call hook!")
}
// where: BlockStateContainer$StateImplementation.getAmbientOcclusionLightValue()
// what: invoke BF code to overrule AO transparency value
// why: allows us to have light behave properly on non-solid log blocks
transformMethod(Refs.getAmbientOcclusionLightValue) {
find(FRETURN)?.insertBefore {
log.info("[BetterFoliageLoader] Applying getAmbientOcclusionLightValue() override")
varinsn(ALOAD, 0)
invokeStatic(Refs.getAmbientOcclusionLightValueOverride)
} ?: log.warn("[BetterFoliageLoader] Failed to apply getAmbientOcclusionLightValue() override!")
}
// where: BlockStateContainer$StateImplementation.useNeighborBrightness()
// what: invoke BF code to overrule _useNeighborBrightness_
// why: allows us to have light behave properly on non-solid log blocks
transformMethod(Refs.useNeighborBrightness) {
find(IRETURN)?.insertBefore {
log.info("[BetterFoliageLoader] Applying useNeighborBrightness() override")
varinsn(ALOAD, 0)
invokeStatic(Refs.useNeighborBrightnessOverride)
} ?: log.warn("[BetterFoliageLoader] Failed to apply useNeighborBrightness() override!")
}
// where: BlockStateContainer$StateImplementation.doesSideBlockRendering()
// what: invoke BF code to overrule condition
// why: allows us to make log blocks non-solid
transformMethod(Refs.doesSideBlockRendering) {
find(IRETURN)?.insertBefore {
log.info("[BetterFoliageLoader] Applying doesSideBlockRendering() override")
varinsn(ALOAD, 1)
varinsn(ALOAD, 2)
varinsn(ALOAD, 3)
invokeStatic(Refs.doesSideBlockRenderingOverride)
} ?: log.warn("[BetterFoliageLoader] Failed to apply doesSideBlockRendering() override!")
}
// where: BlockStateContainer$StateImplementation.isOpaqueCube()
// what: invoke BF code to overrule condition
// why: allows us to make log blocks non-solid
transformMethod(Refs.isOpaqueCube) {
find(IRETURN)?.insertBefore {
log.info("[BetterFoliageLoader] Applying isOpaqueCube() override")
varinsn(ALOAD, 0)
invokeStatic(Refs.isOpaqueCubeOverride)
} ?: log.warn("[BetterFoliageLoader] Failed to apply isOpaqueCube() override!")
}
// where: ModelLoader.setupModelRegistry(), right before the textures are loaded
// what: invoke handler code with ModelLoader instance
// why: allows us to iterate the unbaked models in ModelLoader in time to register textures
transformMethod(Refs.setupModelRegistry) {
find(invokeName("addAll"))?.insertAfter {
log.info("[BetterFoliageLoader] Applying ModelLoader lifecycle callback")
varinsn(ALOAD, 0)
invokeStatic(Refs.onAfterLoadModelDefinitions)
} ?: log.warn("[BetterFoliageLoader] Failed to apply ModelLoader lifecycle callback!")
}
// where: RenderChunk.rebuildChunk()
// what: replace call to BlockRendererDispatcher.renderBlock()
// why: allows us to perform additional rendering for each block
// what: invoke code to overrule result of Block.canRenderInLayer()
// why: allows us to render transparent quads for blocks which are only on the SOLID layer
transformMethod(Refs.rebuildChunk) {
applyWriterFlags(COMPUTE_FRAMES, COMPUTE_MAXS)
find(invokeRef(Refs.renderBlock))?.replace {
log.info("[BetterFoliageLoader] Applying RenderChunk block render override")
varinsn(ALOAD, if (isOptifinePresent) 22 else 20)
invokeStatic(Refs.renderWorldBlock)
}
if (isOptifinePresent) {
find(varinsn(ISTORE, 23))?.insertAfter {
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (Optifine)")
varinsn(ALOAD, 19)
varinsn(ALOAD, 18)
varinsn(ALOAD, 22)
invokeStatic(Refs.canRenderBlockInLayer)
varinsn(ISTORE, 23)
}
} else {
find(invokeRef(Refs.canRenderInLayer))?.replace {
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (non-Optifine)")
invokeStatic(Refs.canRenderBlockInLayer)
}
}
}
// where: net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace
// what: make constructor public
// why: use vanilla AO calculation at will without duplicating code
transformMethod(Refs.AOF_constructor) {
log.info("[BetterFoliageLoader] Setting AmbientOcclusionFace constructor public")
makePublic()
}
// where: shadersmod.client.SVertexBuilder.pushEntity()
// what: invoke code to overrule block data
// why: allows us to change the block ID seen by shader programs
transformMethod(Refs.pushEntity_state) {
find(invokeRef(Refs.pushEntity_num))?.insertBefore {
log.info("[BetterFoliageLoader] Applying SVertexBuilder.pushEntity() block ID override")
varinsn(ALOAD, 0)
invokeStatic(Refs.getBlockIdOverride)
} ?: log.warn("[BetterFoliageLoader] Failed to apply SVertexBuilder.pushEntity() block ID override!")
}
}
}

View File

@@ -0,0 +1,117 @@
package mods.betterfoliage.loader
import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.FieldRef
import mods.octarinecore.metaprog.MethodRef
import net.minecraftforge.fml.relauncher.FMLInjectionData
/** Singleton object holding references to foreign code elements. */
object Refs {
val mcVersion = FMLInjectionData.data()[4].toString()
// Java
val String = ClassRef("java.lang.String")
val Map = ClassRef("java.util.Map")
val List = ClassRef("java.util.List")
val Random = ClassRef("java.util.Random")
// Minecraft
val IBlockAccess = ClassRef("net.minecraft.world.IBlockAccess")
val IBlockState = ClassRef("net.minecraft.block.state.IBlockState")
val BlockStateBase = ClassRef("net.minecraft.block.state.BlockStateBase")
val BlockPos = ClassRef("net.minecraft.util.math.BlockPos")
val MutableBlockPos = ClassRef("net.minecraft.util.math.BlockPos\$MutableBlockPos")
val BlockRenderLayer = ClassRef("net.minecraft.util.BlockRenderLayer")
val EnumFacing = ClassRef("net.minecraft.util.EnumFacing")
val World = ClassRef("net.minecraft.world.World")
val WorldClient = ClassRef("net.minecraft.client.multiplayer.WorldClient")
val ChunkCache = ClassRef("net.minecraft.world.ChunkCache")
val showBarrierParticles = MethodRef(WorldClient, "showBarrierParticles", "func_184153_a", ClassRef.void, ClassRef.int, ClassRef.int, ClassRef.int, ClassRef.int, Random, ClassRef.boolean, MutableBlockPos)
val Block = ClassRef("net.minecraft.block.Block")
val StateImplementation = ClassRef("net.minecraft.block.state.BlockStateContainer\$StateImplementation")
val canRenderInLayer = MethodRef(Block, "canRenderInLayer", ClassRef.boolean, IBlockState, BlockRenderLayer)
val getAmbientOcclusionLightValue = MethodRef(StateImplementation, "getAmbientOcclusionLightValue", "func_185892_j", ClassRef.float)
val useNeighborBrightness = MethodRef(StateImplementation, "useNeighborBrightness", "func_185916_f", ClassRef.boolean)
val doesSideBlockRendering = MethodRef(StateImplementation, "doesSideBlockRendering", ClassRef.boolean, IBlockAccess, BlockPos, EnumFacing)
val isOpaqueCube = MethodRef(StateImplementation, "isOpaqueCube", "func_185914_p", ClassRef.boolean)
val randomDisplayTick = MethodRef(Block, "randomDisplayTick", "func_180655_c", ClassRef.void, IBlockState, World, BlockPos, Random)
val BlockModelRenderer = ClassRef("net.minecraft.client.renderer.BlockModelRenderer")
val AmbientOcclusionFace = ClassRef("net.minecraft.client.renderer.BlockModelRenderer\$AmbientOcclusionFace")
val ChunkCompileTaskGenerator = ClassRef("net.minecraft.client.renderer.chunk.ChunkCompileTaskGenerator")
val BufferBuilder = ClassRef("net.minecraft.client.renderer.BufferBuilder")
val AOF_constructor = MethodRef(AmbientOcclusionFace, "<init>", ClassRef.void, BlockModelRenderer)
val RenderChunk = ClassRef("net.minecraft.client.renderer.chunk.RenderChunk")
val rebuildChunk = MethodRef(RenderChunk, "rebuildChunk", "func_178581_b", ClassRef.void, ClassRef.float, ClassRef.float, ClassRef.float, ChunkCompileTaskGenerator)
val BlockRendererDispatcher = ClassRef("net.minecraft.client.renderer.BlockRendererDispatcher")
val renderBlock = MethodRef(BlockRendererDispatcher, "renderBlock", "func_175018_a", ClassRef.boolean, IBlockState, BlockPos, IBlockAccess, BufferBuilder)
val TextureAtlasSprite = ClassRef("net.minecraft.client.renderer.texture.TextureAtlasSprite")
val IRegistry = ClassRef("net.minecraft.util.registry.IRegistry")
val ModelLoader = ClassRef("net.minecraftforge.client.model.ModelLoader")
val stateModels = FieldRef(ModelLoader, "stateModels", Map)
val setupModelRegistry = MethodRef(ModelLoader, "setupModelRegistry", "func_177570_a", IRegistry)
val IModel = ClassRef("net.minecraftforge.client.model.IModel")
val ModelBlock = ClassRef("net.minecraft.client.renderer.block.model.ModelBlock")
val ResourceLocation = ClassRef("net.minecraft.util.ResourceLocation")
val ModelResourceLocation = ClassRef("net.minecraft.client.renderer.block.model.ModelResourceLocation")
val VanillaModelWrapper = ClassRef("net.minecraftforge.client.model.ModelLoader\$VanillaModelWrapper")
val model_VMW = FieldRef(VanillaModelWrapper, "model", ModelBlock)
val location_VMW = FieldRef(VanillaModelWrapper, "location", ModelBlock)
val WeightedRandomModel = ClassRef("net.minecraftforge.client.model.ModelLoader\$WeightedRandomModel")
val models_WRM = FieldRef(WeightedRandomModel, "models", List)
val MultiModel = ClassRef("net.minecraftforge.client.model.MultiModel")
val base_MM = FieldRef(MultiModel, "base", IModel)
val MultipartModel = ClassRef("net.minecraftforge.client.model.ModelLoader\$MultipartModel")
val partModels_MPM = FieldRef(MultipartModel, "partModels", List)
val BakedQuad = ClassRef("net.minecraft.client.renderer.block.model.BakedQuad")
val resetChangedState = MethodRef(ClassRef("net.minecraftforge.common.config.Configuration"), "resetChangedState", ClassRef.void)
// Better Foliage
val BetterFoliageHooks = ClassRef("mods.betterfoliage.client.Hooks")
val getAmbientOcclusionLightValueOverride = MethodRef(BetterFoliageHooks, "getAmbientOcclusionLightValueOverride", ClassRef.float, ClassRef.float, IBlockState)
val useNeighborBrightnessOverride = MethodRef(BetterFoliageHooks, "getUseNeighborBrightnessOverride", ClassRef.boolean, ClassRef.boolean, IBlockState)
val doesSideBlockRenderingOverride = MethodRef(BetterFoliageHooks, "doesSideBlockRenderingOverride", ClassRef.boolean, ClassRef.boolean, IBlockAccess, BlockPos, EnumFacing)
val isOpaqueCubeOverride = MethodRef(BetterFoliageHooks, "isOpaqueCubeOverride", ClassRef.boolean, ClassRef.boolean, IBlockState)
val onRandomDisplayTick = MethodRef(BetterFoliageHooks, "onRandomDisplayTick", ClassRef.void, World, IBlockState, BlockPos)
val onAfterLoadModelDefinitions = MethodRef(BetterFoliageHooks, "onAfterLoadModelDefinitions", ClassRef.void, ModelLoader)
val onAfterBakeModels = MethodRef(BetterFoliageHooks, "onAfterBakeModels", ClassRef.void, Map)
val renderWorldBlock = MethodRef(BetterFoliageHooks, "renderWorldBlock", ClassRef.boolean, BlockRendererDispatcher, IBlockState, BlockPos, IBlockAccess, BufferBuilder, BlockRenderLayer)
val canRenderBlockInLayer = MethodRef(BetterFoliageHooks, "canRenderBlockInLayer", ClassRef.boolean, Block, IBlockState, BlockRenderLayer)
// Optifine
val OptifineClassTransformer = ClassRef("optifine.OptiFineClassTransformer")
val OptifineChunkCache = ClassRef("net.optifine.override.ChunkCacheOF")
val CCOFChunkCache = FieldRef(OptifineChunkCache, "chunkCache", ChunkCache)
val getBlockId = MethodRef(BlockStateBase, "getBlockId", ClassRef.int);
val getMetadata = MethodRef(BlockStateBase, "getMetadata", ClassRef.int);
// Optifine
val RenderEnv = ClassRef("net.optifine.render.RenderEnv")
val RenderEnv_reset = MethodRef(RenderEnv, "reset", ClassRef.void, IBlockState, BlockPos)
val quadSprite = FieldRef(BufferBuilder, "quadSprite", TextureAtlasSprite)
// Optifine: custom colors
val CustomColors = ClassRef("net.optifine.CustomColors")
val getColorMultiplier = MethodRef(CustomColors, "getColorMultiplier", ClassRef.int, BakedQuad, IBlockState, IBlockAccess, BlockPos, RenderEnv)
// Optifine: shaders
val SVertexBuilder = ClassRef("net.optifine.shaders.SVertexBuilder")
val sVertexBuilder = FieldRef(BufferBuilder, "sVertexBuilder", SVertexBuilder)
val pushEntity_state = MethodRef(SVertexBuilder, "pushEntity", ClassRef.void, IBlockState, BlockPos, IBlockAccess, BufferBuilder)
val pushEntity_num = MethodRef(SVertexBuilder, "pushEntity", ClassRef.void, ClassRef.long)
val popEntity = MethodRef(SVertexBuilder, "popEntity", ClassRef.void)
val ShadersModIntegration = ClassRef("mods.betterfoliage.client.integration.ShadersModIntegration")
val getBlockIdOverride = MethodRef(ShadersModIntegration, "getBlockIdOverride", ClassRef.long, ClassRef.long, IBlockState)
}

View File

@@ -0,0 +1,120 @@
@file:JvmName("Utils")
@file:Suppress("NOTHING_TO_INLINE")
package mods.octarinecore
import mods.betterfoliage.loader.Refs
import net.minecraft.tileentity.TileEntity
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ChunkCache
import net.minecraft.world.IBlockAccess
import net.minecraft.world.World
import kotlin.reflect.KProperty
import java.lang.Math.*
const val PI2 = 2.0 * PI
/** Strip the given prefix off the start of the string, if present */
inline fun String.stripStart(str: String) = if (startsWith(str)) substring(str.length) else this
/** Strip the given prefix off the start of the resource path, if present */
inline fun ResourceLocation.stripStart(str: String) = ResourceLocation(namespace, path.stripStart(str))
/** Mutating version of _map_. Replace each element of the list with the result of the given transformation. */
inline fun <reified T> MutableList<T>.replace(transform: (T) -> T) = forEachIndexed { idx, value -> this[idx] = transform(value) }
/** Exchange the two elements of the list with the given indices */
inline fun <T> MutableList<T>.exchange(idx1: Int, idx2: Int) {
val e = this[idx1]
this[idx1] = this[idx2]
this[idx2] = e
}
/** Cross product of this [Iterable] with the parameter. */
fun <A, B> Iterable<A>.cross(other: Iterable<B>) = flatMap { a -> other.map { b -> a to b } }
inline fun <C, R, T> Iterable<T>.mapAs(transform: (C) -> R) = map { transform(it as C) }
inline fun <T1, T2> forEachNested(list1: Iterable<T1>, list2: Iterable<T2>, func: (T1, T2)-> Unit) =
list1.forEach { e1 ->
list2.forEach { e2 ->
func(e1, e2)
}
}
@Suppress("UNCHECKED_CAST")
inline fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>
inline fun <reified T, R> Iterable<T>.findFirst(func: (T)->R?): R? {
forEach { func(it)?.let { return it } }
return null
}
/**
* Property-level delegate backed by a [ThreadLocal].
*
* @param[init] Lambda to get initial value
*/
class ThreadLocalDelegate<T>(init: () -> T) {
var tlVal = ThreadLocal.withInitial(init)
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = tlVal.get()
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { tlVal.set(value) }
}
/**
* Starting with the second element of this [Iterable] until the last, call the supplied lambda with
* the parameters (index, element, previous element).
*/
inline fun <reified T> Iterable<T>.forEachPairIndexed(func: (Int, T, T)->Unit) {
var previous: T? = null
forEachIndexed { idx, current ->
if (previous != null) func(idx, current, previous!!)
previous = current
}
}
/** Call the supplied lambda and return its result, or the given default value if an exception is thrown. */
fun <T> tryDefault(default: T, work: ()->T) = try { work() } catch (e: Throwable) { default }
/** Return a random [Double] value between the given two limits (inclusive min, exclusive max). */
fun random(min: Double, max: Double) = Math.random().let { min + (max - min) * it }
fun semiRandom(x: Int, y: Int, z: Int, seed: Int): Int {
var value = (x * x + y * y + z * z + x * y + y * z + z * x + (seed * seed)) and 63
value = (3 * x * value + 5 * y * value + 7 * z * value + (11 * seed)) and 63
return value
}
/**
* Return this [Double] value if it lies between the two limits. If outside, return the
* minimum/maximum value correspondingly.
*/
fun Double.minmax(minVal: Double, maxVal: Double) = min(max(this, minVal), maxVal)
/**
* Return this [Int] value if it lies between the two limits. If outside, return the
* minimum/maximum value correspondingly.
*/
fun Int.minmax(minVal: Int, maxVal: Int) = min(max(this, minVal), maxVal)
fun nextPowerOf2(x: Int): Int {
return 1 shl (if (x == 0) 0 else 32 - Integer.numberOfLeadingZeros(x - 1))
}
/**
* Check if the Chunk containing the given [BlockPos] is loaded.
* Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances.
*/
fun IBlockAccess.isBlockLoaded(pos: BlockPos) = when {
this is World -> isBlockLoaded(pos, false)
this is ChunkCache -> world.isBlockLoaded(pos, false)
Refs.OptifineChunkCache.isInstance(this) -> (Refs.CCOFChunkCache.get(this) as ChunkCache).world.isBlockLoaded(pos, false)
else -> false
}
/**
* Get the [TileEntity] at the given position, suppressing exceptions.
* Also returns null if the chunk is unloaded, which can happen because of multithreaded rendering.
*/
fun IBlockAccess.getTileEntitySafe(pos: BlockPos): TileEntity? = tryDefault(null as TileEntity?) {
if (isBlockLoaded(pos)) getTileEntity(pos) else null
}

View File

@@ -0,0 +1,22 @@
package mods.octarinecore.client
import net.minecraft.client.settings.KeyBinding
import net.minecraftforge.fml.client.registry.ClientRegistry
import net.minecraftforge.fml.common.FMLCommonHandler
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.common.gameevent.InputEvent
class KeyHandler(val modId: String, val defaultKey: Int, val lang: String, val action: (InputEvent.KeyInputEvent)->Unit) {
val keyBinding = KeyBinding(lang, defaultKey, modId)
init {
ClientRegistry.registerKeyBinding(keyBinding)
FMLCommonHandler.instance().bus().register(this)
}
@SubscribeEvent
fun handleKeyPress(event: InputEvent.KeyInputEvent) {
if (keyBinding.isPressed) action(event)
}
}

View File

@@ -0,0 +1,61 @@
package mods.octarinecore.client.gui
import net.minecraft.client.gui.GuiScreen
import net.minecraft.client.resources.I18n
import net.minecraft.util.text.TextFormatting.*
import net.minecraftforge.fml.client.config.*
/**
* Base class for a config GUI element.
* The GUI representation is a list of toggleable objects.
* The config representation is an integer list of the selected objects' IDs.
*/
abstract class IdListConfigEntry<T>(
owningScreen: GuiConfig,
owningEntryList: GuiConfigEntries,
configElement: IConfigElement
) : GuiConfigEntries.CategoryEntry(owningScreen, owningEntryList, configElement) {
/** Create the child GUI elements. */
fun createChildren() = baseSet.map {
ItemWrapperElement(it, it.itemId in configElement.list, it.itemId in configElement.defaults)
}
init { stripTooltipDefaultText(toolTip as MutableList<String>) }
override fun buildChildScreen(): GuiScreen {
return GuiConfig(
this.owningScreen,
createChildren(),
this.owningScreen.modID,
owningScreen.allRequireWorldRestart || this.configElement.requiresWorldRestart(),
owningScreen.allRequireMcRestart || this.configElement.requiresMcRestart(),
this.owningScreen.title,
(if (this.owningScreen.titleLine2 == null) "" else this.owningScreen.titleLine2) + " > " + this.name)
}
override fun saveConfigElement(): Boolean {
val requiresRestart = (childScreen as GuiConfig).entryList.saveConfigElements()
val children = (childScreen as GuiConfig).configElements as List<ItemWrapperElement>
val ids = children.filter { it.booleanValue == true }.map { it.item.itemId }
configElement.set(ids.sorted().toTypedArray())
return requiresRestart
}
abstract val baseSet: List<T>
abstract val T.itemId: Int
abstract val T.itemName: String
/** Child config GUI element of a single toggleable object. */
inner class ItemWrapperElement(val item: T, value: Boolean, val default: Boolean) :
DummyConfigElement(item.itemName, default, ConfigGuiType.BOOLEAN, item.itemName) {
init {
this.value = value
this.defaultValue = default
}
override fun getComment() = I18n.format("${configElement.languageKey}.tooltip.element", "${GOLD}${item.itemName}${YELLOW}")
val booleanValue: Boolean get() = defaultValue as Boolean
}
}

View File

@@ -0,0 +1,25 @@
package mods.octarinecore.client.gui
import net.minecraft.client.resources.I18n
import net.minecraft.util.text.TextFormatting.*
import net.minecraftforge.fml.client.config.GuiConfig
import net.minecraftforge.fml.client.config.GuiConfigEntries
import net.minecraftforge.fml.client.config.IConfigElement
class NonVerboseArrayEntry(
owningScreen: GuiConfig,
owningEntryList: GuiConfigEntries,
configElement: IConfigElement
) : GuiConfigEntries.ArrayEntry(owningScreen, owningEntryList, configElement) {
init {
stripTooltipDefaultText(toolTip as MutableList<String>)
val shortDefaults = I18n.format("${configElement.languageKey}.arrayEntry", configElement.defaults.size)
toolTip.addAll(mc.fontRenderer.listFormattedStringToWidth("$AQUA${I18n.format("fml.configgui.tooltip.default", shortDefaults)}", 300))
}
override fun updateValueButtonText() {
btnValue.displayString = I18n.format("${configElement.languageKey}.arrayEntry", currentValues.size)
}
}

View File

@@ -0,0 +1,22 @@
@file:JvmName("Utils")
package mods.octarinecore.client.gui
import net.minecraft.util.text.Style
import net.minecraft.util.text.TextComponentString
import net.minecraft.util.text.TextFormatting
import net.minecraft.util.text.TextFormatting.AQUA
import net.minecraft.util.text.TextFormatting.GRAY
fun stripTooltipDefaultText(tooltip: MutableList<String>) {
var defaultRows = false
val iter = tooltip.iterator()
while (iter.hasNext()) {
if (iter.next().startsWith(AQUA.toString())) defaultRows = true
if (defaultRows) iter.remove()
}
}
fun textComponent(msg: String, color: TextFormatting = GRAY): TextComponentString {
val style = Style().apply { this.color = color }
return TextComponentString(msg).apply { this.style = style }
}

View File

@@ -0,0 +1,122 @@
@file:JvmName("RendererHolder")
package mods.octarinecore.client.render
import mods.betterfoliage.client.render.canRenderInCutout
import mods.betterfoliage.client.render.canRenderInLayer
import mods.betterfoliage.client.render.isCutout
import mods.octarinecore.ThreadLocalDelegate
import mods.octarinecore.client.resource.ResourceHandler
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3
import mods.octarinecore.common.forgeDirOffsets
import mods.octarinecore.common.plus
import mods.octarinecore.semiRandom
import net.minecraft.block.Block
import net.minecraft.block.state.IBlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.color.BlockColors
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.IBlockAccess
import net.minecraft.world.biome.Biome
import kotlin.math.abs
/**
* [ThreadLocal] instance of [BlockContext] representing the block being rendered.
*/
val blockContext by ThreadLocalDelegate { BlockContext() }
/**
* [ThreadLocal] instance of [ModelRenderer].
*/
val modelRenderer by ThreadLocalDelegate { ModelRenderer() }
val blockColors = ThreadLocal<BlockColors>()
abstract class AbstractBlockRenderingHandler(modId: String) : ResourceHandler(modId) {
open val addToCutout: Boolean get() = true
// ============================
// Custom rendering
// ============================
abstract fun isEligible(ctx: BlockContext): Boolean
abstract fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean
// ============================
// Vanilla rendering wrapper
// ============================
/**
* Render the block in the current [BlockContext]
*/
fun renderWorldBlockBase(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer?): Boolean {
ctx.blockState(Int3.zero).let { state ->
if (layer == null ||
state.canRenderInLayer(layer) ||
(state.canRenderInCutout() && layer.isCutout)) {
return dispatcher.renderBlock(state, ctx.pos, ctx.world, renderer)
}
}
return false
}
}
data class BlockData(val state: IBlockState, val color: Int, val brightness: Int)
/**
* Represents the block being rendered. Has properties and methods to query the neighborhood of the block in
* block-relative coordinates.
*/
class BlockContext(
var world: IBlockAccess? = null,
var pos: BlockPos = BlockPos.ORIGIN
) {
fun set(world: IBlockAccess, pos: BlockPos) { this.world = world; this.pos = pos; }
val block: Block get() = block(Int3.zero)
fun block(offset: Int3) = blockState(offset).block
fun blockState(offset: Int3) = (pos + offset).let { world!!.getBlockState(it) }
fun blockData(offset: Int3) = (pos + offset).let { pos ->
world!!.getBlockState(pos).let { state ->
BlockData(
state,
Minecraft.getMinecraft().blockColors.colorMultiplier(state, world!!, pos, 0),
state.block.getPackedLightmapCoords(state, world!!, pos)
)
}
}
/** Get the biome ID at the block position. */
val biomeId: Int get() = Biome.getIdForBiome(world!!.getBiome(pos))
/** Get the centerpoint of the block being rendered. */
val blockCenter: Double3 get() = Double3(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5)
val chunkBase: Double3 get() {
val cX = if (pos.x >= 0) pos.x / 16 else (pos.x + 1) / 16 - 1
val cY = pos.y / 16
val cZ = if (pos.z >= 0) pos.z / 16 else (pos.z + 1) / 16 - 1
return Double3(cX * 16.0, cY * 16.0, cZ * 16.0)
}
/** Is the block surrounded by other blocks that satisfy the predicate on all sides? */
fun isSurroundedBy(predicate: (IBlockState)->Boolean) = forgeDirOffsets.all { predicate(blockState(it)) }
/** Get a semi-random value based on the block coordinate and the given seed. */
fun random(seed: Int) = semiRandom(pos.x, pos.y, pos.z, seed)
/** Get an array of semi-random values based on the block coordinate. */
fun semiRandomArray(num: Int): Array<Int> = Array(num) { random(it) }
/** Get the distance of the block from the camera (player). */
val cameraDistance: Int get() {
val camera = Minecraft.getMinecraft().renderViewEntity ?: return 0
return abs(pos.x - MathHelper.floor(camera.posX)) +
abs(pos.y - MathHelper.floor(camera.posY)) +
abs(pos.z - MathHelper.floor(camera.posZ))
}
}

View File

@@ -0,0 +1,125 @@
package mods.octarinecore.client.render
import mods.octarinecore.PI2
import mods.octarinecore.common.Double3
import net.minecraft.client.Minecraft
import net.minecraft.client.particle.Particle
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.entity.Entity
import net.minecraft.world.World
abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : Particle(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 onUpdate() {
super.onUpdate()
currentPos.setTo(posX, posY, posZ)
prevPos.setTo(prevPosX, prevPosY, prevPosZ)
velocity.setTo(motionX, motionY, motionZ)
update()
posX = currentPos.x; posY = currentPos.y; posZ = currentPos.z;
motionX = velocity.x; motionY = velocity.y; motionZ = velocity.z;
}
/** Render the particle. */
abstract fun render(worldRenderer: BufferBuilder, partialTickTime: Float)
/** 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) Minecraft.getMinecraft().effectRenderer.addEffect(this) }
override fun renderParticle(worldRenderer: BufferBuilder, entity: Entity, partialTickTime: Float, rotX: Float, rotZ: Float, rotYZ: Float, rotXY: Float, rotXZ: Float) {
billboardRot.first.setTo(rotX + rotXY, rotZ, rotYZ + rotXZ)
billboardRot.second.setTo(rotX - rotXY, -rotZ, rotYZ - rotXZ)
render(worldRenderer, partialTickTime)
}
/**
* Render a particle quad.
*
* @param[tessellator] the [Tessellator] instance to use
* @param[partialTickTime] partial tick time
* @param[currentPos] render position
* @param[prevPos] previous tick position for interpolation
* @param[size] particle size
* @param[rotation] viewpoint-dependent particle rotation (64 steps)
* @param[icon] particle texture
* @param[isMirrored] mirror particle texture along V-axis
* @param[alpha] aplha blending
*/
fun renderParticleQuad(worldRenderer: BufferBuilder,
partialTickTime: Float,
currentPos: Double3 = this.currentPos,
prevPos: Double3 = this.prevPos,
size: Double = particleScale.toDouble(),
rotation: Int = 0,
icon: TextureAtlasSprite = particleTexture,
isMirrored: Boolean = false,
alpha: Float = this.particleAlpha) {
val minU = (if (isMirrored) icon.minU else icon.maxU).toDouble()
val maxU = (if (isMirrored) icon.maxU else icon.minU).toDouble()
val minV = icon.minV.toDouble()
val maxV = icon.maxV.toDouble()
val center = currentPos.copy().sub(prevPos).mul(partialTickTime.toDouble()).add(prevPos).sub(interpPosX, interpPosY, interpPosZ)
val v1 = if (rotation == 0) billboardRot.first * size else
Double3.weight(billboardRot.first, cos[rotation and 63] * size, billboardRot.second, sin[rotation and 63] * size)
val v2 = if (rotation == 0) billboardRot.second * size else
Double3.weight(billboardRot.first, -sin[rotation and 63] * size, billboardRot.second, cos[rotation and 63] * size)
val renderBrightness = this.getBrightnessForRender(partialTickTime)
val brLow = renderBrightness shr 16 and 65535
val brHigh = renderBrightness and 65535
worldRenderer
.pos(center.x - v1.x, center.y - v1.y, center.z - v1.z)
.tex(maxU, maxV)
.color(particleRed, particleGreen, particleBlue, alpha)
.lightmap(brLow, brHigh)
.endVertex()
worldRenderer
.pos(center.x - v2.x, center.y - v2.y, center.z - v2.z)
.tex(maxU, minV)
.color(particleRed, particleGreen, particleBlue, alpha)
.lightmap(brLow, brHigh)
.endVertex()
worldRenderer
.pos(center.x + v1.x, center.y + v1.y, center.z + v1.z)
.tex(minU, minV)
.color(particleRed, particleGreen, particleBlue, alpha)
.lightmap(brLow, brHigh)
.endVertex()
worldRenderer
.pos(center.x + v2.x, center.y + v2.y, center.z + v2.z)
.tex(minU, maxV)
.color(particleRed, particleGreen, particleBlue, alpha)
.lightmap(brLow, brHigh)
.endVertex()
}
override fun getFXLayer() = 1
fun setColor(color: Int) {
particleBlue = (color and 255) / 256.0f
particleGreen = ((color shr 8) and 255) / 256.0f
particleRed = ((color shr 16) and 255) / 256.0f
}
}

View File

@@ -0,0 +1,146 @@
package mods.octarinecore.client.render
import mods.octarinecore.common.*
import mods.octarinecore.minmax
import mods.octarinecore.replace
import net.minecraft.util.EnumFacing
import java.lang.Math.max
import java.lang.Math.min
/**
* Vertex UV coordinates
*
* Zero-centered: coordinates fall between (-0.5, 0.5) (inclusive)
*/
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)
}
/**
* Model vertex
*
* @param[xyz] x, y, z coordinates
* @param[uv] u, v coordinates
* @param[aoShader] [Shader] instance to use with AO rendering
* @param[flatShader] [Shader] instance to use with non-AO rendering
*/
data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0),
val uv: UV = UV(0.0, 0.0),
val aoShader: Shader = NoShader,
val flatShader: Shader = NoShader)
/**
* Model quad
*/
data class Quad(val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex) {
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 =
Quad(trans(v1, 0), trans(v2, 1), trans(v3, 2), 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, EnumFacing>) = 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 scaleUV (scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) }
fun rotate(rot: Rotation) = transformV {
it.copy(xyz = it.xyz.rotate(rot), aoShader = it.aoShader.rotate(rot), flatShader = it.flatShader.rotate(rot))
}
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 setAoShader(factory: ShaderFactory, predicate: (Vertex, Int)->Boolean = { v, vi -> true }) =
transformVI { vertex, idx ->
if (!predicate(vertex, idx)) vertex else vertex.copy(aoShader = factory(this@Quad, vertex))
}
fun setFlatShader(factory: ShaderFactory, predicate: (Vertex, Int)->Boolean = { v, vi -> true }) =
transformVI { vertex, idx ->
if (!predicate(vertex, idx)) vertex else vertex.copy(flatShader = factory(this@Quad, vertex))
}
fun setFlatShader(shader: Shader) = transformVI { vertex, idx -> vertex.copy(flatShader = shader) }
val flipped: Quad get() = Quad(v4, v3, v2, v1)
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()
}
}
/**
* Model. The basic unit of rendering blocks with OctarineCore.
*
* 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).
*/
class Model() {
constructor(other: List<Quad>) : this() { quads.addAll(other) }
val quads = mutableListOf<Quad>()
fun Quad.add() = quads.add(this)
fun Iterable<Quad>.addAll() = forEach { quads.add(it) }
fun transformQ(trans: (Quad)->Quad) = quads.replace(trans)
fun transformV(trans: (Vertex)->Vertex) = quads.replace{ it.transformV(trans) }
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: EnumFacing): Quad {
val base = face.vec * 0.5
val top = faceCorners[face.ordinal].topLeft.first.vec * 0.5
val left = faceCorners[face.ordinal].topLeft.second.vec * 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)
)
}
}
val fullCube = Model().apply {
forgeDirs.forEach {
faceQuad(it)
.setAoShader(faceOrientedAuto(corner = cornerAo(it.axis), edge = null))
.setFlatShader(faceOrientedAuto(corner = cornerFlat, edge = null))
.add()
}
}

View File

@@ -0,0 +1,181 @@
package mods.octarinecore.client.render
import mods.betterfoliage.loader.Refs
import mods.octarinecore.common.*
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.EnumFacing
import net.minecraft.util.EnumFacing.*
typealias QuadIconResolver = (ShadingContext, Int, Quad) -> TextureAtlasSprite?
typealias PostProcessLambda = RenderVertex.(ShadingContext, Int, Quad, Int, Vertex) -> Unit
class ModelRenderer : ShadingContext() {
/** Holds final vertex data before it goes to the [Tessellator]. */
val temp = RenderVertex()
/**
* Render a [Model].
* The [blockContext] and [renderBlocks] need to be set up correctly, including first rendering the
* corresponding block to capture shading values!
*
* @param[model] model to render
* @param[rot] rotation to apply to the model
* @param[trans] translation to apply to the model
* @param[forceFlat] force flat shading even if AO is enabled
* @param[icon] lambda to resolve the texture to use for each quad
* @param[rotateUV] lambda to get amount of UV rotation for each quad
* @param[postProcess] lambda to perform arbitrary modifications on the [RenderVertex] just before it goes to the [Tessellator]
*/
fun render(
worldRenderer: BufferBuilder,
model: Model,
rot: Rotation = Rotation.identity,
trans: Double3 = blockContext.blockCenter,
forceFlat: Boolean = false,
quadFilter: (Int, Quad) -> Boolean = { _, _ -> true },
icon: QuadIconResolver,
postProcess: PostProcessLambda
) {
rotation = rot
aoEnabled = Minecraft.isAmbientOcclusionEnabled()
// make sure we have space in the buffer for our quads plus one
worldRenderer.ensureSpaceForQuads(model.quads.size + 1)
model.quads.forEachIndexed { quadIdx, quad ->
if (quadFilter(quadIdx, quad)) {
val drawIcon = icon(this, quadIdx, quad)
if (drawIcon != null) {
// let OptiFine know the texture we're using, so it can
// transform UV coordinates to quad-relative
Refs.quadSprite.set(worldRenderer, drawIcon)
quad.verts.forEachIndexed { vertIdx, vert ->
temp.init(vert).rotate(rotation).translate(trans)
val shader = if (aoEnabled && !forceFlat) vert.aoShader else vert.flatShader
shader.shade(this, temp)
temp.postProcess(this, quadIdx, quad, vertIdx, vert)
temp.setIcon(drawIcon)
worldRenderer
.pos(temp.x, temp.y, temp.z)
.color(temp.red, temp.green, temp.blue, 1.0f)
.tex(temp.u, temp.v)
.lightmap(temp.brightness shr 16 and 65535, temp.brightness and 65535)
.endVertex()
}
}
}
}
}
}
/**
* Queried by [Shader] objects to get rendering-relevant data of the current block in a rotated frame of reference.
*/
open class ShadingContext {
var rotation = Rotation.identity
var aoEnabled = Minecraft.isAmbientOcclusionEnabled()
val aoFaces = Array(6) { AoFaceData(forgeDirs[it]) }
val EnumFacing.aoMultiplier: Float get() = when(this) {
UP -> 1.0f
DOWN -> 0.5f
NORTH, SOUTH -> 0.8f
EAST, WEST -> 0.6f
}
fun updateShading(offset: Int3, predicate: (EnumFacing) -> Boolean = { true }) {
forgeDirs.forEach { if (predicate(it)) aoFaces[it.ordinal].update(offset, multiplier = it.aoMultiplier) }
}
fun aoShading(face: EnumFacing, corner1: EnumFacing, corner2: EnumFacing) =
aoFaces[face.rotate(rotation).ordinal][corner1.rotate(rotation), corner2.rotate(rotation)]
fun blockData(offset: Int3) = blockContext.blockData(offset.rotate(rotation))
}
/**
*
*/
@Suppress("NOTHING_TO_INLINE")
class RenderVertex() {
var x: Double = 0.0
var y: Double = 0.0
var z: Double = 0.0
var u: Double = 0.0
var v: Double = 0.0
var brightness: Int = 0
var red: Float = 0.0f
var green: Float = 0.0f
var blue: Float = 0.0f
val rawData = IntArray(7)
fun init(vertex: Vertex, rot: Rotation, trans: Double3): RenderVertex {
val result = vertex.xyz.rotate(rot) + trans
x = result.x; y = result.y; z = result.z
return this
}
fun init(vertex: Vertex): RenderVertex {
x = vertex.xyz.x; y = vertex.xyz.y; z = vertex.xyz.z;
u = vertex.uv.u; v = vertex.uv.v
return this
}
fun translate(trans: Double3): RenderVertex { x += trans.x; y += trans.y; z += trans.z; return this }
fun rotate(rot: Rotation): RenderVertex {
if (rot === Rotation.identity) return this
val rotX = rot.rotatedComponent(EAST, x, y, z)
val rotY = rot.rotatedComponent(UP, x, y, z)
val rotZ = rot.rotatedComponent(SOUTH, x, y, z)
x = rotX; y = rotY; z = rotZ
return this
}
inline fun rotateUV(n: Int): RenderVertex {
when (n % 4) {
1 -> { val t = v; v = -u; u = t; return this }
2 -> { u = -u; v = -v; return this }
3 -> { val t = -v; v = u; u = t; return this }
else -> { return this }
}
}
inline fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) {
if (mirrorU) u = -u
if (mirrorV) v = -v
}
inline fun setIcon(icon: TextureAtlasSprite): RenderVertex {
u = (icon.maxU - icon.minU) * (u + 0.5) + icon.minU
v = (icon.maxV - icon.minV) * (v + 0.5) + icon.minV
return this
}
inline fun setGrey(level: Float) {
val grey = Math.min((red + green + blue) * 0.333f * level, 1.0f)
red = grey; green = grey; blue = grey
}
inline fun multiplyColor(color: Int) {
red *= (color shr 16 and 255) / 256.0f
green *= (color shr 8 and 255) / 256.0f
blue *= (color and 255) / 256.0f
}
inline fun setColor(color: Int) {
red = (color shr 16 and 255) / 256.0f
green = (color shr 8 and 255) / 256.0f
blue = (color and 255) / 256.0f
}
}
fun BufferBuilder.ensureSpaceForQuads(num: Int) {
rawIntBuffer.position(bufferSize)
growBuffer(num * vertexFormat.size)
}
val allFaces: (EnumFacing) -> Boolean = { true }
val topOnly: (EnumFacing) -> Boolean = { it == UP }
/** Perform no post-processing */
val noPost: PostProcessLambda = { _, _, _, _, _ -> }

View File

@@ -0,0 +1,44 @@
package mods.octarinecore.client.render
import mods.octarinecore.common.Int3
import mods.octarinecore.common.plus
import net.minecraft.util.EnumFacing
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
/**
* 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")
class OffsetBlockAccess(val original: IBlockAccess, val modded: BlockPos, val target: BlockPos) : IBlockAccess {
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 getBiome(pos: BlockPos?) = original.getBiome(actualPos(pos))
override fun getBlockState(pos: BlockPos?) = original.getBlockState(actualPos(pos))
override fun getCombinedLight(pos: BlockPos?, lightValue: Int) = original.getCombinedLight(actualPos(pos), lightValue)
override fun getStrongPower(pos: BlockPos?, direction: EnumFacing?) = original.getStrongPower(actualPos(pos), direction)
override fun getTileEntity(pos: BlockPos?) = original.getTileEntity(actualPos(pos))
override fun getWorldType() = original.worldType
override fun isAirBlock(pos: BlockPos?) = original.isAirBlock(actualPos(pos))
override fun isSideSolid(pos: BlockPos?, side: EnumFacing?, _default: Boolean) = original.isSideSolid(actualPos(pos), side, _default)
}
/**
* Temporarily replaces the [IBlockAccess] used by this [BlockContext] and the corresponding [ExtendedRenderBlocks]
* to use an [OffsetBlockAccess] 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 = world!!
world = OffsetBlockAccess(original, pos + modded, pos + target)
val result = func()
world = original
return result
}

View File

@@ -0,0 +1,69 @@
@file:JvmName("PixelFormat")
package mods.octarinecore.client.render
import java.awt.Color
/** List of bit-shift offsets in packed brightness values where meaningful (4-bit) data is contained. */
var brightnessComponents = listOf(20, 4)
/** Multiply the components of this packed brightness value with the given [Float]. */
infix fun Int.brMul(f: Float): Int {
val weight = (f * 256.0f).toInt()
var result = 0
brightnessComponents.forEach { shift ->
val raw = (this shr shift) and 15
val weighted = (raw) * weight / 256
result = result or (weighted shl shift)
}
return result
}
/** Multiply the components of this packed color value with the given [Float]. */
infix fun Int.colorMul(f: Float): Int {
val weight = (f * 256.0f).toInt()
val red = (this shr 16 and 255) * weight / 256
val green = (this shr 8 and 255) * weight / 256
val blue = (this and 255) * weight / 256
return (red shl 16) or (green shl 8) or blue
}
/** Sum the components of all packed brightness values given. */
fun brSum(multiplier: Float?, vararg brightness: Int): Int {
val sum = Array(brightnessComponents.size) { 0 }
brightnessComponents.forEachIndexed { idx, shift -> brightness.forEach { br ->
val comp = (br shr shift) and 15
sum[idx] += comp
} }
var result = 0
brightnessComponents.forEachIndexed { idx, shift ->
val comp = if (multiplier == null)
((sum[idx]) shl shift)
else
((sum[idx].toFloat() * multiplier).toInt() shl shift)
result = result or comp
}
return result
}
fun brWeighted(br1: Int, weight1: Float, br2: Int, weight2: Float): Int {
val w1int = (weight1 * 256.0f + 0.5f).toInt()
val w2int = (weight2 * 256.0f + 0.5f).toInt()
var result = 0
brightnessComponents.forEachIndexed { idx, shift ->
val comp1 = (br1 shr shift) and 15
val comp2 = (br2 shr shift) and 15
val compWeighted = (comp1 * w1int + comp2 * w2int) / 256
result = result or ((compWeighted and 15) shl shift)
}
return result
}
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
companion object {
fun fromColor(color: Int): HSB {
val hsbVals = 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() = Color.HSBtoRGB(hue, saturation, brightness)
}

View File

@@ -0,0 +1,155 @@
package mods.octarinecore.client.render
import mods.octarinecore.common.*
import net.minecraft.util.EnumFacing
const val defaultCornerDimming = 0.5f
const val defaultEdgeDimming = 0.8f
// ================================
// Shader instantiation lambdas
// ================================
fun cornerAo(fallbackAxis: EnumFacing.Axis): CornerShaderFactory = { face, dir1, dir2 ->
val fallbackDir = listOf(face, dir1, dir2).find { it.axis == fallbackAxis }!!
CornerSingleFallback(face, dir1, dir2, fallbackDir)
}
val cornerFlat = { face: EnumFacing, dir1: EnumFacing, dir2: EnumFacing -> FaceFlat(face) }
fun cornerAoTri(func: (AoData, AoData)-> AoData) = { face: EnumFacing, dir1: EnumFacing, dir2: EnumFacing ->
CornerTri(face, dir1, dir2, func)
}
val cornerAoMaxGreen = cornerAoTri { s1, s2 -> if (s1.green > s2.green) s1 else s2 }
fun cornerInterpolate(edgeAxis: EnumFacing.Axis, weight: Float, dimming: Float): CornerShaderFactory = { dir1, dir2, dir3 ->
val edgeDir = listOf(dir1, dir2, dir3).find { it.axis == edgeAxis }!!
val faceDirs = listOf(dir1, dir2, dir3).filter { it.axis != edgeAxis }
CornerInterpolateDimming(faceDirs[0], faceDirs[1], edgeDir, weight, dimming)
}
// ================================
// Shaders
// ================================
object NoShader : Shader {
override fun shade(context: ShadingContext, vertex: RenderVertex) = vertex.shade(AoData.black)
override fun rotate(rot: Rotation) = this
}
class CornerSingleFallback(val face: EnumFacing, val dir1: EnumFacing, val dir2: EnumFacing, val fallbackDir: EnumFacing, val fallbackDimming: Float = defaultCornerDimming) : Shader {
val offset = Int3(fallbackDir)
override fun shade(context: ShadingContext, vertex: RenderVertex) {
val shading = context.aoShading(face, dir1, dir2)
if (shading.valid)
vertex.shade(shading)
else context.blockData(offset).let {
vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming)
}
}
override fun rotate(rot: Rotation) = CornerSingleFallback(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), fallbackDir.rotate(rot), fallbackDimming)
}
inline fun accumulate(v1: AoData?, v2: AoData?, func: ((AoData, AoData)-> AoData)): AoData? {
val v1ok = v1 != null && v1.valid
val v2ok = v2 != null && v2.valid
if (v1ok && v2ok) return func(v1!!, v2!!)
if (v1ok) return v1
if (v2ok) return v2
return null
}
class CornerTri(val face: EnumFacing, val dir1: EnumFacing, val dir2: EnumFacing,
val func: ((AoData, AoData)-> AoData)) : Shader {
override fun shade(context: ShadingContext, vertex: RenderVertex) {
var acc = accumulate(
context.aoShading(face, dir1, dir2),
context.aoShading(dir1, face, dir2),
func)
acc = accumulate(
acc,
context.aoShading(dir2, face, dir1),
func)
vertex.shade(acc ?: AoData.black)
}
override fun rotate(rot: Rotation) = CornerTri(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), func)
}
class EdgeInterpolateFallback(val face: EnumFacing, val edgeDir: EnumFacing, val pos: Double, val fallbackDimming: Float = defaultEdgeDimming): Shader {
val offset = Int3(edgeDir)
val edgeAxis = axes.find { it != face.axis && it != edgeDir.axis }!!
val weightN = (0.5 - pos).toFloat()
val weightP = (0.5 + pos).toFloat()
override fun shade(context: ShadingContext, vertex: RenderVertex) {
val shadingP = context.aoShading(face, edgeDir, (edgeAxis to EnumFacing.AxisDirection.POSITIVE).face)
val shadingN = context.aoShading(face, edgeDir, (edgeAxis to EnumFacing.AxisDirection.NEGATIVE).face)
if (!shadingP.valid && !shadingN.valid) context.blockData(offset).let {
return vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming)
}
if (!shadingP.valid) return vertex.shade(shadingN)
if (!shadingN.valid) return vertex.shade(shadingP)
vertex.shade(shadingP, shadingN, weightP, weightN)
}
override fun rotate(rot: Rotation) = EdgeInterpolateFallback(face.rotate(rot), edgeDir.rotate(rot), pos)
}
class CornerInterpolateDimming(val face1: EnumFacing, val face2: EnumFacing, val edgeDir: EnumFacing,
val weight: Float, val dimming: Float, val fallbackDimming: Float = defaultCornerDimming) : Shader {
val offset = Int3(edgeDir)
override fun shade(context: ShadingContext, vertex: RenderVertex) {
var shading1 = context.aoShading(face1, edgeDir, face2)
var shading2 = context.aoShading(face2, edgeDir, face1)
var weight1 = weight
var weight2 = 1.0f - weight
if (!shading1.valid && !shading2.valid) context.blockData(offset).let {
return vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming)
}
if (!shading1.valid) { shading1 = shading2; weight1 *= dimming }
if (!shading2.valid) { shading2 = shading1; weight2 *= dimming }
vertex.shade(shading1, shading2, weight1, weight2)
}
override fun rotate(rot: Rotation) =
CornerInterpolateDimming(face1.rotate(rot), face2.rotate(rot), edgeDir.rotate(rot), weight, dimming, fallbackDimming)
}
class FaceCenter(val face: EnumFacing): Shader {
override fun shade(context: ShadingContext, vertex: RenderVertex) {
vertex.red = 0.0f; vertex.green = 0.0f; vertex.blue = 0.0f;
val b = IntArray(4)
faceCorners[face.ordinal].asList.forEachIndexed { idx, corner ->
val shading = context.aoShading(face, corner.first, corner.second)
vertex.red += shading.red
vertex.green += shading.green
vertex.blue += shading.blue
b[idx] = shading.brightness
}
vertex.apply { red *= 0.25f; green *= 0.25f; blue *= 0.25f }
vertex.brightness = brSum(0.25f, *b)
}
override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot))
}
class FaceFlat(val face: EnumFacing): Shader {
override fun shade(context: ShadingContext, vertex: RenderVertex) {
val color = context.blockData(Int3.zero).color
vertex.shade(context.blockData(face.offset).brightness, color)
}
override fun rotate(rot: Rotation): Shader = FaceFlat(face.rotate(rot))
}
class FlatOffset(val offset: Int3): Shader {
override fun shade(context: ShadingContext, vertex: RenderVertex) {
context.blockData(offset).let {
vertex.brightness = it.brightness
vertex.setColor(it.color)
}
}
override fun rotate(rot: Rotation): Shader = this
}
class FlatOffsetNoColor(val offset: Int3): Shader {
override fun shade(context: ShadingContext, vertex: RenderVertex) {
vertex.brightness = context.blockData(offset).brightness
vertex.red = 1.0f; vertex.green = 1.0f; vertex.blue = 1.0f
}
override fun rotate(rot: Rotation): Shader = this
}

View File

@@ -0,0 +1,192 @@
package mods.octarinecore.client.render
import mods.betterfoliage.loader.Refs
import mods.octarinecore.common.*
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.util.EnumFacing
import net.minecraft.util.EnumFacing.*
import java.lang.Math.min
import java.util.*
typealias EdgeShaderFactory = (EnumFacing, EnumFacing) -> Shader
typealias CornerShaderFactory = (EnumFacing, EnumFacing, EnumFacing) -> Shader
typealias ShaderFactory = (Quad, Vertex) -> Shader
/** Holds shading values for block corners as calculated by vanilla Minecraft rendering. */
class AoData() {
var valid = false
var brightness = 0
var red: Float = 0.0f
var green: Float = 0.0f
var blue: Float = 0.0f
fun reset() { valid = false }
fun set(brightness: Int, red: Float, green: Float, blue: Float) {
if (valid) return
this.valid = true
this.brightness = brightness
this.red = red
this.green = green
this.blue = blue
}
fun set(brightness: Int, colorMultiplier: Float) {
this.valid = true
this.brightness = brightness
this.red = colorMultiplier
this.green = colorMultiplier
this.blue = colorMultiplier
}
companion object {
val black = AoData()
}
}
class AoFaceData(val face: EnumFacing) {
val ao = Refs.AmbientOcclusionFace.element!!.getDeclaredConstructor(Refs.BlockModelRenderer.element!!)
.newInstance(BlockModelRenderer(Minecraft.getMinecraft().blockColors))
as BlockModelRenderer.AmbientOcclusionFace
val top = faceCorners[face.ordinal].topLeft.first
val left = faceCorners[face.ordinal].topLeft.second
val topLeft = AoData()
val topRight = AoData()
val bottomLeft = AoData()
val bottomRight = AoData()
val ordered = when(face) {
DOWN -> listOf(topLeft, bottomLeft, bottomRight, topRight)
UP -> listOf(bottomRight, topRight, topLeft, bottomLeft)
NORTH -> listOf(bottomLeft, bottomRight, topRight, topLeft)
SOUTH -> listOf(topLeft, bottomLeft, bottomRight, topRight)
WEST -> listOf(bottomLeft, bottomRight, topRight, topLeft)
EAST -> listOf(topRight, topLeft, bottomLeft, bottomRight)
}
fun update(offset: Int3, useBounds: Boolean = false, multiplier: Float = 1.0f) {
val ctx = blockContext
val blockState = ctx.blockState(offset)
val quadBounds: FloatArray = FloatArray(12)
val flags = BitSet(3).apply { set(0) }
ao.updateVertexBrightness(ctx.world, blockState, ctx.pos + offset, face, quadBounds, flags)
ordered.forEachIndexed { idx, aoData -> aoData.set(ao.vertexBrightness[idx], ao.vertexColorMultiplier[idx] * multiplier) }
}
operator fun get(dir1: EnumFacing, dir2: EnumFacing): AoData {
val isTop = top == dir1 || top == dir2
val isLeft = left == dir1 || left == dir2
return if (isTop) {
if (isLeft) topLeft else topRight
} else {
if (isLeft) bottomLeft else bottomRight
}
}
}
/**
* Instances of this interface are associated with [Model] vertices, and used to apply brightness and color
* values to a [RenderVertex].
*/
interface Shader {
/**
* Set shading values of a [RenderVertex]
*
* @param[context] context that can be queried for shading data in a [Model]-relative frame of reference
* @param[vertex] the [RenderVertex] to manipulate
*/
fun shade(context: ShadingContext, vertex: RenderVertex)
/**
* Return a new rotated version of this [Shader]. Used during [Model] setup when rotating the model itself.
*/
fun rotate(rot: Rotation): Shader
/** Set all shading values on the [RenderVertex] to match the given [AoData]. */
fun RenderVertex.shade(shading: AoData) {
brightness = shading.brightness; red = shading.red; green = shading.green; blue = shading.blue
}
/** Set the shading values on the [RenderVertex] to a weighted average of the two [AoData] instances. */
fun RenderVertex.shade(shading1: AoData, shading2: AoData, weight1: Float = 0.5f, weight2: Float = 0.5f) {
red = min(shading1.red * weight1 + shading2.red * weight2, 1.0f)
green = min(shading1.green * weight1 + shading2.green * weight2, 1.0f)
blue = min(shading1.blue * weight1 + shading2.blue * weight2, 1.0f)
brightness = brWeighted(shading1.brightness, weight1, shading2.brightness, weight2)
}
/**
* Set the shading values on the [RenderVertex] directly.
*
* @param[brightness] packed brightness value
* @param[color] packed color value
*/
fun RenderVertex.shade(brightness: Int, color: Int) {
this.brightness = brightness; setColor(color)
}
}
/**
* Returns a shader resolver for quads that point towards one of the 6 block faces.
* The resolver works the following way:
* - determines which face the _quad_ normal points towards (if not overridden)
* - determines the distance of the _vertex_ to the corners and edge midpoints on that block face
* - if _corner_ is given, and the _vertex_ is closest to a block corner, returns the [Shader] created by _corner_
* - if _edge_ is given, and the _vertex_ is closest to an edge midpoint, returns the [Shader] created by _edge_
*
* @param[overrideFace] assume the given face instead of going by the _quad_ normal
* @param[corner] shader instantiation lambda for corner vertices
* @param[edge] shader instantiation lambda for edge midpoint vertices
*/
fun faceOrientedAuto(overrideFace: EnumFacing? = null,
corner: CornerShaderFactory? = null,
edge: EdgeShaderFactory? = null) =
fun(quad: Quad, vertex: Vertex): Shader {
val quadFace = overrideFace ?: quad.normal.nearestCardinal
val nearestCorner = nearestPosition(vertex.xyz, faceCorners[quadFace.ordinal].asList) {
(quadFace.vec + it.first.vec + it.second.vec) * 0.5
}
val nearestEdge = nearestPosition(vertex.xyz, quadFace.perpendiculars) {
(quadFace.vec + it.vec) * 0.5
}
if (edge != null && (nearestEdge.second < nearestCorner.second || corner == null))
return edge(quadFace, nearestEdge.first)
else return corner!!(quadFace, nearestCorner.first.first, nearestCorner.first.second)
}
/**
* Returns a shader resolver for quads that point towards one of the 12 block edges.
* The resolver works the following way:
* - determines which edge the _quad_ normal points towards (if not overridden)
* - determines which face midpoint the _vertex_ is closest to, of the 2 block faces that share this edge
* - determines which block corner _of this face_ the _vertex_ is closest to
* - returns the [Shader] created by _corner_
*
* @param[overrideEdge] assume the given edge instead of going by the _quad_ normal
* @param[corner] shader instantiation lambda
*/
fun edgeOrientedAuto(overrideEdge: Pair<EnumFacing, EnumFacing>? = null,
corner: CornerShaderFactory) =
fun(quad: Quad, vertex: Vertex): Shader {
val edgeDir = overrideEdge ?: nearestAngle(quad.normal, boxEdges) { it.first.vec + it.second.vec }.first
val nearestFace = nearestPosition(vertex.xyz, edgeDir.toList()) { it.vec }.first
val nearestCorner = nearestPosition(vertex.xyz, faceCorners[nearestFace.ordinal].asList) {
(nearestFace.vec + it.first.vec + it.second.vec) * 0.5
}.first
return corner(nearestFace, nearestCorner.first, nearestCorner.second)
}
fun faceOrientedInterpolate(overrideFace: EnumFacing? = null) =
fun(quad: Quad, vertex: Vertex): Shader {
val resolver = faceOrientedAuto(overrideFace, edge = { face, edgeDir ->
val axis = axes.find { it != face.axis && it != edgeDir.axis }!!
val vec = Double3((axis to EnumFacing.AxisDirection.POSITIVE).face)
val pos = vertex.xyz.dot(vec)
EdgeInterpolateFallback(face, edgeDir, pos)
})
return resolver(quad, vertex)
}

View File

@@ -0,0 +1,33 @@
package mods.octarinecore.client.resource
import java.awt.image.BufferedImage
import java.lang.Math.*
class CenteringTextureGenerator(domain: String, val aspectWidth: Int, val aspectHeight: Int) : TextureGenerator(domain) {
override fun generate(params: ParameterList): BufferedImage? {
val target = targetResource(params)!!
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
val frameWidth = baseTexture.width
val frameHeight = baseTexture.width * aspectHeight / aspectWidth
val frames = baseTexture.height / frameHeight
val size = max(frameWidth, frameHeight)
val resultTexture = BufferedImage(size, size * frames, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = resultTexture.createGraphics()
// iterate all frames
for (frame in 0 .. frames - 1) {
val baseFrame = baseTexture.getSubimage(0, size * frame, frameWidth, frameHeight)
val resultFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
resultFrame.createGraphics().apply {
drawImage(baseFrame, (size - frameWidth) / 2, (size - frameHeight) / 2, null)
}
graphics.drawImage(resultFrame, 0, size * frame, null)
}
return resultTexture
}
}

View File

@@ -0,0 +1,136 @@
package mods.octarinecore.client.resource
import com.google.common.base.Joiner
import mods.betterfoliage.client.Client
import mods.betterfoliage.loader.Refs
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.common.Int3
import mods.octarinecore.common.config.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.filterValuesNotNull
import mods.octarinecore.findFirst
import net.minecraft.block.Block
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.block.model.ModelResourceLocation
import net.minecraft.client.renderer.block.statemap.DefaultStateMapper
import net.minecraft.client.renderer.block.statemap.IStateMapper
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.client.model.IModel
import net.minecraftforge.client.model.ModelLoader
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.eventhandler.Event
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
class LoadModelDataEvent(val loader: ModelLoader) : Event()
interface ModelRenderRegistry<T> {
operator fun get(ctx: BlockContext) = get(ctx.blockState(Int3.zero), ctx.world!!, ctx.pos)
operator fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos): T?
}
interface ModelRenderDataExtractor<T> {
fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<T>?
}
interface ModelRenderKey<T> {
val logger: Logger?
fun onPreStitch(atlas: TextureMap) {}
fun resolveSprites(atlas: TextureMap): T
}
abstract class ModelRenderRegistryRoot<T> : ModelRenderRegistry<T> {
val subRegistries = mutableListOf<ModelRenderRegistry<T>>()
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos) = subRegistries.findFirst { it[state, world, pos] }
fun addRegistry(registry: ModelRenderRegistry<T>) {
subRegistries.add(registry)
MinecraftForge.EVENT_BUS.register(registry)
}
}
abstract class ModelRenderRegistryBase<T> : ModelRenderRegistry<T>, ModelRenderDataExtractor<T> {
open val logger: Logger? = null
open val logName: String get() = this::class.java.name
val stateToKey = mutableMapOf<IBlockState, ModelRenderKey<T>>()
var stateToValue = mapOf<IBlockState, T>()
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos) = stateToValue[state]
@Suppress("UNCHECKED_CAST")
@SubscribeEvent
fun handleLoadModelData(event: LoadModelDataEvent) {
stateToValue = emptyMap()
val stateMappings = Block.REGISTRY.flatMap { block ->
val mapper = event.loader.blockModelShapes.blockStateMapper.blockStateMap[block] as? IStateMapper ?: DefaultStateMapper()
(mapper.putStateModelLocations(block as Block) as Map<IBlockState, ModelResourceLocation>).entries
}
val stateModels = Refs.stateModels.get(event.loader) as Map<ModelResourceLocation, IModel>
stateMappings.forEach { mapping ->
if (mapping.key.block != null) stateModels[mapping.value]?.let { model ->
try {
processModel(mapping.key, mapping.value, model)?.let { stateToKey[mapping.key] = it }
} catch (e: Exception) {
logger?.warn("Exception while trying to process model ${mapping.value}", e)
}
}
}
}
@SubscribeEvent(priority = EventPriority.LOW)
fun handlePreStitch(event: TextureStitchEvent.Pre) {
stateToKey.forEach { (_, key) -> key.onPreStitch(event.map) }
}
@SubscribeEvent(priority = EventPriority.LOW)
fun handlePostStitch(event: TextureStitchEvent.Post) {
stateToValue = stateToKey.mapValues { (_, key) -> key.resolveSprites(event.map) }
stateToKey.clear()
}
}
abstract class ModelRenderRegistryConfigurable<T> : ModelRenderRegistryBase<T>() {
abstract val matchClasses: IBlockMatcher
abstract val modelTextures: List<ModelTextureList>
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<T>? {
val matchClass = matchClasses.matchingClass(state.block) ?: return null
logger?.log(Level.DEBUG, "$logName: block state ${state.toString()}")
logger?.log(Level.DEBUG, "$logName: class ${state.block.javaClass.name} matches ${matchClass.name}")
val allModels = model.modelBlockAndLoc.distinctBy { it.second }
if (allModels.isEmpty()) {
logger?.log(Level.DEBUG, "$logName: no models found")
return null
}
allModels.forEach { blockLoc ->
val modelMatch = modelTextures.firstOrNull { blockLoc.derivesFrom(it.modelLocation) }
if (modelMatch != null) {
logger?.log(Level.DEBUG, "$logName: model ${blockLoc.second} matches ${modelMatch.modelLocation}")
val textures = modelMatch.textureNames.map { it to blockLoc.first.resolveTextureName(it) }
val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
logger?.log(Level.DEBUG, "$logName: textures [$texMapString]")
if (textures.all { it.second != "missingno" }) {
// found a valid model (all required textures exist)
return processModel(state, textures.map { it.second} )
}
}
}
return null
}
abstract fun processModel(state: IBlockState, textures: List<String>) : ModelRenderKey<T>?
}

View File

@@ -0,0 +1,120 @@
package mods.octarinecore.client.resource
import mods.octarinecore.metaprog.reflectField
import net.minecraft.client.resources.IResourcePack
import net.minecraft.client.resources.data.IMetadataSection
import net.minecraft.client.resources.data.IMetadataSectionSerializer
import net.minecraft.client.resources.data.MetadataSerializer
import net.minecraft.client.resources.data.PackMetadataSection
import net.minecraft.util.ResourceLocation
import net.minecraft.util.text.TextComponentString
import net.minecraftforge.fml.client.FMLClientHandler
import java.io.InputStream
import java.util.*
/**
* [IResourcePack] containing generated resources. Adds itself to the default resource pack list
* of Minecraft, so it is invisible and always active.
*
* @param[name] Name of the resource pack
* @param[generators] List of resource generators
*/
class GeneratorPack(val name: String, vararg val generators: GeneratorBase) : IResourcePack {
fun inject() {
FMLClientHandler.instance().reflectField<MutableList<IResourcePack>>("resourcePackList")!!.add(this)
}
override fun getPackName() = name
override fun getPackImage() = null
override fun getResourceDomains() = HashSet(generators.map { it.domain })
override fun <T : IMetadataSection?> getPackMetadata(serializer: MetadataSerializer?, sectionName: String?) =
if (sectionName == "pack") PackMetadataSection(TextComponentString("Generated resources"), 1) as? T else null
override fun resourceExists(location: ResourceLocation?): Boolean =
if (location == null) false
else generators.find {
it.domain == location.namespace && it.resourceExists(location)
} != null
override fun getInputStream(location: ResourceLocation?): InputStream? =
if (location == null) null
else generators.filter {
it.domain == location.namespace && it.resourceExists(location)
}.map { it.getInputStream(location) }
.filterNotNull().first()
// override fun <T : IMetadataSection?> getPackMetadata(p_135058_1_: IMetadataSerializer?, p_135058_2_: String?): T {
// return if (type == "pack") PackMetadataSection(ChatComponentText("Generated resources"), 1) else null
// }
}
/**
* Abstract base class for resource generators
*
* @param[domain] Resource domain of generator
*/
abstract class GeneratorBase(val domain: String) {
/** @see [IResourcePack.resourceExists] */
abstract fun resourceExists(location: ResourceLocation?): Boolean
/** @see [IResourcePack.getInputStream] */
abstract fun getInputStream(location: ResourceLocation?): InputStream?
}
/**
* Collection of named [String]-valued key-value pairs, with an extra unnamed (keyless) value.
* Meant to be encoded as a pipe-delimited list, and used as a [ResourceLocation] path
* to parametrized generated resources.
*
* @param[params] key-value pairs
* @param[value] keyless extra value
*/
class ParameterList(val params: Map<String, String>, val value: String?) {
override fun toString() =
params.entries
.sortedBy { it.key }
.fold("") { result, entry -> result + "|${entry.key}=${entry.value}"} +
(value?.let { "|$it" } ?: "")
/** Return the value of the given parameter. */
operator fun get(key: String) = params[key]
/** Check if the given parameter exists in this list. */
operator fun contains(key: String) = key in params
/** Return a new [ParameterList] with the given key-value pair appended to it. */
operator fun plus(pair: Pair<String, String>) = ParameterList(params + pair, this.value)
companion object {
/**
* Recreate the parameter list from the encoded string, i.e. the opposite of [toString].
*
* Everything before the first pipe character is dropped, so the decoding works even if
* something is prepended to the list (like _textures/blocks/_)
*/
fun fromString(input: String): ParameterList {
val params = hashMapOf<String, String>()
var value: String? = null
val slices = input.dropWhile { it != '|'}.split('|')
slices.forEach {
if (it.contains('=')) {
val keyValue = it.split('=')
if (keyValue.size == 2) params.put(keyValue[0], keyValue[1])
} else value = it
}
return ParameterList(params, value)
}
}
}
abstract class ParameterBasedGenerator(domain: String) : GeneratorBase(domain) {
abstract fun resourceExists(params: ParameterList): Boolean
abstract fun getInputStream(params: ParameterList): InputStream?
override fun resourceExists(location: ResourceLocation?) =
resourceExists(ParameterList.fromString(location?.path ?: ""))
override fun getInputStream(location: ResourceLocation?) =
getInputStream(ParameterList.fromString(location?.path ?: ""))
}

View File

@@ -0,0 +1,140 @@
package mods.octarinecore.client.resource
import mods.octarinecore.client.render.Model
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
import net.minecraft.world.gen.NoiseGeneratorSimplex
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.fml.client.event.ConfigChangedEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import java.util.*
// ============================
// Resource types
// ============================
interface IStitchListener {
fun onPreStitch(atlas: TextureMap)
fun onPostStitch(atlas: TextureMap)
}
interface IConfigChangeListener { fun onConfigChange() }
interface IWorldLoadListener { fun onWorldLoad(world: World) }
/**
* Base class for declarative resource handling.
*
* Resources are automatically reloaded/recalculated when the appropriate events are fired.
*
* @param[modId] mod ID associated with this handler (used to filter config change events)
*/
open class ResourceHandler(val modId: String) {
val resources = mutableListOf<Any>()
open fun afterPreStitch() {}
open fun afterPostStitch() {}
// ============================
// Self-registration
// ============================
init { MinecraftForge.EVENT_BUS.register(this) }
// ============================
// Resource declarations
// ============================
fun iconStatic(domain: String, path: String) = IconHolder(domain, path).apply { resources.add(this) }
fun iconStatic(location: ResourceLocation) = iconStatic(location.namespace, location.path)
fun iconSet(domain: String, pathPattern: String) = IconSet(domain, pathPattern).apply { this@ResourceHandler.resources.add(this) }
fun iconSet(location: ResourceLocation) = iconSet(location.namespace, location.path)
fun model(init: Model.()->Unit) = ModelHolder(init).apply { resources.add(this) }
fun modelSet(num: Int, init: Model.(Int)->Unit) = ModelSet(num, init).apply { resources.add(this) }
fun vectorSet(num: Int, init: (Int)-> Double3) = VectorSet(num, init).apply { resources.add(this) }
fun simplexNoise() = SimplexNoise().apply { resources.add(this) }
// ============================
// Event registration
// ============================
@SubscribeEvent
fun onPreStitch(event: TextureStitchEvent.Pre) {
resources.forEach { (it as? IStitchListener)?.onPreStitch(event.map) }
afterPreStitch()
}
@SubscribeEvent
fun onPostStitch(event: TextureStitchEvent.Post) {
resources.forEach { (it as? IStitchListener)?.onPostStitch(event.map) }
afterPostStitch()
}
@SubscribeEvent
fun handleConfigChange(event: ConfigChangedEvent.OnConfigChangedEvent) {
if (event.modID == modId) resources.forEach { (it as? IConfigChangeListener)?.onConfigChange() }
}
@SubscribeEvent
fun handleWorldLoad(event: WorldEvent.Load) =
resources.forEach { (it as? IWorldLoadListener)?.onWorldLoad(event.world) }
}
// ============================
// Resource container classes
// ============================
class IconHolder(val domain: String, val name: String) : IStitchListener {
val iconRes = ResourceLocation(domain, name)
var icon: TextureAtlasSprite? = null
override fun onPreStitch(atlas: TextureMap) { atlas.registerSprite(iconRes) }
override fun onPostStitch(atlas: TextureMap) { icon = atlas[iconRes] }
}
class ModelHolder(val init: Model.()->Unit): IConfigChangeListener {
var model: Model = Model().apply(init)
override fun onConfigChange() { model = Model().apply(init) }
}
class IconSet(val domain: String, val namePattern: String) : IStitchListener {
val resources = arrayOfNulls<ResourceLocation>(16)
val icons = arrayOfNulls<TextureAtlasSprite>(16)
var num = 0
override fun onPreStitch(atlas: TextureMap) {
num = 0
(0..15).forEach { idx ->
icons[idx] = null
val locReal = ResourceLocation(domain, "textures/${namePattern.format(idx)}.png")
if (resourceManager[locReal] != null) resources[num++] = ResourceLocation(domain, namePattern.format(idx)).apply { atlas.registerSprite(this) }
}
}
override fun onPostStitch(atlas: TextureMap) {
(0 until num).forEach { idx -> icons[idx] = atlas[resources[idx]!!] }
}
operator fun get(idx: Int) = if (num == 0) null else icons[idx % num]
}
class ModelSet(val num: Int, val init: Model.(Int)->Unit): IConfigChangeListener {
val models = Array(num) { Model().apply{ init(it) } }
override fun onConfigChange() { (0 until num).forEach { models[it] = Model().apply{ init(it) } } }
operator fun get(idx: Int) = models[idx % num]
}
class VectorSet(val num: Int, val init: (Int)->Double3): IConfigChangeListener {
val models = Array(num) { init(it) }
override fun onConfigChange() { (0 until num).forEach { models[it] = init(it) } }
operator fun get(idx: Int) = models[idx % num]
}
class SimplexNoise() : IWorldLoadListener {
var noise = NoiseGeneratorSimplex()
override fun onWorldLoad(world: World) { noise = NoiseGeneratorSimplex(Random(world.worldInfo.seed))
}
operator fun get(x: Int, z: Int) = MathHelper.floor((noise.getValue(x.toDouble(), z.toDouble()) + 1.0) * 32.0)
operator fun get(pos: Int3) = get(pos.x, pos.z)
operator fun get(pos: BlockPos) = get(pos.x, pos.z)
}

View File

@@ -0,0 +1,86 @@
package mods.octarinecore.client.resource
import mods.octarinecore.client.resource.ResourceType.*
import net.minecraft.client.resources.IResource
import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage
import java.io.InputStream
/** Type of generated texture resource */
enum class ResourceType {
COLOR, // regular diffuse map
METADATA, // texture metadata
NORMAL, // ShadersMod normal map
SPECULAR // ShadersMod specular map
}
/**
* Generator returning textures based on a single other texture. This texture is located with the
* _dom_ and _path_ parameters of a [ParameterList].
*
* @param[domain] Resource domain of generator
*/
abstract class TextureGenerator(domain: String) : ParameterBasedGenerator(domain) {
/**
* Obtain a [ResourceLocation] to a generated texture
*
* @param[iconName] the name of the [TextureAtlasSprite] (not the full location) backing the generated texture
* @param[extraParams] additional parameters of the generated texture
*/
fun generatedResource(iconName: String, vararg extraParams: Pair<String, Any>) = ResourceLocation(
domain,
textureLocation(iconName).let {
ParameterList(
mapOf("dom" to it.namespace, "path" to it.path) +
extraParams.map { Pair(it.first, it.second.toString()) },
"generate"
).toString()
}
)
/** Get the type and location of the texture resource encoded by the given [ParameterList]. */
fun targetResource(params: ParameterList): Pair<ResourceType, ResourceLocation>? {
val baseTexture =
if (listOf("dom", "path").all { it in params }) ResourceLocation(params["dom"]!!, params["path"]!!)
else return null
return when(params.value?.toLowerCase()) {
"generate.png" -> COLOR to baseTexture + ".png"
"generate.png.mcmeta" -> METADATA to baseTexture + ".png.mcmeta"
"generate_n.png" -> NORMAL to baseTexture + "_n.png"
"generate_s.png" -> SPECULAR to baseTexture + "_s.png"
else -> null
}
}
override fun resourceExists(params: ParameterList) =
targetResource(params)?.second?.let { resourceManager[it] != null } ?: false
override fun getInputStream(params: ParameterList): InputStream? {
val target = targetResource(params)
return when(target?.first) {
null -> null
METADATA -> resourceManager[target!!.second]?.inputStream
else -> generate(params)?.asStream
}
}
/**
* Generate image data from the parameter list.
*/
abstract fun generate(params: ParameterList): BufferedImage?
/**
* Get a texture resource when multiple sizes may exist.
*
* @param[maxSize] Maximum size to consider. This value is progressively halved when searching for smaller versions.
* @param[maskPath] Location of the texture of the given size
*
*/
fun getMultisizeTexture(maxSize: Int, maskPath: (Int)->ResourceLocation): IResource? {
var size = maxSize
val sizes = mutableListOf<Int>()
while(size > 2) { sizes.add(size); size /= 2 }
return sizes.map { resourceManager[maskPath(it)] }.filterNotNull().firstOrNull()
}
}

View File

@@ -0,0 +1,127 @@
@file:JvmName("Utils")
package mods.octarinecore.client.resource
import mods.betterfoliage.loader.Refs
import mods.octarinecore.PI2
import mods.octarinecore.client.render.HSB
import mods.octarinecore.metaprog.reflectField
import mods.octarinecore.stripStart
import mods.octarinecore.tryDefault
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.block.model.ModelBlock
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.client.resources.IResource
import net.minecraft.client.resources.IResourceManager
import net.minecraft.client.resources.SimpleReloadableResourceManager
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.IModel
import java.awt.image.BufferedImage
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.lang.Math.*
import javax.imageio.ImageIO
/** Concise getter for the Minecraft resource manager. */
val resourceManager: SimpleReloadableResourceManager get() =
Minecraft.getMinecraft().resourceManager as SimpleReloadableResourceManager
/** Append a string to the [ResourceLocation]'s path. */
operator fun ResourceLocation.plus(str: String) = ResourceLocation(namespace, path + str)
/** Index operator to get a resource. */
operator fun IResourceManager.get(domain: String, path: String): IResource? = get(ResourceLocation(domain, path))
/** Index operator to get a resource. */
operator fun IResourceManager.get(location: ResourceLocation): IResource? = tryDefault(null) { getResource(location) }
/** Index operator to get a texture sprite. */
operator fun TextureMap.get(name: String): TextureAtlasSprite? = getTextureExtry(ResourceLocation(name).toString())
operator fun TextureMap.get(res: ResourceLocation): TextureAtlasSprite? = getTextureExtry(res.toString())
/** Load an image resource. */
fun IResource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream)
/** Get the lines of a text resource. */
fun IResource.getLines(): List<String> {
val result = arrayListOf<String>()
inputStream.bufferedReader().useLines { it.forEach { result.add(it) } }
return result
}
/** Index operator to get the RGB value of a pixel. */
operator fun BufferedImage.get(x: Int, y: Int) = this.getRGB(x, y)
/** Index operator to set the RGB value of a pixel. */
operator fun BufferedImage.set(x: Int, y: Int, value: Int) = this.setRGB(x, y, value)
/** Get an [InputStream] to an image object in PNG format. */
val BufferedImage.asStream: InputStream get() =
ByteArrayInputStream(ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() })
/**
* Calculate the average color of a texture.
*
* Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average),
* and the result transformed back to the RGB color space.
*/
val TextureAtlasSprite.averageColor: Int? get() {
val locationNoDirs = ResourceLocation(iconName).stripStart("blocks/")
val locationWithDirs = ResourceLocation(locationNoDirs.namespace, "textures/blocks/%s.png".format(locationNoDirs.path))
val image = resourceManager[locationWithDirs]?.loadImage() ?: return null
var numOpaque = 0
var sumHueX = 0.0
var sumHueY = 0.0
var sumSaturation = 0.0f
var sumBrightness = 0.0f
for (x in 0..image.width - 1)
for (y in 0..image.height - 1) {
val pixel = image[x, y]
val alpha = (pixel shr 24) and 255
val hsb = HSB.fromColor(pixel)
if (alpha == 255) {
numOpaque++
sumHueX += cos((hsb.hue.toDouble() - 0.5) * PI2)
sumHueY += sin((hsb.hue.toDouble() - 0.5) * PI2)
sumSaturation += hsb.saturation
sumBrightness += hsb.brightness
}
}
// circular average - transform sum vector to polar angle
val avgHue = (atan2(sumHueY.toDouble(), sumHueX.toDouble()) / PI2 + 0.5).toFloat()
return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat()).asColor
}
/**
* Get the actual location of a texture from the name of its [TextureAtlasSprite].
*/
fun textureLocation(iconName: String) = ResourceLocation(iconName).let {
if (it.path.startsWith("mcpatcher")) it
else ResourceLocation(it.namespace, "textures/${it.path}")
}
@Suppress("UNCHECKED_CAST")
val IModel.modelBlockAndLoc: List<Pair<ModelBlock, ResourceLocation>> get() {
if (Refs.VanillaModelWrapper.isInstance(this))
return listOf(Pair(Refs.model_VMW.get(this) as ModelBlock, Refs.location_VMW.get(this) as ResourceLocation))
else if (Refs.WeightedRandomModel.isInstance(this)) Refs.models_WRM.get(this)?.let {
return (it as List<IModel>).flatMap(IModel::modelBlockAndLoc)
}
else if (Refs.MultipartModel.isInstance(this)) Refs.partModels_MPM.get(this)?.let {
return (it as Map<Any, IModel>).flatMap { it.value.modelBlockAndLoc }
} else {
this::class.java.declaredFields.find { it.type.isInstance(IModel::class.java) }?.let { modelField ->
modelField.isAccessible = true
return (modelField.get(this) as IModel).modelBlockAndLoc
}
}
return listOf()
}
fun Pair<ModelBlock, ResourceLocation>.derivesFrom(targetLocation: ResourceLocation): Boolean {
if (second.stripStart("models/") == targetLocation) return true
if (first.parent != null && first.parentLocation != null)
return Pair(first.parent, first.parentLocation!!).derivesFrom(targetLocation)
return false
}

View File

@@ -0,0 +1,225 @@
package mods.octarinecore.common
import mods.octarinecore.cross
import net.minecraft.util.EnumFacing
import net.minecraft.util.EnumFacing.*
import net.minecraft.util.EnumFacing.Axis.*
import net.minecraft.util.EnumFacing.AxisDirection.NEGATIVE
import net.minecraft.util.EnumFacing.AxisDirection.POSITIVE
import net.minecraft.util.math.BlockPos
// ================================
// Axes and directions
// ================================
val axes = listOf(X, Y, Z)
val axisDirs = listOf(POSITIVE, NEGATIVE)
val EnumFacing.dir: AxisDirection get() = axisDirection
val AxisDirection.sign: String get() = when(this) { POSITIVE -> "+"; NEGATIVE -> "-" }
val forgeDirs = EnumFacing.values()
val forgeDirsHorizontal = listOf(NORTH, SOUTH, EAST, WEST)
val forgeDirOffsets = forgeDirs.map { Int3(it) }
val Pair<Axis, AxisDirection>.face: EnumFacing get() = when(this) {
X to POSITIVE -> EAST; X to NEGATIVE -> WEST;
Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN;
Z to POSITIVE -> SOUTH; else -> NORTH;
}
val EnumFacing.perpendiculars: List<EnumFacing> get() =
axes.filter { it != this.axis }.cross(axisDirs).map { it.face }
val EnumFacing.offset: Int3 get() = forgeDirOffsets[ordinal]
/** Old ForgeDirection rotation matrix yanked from 1.7.10 */
val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
intArrayOf(0, 1, 4, 5, 3, 2, 6),
intArrayOf(0, 1, 5, 4, 2, 3, 6),
intArrayOf(5, 4, 2, 3, 0, 1, 6),
intArrayOf(4, 5, 2, 3, 1, 0, 6),
intArrayOf(2, 3, 1, 0, 4, 5, 6),
intArrayOf(3, 2, 0, 1, 4, 5, 6)
)
// ================================
// Vectors
// ================================
operator fun EnumFacing.times(scale: Double) =
Double3(directionVec.x.toDouble() * scale, directionVec.y.toDouble() * scale, directionVec.z.toDouble() * scale)
val EnumFacing.vec: Double3 get() = Double3(directionVec.x.toDouble(), directionVec.y.toDouble(), directionVec.z.toDouble())
operator fun BlockPos.plus(other: Int3) = BlockPos(x + other.x, y + other.y, z + other.z)
/** 3D vector of [Double]s. Offers both mutable operations, and immutable operations in operator notation. */
data class Double3(var x: Double, var y: Double, var z: Double) {
constructor(x: Float, y: Float, z: Float) : this(x.toDouble(), y.toDouble(), z.toDouble())
constructor(dir: EnumFacing) : this(dir.directionVec.x.toDouble(), dir.directionVec.y.toDouble(), dir.directionVec.z.toDouble())
companion object {
val zero: Double3 get() = Double3(0.0, 0.0, 0.0)
fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) =
Double3(v1.x * weight1 + v2.x * weight2, v1.y * weight1 + v2.y * weight2, v1.z * weight1 + v2.z * weight2)
}
// immutable operations
operator fun plus(other: Double3) = Double3(x + other.x, y + other.y, z + other.z)
operator fun unaryMinus() = Double3(-x, -y, -z)
operator fun minus(other: Double3) = Double3(x - other.x, y - other.y, z - other.z)
operator fun times(scale: Double) = Double3(x * scale, y * scale, z * scale)
operator fun times(other: Double3) = Double3(x * other.x, y * other.y, z * other.z)
/** Rotate this vector, and return coordinates in the unrotated frame */
fun rotate(rot: Rotation) = Double3(
rot.rotatedComponent(EAST, x, y, z),
rot.rotatedComponent(UP, x, y, z),
rot.rotatedComponent(SOUTH, x, y, z)
)
// mutable operations
fun setTo(other: Double3): Double3 { x = other.x; y = other.y; z = other.z; return this }
fun setTo(x: Double, y: Double, z: Double): Double3 { this.x = x; this.y = y; this.z = z; return this }
fun setTo(x: Float, y: Float, z: Float) = setTo(x.toDouble(), y.toDouble(), z.toDouble())
fun add(other: Double3): Double3 { x += other.x; y += other.y; z += other.z; return this }
fun add(x: Double, y: Double, z: Double): Double3 { this.x += x; this.y += y; this.z += z; return this }
fun sub(other: Double3): Double3 { x -= other.x; y -= other.y; z -= other.z; return this }
fun sub(x: Double, y: Double, z: Double): Double3 { this.x -= x; this.y -= y; this.z -= z; return this }
fun invert(): Double3 { x = -x; y = -y; z = -z; return this }
fun mul(scale: Double): Double3 { x *= scale; y *= scale; z *= scale; return this }
fun mul(other: Double3): Double3 { x *= other.x; y *= other.y; z *= other.z; return this }
fun rotateMut(rot: Rotation): Double3 {
val rotX = rot.rotatedComponent(EAST, x, y, z)
val rotY = rot.rotatedComponent(UP, x, y, z)
val rotZ = rot.rotatedComponent(SOUTH, x, y, z)
return setTo(rotX, rotY, rotZ)
}
// misc operations
infix fun dot(other: Double3) = x * other.x + y * other.y + z * other.z
infix fun cross(o: Double3) = Double3(y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x)
val length: Double get() = Math.sqrt(x * x + y * y + z * z)
val normalize: Double3 get() = (1.0 / length).let { Double3(x * it, y * it, z * it) }
val nearestCardinal: EnumFacing get() = nearestAngle(this, forgeDirs.asIterable()) { it.vec }.first
}
/** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */
data class Int3(var x: Int, var y: Int, var z: Int) {
constructor(dir: EnumFacing) : this(dir.directionVec.x, dir.directionVec.y, dir.directionVec.z)
constructor(offset: Pair<Int, EnumFacing>) : this(
offset.first * offset.second.directionVec.x,
offset.first * offset.second.directionVec.y,
offset.first * offset.second.directionVec.z
)
companion object {
val zero = Int3(0, 0, 0)
}
// immutable operations
operator fun plus(other: Int3) = Int3(x + other.x, y + other.y, z + other.z)
operator fun plus(other: Pair<Int, EnumFacing>) = Int3(
x + other.first * other.second.directionVec.x,
y + other.first * other.second.directionVec.y,
z + other.first * other.second.directionVec.z
)
operator fun unaryMinus() = Int3(-x, -y, -z)
operator fun minus(other: Int3) = Int3(x - other.x, y - other.y, z - other.z)
operator fun times(scale: Int) = Int3(x * scale, y * scale, z * scale)
operator fun times(other: Int3) = Int3(x * other.x, y * other.y, z * other.z)
/** Rotate this vector, and return coordinates in the unrotated frame */
fun rotate(rot: Rotation) = Int3(
rot.rotatedComponent(EAST, x, y, z),
rot.rotatedComponent(UP, x, y, z),
rot.rotatedComponent(SOUTH, x, y, z)
)
// mutable operations
fun setTo(other: Int3): Int3 { x = other.x; y = other.y; z = other.z; return this }
fun setTo(x: Int, y: Int, z: Int): Int3 { this.x = x; this.y = y; this.z = z; return this }
fun add(other: Int3): Int3 { x += other.x; y += other.y; z += other.z; return this }
fun sub(other: Int3): Int3 { x -= other.x; y -= other.y; z -= other.z; return this }
fun invert(): Int3 { x = -x; y = -y; z = -z; return this }
fun mul(scale: Int): Int3 { x *= scale; y *= scale; z *= scale; return this }
fun mul(other: Int3): Int3 { x *= other.x; y *= other.y; z *= other.z; return this }
fun rotateMut(rot: Rotation): Int3 {
val rotX = rot.rotatedComponent(EAST, x, y, z)
val rotY = rot.rotatedComponent(UP, x, y, z)
val rotZ = rot.rotatedComponent(SOUTH, x, y, z)
return setTo(rotX, rotY, rotZ)
}
}
// ================================
// Rotation
// ================================
val EnumFacing.rotations: Array<EnumFacing> get() =
Array(6) { idx -> EnumFacing.values()[ROTATION_MATRIX[ordinal][idx]] }
fun EnumFacing.rotate(rot: Rotation) = rot.forward[ordinal]
fun rot(axis: EnumFacing) = Rotation.rot90[axis.ordinal]
/**
* Class representing an arbitrary rotation (or combination of rotations) around cardinal axes by 90 degrees.
* In effect, a permutation of [ForgeDirection]s.
*/
@Suppress("NOTHING_TO_INLINE")
class Rotation(val forward: Array<EnumFacing>, val reverse: Array<EnumFacing>) {
operator fun plus(other: Rotation) = Rotation(
Array(6) { idx -> forward[other.forward[idx].ordinal] },
Array(6) { idx -> other.reverse[reverse[idx].ordinal] }
)
operator fun unaryMinus() = Rotation(reverse, forward)
operator fun times(num: Int) = when(num % 4) { 1 -> this; 2 -> this + this; 3 -> -this; else -> identity }
inline fun rotatedComponent(dir: EnumFacing, x: Int, y: Int, z: Int) =
when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0 }
inline fun rotatedComponent(dir: EnumFacing, x: Double, y: Double, z: Double) =
when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0.0 }
companion object {
// Forge rotation matrix is left-hand
val rot90 = Array(6) { idx -> Rotation(forgeDirs[idx].opposite.rotations, forgeDirs[idx].rotations) }
val identity = Rotation(forgeDirs, forgeDirs)
}
}
// ================================
// Miscellaneous
// ================================
/** List of all 12 box edges, represented as a [Pair] of [ForgeDirection]s */
val boxEdges = forgeDirs.flatMap { face1 -> forgeDirs.filter { it.axis > face1.axis }.map { face1 to it } }
/**
* Get the closest object to the specified point from a list of objects.
*
* @param[vertex] the reference point
* @param[objs] list of geomertric objects
* @param[objPos] lambda to calculate the position of an object
* @return [Pair] of (object, distance)
*/
fun <T> nearestPosition(vertex: Double3, objs: Iterable<T>, objPos: (T)-> Double3): Pair<T, Double> =
objs.map { it to (objPos(it) - vertex).length }.minBy { it.second }!!
/**
* Get the object closest in orientation to the specified vector from a list of objects.
*
* @param[vector] the reference vector (direction)
* @param[objs] list of geomertric objects
* @param[objAngle] lambda to calculate the orientation of an object
* @return [Pair] of (object, normalized dot product)
*/
fun <T> nearestAngle(vector: Double3, objs: Iterable<T>, objAngle: (T)-> Double3): Pair<T, Double> =
objs.map { it to objAngle(it).dot(vector) }.maxBy { it.second }!!
data class FaceCorners(val topLeft: Pair<EnumFacing, EnumFacing>,
val topRight: Pair<EnumFacing, EnumFacing>,
val bottomLeft: Pair<EnumFacing, EnumFacing>,
val bottomRight: Pair<EnumFacing, EnumFacing>) {
constructor(top: EnumFacing, left: EnumFacing) :
this(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite)
val asArray = arrayOf(topLeft, topRight, bottomLeft, bottomRight)
val asList = listOf(topLeft, topRight, bottomLeft, bottomRight)
}
val faceCorners = forgeDirs.map { when(it) {
DOWN -> FaceCorners(SOUTH, WEST)
UP -> FaceCorners(SOUTH, EAST)
NORTH -> FaceCorners(WEST, UP)
SOUTH -> FaceCorners(UP, WEST)
WEST -> FaceCorners(SOUTH, UP)
EAST -> FaceCorners(SOUTH, DOWN)
}}

View File

@@ -0,0 +1,56 @@
package mods.octarinecore.common.config
import mods.octarinecore.client.gui.NonVerboseArrayEntry
import mods.octarinecore.client.resource.get
import mods.octarinecore.client.resource.getLines
import mods.octarinecore.client.resource.resourceManager
import net.minecraftforge.common.config.Configuration
import net.minecraftforge.common.config.Property
abstract class BlackWhiteListConfigOption<VALUE>(val domain: String, val path: String) : ConfigPropertyBase() {
val blackList = mutableListOf<VALUE>()
val whiteList = mutableListOf<VALUE>()
var blacklistProperty: Property? = null
var whitelistProperty: Property? = null
override val hasChanged: Boolean
get() = blacklistProperty?.hasChanged() ?: false || whitelistProperty?.hasChanged() ?: false
override val guiProperties: List<Property> get() = listOf(whitelistProperty!!, blacklistProperty!!)
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
lang = null
val defaults = readDefaults(domain, path)
blacklistProperty = target.get(categoryName, "${propertyName}Blacklist", defaults.first)
whitelistProperty = target.get(categoryName, "${propertyName}Whitelist", defaults.second)
listOf(blacklistProperty!!, whitelistProperty!!).forEach {
it.configEntryClass = NonVerboseArrayEntry::class.java
it.languageKey = "$langPrefix.$categoryName.${it.name}"
}
read()
}
abstract fun convertValue(line: String): VALUE?
override fun read() {
listOf(Pair(blackList, blacklistProperty!!), Pair(whiteList, whitelistProperty!!)).forEach {
it.first.clear()
it.second.stringList.forEach { line ->
val value = convertValue(line)
if (value != null) it.first.add(value)
}
}
}
fun readDefaults(domain: String, path: String): Pair<Array<String>, Array<String>> {
val blackList = arrayListOf<String>()
val whiteList = arrayListOf<String>()
val defaults = resourceManager[domain, path]?.getLines()
defaults?.map{ it.trim() }?.filter { !it.startsWith("//") && it.isNotEmpty() }?.forEach {
if (it.startsWith("-")) { blackList.add(it.substring(1)) }
else { whiteList.add(it) }
}
return (blackList.toTypedArray() to whiteList.toTypedArray())
}
}

View File

@@ -0,0 +1,266 @@
package mods.octarinecore.common.config
import com.google.common.collect.LinkedListMultimap
import mods.betterfoliage.loader.Refs
import mods.octarinecore.metaprog.reflectField
import mods.octarinecore.metaprog.reflectFieldsOfType
import mods.octarinecore.metaprog.reflectNestedObjects
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.common.config.ConfigElement
import net.minecraftforge.common.config.Configuration
import net.minecraftforge.common.config.Property
import net.minecraftforge.fml.client.config.GuiConfigEntries
import net.minecraftforge.fml.client.config.IConfigElement
import net.minecraftforge.fml.client.event.ConfigChangedEvent
import net.minecraftforge.fml.common.FMLCommonHandler
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import kotlin.reflect.KProperty
// ============================
// Configuration object base
// ============================
/**
* Base class for declarative configuration handling.
*
* Subclasses should be singleton objects, containing one layer of further singleton objects representing
* config categories (nesting is not supported).
*
* Both the root object (maps to the category _global_) and category objects can contain [ConfigPropertyBase]
* instances (either directly or as a delegate), which handle the Forge [Configuration] itself.
*
* Config properties map to language keys by their field names.
*
* @param[modId] mod ID this configuration is linked to
* @param[langPrefix] prefix to use for language keys
*/
abstract class DelegatingConfig(val modId: String, val langPrefix: String) {
init { MinecraftForge.EVENT_BUS.register(this) }
/** The [Configuration] backing this config object. */
var config: Configuration? = null
val rootGuiElements = mutableListOf<IConfigElement>()
/** Attach this config object to the given [Configuration] and update all properties. */
fun attach(config: Configuration) {
this.config = config
val subProperties = LinkedListMultimap.create<String, String>()
rootGuiElements.clear()
forEachProperty { category, name, property ->
property.lang = property.lang ?: "$category.$name"
property.attach(config, langPrefix, category, name)
property.guiProperties.forEach { guiProperty ->
property.guiClass?.let { guiProperty.setConfigEntryClass(it) }
if (category == "global") rootGuiElements.add(ConfigElement(guiProperty))
else subProperties.put(category, guiProperty.name)
}
}
for (category in subProperties.keySet()) {
val configCategory = config.getCategory(category)
configCategory.setLanguageKey("$langPrefix.$category")
configCategory.setPropertyOrder(subProperties[category])
rootGuiElements.add(ConfigElement(configCategory))
}
save()
// hide all categories not in the config singleton
config.categoryNames.forEach {
config.getCategory(it).setShowInGui(it in subProperties.keySet())
}
}
/**
* Execute the given lambda for all config properties.
* Lambda params: (category name, property name, property instance)
*/
inline fun forEachProperty(init: (String, String, ConfigPropertyBase)->Unit) {
reflectFieldsOfType(ConfigPropertyBase::class.java).forEach { property ->
init("global", property.first.split("$")[0], property.second as ConfigPropertyBase)
}
for (category in reflectNestedObjects) {
category.second.reflectFieldsOfType(ConfigPropertyBase::class.java).forEach { property ->
init(category.first, property.first.split("$")[0], property.second as ConfigPropertyBase)
}
}
}
/** Save changes to the [Configuration]. */
fun save() { if (config?.hasChanged() ?: false) config!!.save() }
/**
* Returns true if any of the given configuration elements have changed.
* Supports both categories and
*/
fun hasChanged(elements: List<ConfigPropertyBase>): Boolean {
reflectNestedObjects.forEach { category ->
if (category.second in elements && config?.getCategory(category.first)?.hasChanged() ?: false) return true
}
forEachProperty { category, name, property ->
if (property in elements && property.hasChanged) return true
}
return false
}
/** Called when the configuration for the mod changes. */
abstract fun onChange(event: ConfigChangedEvent.PostConfigChangedEvent)
@SubscribeEvent
fun handleConfigChange(event: ConfigChangedEvent.PostConfigChangedEvent) {
if (event.modID == modId) {
// refresh values
forEachProperty { c, n, prop -> prop.read() }
// call mod-specific handler
onChange(event)
// save to file
save()
Refs.resetChangedState.invoke(config!!)
}
}
/** Extension to get the underlying delegate of a field */
operator fun Any.get(name: String) = this.reflectField<ConfigPropertyBase>("$name\$delegate")
}
// ============================
// Property delegates
// ============================
/** Base class for config property delegates. */
abstract class ConfigPropertyBase {
/** Language key of the property. */
var lang: String? = null
/** GUI class to use. */
var guiClass: Class<out GuiConfigEntries.IConfigEntry>? = null
/** @return true if the property has changed. */
abstract val hasChanged: Boolean
/** Attach this delegate to a Forge [Configuration]. */
abstract fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String)
/** List of [Property] instances backing this delegate. */
abstract val guiProperties: List<Property>
/** Re-read the property value from the [Configuration]. */
open fun read() {}
}
class ObsoleteConfigProperty : ConfigPropertyBase() {
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
target.getCategory(categoryName)?.remove(propertyName)
}
override val guiProperties = emptyList<Property>()
override val hasChanged: Boolean get() = false
}
/** Delegate for a property backed by a single [Property] instance. */
abstract class ConfigPropertyDelegate<T>() : ConfigPropertyBase() {
/** Cached value of the property. */
var cached: T? = null
/** The [Property] backing this delegate. */
var property: Property? = null
override val guiProperties: List<Property> get() = listOf(property!!)
override val hasChanged: Boolean get() = property?.hasChanged() ?: false
/** Chained setter for the language key. */
fun lang(lang: String) = apply { this.lang = lang }
/** Read the backing [Property] instance. */
abstract fun Property.read(): T
/** Write the backing [Property] instance. */
abstract fun Property.write(value: T)
/** Get the backing [Property] instance. */
abstract fun resolve(target: Configuration, category: String, name: String): Property
/** Kotlin deleagation implementation. */
operator fun getValue(thisRef: Any, delegator: KProperty<*>): T {
if (cached != null) return cached!!
cached = property!!.read()
return cached!!
}
/** Kotlin deleagation implementation. */
operator fun setValue(thisRef: Any, delegator: KProperty<*>, value: T) {
cached = value
property!!.write(value)
}
override fun read() { cached = null }
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
cached = null
property = resolve(target, categoryName, propertyName)
property!!.setLanguageKey("$langPrefix.$lang")
}
}
/** [Double]-typed property delegate. */
class ConfigPropertyDouble(val min: Double, val max: Double, val default: Double) :
ConfigPropertyDelegate<Double>() {
override fun resolve(target: Configuration, category: String, name: String) =
target.get(category, name, default, null).apply { setMinValue(min); setMaxValue(max) }
override fun Property.read() = property!!.double
override fun Property.write(value: Double) = property!!.set(value)
}
/** [Float]-typed property delegate. */
class ConfigPropertyFloat(val min: Double, val max: Double, val default: Double) :
ConfigPropertyDelegate<Float>() {
override fun resolve(target: Configuration, category: String, name: String) =
target.get(category, name, default, null).apply { setMinValue(min); setMaxValue(max) }
override fun Property.read() = property!!.double.toFloat()
override fun Property.write(value: Float) = property!!.set(value.toDouble())
}
/** [Int]-typed property delegate. */
class ConfigPropertyInt(val min: Int, val max: Int, val default: Int) :
ConfigPropertyDelegate<Int>() {
override fun resolve(target: Configuration, category: String, name: String) =
target.get(category, name, default, null).apply { setMinValue(min); setMaxValue(max) }
override fun Property.read() = property!!.int
override fun Property.write(value: Int) = property!!.set(value)
}
/** [Long]-typed property delegate. Still uses [Int] internally */
class ConfigPropertyLong(val min: Int, val max: Int, val default: Int) :
ConfigPropertyDelegate<Long>() {
override fun resolve(target: Configuration, category: String, name: String) =
target.get(category, name, default, null).apply { setMinValue(min); setMaxValue(max) }
override fun Property.read() = property!!.long
override fun Property.write(value: Long) = property!!.set(value)
}
/** [Boolean]-typed property delegate. */
class ConfigPropertyBoolean(val default: Boolean) :
ConfigPropertyDelegate<Boolean>() {
override fun resolve(target: Configuration, category: String, name: String) =
target.get(category, name, default, null)
override fun Property.read() = property!!.boolean
override fun Property.write(value: Boolean) = property!!.set(value)
}
/** [Int] array typed property delegate. */
class ConfigPropertyIntList(val defaults: ()->Array<Int>) :
ConfigPropertyDelegate<Array<Int>>() {
override fun resolve(target: Configuration, category: String, name: String) =
target.get(category, name, defaults().toIntArray(), null)
override fun Property.read() = property!!.intList.toTypedArray()
override fun Property.write(value: Array<Int>) = property!!.set(value.toIntArray())
}
// ============================
// Delegate factory methods
// ============================
fun double(min: Double = 0.0, max: Double = 1.0, default: Double) = ConfigPropertyDouble(min, max, default)
fun float(min: Double = 0.0, max: Double = 1.0, default: Double) = ConfigPropertyFloat(min, max, default)
fun int(min: Int = 0, max: Int, default: Int) = ConfigPropertyInt(min, max, default)
fun long(min: Int = 0, max: Int, default: Int) = ConfigPropertyLong(min, max, default)
fun intList(defaults: ()->Array<Int>) = ConfigPropertyIntList(defaults)
fun boolean(default: Boolean) = ConfigPropertyBoolean(default)

View File

@@ -0,0 +1,50 @@
package mods.octarinecore.common.config
import mods.octarinecore.metaprog.getJavaClass
import net.minecraft.block.Block
import net.minecraft.util.ResourceLocation
interface IBlockMatcher {
fun matchesClass(block: Block): Boolean
fun matchingClass(block: Block): Class<*>?
}
class SimpleBlockMatcher(vararg val classes: Class<*>) : IBlockMatcher {
override fun matchesClass(block: Block) = matchingClass(block) != null
override fun matchingClass(block: Block): Class<*>? {
val blockClass = block.javaClass
classes.forEach { if (it.isAssignableFrom(blockClass)) return it }
return null
}
}
class ConfigurableBlockMatcher(domain: String, path: String) : IBlockMatcher, BlackWhiteListConfigOption<Class<*>>(domain, path) {
override fun convertValue(line: String) = getJavaClass(line)
override fun matchesClass(block: Block): Boolean {
val blockClass = block.javaClass
blackList.forEach { if (it.isAssignableFrom(blockClass)) return false }
whiteList.forEach { if (it.isAssignableFrom(blockClass)) return true }
return false
}
override fun matchingClass(block: Block): Class<*>? {
val blockClass = block.javaClass
blackList.forEach { if (it.isAssignableFrom(blockClass)) return null }
whiteList.forEach { if (it.isAssignableFrom(blockClass)) return it }
return null
}
}
data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List<String>) {
constructor(vararg args: String) : this(ResourceLocation(args[0]), listOf(*args).drop(1))
}
class ModelTextureListConfigOption(domain: String, path: String, val minTextures: Int) : StringListConfigOption<ModelTextureList>(domain, path) {
override fun convertValue(line: String): ModelTextureList? {
val elements = line.split(",")
if (elements.size < minTextures + 1) return null
return ModelTextureList(ResourceLocation(elements.first()), elements.drop(1))
}
}

View File

@@ -0,0 +1,43 @@
package mods.octarinecore.common.config
import mods.octarinecore.client.gui.NonVerboseArrayEntry
import mods.octarinecore.client.resource.get
import mods.octarinecore.client.resource.getLines
import mods.octarinecore.client.resource.resourceManager
import net.minecraftforge.common.config.Configuration
import net.minecraftforge.common.config.Property
abstract class StringListConfigOption<VALUE>(val domain: String, val path: String) : ConfigPropertyBase() {
val list = mutableListOf<VALUE>()
lateinit var listProperty: Property
override val hasChanged: Boolean get() = listProperty.hasChanged() ?: false
override val guiProperties: List<Property> get() = listOf(listProperty)
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
lang = null
val defaults = readDefaults(domain, path)
listProperty = target.get(categoryName, "${propertyName}", defaults)
listProperty.configEntryClass = NonVerboseArrayEntry::class.java
listProperty.languageKey = "$langPrefix.$categoryName.${listProperty.name}"
read()
}
abstract fun convertValue(line: String): VALUE?
override fun read() {
list.clear()
listProperty.stringList.forEach { line ->
val value = convertValue(line)
if (value != null) list.add(value)
}
}
fun readDefaults(domain: String, path: String): Array<String> {
val list = arrayListOf<String>()
val defaults = resourceManager[domain, path]?.getLines()
defaults?.map { it.trim() }?.filter { !it.startsWith("//") && it.isNotEmpty() }?.forEach { list.add(it) }
return list.toTypedArray()
}
}

View File

@@ -0,0 +1,169 @@
@file:JvmName("Reflection")
package mods.octarinecore.metaprog
import java.lang.reflect.Field
import java.lang.reflect.Method
import mods.octarinecore.metaprog.Namespace.*
import mods.octarinecore.tryDefault
/** Get a Java class with the given name. */
fun getJavaClass(name: String) = tryDefault(null) { Class.forName(name) }
/** Get the field with the given name and type using reflection. */
inline fun <reified T> Any.reflectField(field: String): T? =
tryDefault(null) { this.javaClass.getDeclaredField(field) }?.let {
it.isAccessible = true
it.get(this) as T
}
/** Get the static field with the given name and type using reflection. */
inline fun <reified T> Class<*>.reflectStaticField(field: String): T? =
tryDefault(null) { this.getDeclaredField(field) }?.let {
it.isAccessible = true
it.get(null) as T
}
/**
* Get all nested _object_s of this _object_ with reflection.
*
* @return [Pair]s of (name, instance)
*/
val Any.reflectNestedObjects: List<Pair<String, Any>> get() = this.javaClass.declaredClasses.map {
tryDefault(null) { it.name.split("$")[1] to it.getField("INSTANCE").get(null) }
}.filterNotNull()
/**
* Get all fields of this instance that match (or subclass) any of the given classes.
*
* @param[types] classes to look for
* @return [Pair]s of (field name, instance)
*/
fun Any.reflectFieldsOfType(vararg types: Class<*>) = this.javaClass.declaredFields
.filter { field -> types.any { it.isAssignableFrom(field.type) } }
.map { field -> field.name to field.let { it.isAccessible = true; it.get(this) } }
.filterNotNull()
enum class Namespace { MCP, SRG }
abstract class Resolvable<T> {
abstract fun resolve(): T?
val element: T? by lazy { resolve() }
}
/** Return true if all given elements are found. */
fun allAvailable(vararg codeElement: Resolvable<*>) = codeElement.all { it.element != null }
/**
* Reference to a class.
*
* @param[name] MCP name of the class
*/
open class ClassRef(val name: String) : Resolvable<Class<*>>() {
companion object {
val int = ClassRefPrimitive("I", Int::class.java)
val long = ClassRefPrimitive("J", Long::class.java)
val float = ClassRefPrimitive("F", Float::class.java)
val boolean = ClassRefPrimitive("Z", Boolean::class.java)
val void = ClassRefPrimitive("V", null)
}
open fun asmDescriptor(namespace: Namespace) = "L${name.replace(".", "/")};"
override fun resolve() = getJavaClass(name)
fun isInstance(obj: Any) = element?.isInstance(obj) ?: false
}
/**
* Reference to a primitive type.
*
* @param[name] ASM descriptor of this primitive type
* @param[clazz] class of this primitive type
*/
class ClassRefPrimitive(name: String, val clazz: Class<*>?) : ClassRef(name) {
override fun asmDescriptor(namespace: Namespace) = name
override fun resolve() = clazz
}
class ClassRefArray(name: String) : ClassRef(name) {
override fun asmDescriptor(namespace: Namespace) = "[" + super.asmDescriptor(namespace)
override fun resolve() = getJavaClass("[L$name;")
}
fun ClassRef.array() = ClassRefArray(name)
/**
* Reference to a method.
*
* @param[parentClass] reference to the class containing the method
* @param[mcpName] MCP name of the method
* @param[srgName] SRG name of the method
* @param[returnType] reference to the return type
* @param[returnType] references to the argument types
*/
class MethodRef(val parentClass: ClassRef,
val mcpName: String,
val srgName: String,
val returnType: ClassRef,
vararg val argTypes: ClassRef
) : Resolvable<Method>() {
constructor(parentClass: ClassRef, mcpName: String, returnType: ClassRef, vararg argTypes: ClassRef) :
this(parentClass, mcpName, mcpName, returnType, *argTypes)
fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
fun asmDescriptor(namespace: Namespace) = "(${argTypes.map { it.asmDescriptor(namespace) }.fold(""){ s1, s2 -> s1 + s2 } })${returnType.asmDescriptor(namespace)}"
override fun resolve(): Method? =
if (parentClass.element == null || argTypes.any { it.element == null }) null
else {
val args = argTypes.map { it.element!! }.toTypedArray()
listOf(srgName, mcpName).map { tryDefault(null) {
parentClass.element!!.getDeclaredMethod(it, *args)
}}.filterNotNull().firstOrNull()
?.apply { isAccessible = true }
}
/** Invoke this method using reflection. */
fun invoke(receiver: Any, vararg args: Any?) = element?.invoke(receiver, *args)
/** Invoke this static method using reflection. */
fun invokeStatic(vararg args: Any) = element?.invoke(null, *args)
}
/**
* Reference to a field.
*
* @param[parentClass] reference to the class containing the field
* @param[mcpName] MCP name of the field
* @param[srgName] SRG name of the field
* @param[type] reference to the field type
*/
class FieldRef(val parentClass: ClassRef,
val mcpName: String,
val srgName: String,
val type: ClassRef?
) : Resolvable<Field>() {
constructor(parentClass: ClassRef, mcpName: String, type: ClassRef?) : this(parentClass, mcpName, mcpName, type)
fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
fun asmDescriptor(namespace: Namespace) = type!!.asmDescriptor(namespace)
override fun resolve(): Field? =
if (parentClass.element == null) null
else {
listOf(srgName, mcpName).map { tryDefault(null) {
parentClass.element!!.getDeclaredField(it)
}}.filterNotNull().firstOrNull()
?.apply{ isAccessible = true }
}
/** Get this field using reflection. */
fun get(receiver: Any?) = element?.get(receiver)
/** Get this static field using reflection. */
fun getStatic() = get(null)
fun set(receiver: Any?, obj: Any?) { element?.set(receiver, obj) }
}

View File

@@ -0,0 +1,212 @@
package mods.octarinecore.metaprog
import mods.octarinecore.metaprog.Namespace.*
import net.minecraft.launchwrapper.IClassTransformer
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin
import org.apache.logging.log4j.LogManager
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.*
import java.io.File
import java.io.FileOutputStream
/**
* Base class for convenient bytecode transformers.
*/
open class Transformer : IClassTransformer {
val log = LogManager.getLogger(this)
/** The list of transformers and targets. */
var methodTransformers: MutableList<Pair<MethodRef, MethodTransformContext.()->Unit>> = arrayListOf()
/** Add a transformation to perform. Call this during instance initialization.
*
* @param[method] the target method of the transformation
* @param[trans] method transformation lambda
*/
fun transformMethod(method: MethodRef, trans: MethodTransformContext.()->Unit) = methodTransformers.add(method to trans)
override fun transform(name: String?, transformedName: String?, classData: ByteArray?): ByteArray? {
if (classData == null) return null
val classNode = ClassNode().apply { val reader = ClassReader(classData); reader.accept(this, 0) }
var workDone = false
var writerFlags = 0
synchronized(this) {
methodTransformers.forEach { (targetMethod, transform) ->
if (transformedName != targetMethod.parentClass.name) return@forEach
for (method in classNode.methods) {
val namespace = Namespace.values().find {
method.name == targetMethod.name(it) && method.desc == targetMethod.asmDescriptor(it)
} ?: continue
when (namespace) {
MCP -> log.info("Found method ${targetMethod.parentClass.name}.${targetMethod.name(MCP)} ${targetMethod.asmDescriptor(MCP)}")
SRG -> log.info("Found method ${targetMethod.parentClass.name}.${targetMethod.name(namespace)} ${targetMethod.asmDescriptor(namespace)} (matching ${targetMethod.name(MCP)})")
}
// write input bytecode for debugging - definitely not in production...
//File("BF_debug").mkdir()
//FileOutputStream(File("BF_debug/$transformedName.class")).apply {
// write(classData)
// close()
//}
// transform
writerFlags = MethodTransformContext(method, namespace, writerFlags).apply(transform).writerFlags
workDone = true
}
}
}
return if (!workDone) classData else ClassWriter(writerFlags).apply { classNode.accept(this) }.toByteArray()
}
}
/**
* Allows builder-style declarative definition of transformations. Transformation lambdas are extension
* methods on this class.
*
* @param[method] the [MethodNode] currently being transformed
* @param[environment] the type of environment we are in
*/
class MethodTransformContext(val method: MethodNode, val environment: Namespace, var writerFlags: Int) {
fun applyWriterFlags(vararg flagValue: Int) { flagValue.forEach { writerFlags = writerFlags or it } }
fun makePublic() {
method.access = (method.access or Opcodes.ACC_PUBLIC) and (Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED).inv()
}
/**
* Find the first instruction that matches a predicate.
*
* @param[start] the instruction node to start iterating from
* @param[predicate] the predicate to check
*/
fun find(start: AbstractInsnNode, predicate: (AbstractInsnNode) -> Boolean): AbstractInsnNode? {
var current: AbstractInsnNode? = start
while (current != null && !predicate(current)) current = current.next
return current
}
/** Find the first instruction in the current [MethodNode] that matches a predicate. */
fun find(predicate: (AbstractInsnNode)->Boolean): AbstractInsnNode? = find(method.instructions.first, predicate)
/** Find the first instruction in the current [MethodNode] with the given opcode. */
fun find(opcode: Int) = find { it.opcode == opcode }
/**
* Insert new instructions after this one.
*
* @param[init] builder-style lambda to assemble instruction list
*/
fun AbstractInsnNode.insertAfter(init: InstructionList.()->Unit) = InstructionList(environment).apply{
this.init(); list.reversed().forEach { method.instructions.insert(this@insertAfter, it) }
}
/**
* Insert new instructions before this one.
*
* @param[init] builder-style lambda to assemble instruction list
*/
fun AbstractInsnNode.insertBefore(init: InstructionList.()->Unit) = InstructionList(environment).apply{
val insertBeforeNode = this@insertBefore //.let { if (it.previous is FrameNode) it.previous else it }
this.init(); list.forEach { method.instructions.insertBefore(insertBeforeNode, it) }
}
fun AbstractInsnNode.replace(init: InstructionList.()->Unit) = InstructionList(environment).apply {
insertAfter(init)
method.instructions.remove(this@replace)
}
/** Remove all isntructiuons between the given two (inclusive). */
fun Pair<AbstractInsnNode, AbstractInsnNode>.remove() {
var current: AbstractInsnNode? = first
while (current != null && current != second) {
val next = current.next
method.instructions.remove(current)
current = next
}
if (current != null) method.instructions.remove(current)
}
/**
* Replace all isntructiuons between the given two (inclusive) with the specified instruction list.
*
* @param[init] builder-style lambda to assemble instruction list
*/
fun Pair<AbstractInsnNode, AbstractInsnNode>.replace(init: InstructionList.()->Unit) {
val beforeInsn = first.previous
remove()
beforeInsn.insertAfter(init)
}
/**
* Matches variable instructions.
*
* @param[opcode] instruction opcode
* @param[idx] variable the opcode references
*/
fun varinsn(opcode: Int, idx: Int): (AbstractInsnNode)->Boolean = { insn ->
insn.opcode == opcode && insn is VarInsnNode && insn.`var` == idx
}
fun invokeName(name: String): (AbstractInsnNode)->Boolean = { insn ->
(insn as? MethodInsnNode)?.name == name
}
fun invokeRef(ref: MethodRef): (AbstractInsnNode)->Boolean = { insn ->
(insn as? MethodInsnNode)?.let {
it.name == ref.name(environment) && it.owner == ref.parentClass.name.replace(".", "/")
} ?: false
}
}
/**
* Allows builder-style declarative definition of instruction lists.
*
* @param[environment] the type of environment we are in
*/
class InstructionList(val environment: Namespace) {
fun insn(opcode: Int) = list.add(InsnNode(opcode))
/** The instruction list being assembled. */
val list: MutableList<AbstractInsnNode> = arrayListOf()
/**
* Adds a variable instruction.
*
* @param[opcode] instruction opcode
* @param[idx] variable the opcode references
*/
fun varinsn(opcode: Int, idx: Int) = list.add(VarInsnNode(opcode, idx))
/**
* Adds an INVOKESTATIC instruction.
*
* @param[target] the target method of the instruction
* @param[isInterface] true if the target method is defined by an interface
*/
fun invokeStatic(target: MethodRef, isInterface: Boolean = false) = list.add(MethodInsnNode(
Opcodes.INVOKESTATIC,
target.parentClass.name.replace(".", "/"),
target.name(environment),
target.asmDescriptor(environment),
isInterface
))
/**
* Adds a GETFIELD instruction.
*
* @param[target] the target field of the instruction
*/
fun getField(target: FieldRef) = list.add(FieldInsnNode(
Opcodes.GETFIELD,
target.parentClass.name.replace(".", "/"),
target.name(environment),
target.asmDescriptor(environment)
))
}

View File

@@ -0,0 +1,15 @@
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178206_b # vertexColorMultiplier
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178207_c # vertexBrightness
public net.minecraft.client.renderer.block.model.ModelBakery field_177610_k # blockModelShapes
public net.minecraft.client.renderer.block.statemap.BlockStateMapper field_178450_a # blockStateMap
public net.minecraft.client.renderer.BufferBuilder field_178999_b # rawIntBuffer
public net.minecraft.client.renderer.BufferBuilder func_181670_b(I)V # growBuffer
public net.minecraft.client.renderer.BufferBuilder func_181664_j()I # getBufferSize
public net.minecraft.client.renderer.BlockModelRenderer field_187499_a # blockColors
public net.minecraft.world.ChunkCache field_72815_e # world

View File

@@ -0,0 +1,5 @@
// Vanilla
net.minecraft.block.BlockCactus
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Vanilla.BlockCustomCactus

View File

@@ -0,0 +1,43 @@
// Vanilla
net.minecraft.block.BlockTallGrass
net.minecraft.block.BlockCrops
-net.minecraft.block.BlockReed
-net.minecraft.block.BlockDoublePlant
-net.minecraft.block.BlockCarrot
-net.minecraft.block.BlockPotato
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPFlower
biomesoplenty.common.block.BlockBOPTurnip
biomesoplenty.common.block.BlockBOPPlant
// Tinkers' Construct
tconstruct.blocks.slime.SlimeTallGrass
// Plant Mega Pack
plantmegapack.block.PMPBlockBerrybush
plantmegapack.block.PMPBlockCrops
plantmegapack.block.PMPBlockDesert
plantmegapack.block.PMPBlockFern
plantmegapack.block.PMPBlockFlowerMulti
plantmegapack.block.PMPBlockFlowerSingle
plantmegapack.block.PMPBlockForest
plantmegapack.block.PMPBlockGrass
plantmegapack.block.PMPBlockJungle
plantmegapack.block.PMPBlockMountain
plantmegapack.block.PMPBlockSavanna
plantmegapack.block.PMPBlockShrub
plantmegapack.block.PMPBlockWetlands
// Pam's HarvestCraft
com.pam.harvestcraft.BlockPamCrop
com.pam.harvestcraft.BlockPamDesertGarden
com.pam.harvestcraft.BlockPamNormalGarden
com.pam.harvestcraft.BlockPamWaterGarden
// Plants
shadows.plants2.block.BlockEnumCrop
// Cuisine
snownee.cuisine.blocks.BlockCuisineCrops
-snownee.cuisine.blocks.BlockDoubleCrops

View File

@@ -0,0 +1,18 @@
// Vanilla
net.minecraft.block.BlockDirt
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPDirt
// Enhanced Biomes
enhancedbiomes.blocks.BlockSoilEB
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Terrain.BlockDirt
// Aether
net.aetherteam.aether.blocks.natural.BlockAetherDirt
com.gildedgames.aether.common.blocks.natural.BlockAetherDirt
// Tinker's Construct
slimeknights.tconstruct.world.block.BlockSlimeDirt

View File

@@ -0,0 +1,18 @@
// Vanilla
net.minecraft.block.BlockGrass
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPGrass
// Tinker's Construct
tconstruct.blocks.slime.SlimeGrass
// Enhanced Biomes
enhancedbiomes.blocks.BlockGrassEB
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Terrain.BlockGrass
// AbyssalCraft
com.shinoow.abyssalcraft.common.blocks.BlockDreadGrass
com.shinoow.abyssalcraft.common.blocks.BlockDarklandsgrass

View File

@@ -0,0 +1,6 @@
// Vanilla
block/grass,top
block/cube_bottom_top,top
// Lithos Core
block/soil/grass_master,top

View File

@@ -0,0 +1,250 @@
key.betterfoliage.gui=Open Settings
betterfoliage.global.enabled=Enable Mod
betterfoliage.global.enabled.tooltip=If set to false, BetterFoliage will not render anything
betterfoliage.global.nVidia=nVidia GPU
betterfoliage.global.nVidia.tooltip=Specify whether you have an nVidia GPU
betterfoliage.enabled=Enable
betterfoliage.enabled.tooltip=Is this feature enabled?
betterfoliage.hOffset=Horizontal offset
betterfoliage.hOffset.tooltip=The distance this element is shifted horizontally, in blocks
betterfoliage.vOffset=Vertical offset
betterfoliage.vOffset.tooltip=The distance this element is shifted vertically, in blocks
betterfoliage.size=Size
betterfoliage.size.tooltip=Size of this element
betterfoliage.heightMin=Minimum height
betterfoliage.heightMin.tooltip=Minimum height of element
betterfoliage.heightMax=Maximum height
betterfoliage.heightMax.tooltip=Maximum height of element
betterfoliage.population=Population
betterfoliage.population.tooltip=Chance (N in 64) that an eligible block will have this feature
betterfoliage.shaderWind=Shader wind effects
betterfoliage.shaderWind.tooltip=Apply wind effects from ShaderMod shaders to this element?
betterfoliage.distance=Distance limit
betterfoliage.distance.tooltip=Maximum distance from player at which to render this feature
betterfoliage.rendererror=§a[BetterFoliage]§f Error rendering block %s at position %s
betterfoliage.blocks=Block Types
betterfoliage.blocks.tooltip=Configure lists of block classes that will have specific features applied to them
betterfoliage.blocks.dirtWhitelist=Dirt Whitelist
betterfoliage.blocks.dirtBlacklist=Dirt Blacklist
betterfoliage.blocks.dirtWhitelist.arrayEntry=%d entries
betterfoliage.blocks.dirtBlacklist.arrayEntry=%d entries
betterfoliage.blocks.dirtWhitelist.tooltip=Blocks recognized as Dirt. Has an impact on Reeds, Algae, Connected Grass
betterfoliage.blocks.dirtBlacklist.tooltip=Blocks never accepted as Dirt. Has an impact on Reeds, Algae, Connected Grass
betterfoliage.blocks.grassClassesWhitelist=Grass Whitelist
betterfoliage.blocks.grassClassesBlacklist=Grass Blacklist
betterfoliage.blocks.grassClassesWhitelist.arrayEntry=%d entries
betterfoliage.blocks.grassClassesBlacklist.arrayEntry=%d entries
betterfoliage.blocks.grassModels=Grass Models
betterfoliage.blocks.grassModels.arrayEntry=%d entries
betterfoliage.blocks.grassWhitelist.tooltip=Blocks recognized as Grass. Has an impact on Short Grass, Connected Grass
betterfoliage.blocks.grassBlacklist.tooltip=Blocks never accepted as Grass. Has an impact on Short Grass, Connected Grass
betterfoliage.blocks.grassModels.tooltip=Models and textures recognized for grass blocks
betterfoliage.blocks.leavesClassesWhitelist=Leaves Whitelist
betterfoliage.blocks.leavesClassesBlacklist=Leaves Blacklist
betterfoliage.blocks.leavesClassesWhitelist.arrayEntry=%d entries
betterfoliage.blocks.leavesClassesBlacklist.arrayEntry=%d entries
betterfoliage.blocks.leavesModels=Leaves Models
betterfoliage.blocks.leavesModels.arrayEntry=%d entries
betterfoliage.blocks.leavesClassesWhitelist.tooltip=Blocks recognized as Leaves. Has an impact on Extra Leaves, Falling Leaves. Leaves will render with leaves block ID in shader programs
betterfoliage.blocks.leavesClassesBlacklist.tooltip=Blocks never accepted as Leaves. Has an impact on Extra Leaves, Falling Leaves. Leaves will render with leaves block ID in shader programs
betterfoliage.blocks.leavesModels.tooltip=Models and textures recognized for leaves blocks
betterfoliage.blocks.cropsWhitelist=Crop Whitelist
betterfoliage.blocks.cropsBlacklist=Crop Blacklist
betterfoliage.blocks.cropsWhitelist.arrayEntry=%d entries
betterfoliage.blocks.cropsBlacklist.arrayEntry=%d entries
betterfoliage.blocks.cropsWhitelist.tooltip=Blocks recognized as crops. Crops will render with tallgrass block ID in shader programs
betterfoliage.blocks.cropsBlacklist.tooltip=Blocks never accepted as crops. Crops will render with tallgrass block ID in shader programs
betterfoliage.blocks.logClassesWhitelist=Wood Log Whitelist
betterfoliage.blocks.logClassesBlacklist=Wood Log Blacklist
betterfoliage.blocks.logClassesWhitelist.arrayEntry=%d entries
betterfoliage.blocks.logClassesBlacklist.arrayEntry=%d entries
betterfoliage.blocks.logModels=Wood Log Models
betterfoliage.blocks.logModels.arrayEntry=%d entries
betterfoliage.blocks.logClassesWhitelist.tooltip=Blocks recognized as wooden logs. Has an impact on Rounded Logs
betterfoliage.blocks.logClassesBlacklist.tooltip=Blocks never accepted as wooden logs. Has an impact on Rounded Logs
betterfoliage.blocks.logModels.tooltip=Models and textures recognized for wood log blocks
betterfoliage.blocks.sandWhitelist=Sand Whitelist
betterfoliage.blocks.sandBlacklist=Sand Blacklist
betterfoliage.blocks.sandWhitelist.arrayEntry=%d entries
betterfoliage.blocks.sandBlacklist.arrayEntry=%d entries
betterfoliage.blocks.sandWhitelist.tooltip=Blocks recognized as Sand. Has an impact on Coral
betterfoliage.blocks.sandBlacklist.tooltip=Blocks never accepted Sand. Has an impact on Coral
betterfoliage.blocks.lilypadWhitelist=Lilypad Whitelist
betterfoliage.blocks.lilypadBlacklist=Lilypad Blacklist
betterfoliage.blocks.lilypadWhitelist.arrayEntry=%d entries
betterfoliage.blocks.lilypadBlacklist.arrayEntry=%d entries
betterfoliage.blocks.lilypadWhitelist.tooltip=Blocks recognized as Lilypad. Has an impact on Better Lilypad
betterfoliage.blocks.lilypadBlacklist.tooltip=Blocks never accepted Lilypad. Has an impact on Better Lilypad
betterfoliage.blocks.cactusWhitelist=Cactus Whitelist
betterfoliage.blocks.cactusBlacklist=Cactus Blacklist
betterfoliage.blocks.cactusWhitelist.arrayEntry=%d entries
betterfoliage.blocks.cactusBlacklist.arrayEntry=%d entries
betterfoliage.blocks.cactusWhitelist.tooltip=Blocks recognized as Cactus. Has an impact on Better Cactus
betterfoliage.blocks.cactusBlacklist.tooltip=Blocks never accepted Cactus. Has an impact on Better Cactus
betterfoliage.blocks.myceliumWhitelist=Mycelium Whitelist
betterfoliage.blocks.myceliumBlacklist=Mycelium Blacklist
betterfoliage.blocks.myceliumWhitelist.arrayEntry=%d entries
betterfoliage.blocks.myceliumBlacklist.arrayEntry=%d entries
betterfoliage.blocks.myceliumWhitelist.tooltip=Blocks recognized as Mycelium. Has an impact on Better Grass
betterfoliage.blocks.myceliumBlacklist.tooltip=Blocks never accepted Mycelium. Has an impact on Better Grass
betterfoliage.blocks.netherrackWhitelist=Netherrack Whitelist
betterfoliage.blocks.netherrackBlacklist=Netherrack Blacklist
betterfoliage.blocks.netherrackWhitelist.arrayEntry=%d entries
betterfoliage.blocks.netherrackBlacklist.arrayEntry=%d entries
betterfoliage.blocks.netherrackWhitelist.tooltip=Blocks recognized as Netherrack. Has an impact on Netherrack Vines
betterfoliage.blocks.netherrackBlacklist.tooltip=Blocks never accepted Netherrack. Has an impact on Netherrack Vines
betterfoliage.shaders=Shader configuration
betterfoliage.shaders.tooltip=Configure integration with shaders
betterfoliage.shaders.leavesId=Leaves ID
betterfoliage.shaders.leavesId.tooltip=Block ID reported to shader programs for all kinds of leaves. If your shader uses a §6block.properties§e file, you'll probably need to change this to match the shader's mappings.
betterfoliage.shaders.grassId=Grass ID
betterfoliage.shaders.grassId.tooltip=Block ID reported to shader programs for all grasses and crops. If your shader uses a §6block.properties§e file, you'll probably need to change this to match the shader's mappings.
betterfoliage.leaves=Extra Leaves
betterfoliage.leaves.tooltip=Extra round leaves on leaf blocks
betterfoliage.leaves.dense=Dense mode
betterfoliage.leaves.dense.tooltip=Dense mode has more round leaves
betterfoliage.leaves.snowEnabled=Enable snow
betterfoliage.leaves.snowEnabled.tooltip=Enable snow on extra leaves?
betterfoliage.leaves.hideInternal=Hide internal leaves
betterfoliage.leaves.hideInternal.tooltip=Skip rendering extra leaves if leaf block is completely surrounded by other leaves or solid blocks
betterfoliage.shortGrass=Short Grass & Mycelium
betterfoliage.shortGrass.tooltip=Tufts of grass/mycelium on top of appropriate blocks
betterfoliage.shortGrass.useGenerated=Use generated texture for grass
betterfoliage.shortGrass.useGenerated.tooltip=Generated texture is made by slicing the tallgrass texture from the active resource pack in half
betterfoliage.shortGrass.myceliumEnabled=Enable Mycelium
betterfoliage.shortGrass.myceliumEnabled.tooltip=Is this feature enabled for mycelium blocks?
betterfoliage.shortGrass.grassEnabled=Enable Grass
betterfoliage.shortGrass.grassEnabled.tooltip=Is this feature enabled for grass blocks?
betterfoliage.shortGrass.snowEnabled=Enable under snow
betterfoliage.shortGrass.snowEnabled.tooltip=Enable on snowed grass blocks?
betterfoliage.shortGrass.saturationThreshold=Saturation threshold
betterfoliage.shortGrass.saturationThreshold.tooltip=Color saturation cutoff between "colorless" blocks (using biome color) and "colorful" blocks (using their own specific color)
betterfoliage.hangingGrass=Hanging Grass
betterfoliage.hangingGrass.tooltip=Grass tufts hanging down from the top edges of grass blocks
betterfoliage.hangingGrass.separation=Separation
betterfoliage.hangingGrass.separation.tooltip=How much the hanging grass stands out from the block
betterfoliage.cactus=Better Cactus
betterfoliage.cactus.tooltip=Enhance cactus with extra bits and smooth shading
betterfoliage.cactus.sizeVariation=Size variation
betterfoliage.cactus.sizeVariation.tooltip=Amount of random variation on cactus size
betterfoliage.lilypad=Better Lilypad
betterfoliage.lilypad.tooltip=Enhance lilypad with roots and occasional flowers
betterfoliage.lilypad.flowerChance=Flower chance
betterfoliage.lilypad.flowerChance.tooltip=Chance (N in 64) of a lilypad having a flower on it
betterfoliage.reed=Reeds
betterfoliage.reed.tooltip=Reeds on dirt blocks in shallow water
betterfoliage.reed.biomes=Biome List
betterfoliage.reed.biomes.tooltip=Configure which biomes reeds are allowed to appear in
betterfoliage.reed.biomes.tooltip.element=Should reeds appear in the %s biome?
betterfoliage.algae=Algae
betterfoliage.algae.tooltip=Algae on dirt blocks in deep water
betterfoliage.algae.biomes=Biome List
betterfoliage.algae.biomes.tooltip=Configure which biomes algae is allowed to appear in
betterfoliage.algae.biomes.tooltip.element=Should algae appear in the %s biome?
betterfoliage.coral=Coral
betterfoliage.coral.tooltip=Coral on sand blocks in deep water
betterfoliage.coral.size=Coral size
betterfoliage.coral.size.tooltip=Size of coral bits sticking out
betterfoliage.coral.crustSize=Crust size
betterfoliage.coral.crustSize.tooltip=Size of the flat coral part
betterfoliage.coral.chance=Coral chance
betterfoliage.coral.chance.tooltip=Chance (N in 64) of a specific face of the block to show coral
betterfoliage.coral.biomes=Biome List
betterfoliage.coral.biomes.tooltip=Configure which biomes coral is allowed to appear in
betterfoliage.coral.biomes.tooltip.element=Should coral appear in the %s biome?
betterfoliage.coral.shallowWater=Shallow water coral
betterfoliage.coral.shallowWater.tooltip=Should coral appear in 1 block deep water?
betterfoliage.netherrack=Netherrack Vines
betterfoliage.netherrack.tooltip=Hanging Vines under netherrack
betterfoliage.fallingLeaves=Falling leaves
betterfoliage.fallingLeaves.tooltip=Falling leaf particle FX emitted from the bottom of leaf blocks
betterfoliage.fallingLeaves.speed=Particle speed
betterfoliage.fallingLeaves.speed.tooltip=Overall particle speed
betterfoliage.fallingLeaves.windStrength=Wind strength
betterfoliage.fallingLeaves.windStrength.tooltip=Magnitude of wind effects in good weather (spread of normal distribution centered on 0)
betterfoliage.fallingLeaves.stormStrength=Storm strength
betterfoliage.fallingLeaves.stormStrength.tooltip=Additional magnitude of wind effects in rainy weather (spread of normal distribution centered on 0)
betterfoliage.fallingLeaves.size=Particle size
betterfoliage.fallingLeaves.chance=Particle chance
betterfoliage.fallingLeaves.chance.tooltip=Chance of each random render tick hitting a leaf block to spawn a particle
betterfoliage.fallingLeaves.perturb=Perturbation
betterfoliage.fallingLeaves.perturb.tooltip=Magnitude of perturbation effect. Adds a corkscrew-like motion to the particle synchronized to its rotation
betterfoliage.fallingLeaves.lifetime=Maximum lifetime
betterfoliage.fallingLeaves.lifetime.tooltip=Maximum lifetime of particle in seconds. Minimum lifetime is 60%% of this value
betterfoliage.fallingLeaves.opacityHack=Opaque particles
betterfoliage.fallingLeaves.opacityHack.tooltip=Stop transparent blocks obscuring particles even when particle is in front. WARNING: may cause glitches.
betterfoliage.risingSoul=Rising souls
betterfoliage.risingSoul.tooltip=Rising soul particle FX emitted from the top of soulsand blocks
betterfoliage.risingSoul.chance=Particle chance
betterfoliage.risingSoul.chance.tooltip=Chance of each random render tick hitting a soulsand block to spawn a particle
betterfoliage.risingSoul.speed=Particle speed
betterfoliage.risingSoul.speed.tooltip=Vertical speed of soul particles
betterfoliage.risingSoul.perturb=Perturbation
betterfoliage.risingSoul.perturb.tooltip=Magnitude of perturbation effect. Adds a corkscrew-like motion to the particle
betterfoliage.risingSoul.headSize=Soul size
betterfoliage.risingSoul.headSize.tooltip=Size of the soul particle
betterfoliage.risingSoul.trailSize=Trail size
betterfoliage.risingSoul.trailSize.tooltip=Initial size of the particle trail
betterfoliage.risingSoul.opacity=Opacity
betterfoliage.risingSoul.opacity.tooltip=Opacity of the particle effect
betterfoliage.risingSoul.sizeDecay=Size decay
betterfoliage.risingSoul.sizeDecay.tooltip=Trail particle size relative to its size in the previous tick
betterfoliage.risingSoul.opacityDecay=Opacity decay
betterfoliage.risingSoul.opacityDecay.tooltip=Trail particle opacity relative to its opacity in the previous tick
betterfoliage.risingSoul.lifetime=Maximum lifetime
betterfoliage.risingSoul.lifetime.tooltip=Maximum lifetime of particle effect in seconds. Minimum lifetime is 60%% of this value
betterfoliage.risingSoul.trailLength=Trail length
betterfoliage.risingSoul.trailLength.tooltip=Number of previous positions the particle remembers in ticks
betterfoliage.risingSoul.trailDensity=Trail density
betterfoliage.risingSoul.trailDensity.tooltip=Render every Nth previous position in the particle trail
betterfoliage.connectedGrass=Connected grass textures
betterfoliage.connectedGrass.enabled=Enable
betterfoliage.connectedGrass.enabled.tooltip=If there is a grass block on top of a dirt block: draw grass top texture on all grass block sides,
betterfoliage.roundLogs=Round Logs
betterfoliage.roundLogs.tooltip=Connect round blocks to solid full blocks?
betterfoliage.roundLogs.connectSolids=Connect to solid
betterfoliage.roundLogs.connectSolids.tooltip=Connect round blocks to solid full blocks?
betterfoliage.roundLogs.connectPerpendicular=Connect to perpendicular logs
betterfoliage.roundLogs.connectPerpendicular.tooltip=Connect round logs to perpendicular logs along its axis?
betterfoliage.roundLogs.lenientConnect=Lenient rounding
betterfoliage.roundLogs.lenientConnect.tooltip=Connect parallel round logs in an L-shape too, not just 2x2
betterfoliage.roundLogs.connectGrass=Connect Grass
betterfoliage.roundLogs.connectGrass.tooltip=Render grass block under trees instead of dirt if there is grass nearby
betterfoliage.roundLogs.radiusSmall=Chamfer radius
betterfoliage.roundLogs.radiusSmall.tooltip=How much to chop off from the log corner
betterfoliage.roundLogs.radiusLarge=Connected chamfer radius
betterfoliage.roundLogs.radiusLarge.tooltip=How much to chop off from the outer corner of connected logs
betterfoliage.roundLogs.dimming=Dimming
betterfoliage.roundLogs.dimming.tooltip=Amount to darken obscured log faces
betterfoliage.roundLogs.zProtection=Z-Protection
betterfoliage.roundLogs.zProtection.tooltip=Amount to scale parallel log connection bits to stop Z-fighting (flickering). Try to set it as high as possible without having glitches.
betterfoliage.roundLogs.defaultY=Default to vertical
betterfoliage.roundLogs.defaultY.tooltip=If true, log blocks where the orientation cannot be determined will be rendered as vertical. Otherwise, they will be rendered as cube blocks.

View File

@@ -0,0 +1,216 @@
key.betterfoliage.gui=설정
betterfoliage.global.enabled=모드 활성화
betterfoliage.global.enabled.tooltip=비활성화 할 경우, 환경강화모드 렌더링이 보이지 않습니다.
betterfoliage.enabled=활성화
betterfoliage.enabled.tooltip=이 기능이 활성화 되어 있습니까?
betterfoliage.hOffset=수평(가로) 상쇄시키다
betterfoliage.hOffset.tooltip=이 성분이 블럭 수평으로 이동된다.
betterfoliage.vOffset=수직(세로) 상쇄시키다
betterfoliage.vOffset.tooltip=이 성분이 블럭 수직으로 이동된다.
betterfoliage.size=사이즈(크기)
betterfoliage.size.tooltip=사이즈(크기)의 최소
betterfoliage.heightMin=최소한 높이
betterfoliage.heightMin.tooltip=높이 최소한의 최소
betterfoliage.heightMax=최대한 높이
betterfoliage.heightMax.tooltip=높이 최대한의 최소
betterfoliage.population=주민
betterfoliage.population.tooltip=자격을 갖춘 블럭의 기능 확률(N 분의 64)
betterfoliage.shaderWind=쉐이더 바람 효과
betterfoliage.shaderWind.tooltip=바람효과를 쉐이더에 적용시키겠습니까?
betterfoliage.distance=거리 제한
betterfoliage.distance.tooltip=이 기능을 렌더링하는 플레이어의 최대거리
betterfoliage.blocks=블록 타입
betterfoliage.blocks.tooltip=세팅 된 것에 따라 블록이 바뀔 것입니다.
betterfoliage.blocks.dirtWhitelist=흙 허용목록
betterfoliage.blocks.dirtBlacklist=흙 차단목록
betterfoliage.blocks.dirtWhitelist.arrayEntry=%d entries
betterfoliage.blocks.dirtBlacklist.arrayEntry=%d entries
betterfoliage.blocks.grassWhitelist=잔디 허용목록
betterfoliage.blocks.grassBlacklist=잔디 차단목록
betterfoliage.blocks.grassWhitelist.arrayEntry=%d entries
betterfoliage.blocks.grassBlacklist.arrayEntry=%d entries
betterfoliage.blocks.leavesWhitelist=잎 허용목록
betterfoliage.blocks.leavesBlacklist=잎 차단목록
betterfoliage.blocks.leavesWhitelist.arrayEntry=%d entries
betterfoliage.blocks.leavesBlacklist.arrayEntry=%d entries
betterfoliage.blocks.cropsWhitelist=농작물 허용목록
betterfoliage.blocks.cropsBlacklist=농작물 차단목록
betterfoliage.blocks.cropsWhitelist.arrayEntry=%d entries
betterfoliage.blocks.cropsBlacklist.arrayEntry=%d entries
betterfoliage.blocks.logsWhitelist=나무 허용목록
betterfoliage.blocks.logsBlacklist=나무 차단목록
betterfoliage.blocks.logsWhitelist.arrayEntry=%d entries
betterfoliage.blocks.logsBlacklist.arrayEntry=%d entries
betterfoliage.blocks.sandWhitelist=모래 허용목록
betterfoliage.blocks.sandBlacklist=모래 차단목록
betterfoliage.blocks.sandWhitelist.arrayEntry=%d entries
betterfoliage.blocks.sandBlacklist.arrayEntry=%d entries
betterfoliage.blocks.lilypadWhitelist=연꽃 허용목록
betterfoliage.blocks.lilypadBlacklist=연꽃 차단목록
betterfoliage.blocks.lilypadWhitelist.arrayEntry=%d entries
betterfoliage.blocks.lilypadBlacklist.arrayEntry=%d entries
betterfoliage.blocks.cactusWhitelist=선인장 허용목록
betterfoliage.blocks.cactusBlacklist=선인장 차단목록
betterfoliage.blocks.cactusWhitelist.arrayEntry=%d entries
betterfoliage.blocks.cactusBlacklist.arrayEntry=%d entries
betterfoliage.blocks.dirtWhitelist.tooltip=흙으로 인식됩니다. 갈대, 조류, 잔디 텍스쳐 연결에 영향을 줍니다.
betterfoliage.blocks.dirtBlacklist.tooltip=흙으로 인식 되지 않습니다. 갈대, 조류, 잔디 텍스쳐 연결에 영향을 주지 않습니다.
betterfoliage.blocks.grassWhitelist.tooltip=잔디로 인식됩니다. 짧은 잔디, 잔디 텍스쳐 연결에 영향을 줍니다.
betterfoliage.blocks.grassBlacklist.tooltip=잔디로 인식 되지 않습니다. 짧은 잔디, 잔디 텍스쳐 연결에 영향을 주지 않습니다.
betterfoliage.blocks.leavesWhitelist.tooltip=잎으로 인식됩니다. 추가 잎, 떨어지는 잎에 영향을 줍니다.
betterfoliage.blocks.leavesBlacklist.tooltip=잎으로 인식 되지 않습니다. 추가 잎, 떨어지는 잎에 영향을 주지 않습니다.
betterfoliage.blocks.cropsWhitelist.tooltip=농작물로 인식됩니다. 농작물은 쉐이더 적용하면 큰잔디로 렌더링 됩니다.
betterfoliage.blocks.cropsBlacklist.tooltip=농작물로 인식 되지 않습니다. 농작물은 쉐이더 적용하면 큰잔디로 렌더링 되지 않습니다.
betterfoliage.blocks.logsWhitelist.tooltip=나무로 인식됩니다. 둥근 나무에 영향을 줍니다.
betterfoliage.blocks.logsBlacklist.tooltip=나무로 인식 되지 않습니다. 둥근 나무에 영향을 주지 않습니다.
betterfoliage.blocks.sandWhitelist.tooltip=모래로 인식됩니다. 산호에 영향을 줍니다
betterfoliage.blocks.sandBlacklist.tooltip=모래로 인식되지 않습니다. 산호에 영향을 주지 않습니다.
betterfoliage.blocks.lilypadWhitelist.tooltip=연꽃으로 인식됩니다. 보다 나은 연꽃에 영향을 줍니다.
betterfoliage.blocks.lilypadBlacklist.tooltip=연꽃으로 인식되지 않습니다. 보다 나은 연꽃에 영향을 주지 않습니다.
betterfoliage.blocks.cactusWhitelist.tooltip=선인장으로 인식됩니다. 보다 나은 선인장에 영향을 줍니다.
betterfoliage.blocks.cactusBlacklist.tooltip=선인장으로 인식되지 않습니다. 보다 나은 선인장에 영향을 주지 않습니다.
betterfoliage.leaves=잎 추가
betterfoliage.leaves.tooltip=둥글게 나뭇잎을 추가시켜줍니다.
betterfoliage.leaves.dense=조밀한 모드
betterfoliage.leaves.dense.tooltip=조밀한 모드는 둥근 나뭇잎을 더 추가시켜줍니다.
betterfoliage.shortGrass=이쁜 잔디 & 균사체
betterfoliage.shortGrass.tooltip=블록 상단에 잔디 / 균사체
betterfoliage.shortGrass.useGenerated=잔디텍스쳐를 활성화 합니다.
betterfoliage.shortGrass.useGenerated.tooltip=소스팩의 일부분에 활성화 되어있는 큰잔디 텍스쳐를 생성시킵니다.
betterfoliage.shortGrass.myceliumEnabled=균사체 활성화
betterfoliage.shortGrass.myceliumEnabled.tooltip=균사체 블록에 있는 기능을 활성화 시키겠습니까?
betterfoliage.shortGrass.grassEnabled=잔디 활성화
betterfoliage.shortGrass.grassEnabled.tooltip=잔디 블록에 있는 기능을 활성화 시키겠습니까?
betterfoliage.shortGrass.snowEnabled=눈 활성화
betterfoliage.shortGrass.snowEnabled.tooltip=잔디 블록위에 있는 눈을 활성화 시키겠습니까?
betterfoliage.shortGrass.saturationThreshold=채도 임계값
betterfoliage.shortGrass.saturationThreshold.tooltip=(특정 색상을 사용하여)"무채색"블록과 (바이옴 색을 사용하여)"화려한"블록 사이의 채도 차단
betterfoliage.hangingGrass=매달려있는 잔디
betterfoliage.hangingGrass.tooltip=잔디 블록 상단 가장자리에서 아래로 매달려 있는 잔디 다발
betterfoliage.hangingGrass.separation=분리
betterfoliage.hangingGrass.separation.tooltip=잔디블럭에 매달려있는 잔디의 양
betterfoliage.cactus=선인장
betterfoliage.cactus.tooltip=선인장의 비트 수와 부드러운 그림자를 추가
betterfoliage.cactus.sizeVariation=사이즈 변화
betterfoliage.cactus.sizeVariation.tooltip=선인장의 사이즈 크기를 무작위 변화 시킵니다.
betterfoliage.lilypad=연꽃잎
betterfoliage.lilypad.tooltip=연꽃의 뿌리와 약간의 꽃 추가
betterfoliage.lilypad.flowerChance=꽃 확률
betterfoliage.lilypad.flowerChance.tooltip=연꽃 위에 있는 꽃 확률(N 분의 64)
betterfoliage.reed=갈대
betterfoliage.reed.tooltip=물 속에서 흙블럭 위에 있는 갈대
betterfoliage.reed.biomes=바이옴 리스트
betterfoliage.reed.biomes.tooltip=갈대를 바이옴에 따라 표시
betterfoliage.reed.biomes.tooltip.element=갈대를 %s 바이옴에 따라 나타내시겠습니까?
betterfoliage.algae=해조류
betterfoliage.algae.tooltip=깊은 물 속 흙 블럭 위에 있는 해조류
betterfoliage.algae.biomes=바이옴 리스트
betterfoliage.algae.biomes.tooltip=해조류를 바이옴에 따라 표시
betterfoliage.algae.biomes.tooltip.element=해조류를 %s 바이옴에 따라 나타내시겠습니까?
betterfoliage.coral=산호
betterfoliage.coral.tooltip=깊은 물 속 모래 블럭 위에있는 산호
betterfoliage.coral.size=산호 사이즈(크기)
betterfoliage.coral.size.tooltip=산호 이미지만큼 적용
betterfoliage.coral.crustSize=껍질 크기
betterfoliage.coral.crustSize.tooltip=산호 부분의 크기
betterfoliage.coral.chance=산호 확률
betterfoliage.coral.chance.tooltip=산호를 특정블록에 표시하는 확률(N 분의 64)
betterfoliage.coral.biomes=바이옴 리스트
betterfoliage.coral.biomes.tooltip=산호를 바이옴에 따라 표시
betterfoliage.coral.biomes.tooltip.element=산호를 %s 바이옴에 따라 나타내시겠습니까?
betterfoliage.coral.shallowWater=얕은 물 산호
betterfoliage.coral.shallowWater.tooltip=산호를 깊은 물에 표시하시겠습니까?
betterfoliage.netherrack=네더랙 덩굴
betterfoliage.netherrack.tooltip=네더랙에 매달려있는 덩굴
betterfoliage.fallingLeaves=떨어지는 나뭇잎
betterfoliage.fallingLeaves.tooltip=잎 블록에서 바닥으로 떨어지는 잎 파티클
betterfoliage.fallingLeaves.speed=파티클 속도
betterfoliage.fallingLeaves.speed.tooltip=전체 파티클 속도
betterfoliage.fallingLeaves.windStrength=바람 세기
betterfoliage.fallingLeaves.windStrength.tooltip=날씨 바람 추가효과 (0을 중심으로 정규분포의 확산)
betterfoliage.fallingLeaves.stormStrength=폭풍 세기
betterfoliage.fallingLeaves.stormStrength.tooltip=비 날씨에 바람 추가효과 (0을 중심으로 정규분포의 확산)
betterfoliage.fallingLeaves.size=파티클 크기
betterfoliage.fallingLeaves.chance=파티클 확률
betterfoliage.fallingLeaves.chance.tooltip=잎 파티클 랜덤 확률 설정
betterfoliage.fallingLeaves.perturb=움직임
betterfoliage.fallingLeaves.perturb.tooltip=움직임 효과의 크기. 코르크 같은 움직임을 추가합니다.
betterfoliage.fallingLeaves.lifetime=파티클 지속시간
betterfoliage.fallingLeaves.lifetime.tooltip=최대 파티클 지속시간을 설정합니다. 최소 지속시간은 60%입니다.
betterfoliage.fallingLeaves.opacityHack=불투명 파티클
betterfoliage.fallingLeaves.opacityHack.tooltip=파티클이 앞에 있어도 파티클을 가리는 투명블럭을 없앱니다. 경고: 오류주의
betterfoliage.risingSoul=소울 상승
betterfoliage.risingSoul.tooltip=소울 블럭에서 올라오는 파티클
betterfoliage.risingSoul.chance=파티클 수정
betterfoliage.risingSoul.chance.tooltip=소울 파티클 랜덤 확률 설정
betterfoliage.risingSoul.speed=파티클 속도
betterfoliage.risingSoul.speed.tooltip=파티클 속도
betterfoliage.risingSoul.perturb=움직임
betterfoliage.risingSoul.perturb.tooltip=움직임 효과의 크기. 코르크 같은 움직임을 추가합니다.
betterfoliage.risingSoul.headSize=소울 크기
betterfoliage.risingSoul.headSize.tooltip=소울 파티클의 크기
betterfoliage.risingSoul.trailSize=자취 사이즈
betterfoliage.risingSoul.trailSize.tooltip=자취의 크기
betterfoliage.risingSoul.opacity=불투명
betterfoliage.risingSoul.opacity.tooltip=파티클의 불투명
betterfoliage.risingSoul.sizeDecay=크기 감소
betterfoliage.risingSoul.sizeDecay.tooltip=상대적인 자취 파티클 크기
betterfoliage.risingSoul.opacityDecay=불투명 감소
betterfoliage.risingSoul.opacityDecay.tooltip=상대적인 입자의 파티클 불투명도
betterfoliage.risingSoul.lifetime=최대 지속시간
betterfoliage.risingSoul.lifetime.tooltip=최대 파티클 지속시간을 설정합니다. 최소 지속시간은 60%입니다.
betterfoliage.risingSoul.trailLength=자취의 세기
betterfoliage.risingSoul.trailLength.tooltip=이전 파티클 틱으로 자취를 생성합니다.
betterfoliage.risingSoul.trailDensity=자취의 밀도
betterfoliage.risingSoul.trailDensity.tooltip=파티클 자취를 모두 렌더링 합니다.
betterfoliage.connectedGrass=잔디 연결 텍스쳐
betterfoliage.connectedGrass.enabled=활성화
betterfoliage.connectedGrass.enabled.tooltip=잔디블록이 흙블록 위에 있을 경우 잔디블록 윗텍스쳐를 옆텍스쳐에 전부 씌웁니다.
betterfoliage.roundLogs=둥근 나무
betterfoliage.roundLogs.tooltip=둥근부분을 블럭과 연결.
betterfoliage.roundLogs.connectSolids=블럭과 연결
betterfoliage.roundLogs.connectSolids.tooltip=둥근부분을 블럭과 연결.
betterfoliage.roundLogs.connectPerpendicular=나무 세로부분과 연결
betterfoliage.roundLogs.connectPerpendicular.tooltip=나무 세로부분을 나무부분끼리 연결
betterfoliage.roundLogs.lenientConnect=부드럽게 둥글게 연결
betterfoliage.roundLogs.lenientConnect.tooltip=2x2사이즈처럼 나무 평형된 부분끼리 서로 연결합니다.
betterfoliage.roundLogs.connectGrass=잔디 연결 텍스쳐
betterfoliage.roundLogs.connectGrass.tooltip=잔디가 근처에 있을 경우 나무 아래 잔디 블록을 렌더링
betterfoliage.roundLogs.radiusSmall=모서리 깎는 반지름
betterfoliage.roundLogs.radiusSmall.tooltip=나무 모서리 깎는 정도
betterfoliage.roundLogs.radiusLarge=모서리 깎는 부분 연결
betterfoliage.roundLogs.radiusLarge.tooltip=나무 모서리 부분을 연결
betterfoliage.roundLogs.dimming=조광
betterfoliage.roundLogs.dimming.tooltip=나무 표면부분을 어둡게하는 양
betterfoliage.roundLogs.zProtection=Z-Protection
betterfoliage.roundLogs.zProtection.tooltip=Amount to scale parallel log connection bits to stop Z-fighting (flickering). Try to set it as high as possible without having glitches.
betterfoliage.roundLogs.defaultY=수직선에대한 기본값
betterfoliage.roundLogs.defaultY.tooltip=true 일경우, 나무 블럭이 수직선에대한 렌더링을 할수가 없습니다. 그렇지 아니하면, 네모난 블럭으로 렌더링 될것임니다.
# Translate by IS_Jump for feedback mail acarus22@gmail.com

View File

@@ -0,0 +1,213 @@
key.betterfoliage.gui=Открыть настройки
betterfoliage.global.enabled=Включить мод
betterfoliage.global.enabled.tooltip=Если установлено на false, BetterFoliage не будет ничего рендерить
betterfoliage.enabled=Включить
betterfoliage.enabled.tooltip=Включена ли эта функция?
betterfoliage.hOffset=Горизонтальное смещение
betterfoliage.hOffset.tooltip=Дистанция горизонтального смещения этого элемента в блоках
betterfoliage.vOffset=Вертикальное смещение
betterfoliage.vOffset.tooltip=Дистанция вертикального смещения этого элемента в блоках
betterfoliage.size=Размер
betterfoliage.size.tooltip=Размер этого элемента
betterfoliage.heightMin=Минимальная высота
betterfoliage.heightMin.tooltip=Минимальная высота элемента
betterfoliage.heightMax=Максимальная высота
betterfoliage.heightMax.tooltip=Максимальная высота этого элемента
betterfoliage.population=Популяция
betterfoliage.population.tooltip=Шанс (N к 64), что блок будет иметь эту функцию
betterfoliage.shaderWind=Шейдерные эффекты ветра
betterfoliage.shaderWind.tooltip=Применить эффекты ветра с ShaderMod для этого элемента?
betterfoliage.distance=Лимит дистанции
betterfoliage.distance.tooltip=Максимальное расстояние от игрока для рендеринга этой функции
betterfoliage.blocks=Типы блоков
betterfoliage.blocks.tooltip=Настройки списка классов блоков, которые будут иметь примененные к ним функции
betterfoliage.blocks.dirtWhitelist=Белый список земли
betterfoliage.blocks.dirtBlacklist=Черный список земли
betterfoliage.blocks.dirtWhitelist.arrayEntry=%d записей
betterfoliage.blocks.dirtBlacklist.arrayEntry=%d записей
betterfoliage.blocks.grassWhitelist=Белый список травы
betterfoliage.blocks.grassBlacklist=Черный список травы
betterfoliage.blocks.grassWhitelist.arrayEntry=%d записей
betterfoliage.blocks.grassBlacklist.arrayEntry=%d записей
betterfoliage.blocks.leavesWhitelist=Белый список листвы
betterfoliage.blocks.leavesBlacklist=Черный список листвы
betterfoliage.blocks.leavesWhitelist.arrayEntry=%d записей
betterfoliage.blocks.leavesBlacklist.arrayEntry=%d записей
betterfoliage.blocks.cropsWhitelist=Белый список урожая
betterfoliage.blocks.cropsBlacklist=Черный список урожая
betterfoliage.blocks.cropsWhitelist.arrayEntry=%d записей
betterfoliage.blocks.cropsBlacklist.arrayEntry=%d записей
betterfoliage.blocks.logsWhitelist=Белый список древесины
betterfoliage.blocks.logsBlacklist=Черный список древесины
betterfoliage.blocks.logsWhitelist.arrayEntry=%d записей
betterfoliage.blocks.logsBlacklist.arrayEntry=%d записей
betterfoliage.blocks.sandWhitelist=Белый список песка
betterfoliage.blocks.sandBlacklist=Черный список песка
betterfoliage.blocks.sandWhitelist.arrayEntry=%d записей
betterfoliage.blocks.sandBlacklist.arrayEntry=%d записей
betterfoliage.blocks.lilypadWhitelist=Белый список кувшинок
betterfoliage.blocks.lilypadBlacklist=Черный список кувшинок
betterfoliage.blocks.lilypadWhitelist.arrayEntry=%d записей
betterfoliage.blocks.lilypadBlacklist.arrayEntry=%d записей
betterfoliage.blocks.cactusWhitelist=Белый список кактусов
betterfoliage.blocks.cactusBlacklist=Черный список кактусов
betterfoliage.blocks.cactusWhitelist.arrayEntry=%d записей
betterfoliage.blocks.cactusBlacklist.arrayEntry=%d записей
betterfoliage.blocks.dirtWhitelist.tooltip=Блоки, которые будут восприниматься в качестве земли. Влияет на камыши, водоросли, соединенную траву.
betterfoliage.blocks.dirtBlacklist.tooltip=Блоки, которые не будут восприниматься в качестве земли. Влияет на камыши, водоросли, соединенную траву.
betterfoliage.blocks.grassWhitelist.tooltip=Блоки, которые будут восприниматься в качестве травы. Влияет на короткую и соединенную траву.
betterfoliage.blocks.grassBlacklist.tooltip=Блоки, которые не будут восприниматься в качестве травы. Влияет на короткую и соединенную траву.
betterfoliage.blocks.leavesWhitelist.tooltip=Блоки, которые будут восприниматься в качестве листвы. Влияет на дополнительную листву, падающую листву. Листва будут рендериться с ID листвы в шейдер-программах.
betterfoliage.blocks.leavesBlacklist.tooltip=Блоки, которые никогда не будут восприниматься как листва. Влияет на дополнительную листву, падающую листву. Листва будут рендериться с ID листвы в шейдер-программах.
betterfoliage.blocks.cropsWhitelist.tooltip=Блоки, которые будут восприниматься как культуры. Культуры будут рендериться с ID высокой травы в шейдер-программах.
betterfoliage.blocks.cropsBlacklist.tooltip= Блоки, которые никогда не будут восприниматься как культуры. Культуры будут рендериться с ID высокой травы в шейдер-программах.
betterfoliage.blocks.logsWhitelist.tooltip=Блоки, которые будут восприниматься в качестве деревянных брёвен. Влияет на цилиндрические брёвна.
betterfoliage.blocks.logsBlacklist.tooltip=Блоки, которые никогда не будут восприниматься в качестве деревянных брёвен. Влияет на цилиндрические брёвна.
betterfoliage.blocks.sandWhitelist.tooltip=Блоки, которые будут восприниматься в качестве песка. Влияет на кораллы.
betterfoliage.blocks.sandBlacklist.tooltip=Блоки, которые никогда не будут восприниматься в качестве песка. Влияет на кораллы.
betterfoliage.blocks.lilypadWhitelist.tooltip=Блоки, которые будут восприниматься в качестве кувшинок. Влияет на улучшенные кувшинки.
betterfoliage.blocks.lilypadBlacklist.tooltip=Блоки, которые никогда не будут восприниматься в качестве кувшинок. Влияет на улучшенные кувшинки.
betterfoliage.blocks.cactusWhitelist.tooltip=Блоки, которые будут восприниматься в качестве кактусов. Влияет на улучшенные кактусы.
betterfoliage.blocks.cactusBlacklist.tooltip=Блоки, которые никогда не будут восприниматься в качестве кувшинок. Влияет на улучшенные кактусы.
betterfoliage.leaves=Улучшенная листва
betterfoliage.leaves.tooltip=Дополнительное округление листьев на блоках листвы.
betterfoliage.leaves.dense=Плотный режим
betterfoliage.leaves.dense.tooltip=Плотный режим имеет более округлые листья.
betterfoliage.shortGrass=Низкая трава и мицелий
betterfoliage.shortGrass.tooltip=Пучки травы / мицелия на поверхности соответствующих блоков.
betterfoliage.shortGrass.useGenerated=Использовать сгенерированные текстуры для травы.
betterfoliage.shortGrass.useGenerated.tooltip=Сгенерированная текстура создается путём разрезания текстуры высокой травы с активного ресурс-пака пополам.
betterfoliage.shortGrass.myceliumEnabled=Включить мицелий
betterfoliage.shortGrass.myceliumEnabled.tooltip=Включить эту особенность для блоков мицелия?
betterfoliage.shortGrass.grassEnabled=Включить траву
betterfoliage.shortGrass.grassEnabled.tooltip=Включить эту особенность для блоков травы?
betterfoliage.shortGrass.snowEnabled=Включить траву под снегом
betterfoliage.shortGrass.snowEnabled.tooltip=Включить эту особенность для заснеженных блоков травы?
betterfoliage.shortGrass.saturationThreshold=Порог насыщения
betterfoliage.shortGrass.saturationThreshold.tooltip=Насыщенность цвета разделяется на: "обесцвеченные" блоки (используя цвет биома) и "цветные" блоки (используя их собственный цвет)
betterfoliage.hangingGrass=Висячая трава
betterfoliage.hangingGrass.tooltip=Пучки травы свисают вниз с верхних краев блока травы.
betterfoliage.hangingGrass.separation=Разделение
betterfoliage.hangingGrass.separation.tooltip=Как долго подвесная трава выделяется из блока?
betterfoliage.cactus=Улучшенные кактусы
betterfoliage.cactus.tooltip=Улучшить кактус с дополнительными частицами и плавными тенями.
betterfoliage.cactus.sizeVariation=Вариации размера
betterfoliage.cactus.sizeVariation.tooltip=Количество случайных изменений в размере кактусов.
betterfoliage.lilypad=Улучшенные кувшинки
betterfoliage.lilypad.tooltip=Добавить кувшинкам корни и цветы.
betterfoliage.lilypad.flowerChance=Шанс появления цветов
betterfoliage.lilypad.flowerChance.tooltip=Шанс (N к 64) появления цветка на кувшинке.
betterfoliage.reed=Камыши
betterfoliage.reed.tooltip=Мелководные камыши на блоках земли.
betterfoliage.reed.biomes=Список биомов
betterfoliage.reed.biomes.tooltip=Настройка биомов, в которых камышам разрешено появляться.
betterfoliage.reed.biomes.tooltip.element=Должны ли камыши встречаться в %s биоме?
betterfoliage.algae=Морские водоросли
betterfoliage.algae.tooltip=Глубоководные водоросли на блоках земли.
betterfoliage.algae.biomes=Список биомов
betterfoliage.algae.biomes.tooltip=Настройка биомов, в которых водорослям разрешено появляться.
betterfoliage.algae.biomes.tooltip.element=Должны ли водоросли встречаться в %s биоме?
betterfoliage.coral=Кораллы
betterfoliage.coral.tooltip=Кораллы на песчаных блоках в глубокой воде.
betterfoliage.coral.size=Размер кораллов
betterfoliage.coral.size.tooltip=Размер торчащих частичек кораллов.
betterfoliage.coral.crustSize=Размер коры
betterfoliage.coral.crustSize.tooltip=Размер плоской части кораллов.
betterfoliage.coral.chance=Шанс кораллов
betterfoliage.coral.chance.tooltip=Шанс (N in 64) появления кораллов на определенном блоке.
betterfoliage.coral.biomes=Список биомов
betterfoliage.coral.biomes.tooltip=Настройка биомов, в которых разрешено появляться кораллам.
betterfoliage.coral.biomes.tooltip.element=Должны ли кораллы появляться в %s биоме?
betterfoliage.coral.shallowWater=Мелководные кораллы
betterfoliage.coral.shallowWater.tooltip=Должны ли появляться кораллы в воде, глубиной в 1 блок?
betterfoliage.netherrack=Адская лоза
betterfoliage.netherrack.tooltip=Висячая лоза под адским камнем
betterfoliage.fallingLeaves=Падающие листья
betterfoliage.fallingLeaves.tooltip=Падение FX частиц листвы исходящие из низа блоков листвы
betterfoliage.fallingLeaves.speed=Скорость частиц
betterfoliage.fallingLeaves.speed.tooltip=Общая скорость частиц
betterfoliage.fallingLeaves.windStrength=Сила ветра
betterfoliage.fallingLeaves.windStrength.tooltip=Величина воздействия ветра в хорошую погоду (распространение нормального распределения сосредоточено на 0)
betterfoliage.fallingLeaves.stormStrength=Сила шторма
betterfoliage.fallingLeaves.stormStrength.tooltip=Дополнительная величина воздействия ветра в ненастную погоду (распространение нормального распределения сосредоточено на 0)
betterfoliage.fallingLeaves.size=Размер частиц
betterfoliage.fallingLeaves.chance=Шанс частиц
betterfoliage.fallingLeaves.chance.tooltip=Вероятность каждого случайного рендеринга в такт (1/20 секунды) опадения частицы блока листвы.
betterfoliage.fallingLeaves.perturb=Возмущение
betterfoliage.fallingLeaves.perturb.tooltip=Величина эффекта возмущений. Добавляет штопорообразное движение к частице синхронизированной с его вращением.
betterfoliage.fallingLeaves.lifetime=Максимальное время жизни
betterfoliage.fallingLeaves.lifetime.tooltip=Максимальное время жизни частиц. Минимальное время жизни - 60%% от этого значения.
betterfoliage.fallingLeaves.opacityHack=Непрозрачные частицы
betterfoliage.fallingLeaves.opacityHack.tooltip=Запретить прозрачным блокам затемнять частицы даже тогда, когда частицы впереди. ВНИМАНИЕ: может спровоцировать баги.
betterfoliage.risingSoul=Адские духи
betterfoliage.risingSoul.tooltip=Количество душ-частиц FX, испускаемых из верхней части блоков песка душ.
betterfoliage.risingSoul.chance=Шанс частиц
betterfoliage.risingSoul.chance.tooltip=Частота генерации частиц на песке душ.
betterfoliage.risingSoul.speed=Скорость частиц
betterfoliage.risingSoul.speed.tooltip=Вертикальная скорость движения частиц духов.
betterfoliage.risingSoul.perturb=Возмущение
betterfoliage.risingSoul.perturb.tooltip=Магнитуда эффекта возмущений. Добавляет штопороподобное движение частиц.
betterfoliage.risingSoul.headSize=Размер духа
betterfoliage.risingSoul.headSize.tooltip=Размер частицы духа
betterfoliage.risingSoul.trailSize=Размер следов
betterfoliage.risingSoul.trailSize.tooltip=Начальный размер следа частиц
betterfoliage.risingSoul.opacity=Прозрачность
betterfoliage.risingSoul.opacity.tooltip=Непрозрачность эффекта частиц
betterfoliage.risingSoul.sizeDecay=Размер распада
betterfoliage.risingSoul.sizeDecay.tooltip=Следующий размер частицы соответствует их же размеру в предыдущем такте (1/20 секунды).
betterfoliage.risingSoul.opacityDecay=Непрозрачность распада
betterfoliage.risingSoul.opacityDecay.tooltip=Следующий уровень прозрачности частицы соответствует их же уровню прозрачности в предыдущем такте (1/20 секунды).
betterfoliage.risingSoul.lifetime=Максимальное время жизни
betterfoliage.risingSoul.lifetime.tooltip=Максимальное время жизни эффекта частиц. Минимальное время жизни равно 60%% от этого числа.
betterfoliage.risingSoul.trailLength=Длина следов
betterfoliage.risingSoul.trailLength.tooltip=Количество предыдущих позиций, которые запомнила частица в тактах (1/20 секунды).
betterfoliage.risingSoul.trailDensity=Плотность следов
betterfoliage.risingSoul.trailDensity.tooltip=Рендер каждой предыдущий Nой позиции в следах частиц.
betterfoliage.connectedGrass=Соединенные текстуры травы
betterfoliage.connectedGrass.enabled=Включить
betterfoliage.connectedGrass.enabled.tooltip=Если блок травы находится над блоком земли: прорисовать верхнюю текстуру травы на всех сторонах блока травы.
betterfoliage.roundLogs=Цилиндрические брёвна
betterfoliage.roundLogs.tooltip=Соединить круглые блоки в сплошные, полные блоки?
betterfoliage.roundLogs.connectSolids=Соединение в крупные брёвна
betterfoliage.roundLogs.connectSolids.tooltip=Соединить круглые блоки в сплошные, полные блоки?
betterfoliage.roundLogs.connectPerpendicular=Соединение в перпендикулярные брёвна
betterfoliage.roundLogs.connectPerpendicular.tooltip=Соединить круглые брёвна к перпендикулярным брёвнам относительно их оси?
betterfoliage.roundLogs.lenientConnect=Мягкое округление
betterfoliage.roundLogs.lenientConnect.tooltip=Соединение в параллельные круглые брёвна L-формы, не только 2х2.
betterfoliage.roundLogs.connectGrass=Соединенная трава
betterfoliage.roundLogs.connectGrass.tooltip=Заменяет землю под деревьями на траву, если она есть поблизости.
betterfoliage.roundLogs.radiusSmall=Радиус фаски
betterfoliage.roundLogs.radiusSmall.tooltip=Радиус обрезки углов от бревна.
betterfoliage.roundLogs.radiusLarge=Радиус соединенной фаски
betterfoliage.roundLogs.radiusLarge.tooltip=Радиус среза внешнего угла соединённых брёвен.
betterfoliage.roundLogs.dimming=Затемнение
betterfoliage.roundLogs.dimming.tooltip=Затемнить неясные длинные грани.
betterfoliage.roundLogs.zProtection=Z-Защита
betterfoliage.roundLogs.zProtection.tooltip=Для масштабирования параллельных битов соединения бревен, чтобы остановить Z-бой (мерцание). Попробуйте установить его как можно выше, для устранения мерцания.

View File

@@ -0,0 +1,9 @@
// Vanilla
spruce=spruce
jungle=jungle
// Biomes O' Plenty
fir=spruce
// Forestry
forestry:conifers=spruce

View File

@@ -0,0 +1,16 @@
// Vanilla
net.minecraft.block.BlockLeaves
// Biomes O' Plenty
biomesoplenty.common.block.BlockBOPLeaves
// Aether II
com.gildedgames.aether.common.blocks.natural.BlockAetherLeaves
// Plants
shadows.plants2.block.BlockEnumLeaves
shadows.plants2.block.BlockEnumNetherLeaves
// Cuisine
snownee.cuisine.blocks.BlockModLeaves
snownee.cuisine.blocks.BlockShearedLeaves

View File

@@ -0,0 +1,4 @@
minecraft:block/leaves,all
minecraft:block/cube_all,all
biomesoplenty:block/leaves_overlay,under

View File

@@ -0,0 +1,8 @@
// Vanilla
net.minecraft.block.BlockLilyPad
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPLilypad
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Vanilla.BlockCustomLilyPad

View File

@@ -0,0 +1,39 @@
// Vanilla
net.minecraft.block.BlockLog
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPLog
// Natura
com.progwml6.natura.common.block.BlockEnumLog
// Thaumcraft
thaumcraft.common.blocks.world.plants.BlockLogsTC
// Forestry
forestry.arboriculture.gadgets.BlockLog
// Extra Biomes XL
-extrabiomes.blocks.BlockMiniLog
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Flora.BlockLogVert
com.bioxx.tfc.Blocks.Flora.BlockLogNatural
// The Agricultural Revolution a.k.a. Cooking Plus
CookingPlus.blocks.CookingPlusPalmLog
CookingPlus.blocks.CookingPlusTangleLog
CookingPlus.blocks.CookingPlusTangleHeart
// IC2
ic2.core.block.BlockRubWood
// TechReborn
techreborn.blocks.BlockRubberLog
// Better With Mods
betterwithmods.blocks.BlockStump
// Plants
shadows.plants2.block.BlockEnumLog
shadows.plants2.block.BlockEnumNetherLog

View File

@@ -0,0 +1,12 @@
// Vanilla
block/column_side,end,end,side
block/cube_column,end,end,side
block/cube_all,all,all,all
// Agricultural Revolution
agriculturalrevolution:block/palmlog,top,top,texture
// Lithos Core
block/column_top,end,end,side_a,side_b
block/column_side_x,end,end,side_a,side_b
block/column_side_z,end,end,side_a,side_b

View File

@@ -0,0 +1,5 @@
// Vanilla
net.minecraft.block.BlockMycelium
// NetherEx
nex.block.BlockMycelium

View File

@@ -0,0 +1,5 @@
// Vanilla
net.minecraft.block.BlockNetherrack
// NetherEx
nex.block.BlockNetherrack

View File

@@ -0,0 +1,5 @@
// Vanilla
net.minecraft.block.BlockSand
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Terrain.BlockSand

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

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