├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ ├── resources │ ├── assets │ │ └── better_weather │ │ │ ├── icon.png │ │ │ ├── stationapi │ │ │ ├── blockstates │ │ │ │ └── lightning_light.json │ │ │ └── sounds │ │ │ │ └── ambient │ │ │ │ └── rain.ogg │ │ │ └── textures │ │ │ ├── cloud.png │ │ │ ├── lightning.png │ │ │ └── water_circles.png │ ├── data │ │ └── better_weather │ │ │ ├── stationapi │ │ │ └── tags │ │ │ │ └── blocks │ │ │ │ └── lightning_rod.json │ │ │ └── clouds │ │ │ ├── main_shape.png │ │ │ ├── variation.png │ │ │ ├── large_details.png │ │ │ ├── rain_density.png │ │ │ ├── rain_fronts.png │ │ │ ├── thunderstorms.png │ │ │ └── vanilla_clouds.png │ ├── betterweather.mixins.common.json │ ├── betterweather.mixins.client.json │ └── fabric.mod.json │ └── java │ └── paulevs │ └── betterweather │ ├── util │ ├── MathUtil.java │ ├── WeatherTags.java │ ├── LightningUtil.java │ ├── ImageSampler.java │ └── LightningLightBlock.java │ ├── listeners │ ├── ClientListener.java │ └── CommonListener.java │ ├── mixin │ ├── client │ │ ├── MainMenuMixin.java │ │ ├── GameOptionsMixin.java │ │ ├── LightningRendererMixin.java │ │ ├── FogRendererImplMixin.java │ │ ├── SoundHelperMixin.java │ │ ├── DimensionMixin.java │ │ ├── LevelRendererMixin.java │ │ └── GameRendererMixin.java │ └── common │ │ ├── LevelPropertiesMixin.java │ │ ├── FireBlockMixin.java │ │ ├── LightningEntityMixin.java │ │ └── LevelMixin.java │ ├── config │ ├── ConfigEntry.java │ ├── ClientConfig.java │ ├── CommonConfig.java │ └── Config.java │ ├── client │ ├── rendering │ │ ├── CloudTexture.java │ │ ├── FrustumCulling.java │ │ ├── BetterWeatherRenderer.java │ │ ├── BWLightningRenderer.java │ │ ├── CloudRenderer.java │ │ ├── CloudChunk.java │ │ └── WeatherRenderer.java │ └── sound │ │ └── WeatherSounds.java │ └── api │ └── WeatherAPI.java ├── jitpack.yml ├── .gitignore ├── gradle.properties ├── settings.gradle ├── LICENSE ├── README.md ├── gradlew.bat └── gradlew /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/assets/better_weather/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/assets/better_weather/icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/better_weather/stationapi/blockstates/lightning_light.json: -------------------------------------------------------------------------------- 1 | { 2 | "variants": { 3 | "": { "model": "minecraft:block/block" } 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/data/better_weather/stationapi/tags/blocks/lightning_rod.json: -------------------------------------------------------------------------------- 1 | { 2 | "values": [ 3 | "minecraft:iron_block", 4 | "minecraft:iron_door" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/assets/better_weather/textures/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/assets/better_weather/textures/cloud.png -------------------------------------------------------------------------------- /src/main/resources/data/better_weather/clouds/main_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/data/better_weather/clouds/main_shape.png -------------------------------------------------------------------------------- /src/main/resources/data/better_weather/clouds/variation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/data/better_weather/clouds/variation.png -------------------------------------------------------------------------------- /src/main/resources/assets/better_weather/textures/lightning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/assets/better_weather/textures/lightning.png -------------------------------------------------------------------------------- /src/main/resources/data/better_weather/clouds/large_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/data/better_weather/clouds/large_details.png -------------------------------------------------------------------------------- /src/main/resources/data/better_weather/clouds/rain_density.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/data/better_weather/clouds/rain_density.png -------------------------------------------------------------------------------- /src/main/resources/data/better_weather/clouds/rain_fronts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/data/better_weather/clouds/rain_fronts.png -------------------------------------------------------------------------------- /src/main/resources/data/better_weather/clouds/thunderstorms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/data/better_weather/clouds/thunderstorms.png -------------------------------------------------------------------------------- /src/main/resources/data/better_weather/clouds/vanilla_clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/data/better_weather/clouds/vanilla_clouds.png -------------------------------------------------------------------------------- /src/main/resources/assets/better_weather/textures/water_circles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/assets/better_weather/textures/water_circles.png -------------------------------------------------------------------------------- /src/main/resources/assets/better_weather/stationapi/sounds/ambient/rain.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulevsGitch/BetterWeather/HEAD/src/main/resources/assets/better_weather/stationapi/sounds/ambient/rain.ogg -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | # From https://github.com/jitpack/jitpack.io/issues/4506#issuecomment-864562270 2 | before_install: 3 | - source "$HOME/.sdkman/bin/sdkman-init.sh" 4 | - sdk update 5 | - sdk install java 17.0.1-tem 6 | - sdk use java 17.0.1-tem -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jun 14 19:21:24 BST 2020 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | classes/ 7 | 8 | # eclipse 9 | 10 | *.launch 11 | 12 | # idea 13 | 14 | .idea/ 15 | *.iml 16 | *.ipr 17 | *.iws 18 | 19 | # vscode 20 | 21 | .settings/ 22 | .vscode/ 23 | bin/ 24 | .classpath 25 | .project 26 | 27 | # fabric 28 | 29 | run/ 30 | -------------------------------------------------------------------------------- /src/main/resources/betterweather.mixins.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "paulevs.betterweather.mixin.common", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "LightningEntityMixin", 8 | "LevelPropertiesMixin", 9 | "FireBlockMixin", 10 | "LevelMixin" 11 | ], 12 | "injectors": { 13 | "defaultRequire": 1 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/util/MathUtil.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.util; 2 | 3 | public class MathUtil { 4 | public static int wrap(int value, int side) { 5 | if (net.modificationstation.stationapi.api.util.math.MathHelper.isPowerOfTwo(side)) { 6 | return value & (side - 1); 7 | } 8 | int result = (value - value / side * side); 9 | return result < 0 ? result + side : result; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx4G 3 | 4 | # Fabric Properties 5 | # check these on https://fabricmc.net/use 6 | minecraft_version=b1.7.3 7 | loader_version = 0.15.6-babric.1 8 | mappings = 0.1.1 9 | 10 | # Mod Properties 11 | mod_version = 0.3.1 12 | maven_group = paulevs.betterweather 13 | archives_base_name = better_weather 14 | 15 | # Dependencies 16 | stapi_version = 2.0-alpha.2 17 | bhcreative_version = 0.4.6 18 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | maven { 8 | name = 'Babric' 9 | url = 'https://maven.glass-launcher.net/babric' 10 | } 11 | maven { 12 | name = "Jitpack" 13 | url "https://jitpack.io/" 14 | } 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/betterweather.mixins.client.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "paulevs.betterweather.mixin.client", 5 | "compatibilityLevel": "JAVA_17", 6 | "client": [ 7 | "LightningRendererMixin", 8 | "FogRendererImplMixin", 9 | "DimensionMixin", 10 | "LevelRendererMixin", 11 | "GameRendererMixin", 12 | "GameOptionsMixin", 13 | "SoundHelperMixin", 14 | "MainMenuMixin" 15 | ], 16 | "injectors": { 17 | "defaultRequire": 1 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/listeners/ClientListener.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.listeners; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.mine_diver.unsafeevents.listener.EventListener; 6 | import net.modificationstation.stationapi.api.event.mod.InitEvent; 7 | import paulevs.betterweather.config.ClientConfig; 8 | 9 | @Environment(EnvType.CLIENT) 10 | public class ClientListener { 11 | @EventListener 12 | public void onInit(InitEvent event) { 13 | ClientConfig.init(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/client/MainMenuMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.client; 2 | 3 | import net.minecraft.client.gui.screen.menu.MainMenuScreen; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.Inject; 7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 8 | import paulevs.betterweather.client.sound.WeatherSounds; 9 | 10 | @Mixin(MainMenuScreen.class) 11 | public class MainMenuMixin { 12 | @Inject(method = "", at = @At("TAIL")) 13 | private void betterweather_onInit(CallbackInfo info) { 14 | WeatherSounds.stop(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/common/LevelPropertiesMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.common; 2 | 3 | import net.minecraft.level.LevelProperties; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.Inject; 7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 8 | 9 | @Mixin(LevelProperties.class) 10 | public class LevelPropertiesMixin { 11 | @Inject(method = "isRaining", at = @At("HEAD"), cancellable = true) 12 | private void betterweather_isRaining(CallbackInfoReturnable info) { 13 | info.setReturnValue(false); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/client/GameOptionsMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.client; 2 | 3 | import net.minecraft.client.options.GameOptions; 4 | import net.minecraft.client.options.Option; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | import paulevs.betterweather.client.rendering.BetterWeatherRenderer; 10 | 11 | @Mixin(GameOptions.class) 12 | public class GameOptionsMixin { 13 | @Inject(method = "changeOption", at = @At( 14 | value = "INVOKE", 15 | target = "Lnet/minecraft/client/render/LevelRenderer;updateFromOptions()V", 16 | ordinal = 1 17 | )) 18 | private void betterweather_changeOption(Option option, int value, CallbackInfo info) { 19 | BetterWeatherRenderer.updateClouds(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/listeners/CommonListener.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.listeners; 2 | 3 | import net.mine_diver.unsafeevents.listener.EventListener; 4 | import net.minecraft.block.Block; 5 | import net.modificationstation.stationapi.api.event.mod.InitEvent; 6 | import net.modificationstation.stationapi.api.event.registry.BlockRegistryEvent; 7 | import net.modificationstation.stationapi.api.util.Namespace; 8 | import paulevs.betterweather.config.CommonConfig; 9 | import paulevs.betterweather.util.LightningLightBlock; 10 | 11 | public class CommonListener { 12 | public static Block lightningLight; 13 | 14 | @EventListener 15 | public void onInit(InitEvent event) { 16 | CommonConfig.init(); 17 | } 18 | 19 | @EventListener 20 | public void onBlockRegister(BlockRegistryEvent event) { 21 | Namespace modID = Namespace.of("better_weather"); 22 | lightningLight = new LightningLightBlock(modID.id("lightning_light")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/common/FireBlockMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.common; 2 | 3 | import net.minecraft.block.FireBlock; 4 | import net.minecraft.level.Level; 5 | import net.modificationstation.stationapi.api.block.States; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | import paulevs.betterweather.api.WeatherAPI; 11 | 12 | import java.util.Random; 13 | 14 | @Mixin(FireBlock.class) 15 | public class FireBlockMixin { 16 | @Inject(method = "onScheduledTick", at = @At("HEAD"), cancellable = true) 17 | private void betterweather_onScheduledTick(Level level, int x, int y, int z, Random random, CallbackInfo info) { 18 | if (WeatherAPI.isRaining(level, x, y + 1, z)) { 19 | level.setBlockState(x, y, z, States.AIR.get()); 20 | info.cancel(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/config/ConfigEntry.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.config; 2 | 3 | import java.io.FileWriter; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Class represents config entry 10 | * @param stored data type 11 | */ 12 | public class ConfigEntry { 13 | private final List comments = new ArrayList<>(); 14 | private final String name; 15 | protected final T value; 16 | 17 | protected ConfigEntry(String name, T value, List comments) { 18 | this.comments.addAll(comments); 19 | this.value = value; 20 | this.name = name; 21 | } 22 | 23 | protected void append(FileWriter writer) throws IOException { 24 | for (String comment : comments) { 25 | writer.append("# "); 26 | writer.append(comment); 27 | writer.append('\n'); 28 | } 29 | writer.append(name); 30 | writer.append(" = "); 31 | writer.append(value.toString()); 32 | writer.append('\n'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/client/LightningRendererMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.client; 2 | 3 | import net.minecraft.client.render.entity.EntityRenderer; 4 | import net.minecraft.client.render.entity.LightningRenderer; 5 | import net.minecraft.entity.technical.LightningEntity; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | import paulevs.betterweather.client.rendering.BWLightningRenderer; 11 | 12 | @Mixin(LightningRenderer.class) 13 | public abstract class LightningRendererMixin extends EntityRenderer { 14 | @Inject(method = "method_1793", at = @At("HEAD"), cancellable = true) 15 | private void betterweather_render(LightningEntity entity, double dx, double dy, double dz, float delta, float h, CallbackInfo info) { 16 | BWLightningRenderer.render(entity, (float) dx, (float) dy, (float) dz, dispatcher.textureManager); 17 | info.cancel(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "better_weather", 4 | "version": "${version}", 5 | 6 | "name": "Better Weather", 7 | "description": "Makes clouds fluffy and weather local with better visual effects", 8 | "authors": [ 9 | "paulevs" 10 | ], 11 | "contact": { 12 | "homepage": "https://github.com/paulevsGitch/BetterWeather", 13 | "sources": "https://github.com/paulevsGitch/BetterWeather", 14 | "issues": "https://github.com/paulevsGitch/BetterWeather/issues" 15 | }, 16 | 17 | "license": "MIT", 18 | "icon": "assets/better_weather/icon.png", 19 | 20 | "environment": "*", 21 | "entrypoints": { 22 | "stationapi:event_bus": [ 23 | "paulevs.betterweather.listeners.CommonListener" 24 | ], 25 | "stationapi:event_bus_client": [ 26 | "paulevs.betterweather.listeners.ClientListener" 27 | ] 28 | }, 29 | 30 | "mixins": [ 31 | "betterweather.mixins.client.json", 32 | "betterweather.mixins.common.json" 33 | ], 34 | 35 | "depends": { 36 | "minecraft": "1.0.0-beta.7.3", 37 | "stationapi": ">=2.0-alpha.2", 38 | "fabricloader": ">=0.15.6-babric.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 paulevsGitch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 |
6 |

Better Weather

7 | 8 |

9 | This mod improves Minecraft weather by making clouds volumetric and weather effects local. 10 |

11 |

12 | Dependencies: 13 |

17 |

18 |
22 | 23 | ### Mod Content: 24 | - **Volumetric Clouds:** clouds now can be 32-64 blocks tall, they are fluffy and have better shading 25 | - **Local weather:** rain and snow are now local, they will move together with clouds inside rain fronts 26 | - **Better weather sounds:** better rain effect sounds 27 | - **Better rain/snow rendering:** snow and rain now have LOD and can render on larger distances -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/util/WeatherTags.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.util; 2 | 3 | import net.minecraft.block.Block; 4 | import net.modificationstation.stationapi.api.registry.BlockRegistry; 5 | import net.modificationstation.stationapi.api.registry.DimensionContainer; 6 | import net.modificationstation.stationapi.api.registry.DimensionRegistry; 7 | import net.modificationstation.stationapi.api.tag.TagKey; 8 | import net.modificationstation.stationapi.api.util.Namespace; 9 | 10 | public class WeatherTags { 11 | public static final TagKey LIGHTNING_ROD; 12 | public static final TagKey> NO_RAIN; 13 | public static final TagKey> NO_THUNDER; 14 | public static final TagKey> ETERNAL_RAIN; 15 | public static final TagKey> ETERNAL_THUNDER; 16 | 17 | static { 18 | Namespace modID = Namespace.of("better_weather"); 19 | LIGHTNING_ROD = TagKey.of(BlockRegistry.KEY, modID.id("lightning_rod")); 20 | NO_RAIN = TagKey.of(DimensionRegistry.KEY, modID.id("no_rain")); 21 | NO_THUNDER = TagKey.of(DimensionRegistry.KEY, modID.id("no_thunder")); 22 | ETERNAL_RAIN = TagKey.of(DimensionRegistry.KEY, modID.id("eternal_rain")); 23 | ETERNAL_THUNDER = TagKey.of(DimensionRegistry.KEY, modID.id("eternal_thunder")); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/client/FogRendererImplMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.client; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.modificationstation.stationapi.impl.worldgen.FogRendererImpl; 5 | import org.spongepowered.asm.mixin.Final; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Shadow; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | import paulevs.betterweather.client.rendering.BetterWeatherRenderer; 12 | 13 | @Mixin(value = FogRendererImpl.class, remap = false) 14 | public class FogRendererImplMixin { 15 | @Shadow @Final private static float[] FOG_COLOR; 16 | 17 | @Inject(method = "setupFog", at = @At("TAIL")) 18 | private static void betterweather_changeFogColor(Minecraft minecraft, float delta, CallbackInfo info) { 19 | BetterWeatherRenderer.fogColorR = FOG_COLOR[0]; 20 | BetterWeatherRenderer.fogColorG = FOG_COLOR[1]; 21 | BetterWeatherRenderer.fogColorB = FOG_COLOR[2]; 22 | BetterWeatherRenderer.updateFogColor(minecraft, delta); 23 | FOG_COLOR[0] = BetterWeatherRenderer.fogColorR; 24 | FOG_COLOR[1] = BetterWeatherRenderer.fogColorG; 25 | FOG_COLOR[2] = BetterWeatherRenderer.fogColorB; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/client/SoundHelperMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.client; 2 | 3 | import net.fabricmc.loader.api.FabricLoader; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.options.GameOptions; 6 | import net.minecraft.client.sound.SoundHelper; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | import paulevs.betterweather.client.sound.WeatherSounds; 13 | import paulscode.sound.SoundSystem; 14 | 15 | @Mixin(SoundHelper.class) 16 | public class SoundHelperMixin { 17 | @Shadow private static boolean initialized; 18 | @Shadow private static SoundSystem soundSystem; 19 | @Shadow private GameOptions gameOptions; 20 | 21 | @Inject(method = "setLibsAndCodecs", at = @At("TAIL")) 22 | private void betterweather_setLibsAndCodecs(CallbackInfo info) { 23 | WeatherSounds.init(soundSystem); 24 | } 25 | 26 | @Inject(method = "handleBackgroundMusic", at = @At("HEAD")) 27 | private void betterweather_handleBackgroundMusic(CallbackInfo info) { 28 | if (!initialized) return; 29 | @SuppressWarnings({"deprecated", "deprecation"}) 30 | Minecraft minecraft = (Minecraft) FabricLoader.getInstance().getGameInstance(); 31 | WeatherSounds.updateSound(minecraft.level, minecraft.viewEntity, soundSystem, gameOptions.sound); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/config/ClientConfig.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.config; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.fabricmc.loader.api.FabricLoader; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.options.GameOptions; 8 | 9 | import java.io.File; 10 | 11 | @Environment(EnvType.CLIENT) 12 | public class ClientConfig { 13 | private static final Config CONFIG = new Config(new File("config/better_weather/client.cfg")); 14 | private static boolean fluffyClouds; 15 | private static GameOptions options; 16 | private static int owerworldCloudHeight; 17 | 18 | public static void init() { 19 | CONFIG.addEntry("fluffyClouds", true, 20 | "Render clouds fluffy, if false clouds will use block-like rendering", 21 | "Default value is true" 22 | ); 23 | CONFIG.addEntry("owerworldCloudHeight", 108, 24 | "Height of clouds in the Owerworld", 25 | "Default value is 108" 26 | ); 27 | CONFIG.save(); 28 | 29 | fluffyClouds = CONFIG.getBool("fluffyClouds"); 30 | owerworldCloudHeight = CONFIG.getInt("owerworldCloudHeight"); 31 | } 32 | 33 | @SuppressWarnings("deprecation") 34 | public static boolean renderFluffy() { 35 | if (options == null) { 36 | options = ((Minecraft) FabricLoader.getInstance().getGameInstance()).options; 37 | } 38 | return fluffyClouds && options.fancyGraphics; 39 | } 40 | 41 | public static int getOwerworldCloudHeight() { 42 | return owerworldCloudHeight; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/client/DimensionMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.client; 2 | 3 | import net.fabricmc.loader.api.FabricLoader; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.entity.living.LivingEntity; 6 | import net.minecraft.level.dimension.Dimension; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 12 | import paulevs.betterweather.api.WeatherAPI; 13 | import paulevs.betterweather.config.ClientConfig; 14 | 15 | @Mixin(Dimension.class) 16 | public class DimensionMixin { 17 | @Shadow public int id; 18 | 19 | @Inject(method = "getSunsetDawnColor", at = @At("RETURN")) 20 | private void betterweather_getSunsetDawnColor(float f, float g, CallbackInfoReturnable info) { 21 | float[] data = info.getReturnValue(); 22 | if (data == null) return; 23 | @SuppressWarnings("deprecation") 24 | Minecraft minecraft = (Minecraft) FabricLoader.getInstance().getGameInstance(); 25 | LivingEntity entity = minecraft.viewEntity; 26 | data[3] *= 1F - WeatherAPI.getRainDensity(minecraft.level, entity.x, entity.y, entity.z, true); 27 | } 28 | 29 | @Inject(method = "getCloudHeight", at = @At("HEAD"), cancellable = true) 30 | private void betterweather_changeOverworldHeight(CallbackInfoReturnable info) { 31 | if (id != 0) return; 32 | info.setReturnValue((float) ClientConfig.getOwerworldCloudHeight()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/util/LightningUtil.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.util; 2 | 3 | import net.minecraft.entity.technical.LightningEntity; 4 | import net.minecraft.level.Level; 5 | import paulevs.betterweather.api.WeatherAPI; 6 | import paulevs.betterweather.config.CommonConfig; 7 | 8 | public class LightningUtil { 9 | private static int lightningTicks; 10 | 11 | public static void tick() { 12 | lightningTicks = (lightningTicks + 1) & 31; 13 | } 14 | 15 | public static void processChunk(Level level, int cx, int cz) { 16 | if (lightningTicks > 0) return; 17 | if (CommonConfig.getLightningChance() > 1 && level.random.nextInt(CommonConfig.getLightningChance()) > 0) return; 18 | 19 | int px, py, pz; 20 | int lx = 0, ly = Integer.MIN_VALUE, lz = 0; 21 | 22 | short radius = CommonConfig.getRodCheckSide(); 23 | for (short dx = (short) -radius; dx <= radius; dx++) { 24 | px = (cx << 4) + dx + 8; 25 | for (short dz = (short) -radius; dz <= radius; dz++) { 26 | pz = (cz << 4) + dz + 8; 27 | py = WeatherAPI.getRainHeight(level, px, pz); 28 | if (!WeatherAPI.isThundering(level, px, py, pz)) continue; 29 | if (!level.getBlockState(px, py - 1, pz).isIn(WeatherTags.LIGHTNING_ROD)) continue; 30 | lx = px; 31 | ly = py; 32 | lz = pz; 33 | break; 34 | } 35 | } 36 | 37 | if (ly == Integer.MIN_VALUE) { 38 | lx = (cx << 4) | level.random.nextInt(16); 39 | lz = (cz << 4) | level.random.nextInt(16); 40 | ly = WeatherAPI.getRainHeight(level, lx, lz); 41 | if (!WeatherAPI.isThundering(level, lx, ly, lz)) return; 42 | } 43 | 44 | level.addEntity(new LightningEntity(level, lx, ly, lz)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/client/LevelRendererMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.client; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.client.render.LevelRenderer; 5 | import net.minecraft.client.texture.TextureManager; 6 | import org.lwjgl.opengl.GL11; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.ModifyArgs; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | import org.spongepowered.asm.mixin.injection.invoke.arg.Args; 14 | import paulevs.betterweather.client.rendering.BetterWeatherRenderer; 15 | 16 | @Mixin(LevelRenderer.class) 17 | public class LevelRendererMixin { 18 | @Shadow private Minecraft minecraft; 19 | @Shadow private int glEndList; 20 | 21 | @Inject(method = "", at = @At("TAIL")) 22 | private void betterweather_onInit(Minecraft minecraft, TextureManager manager, CallbackInfo info) { 23 | BetterWeatherRenderer.update(manager); 24 | GL11.glNewList(this.glEndList, GL11.GL_COMPILE); 25 | GL11.glEndList(); 26 | } 27 | 28 | @Inject(method = "renderEnvironment", at = @At("HEAD"), cancellable = true) 29 | private void betterweather_renderAfterWater(float delta, CallbackInfo info) { 30 | BetterWeatherRenderer.renderAfterWater(delta, minecraft); 31 | info.cancel(); 32 | } 33 | 34 | @ModifyArgs(method = "renderSky", at = @At( 35 | value = "INVOKE", 36 | target = "Lorg/lwjgl/opengl/GL11;glColor3f(FFF)V", 37 | remap = false 38 | )) 39 | private void betterweather_changeFogDensity(Args args) { 40 | BetterWeatherRenderer.changeFogDensity(args, minecraft); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/client/rendering/CloudTexture.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.client.rendering; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.client.texture.TextureManager; 6 | import net.minecraft.util.maths.Vec3D; 7 | import org.lwjgl.opengl.GL11; 8 | import org.lwjgl.opengl.GL12; 9 | 10 | import java.nio.ByteBuffer; 11 | import java.nio.IntBuffer; 12 | 13 | @Environment(EnvType.CLIENT) 14 | public class CloudTexture { 15 | private final IntBuffer pixels; 16 | private final int textureID; 17 | private final int capacity; 18 | private final int width; 19 | private final int height; 20 | 21 | public CloudTexture(TextureManager manager) { 22 | textureID = manager.getTextureId("/assets/better_weather/textures/cloud.png"); 23 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureID); 24 | width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH); 25 | height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT); 26 | capacity = width * height; 27 | pixels = ByteBuffer.allocateDirect(capacity << 2).asIntBuffer(); 28 | GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, pixels); 29 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); 30 | pixels.position(0); 31 | } 32 | 33 | public void bindAndUpdate(Vec3D color) { 34 | update(color); 35 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureID); 36 | GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, pixels); 37 | } 38 | 39 | private void update(Vec3D color) { 40 | int rgb = (int) (color.x * 255) << 24 | (int) (color.y * 255) << 16 | (int) (color.z * 255) << 8; 41 | for (int i = 0; i < capacity; i++) { 42 | int alpha = pixels.get(i) & 255; 43 | pixels.put(i, rgb | alpha); 44 | } 45 | pixels.position(0); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/client/GameRendererMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.client; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.client.render.GameRenderer; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Shadow; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.At.Shift; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | import paulevs.betterweather.client.rendering.BetterWeatherRenderer; 12 | 13 | @Mixin(GameRenderer.class) 14 | public class GameRendererMixin { 15 | @Shadow private int ambientSoundTick; 16 | @Shadow private Minecraft minecraft; 17 | @Shadow private float fogDistance; 18 | 19 | @Inject(method = "renderWeather", at = @At("HEAD"), cancellable = true) 20 | private void betterweather_renderWeather(float delta, CallbackInfo info) { 21 | info.cancel(); 22 | } 23 | 24 | @Inject(method = "weatherEffects", at = @At("HEAD")) 25 | private void betterweather_resetSounds(CallbackInfo info) { 26 | this.ambientSoundTick = -10; 27 | } 28 | 29 | @Inject(method = "setupFog", at = @At( 30 | value = "INVOKE", 31 | target = "Lorg/lwjgl/opengl/GL11;glFogf(IF)V", 32 | ordinal = 4, 33 | shift = Shift.AFTER, 34 | remap = false 35 | )) 36 | private void betterweather_changeFogDepth(int f, float par2, CallbackInfo ci) { 37 | BetterWeatherRenderer.updateFogDepth(fogDistance); 38 | } 39 | 40 | @Inject(method = "delta", at = @At( 41 | value = "INVOKE", 42 | target = "Lnet/minecraft/client/particle/ParticleManager;renderAll(Lnet/minecraft/entity/Entity;F)V", 43 | shift = Shift.AFTER 44 | )) 45 | private void betterweather_renderBeforeWater(float delta, long time, CallbackInfo info) { 46 | BetterWeatherRenderer.renderBeforeWater(delta, minecraft); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/client/sound/WeatherSounds.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.client.sound; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.client.sound.SoundEntry; 6 | import net.minecraft.entity.living.LivingEntity; 7 | import net.minecraft.level.Level; 8 | import net.minecraft.util.maths.MCMath; 9 | import paulevs.betterweather.api.WeatherAPI; 10 | import paulscode.sound.SoundSystem; 11 | 12 | import java.net.URL; 13 | 14 | @Environment(EnvType.CLIENT) 15 | public class WeatherSounds { 16 | public static final SoundEntry RAIN = getSound("ambient/rain"); 17 | private static final String RAIN_KEY = "ambient.weather.rain"; 18 | 19 | private static boolean underRoof = false; 20 | private static SoundSystem soundSystem; 21 | 22 | public static void init(SoundSystem soundSystem) { 23 | WeatherSounds.soundSystem = soundSystem; 24 | } 25 | 26 | public static SoundEntry getSound(String name) { 27 | name = "assets/better_weather/stationapi/sounds/" + name + ".ogg"; 28 | URL url = Thread.currentThread().getContextClassLoader().getResource(name); 29 | return new SoundEntry(name, url); 30 | } 31 | 32 | public static void stop() { 33 | if (soundSystem == null) return; 34 | if (soundSystem.playing(RAIN_KEY)) soundSystem.stop(RAIN_KEY); 35 | } 36 | 37 | public static void updateSound(Level level, LivingEntity entity, SoundSystem soundSystem, float volume) { 38 | if (level == null || entity == null) { 39 | stop(); 40 | return; 41 | } 42 | 43 | volume *= WeatherAPI.getRainDensity(level, entity.x, entity.y, entity.z, false) * 0.5F; 44 | if (volume == 0) { 45 | stop(); 46 | return; 47 | } 48 | else if (!soundSystem.playing(RAIN_KEY)) { 49 | soundSystem.backgroundMusic(RAIN_KEY, RAIN.soundUrl, RAIN.soundName, true); 50 | soundSystem.play(RAIN_KEY); 51 | } 52 | 53 | boolean newRoof = entity.y < WeatherAPI.getRainHeight(level, MCMath.floor(entity.x), MCMath.floor(entity.z)); 54 | if (newRoof != underRoof) { 55 | soundSystem.setPitch(RAIN_KEY, newRoof ? 0.25F : 1.0F); 56 | underRoof = newRoof; 57 | } 58 | 59 | soundSystem.setVolume(RAIN_KEY, volume); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/util/ImageSampler.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.util; 2 | 3 | import net.minecraft.util.maths.MCMath; 4 | 5 | import javax.imageio.ImageIO; 6 | import java.awt.image.BufferedImage; 7 | import java.io.IOException; 8 | import java.net.URL; 9 | 10 | public class ImageSampler { 11 | private final float[] data; 12 | private final int width; 13 | private final int height; 14 | 15 | private boolean smooth; 16 | 17 | public ImageSampler(String path) { 18 | URL url = Thread.currentThread().getContextClassLoader().getResource(path); 19 | BufferedImage image; 20 | 21 | try { 22 | image = ImageIO.read(url); 23 | } 24 | catch (IOException e) { 25 | e.printStackTrace(); 26 | image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); 27 | } 28 | 29 | width = image.getWidth(); 30 | height = image.getWidth(); 31 | data = new float[width * height]; 32 | 33 | int[] pixels = new int[data.length]; 34 | image.getRGB(0, 0, width, height, pixels, 0, width); 35 | 36 | for (int i = 0; i < data.length; i++) { 37 | data[i] = (pixels[i] & 255) / 255F; 38 | } 39 | } 40 | 41 | public float sample(double x, double z) { 42 | int x1 = MCMath.floor(x); 43 | int z1 = MCMath.floor(z); 44 | int x2 = MathUtil.wrap(x1 + 1, width); 45 | int z2 = MathUtil.wrap(z1 + 1, height); 46 | float dx = (float) (x - x1); 47 | float dz = (float) (z - z1); 48 | x1 = MathUtil.wrap(x1, width); 49 | z1 = MathUtil.wrap(z1, height); 50 | 51 | float a = data[getIndex(x1, z1)]; 52 | float b = data[getIndex(x2, z1)]; 53 | float c = data[getIndex(x1, z2)]; 54 | float d = data[getIndex(x2, z2)]; 55 | 56 | if (smooth) { 57 | dx = smoothStep(dx); 58 | dz = smoothStep(dz); 59 | } 60 | 61 | return net.modificationstation.stationapi.api.util.math.MathHelper.interpolate2D( 62 | dx, dz, a, b, c, d 63 | ); 64 | } 65 | 66 | private int getIndex(int x, int z) { 67 | return z * width + x; 68 | } 69 | 70 | public ImageSampler setSmooth(boolean smooth) { 71 | this.smooth = smooth; 72 | return this; 73 | } 74 | 75 | private float smoothStep(float x) { 76 | return x * x * x * (x * (x * 6 - 15) + 10); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/util/LightningLightBlock.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.util; 2 | 3 | import net.minecraft.block.material.Material; 4 | import net.minecraft.entity.technical.LightningEntity; 5 | import net.minecraft.level.BlockView; 6 | import net.minecraft.level.Level; 7 | import net.minecraft.util.hit.HitResult; 8 | import net.minecraft.util.maths.Box; 9 | import net.minecraft.util.maths.Vec3D; 10 | import net.modificationstation.stationapi.api.block.BlockState; 11 | import net.modificationstation.stationapi.api.block.States; 12 | import net.modificationstation.stationapi.api.item.ItemPlacementContext; 13 | import net.modificationstation.stationapi.api.template.block.TemplateBlock; 14 | import net.modificationstation.stationapi.api.util.Identifier; 15 | 16 | import java.util.Random; 17 | 18 | public class LightningLightBlock extends TemplateBlock { 19 | public LightningLightBlock(Identifier id) { 20 | super(id, Material.AIR); 21 | setHardness(0); 22 | setBlastResistance(0); 23 | setTranslationKey("minecraft:air"); 24 | disableNotifyOnMetaDataChange(); 25 | disableStat(); 26 | setLightOpacity(0); 27 | setLightEmittance(1); 28 | setTicksRandomly(true); 29 | } 30 | 31 | @Override 32 | public boolean isFullCube() { 33 | return false; 34 | } 35 | 36 | @Override 37 | public boolean isSideRendered(BlockView tileView, int x, int y, int z, int side) { 38 | return false; 39 | } 40 | 41 | @Override 42 | public Box getOutlineShape(Level level, int x, int y, int z) { 43 | return null; 44 | } 45 | 46 | @Override 47 | public Box getCollisionShape(Level level, int x, int y, int z) { 48 | return null; 49 | } 50 | 51 | @Override 52 | public boolean isFullOpaque() { 53 | return false; 54 | } 55 | 56 | @Override 57 | public boolean isSelectable(int meta, boolean flag) { 58 | return false; 59 | } 60 | 61 | @Override 62 | public int getDropCount(Random rand) { 63 | return 0; 64 | } 65 | 66 | @Override 67 | public HitResult getHitResult(Level level, int x, int y, int z, Vec3D arg1, Vec3D arg2) { 68 | return null; 69 | } 70 | 71 | @Override 72 | public boolean canReplace(BlockState state, ItemPlacementContext context) { 73 | return true; 74 | } 75 | 76 | @Override 77 | public void onScheduledTick(Level level, int x, int y, int z, Random random) { 78 | if (level.getEntities(LightningEntity.class, Box.createAndCache(x, y, z, x, y, z)).isEmpty()) { 79 | level.setBlockState(x, y, z, States.AIR.get()); 80 | level.updateArea(x - 15, y - 15, z - 15, x + 15, y + 15, z + 15); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/client/rendering/FrustumCulling.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.client.rendering; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.util.maths.MCMath; 6 | import net.modificationstation.stationapi.api.util.math.Quaternion; 7 | import net.modificationstation.stationapi.api.util.math.Vec3f; 8 | 9 | @Environment(EnvType.CLIENT) 10 | public class FrustumCulling { 11 | private static final float TO_RADIANS = (float) (Math.PI / 180); 12 | private static final Vec3f[] NORMALS; 13 | private final Quaternion rotation = new Quaternion(0, 0, 0, 0); 14 | private final Quaternion rotation2 = new Quaternion(0, 0, 0, 0); 15 | private final Vec3f[] defaultNormals; 16 | private final Vec3f[] planes; 17 | 18 | public FrustumCulling() { 19 | defaultNormals = new Vec3f[4]; 20 | planes = new Vec3f[4]; 21 | for (byte i = 0; i < 4; i++) { 22 | defaultNormals[i] = NORMALS[i].copy(); 23 | planes[i] = NORMALS[i].copy(); 24 | } 25 | } 26 | 27 | public void setFOV(float angle) { 28 | for (byte i = 0; i < 4; i++) { 29 | Vec3f original = NORMALS[i]; 30 | Vec3f normal = defaultNormals[i]; 31 | normal.set(original.getX(), original.getY(), original.getZ()); 32 | if (normal.getX() != 0) setRotation(rotation, Vec3f.POSITIVE_Y, normal.getX() > 0 ? angle : -angle); 33 | else setRotation(rotation, Vec3f.POSITIVE_X, normal.getY() > 0 ? -angle : angle); 34 | normal.rotate(rotation); 35 | } 36 | } 37 | 38 | public void rotate(float yaw, float pitch) { 39 | setRotation(rotation2, Vec3f.POSITIVE_X, pitch * TO_RADIANS); 40 | setRotation(rotation, Vec3f.POSITIVE_Y, -yaw * TO_RADIANS); 41 | rotation.hamiltonProduct(rotation2); 42 | for (byte i = 0; i < 4; i++) { 43 | Vec3f normal = defaultNormals[i]; 44 | planes[i].set(normal.getX(), normal.getY(), normal.getZ()); 45 | planes[i].rotate(rotation); 46 | } 47 | } 48 | 49 | public boolean isOutside(Vec3f pos, float distance) { 50 | for (byte i = 0; i < 4; i++) { 51 | if (planes[i].dot(pos) > distance) return true; 52 | } 53 | return false; 54 | } 55 | 56 | private void setRotation(Quaternion rotation, Vec3f axis, float angle) { 57 | angle *= 0.5F; 58 | float sin = MCMath.sin(angle); 59 | rotation.set( 60 | axis.getX() * sin, 61 | axis.getY() * sin, 62 | axis.getZ() * sin, 63 | MCMath.cos(angle) 64 | ); 65 | } 66 | 67 | static { 68 | NORMALS = new Vec3f[] { 69 | new Vec3f( 1, 0, 0), 70 | new Vec3f(-1, 0, 0), 71 | new Vec3f(0, 1, 0), 72 | new Vec3f(0, -1, 0) 73 | }; 74 | } 75 | } -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/config/CommonConfig.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.config; 2 | 3 | import java.io.File; 4 | 5 | public class CommonConfig { 6 | private static final Config CONFIG = new Config(new File("config/better_weather/common.cfg")); 7 | private static boolean useVanillaClouds; 8 | private static double cloudsSpeed; 9 | private static boolean eternalRain; 10 | private static boolean eternalThunder; 11 | private static boolean frequentRain; 12 | private static int rodCheckSide; 13 | private static int lightningChance; 14 | 15 | public static void init() { 16 | CONFIG.addEntry("useVanillaClouds", false, 17 | "Use vanilla clouds texture as a base map for clouds", 18 | "Default value is false" 19 | ); 20 | CONFIG.addEntry("cloudsSpeed", 0.001F, 21 | "Clouds speed in ticks per chunk, larger values will cause clouds move faster", 22 | "Default value is 0.001" 23 | ); 24 | CONFIG.addEntry("eternalRain", false, 25 | "Makes weather in the whole world rain only", 26 | "Default value is false" 27 | ); 28 | CONFIG.addEntry("eternalThunder", false, 29 | "Makes weather in the whole world thunderstorm", 30 | "Default value is false" 31 | ); 32 | CONFIG.addEntry("frequentRain", false, 33 | "Makes rain more frequent instead of vanilla behaviour", 34 | "Default value is false" 35 | ); 36 | CONFIG.addEntry("rodCheckSide", 32, 37 | "Distance in blocks (from the rod block) that will be protected from lightnings", 38 | "The area is square with center on rod block and radius equal to this number", 39 | "Max value is " + Short.MAX_VALUE + " and min is 0", 40 | "Default value is 32" 41 | ); 42 | CONFIG.addEntry("lightningChance", 300, 43 | "Chance that lighting will happen in this chunk (during thunderstorm)", 44 | "The actual chance is calculated as 1/lightningChance", 45 | "Smaller values will result with more lighting and visa versa", 46 | "Max value is " + Short.MAX_VALUE + " and min is 1", 47 | "Default value is 300" 48 | ); 49 | CONFIG.save(); 50 | 51 | useVanillaClouds = CONFIG.getBool("useVanillaClouds"); 52 | cloudsSpeed = CONFIG.getFloat("cloudsSpeed"); 53 | eternalRain = CONFIG.getBool("eternalRain"); 54 | eternalThunder = CONFIG.getBool("eternalThunder"); 55 | frequentRain = CONFIG.getBool("frequentRain"); 56 | rodCheckSide = CONFIG.getInt("rodCheckSide"); 57 | lightningChance = CONFIG.getInt("lightningChance"); 58 | 59 | if (rodCheckSide > Short.MAX_VALUE) rodCheckSide = Short.MAX_VALUE; 60 | if (rodCheckSide < 0) rodCheckSide = 0; 61 | 62 | if (lightningChance > Short.MAX_VALUE) lightningChance = Short.MAX_VALUE; 63 | if (lightningChance < 1) rodCheckSide = 1; 64 | } 65 | 66 | public static boolean useVanillaClouds() { 67 | return useVanillaClouds; 68 | } 69 | 70 | public static double getCloudsSpeed() { 71 | return cloudsSpeed; 72 | } 73 | 74 | public static boolean isEternalRain() { 75 | return eternalRain; 76 | } 77 | 78 | public static boolean isEternalThunder() { 79 | return eternalThunder; 80 | } 81 | 82 | public static boolean isFrequentRain() { 83 | return frequentRain; 84 | } 85 | 86 | public static short getRodCheckSide() { 87 | return (short) rodCheckSide; 88 | } 89 | 90 | public static int getLightningChance() { 91 | return lightningChance; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/common/LightningEntityMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.common; 2 | 3 | import net.minecraft.entity.technical.AbstractLightning; 4 | import net.minecraft.entity.technical.LightningEntity; 5 | import net.minecraft.level.Level; 6 | import net.minecraft.util.maths.MCMath; 7 | import net.modificationstation.stationapi.api.block.States; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Unique; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Constant; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.ModifyConstant; 14 | import org.spongepowered.asm.mixin.injection.Redirect; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | import paulevs.betterweather.api.WeatherAPI; 17 | import paulevs.betterweather.listeners.CommonListener; 18 | import paulevs.betterweather.util.WeatherTags; 19 | 20 | @Mixin(LightningEntity.class) 21 | public abstract class LightningEntityMixin extends AbstractLightning { 22 | @Unique private boolean betterweather_isOnRod; 23 | 24 | public LightningEntityMixin(Level arg) { 25 | super(arg); 26 | } 27 | 28 | @ModifyConstant(method = "", constant = @Constant(intValue = 2, ordinal = 1)) 29 | private int betterweather_disableFireInInit(int constant) { 30 | int px = MCMath.floor(this.x); 31 | int pz = MCMath.floor(this.z); 32 | int py = WeatherAPI.getRainHeight(level, px, pz) - 1; 33 | betterweather_isOnRod = level.getBlockState(px, py, pz).isIn(WeatherTags.LIGHTNING_ROD); 34 | return betterweather_isOnRod ? 200 : constant; 35 | } 36 | 37 | @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/level/Level;isAreaLoaded(IIII)Z")) 38 | private boolean betterweather_disableFireonTick(Level level, int x, int y, int z, int side) { 39 | return !betterweather_isOnRod && level.isAreaLoaded(x, y, z, side); 40 | } 41 | 42 | @Inject(method = "", at = @At("TAIL")) 43 | private void betterweather_onInit(Level level, double x, double y, double z, CallbackInfo info) { 44 | int px = MCMath.floor(this.x); 45 | int py = MCMath.floor(this.y); 46 | int pz = MCMath.floor(this.z); 47 | if (level.getBlockState(px, py, pz).isAir()) { 48 | level.setBlockState(px, py, pz, CommonListener.lightningLight.getDefaultState()); 49 | level.updateArea(px - 15, py - 15, pz - 15, px + 15, py + 15, pz + 15); 50 | } 51 | } 52 | 53 | @Inject(method = "tick", at = @At( 54 | value = "INVOKE", 55 | target = "Lnet/minecraft/entity/technical/LightningEntity;remove()V" 56 | )) 57 | private void betterweather_onRemove(CallbackInfo info) { 58 | int px = MCMath.floor(this.x); 59 | int py = MCMath.floor(this.y); 60 | int pz = MCMath.floor(this.z); 61 | if (level.getBlockState(px, py, pz).isOf(CommonListener.lightningLight)) { 62 | level.setBlockState(px, py, pz, States.AIR.get()); 63 | level.updateArea(px - 15, py - 15, pz - 15, px + 15, py + 15, pz + 15); 64 | } 65 | } 66 | 67 | @ModifyConstant(method = "tick", constant = @Constant(floatValue = 10000.0f)) 68 | private float betterweather_changeThunderDistance(float constant) { 69 | return 200F; 70 | } 71 | 72 | @Redirect(method = "tick", at = @At( 73 | value = "INVOKE", 74 | target = "Lnet/minecraft/level/Level;playSound(DDDLjava/lang/String;FF)V", 75 | ordinal = 1 76 | )) 77 | private void betterweather_disableExplosionSound(Level level, double e, double f, double string, String g, float h, float v) {} 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/mixin/common/LevelMixin.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.mixin.common; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.fabricmc.loader.api.FabricLoader; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.entity.Entity; 8 | import net.minecraft.entity.living.LivingEntity; 9 | import net.minecraft.level.Level; 10 | import net.minecraft.util.maths.Vec2I; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.Unique; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.At.Shift; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 19 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 20 | import paulevs.betterweather.api.WeatherAPI; 21 | import paulevs.betterweather.util.LightningUtil; 22 | 23 | import java.util.Iterator; 24 | import java.util.Random; 25 | 26 | @Mixin(Level.class) 27 | public abstract class LevelMixin { 28 | @Unique private boolean betterweather_flag; 29 | 30 | @Shadow public Random random; 31 | 32 | @Shadow public abstract boolean addEntity(Entity arg); 33 | 34 | @Inject(method = "isRaining", at = @At("HEAD"), cancellable = true) 35 | private void betterweather_isRaining(CallbackInfoReturnable info) { 36 | info.setReturnValue(false); 37 | } 38 | 39 | @Inject(method = "isThundering", at = @At("HEAD"), cancellable = true) 40 | private void betterweather_isThundering(CallbackInfoReturnable info) { 41 | info.setReturnValue(false); 42 | } 43 | 44 | @Inject(method = "canRainAt", at = @At("HEAD"), cancellable = true) 45 | private void betterweather_isRainingAt(int x, int y, int z, CallbackInfoReturnable info) { 46 | info.setReturnValue(WeatherAPI.isRaining(Level.class.cast(this), x, y, z)); 47 | } 48 | 49 | @SuppressWarnings("rawtypes") 50 | @Inject(method = "processLoadedChunks", at = @At( 51 | value = "INVOKE", 52 | target = "Lnet/minecraft/level/Level;getChunkFromCache(II)Lnet/minecraft/level/chunk/Chunk;", 53 | shift = Shift.AFTER 54 | ), locals = LocalCapture.CAPTURE_FAILSOFT) 55 | private void betterweather_processLoadedChunks(CallbackInfo info, Iterator iterator, Vec2I pos) { 56 | LightningUtil.processChunk(Level.class.cast(this), pos.x, pos.z); 57 | } 58 | 59 | @Inject(method = "processLoadedChunks", at = @At("HEAD")) 60 | private void betterweather_tick(CallbackInfo info) { 61 | LightningUtil.tick(); 62 | } 63 | 64 | @Environment(EnvType.CLIENT) 65 | @Inject(method = "getRainGradient", at = @At("HEAD"), cancellable = true) 66 | private void betterweather_getRainGradient(float delta, CallbackInfoReturnable info) { 67 | if (betterweather_flag) { 68 | betterweather_flag = false; 69 | info.setReturnValue(0F); 70 | return; 71 | } 72 | 73 | @SuppressWarnings("deprecation") 74 | Minecraft minecraft = (Minecraft) FabricLoader.getInstance().getGameInstance(); 75 | LivingEntity entity = minecraft.viewEntity; 76 | if (entity == null || minecraft.level == null) return; 77 | info.setReturnValue(WeatherAPI.getRainDensity(minecraft.level, entity.x, entity.y, entity.z, true)); 78 | } 79 | 80 | @Inject(method = "getEnvironmentLight", at = @At("HEAD")) 81 | private void betterweather_getEnvironmentLight(float delta, CallbackInfoReturnable info) { 82 | betterweather_flag = true; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/client/rendering/BetterWeatherRenderer.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.client.rendering; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.block.material.Material; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.texture.TextureManager; 8 | import net.minecraft.util.maths.Vec3D; 9 | import net.modificationstation.stationapi.api.util.math.MathHelper; 10 | import org.lwjgl.opengl.GL11; 11 | import org.spongepowered.asm.mixin.injection.invoke.arg.Args; 12 | import paulevs.betterweather.api.WeatherAPI; 13 | 14 | @Environment(EnvType.CLIENT) 15 | public class BetterWeatherRenderer { 16 | private static final CloudRenderer CLOUD_RENDERER = new CloudRenderer(); 17 | private static final WeatherRenderer WEATHER_RENDERER = new WeatherRenderer(); 18 | private static float fogDistance = 1F; 19 | private static boolean isInWater; 20 | public static float fogColorR; 21 | public static float fogColorG; 22 | public static float fogColorB; 23 | 24 | public static void update(TextureManager manager) { 25 | CLOUD_RENDERER.update(manager); 26 | WEATHER_RENDERER.update(manager); 27 | } 28 | 29 | public static void renderBeforeWater(float delta, Minecraft minecraft) { 30 | if (minecraft.level.dimension.evaporatesWater) return; 31 | isInWater = minecraft.viewEntity.isInFluid(Material.WATER); 32 | if (!isInWater) return; 33 | render(delta, minecraft); 34 | } 35 | 36 | public static void renderAfterWater(float delta, Minecraft minecraft) { 37 | if (minecraft.level.dimension.evaporatesWater) return; 38 | if (isInWater) return; 39 | render(delta, minecraft); 40 | } 41 | 42 | private static void render(float delta, Minecraft minecraft) { 43 | CLOUD_RENDERER.render(delta, minecraft); 44 | WEATHER_RENDERER.render(delta, minecraft); 45 | } 46 | 47 | public static void changeFogDensity(Args args, Minecraft minecraft) { 48 | float density = WeatherAPI.getRainDensity( 49 | minecraft.level, 50 | minecraft.viewEntity.x, 51 | minecraft.viewEntity.y, 52 | minecraft.viewEntity.z, 53 | true 54 | ); 55 | if (density == 0) return; 56 | density = 1F - density * 0.75F; 57 | args.set(0, (float) args.get(0) * density); 58 | args.set(1, (float) args.get(1) * density); 59 | args.set(2, (float) args.get(2) * density); 60 | } 61 | 62 | public static void updateFogColor(Minecraft minecraft, float delta) { 63 | fogDistance = 1F; 64 | 65 | fogDistance = WeatherAPI.getRainDensity( 66 | minecraft.level, 67 | minecraft.viewEntity.x, 68 | minecraft.viewEntity.y, 69 | minecraft.viewEntity.z, 70 | true 71 | ); 72 | fogDistance = 1F - fogDistance * 0.75F; 73 | 74 | fogColorR *= fogDistance; 75 | fogColorG *= fogDistance; 76 | fogColorB *= fogDistance; 77 | 78 | float inCloud = WeatherAPI.inCloud( 79 | minecraft.level, 80 | minecraft.viewEntity.x, 81 | minecraft.viewEntity.y, 82 | minecraft.viewEntity.z 83 | ); 84 | 85 | if (inCloud > 0) { 86 | Vec3D fogColor = minecraft.level.getSkyColor(delta); 87 | fogDistance = MathHelper.lerp(inCloud, fogDistance, 0.02F); 88 | fogColorR = MathHelper.lerp(inCloud, fogColorR, (float) fogColor.x); 89 | fogColorG = MathHelper.lerp(inCloud, fogColorG, (float) fogColor.y); 90 | fogColorB = MathHelper.lerp(inCloud, fogColorB, (float) fogColor.z); 91 | } 92 | } 93 | 94 | public static void updateFogDepth(float defaultFogDistance) { 95 | CloudRenderer.fogDistance = defaultFogDistance; 96 | if (fogDistance == 1) return; 97 | GL11.glFogf(GL11.GL_FOG_START, defaultFogDistance * fogDistance * 0.25F); 98 | GL11.glFogf(GL11.GL_FOG_END, defaultFogDistance * fogDistance); 99 | } 100 | 101 | public static void updateClouds() { 102 | CLOUD_RENDERER.updateAll(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/client/rendering/BWLightningRenderer.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.client.rendering; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.client.render.Tessellator; 6 | import net.minecraft.client.texture.TextureManager; 7 | import net.minecraft.entity.technical.LightningEntity; 8 | import net.minecraft.util.maths.MCMath; 9 | import org.lwjgl.opengl.GL11; 10 | import paulevs.betterweather.config.CommonConfig; 11 | 12 | import java.util.Random; 13 | 14 | @Environment(EnvType.CLIENT) 15 | public class BWLightningRenderer { 16 | private static final Random RANDOM = new Random(0); 17 | private static int texture = -1; 18 | 19 | public static void render(LightningEntity entity, float x, float y, float z, TextureManager manager) { 20 | if (texture == -1) { 21 | texture = manager.getTextureId("/assets/better_weather/textures/lightning.png"); 22 | } 23 | 24 | float y2 = CommonConfig.useVanillaClouds() ? 2.5F : 8.5F; 25 | y2 += entity.level.dimension.getCloudHeight(); 26 | y2 = (float) (y2 - entity.y) + y; 27 | 28 | Tessellator tessellator = Tessellator.INSTANCE; 29 | 30 | float dx = x; 31 | float dz = z; 32 | float l = dx * dx + dz * dz; 33 | if (l > 0) { 34 | l = MCMath.sqrt(l) / 0.5F; 35 | dx /= l; 36 | dz /= l; 37 | float v = dx; 38 | dx = -dz; 39 | dz = v; 40 | } 41 | else { 42 | dx = 0.5F; 43 | dz = 0; 44 | } 45 | 46 | float x1 = x + 0.5F + dx; 47 | float x2 = x + 0.5F - dx; 48 | float z1 = z + 0.5F + dz; 49 | float z2 = z + 0.5F - dz; 50 | float x1_2 = x + 0.5F + dx * 0.5F; 51 | float x2_2 = x + 0.5F - dx * 0.5F; 52 | float z1_2 = z + 0.5F + dz * 0.5F; 53 | float z2_2 = z + 0.5F - dz * 0.5F; 54 | 55 | int sectionCount = MCMath.floor((y2 - y) / 8F + 1); 56 | float secDelta = (y2 - y) / sectionCount; 57 | 58 | GL11.glDisable(GL11.GL_CULL_FACE); 59 | GL11.glDisable(GL11.GL_LIGHTING); 60 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture); 61 | GL11.glEnable(GL11.GL_BLEND); 62 | GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); 63 | GL11.glAlphaFunc(GL11.GL_GREATER, 0.01F); 64 | 65 | GL11.glColor4f(1F, 1F, 1F, 1F); 66 | 67 | tessellator.start(); 68 | tessellator.color(255, 255, 255, 255); 69 | 70 | float dx1 = 0; 71 | float dz1 = 0; 72 | float dx2 = 0; 73 | float dz2 = 0; 74 | 75 | RANDOM.setSeed(entity.entityId); 76 | 77 | for (int i = 0; i < sectionCount; i++) { 78 | y2 = y + secDelta; 79 | 80 | tessellator.vertex(x1 + dx1, y, z1 + dz1, 0F, 0F); 81 | tessellator.vertex(x1 + dx2, y2, z1 + dz2, 0F, 1F); 82 | tessellator.vertex(x2 + dx2, y2, z2 + dz2, 1F, 1F); 83 | tessellator.vertex(x2 + dx1, y, z2 + dz1, 1F, 0F); 84 | 85 | if (i > 0 && RANDOM.nextInt(3) == 0) { 86 | float dist = RANDOM.nextFloat() * 15; 87 | float dx3 = dx * dist; 88 | float dz3 = dz * dist; 89 | 90 | tessellator.vertex(x1_2 + dx3, y, z1_2 + dz3, 0F, 0F); 91 | tessellator.vertex(x1_2 + dx2, y2, z1_2 + dz2, 0F, 1F); 92 | tessellator.vertex(x2_2 + dx2, y2, z2_2 + dz2, 1F, 1F); 93 | tessellator.vertex(x2_2 + dx3, y, z2_2 + dz3, 1F, 0F); 94 | 95 | if (RANDOM.nextBoolean()) { 96 | dist = RANDOM.nextFloat() * 15; 97 | float dx4 = dx * dist; 98 | float dz4 = dz * dist; 99 | float y3 = y - secDelta; 100 | 101 | tessellator.vertex(x1_2 + dx4, y3, z1_2 + dz4, 0F, 0F); 102 | tessellator.vertex(x1_2 + dx3, y, z1_2 + dz3, 0F, 1F); 103 | tessellator.vertex(x2_2 + dx3, y, z2_2 + dz3, 1F, 1F); 104 | tessellator.vertex(x2_2 + dx4, y3, z2_2 + dz4, 1F, 0F); 105 | } 106 | } 107 | 108 | dx1 = dx2; 109 | dz1 = dz2; 110 | float dist = RANDOM.nextFloat() * 7; 111 | dx2 = dx * dist; 112 | dz2 = dz * dist; 113 | 114 | y = y2; 115 | } 116 | 117 | tessellator.render(); 118 | 119 | GL11.glEnable(GL11.GL_CULL_FACE); 120 | GL11.glEnable(GL11.GL_LIGHTING); 121 | GL11.glDisable(GL11.GL_BLEND); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/config/Config.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.config; 2 | 3 | import java.io.File; 4 | import java.io.FileWriter; 5 | import java.io.IOException; 6 | import java.nio.file.Files; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * Config holder class, can save/load config data and provide stored values. 14 | * Values will be updated if necessary when "addEntry" method is called 15 | */ 16 | public class Config { 17 | private final Map> entries = new HashMap<>(); 18 | private final Map preEntries = new HashMap<>(); 19 | private final List order = new ArrayList<>(); 20 | private final File file; 21 | 22 | public Config(File file) { 23 | this.file = file; 24 | if (file.exists()) load(); 25 | } 26 | 27 | /** 28 | * Saves config file if necessary (there are changes that require saving) 29 | */ 30 | public void save() { 31 | if (!file.exists()) writeFile(); 32 | else if (entries.size() != preEntries.size()) writeFile(); 33 | } 34 | 35 | /** 36 | * Add boolean entry to the config. 37 | * If there is stored value in File it will use it instead 38 | * @param name {@link String} entry name 39 | * @param value {@code boolean} value 40 | * @param comments Array of strings, comments will be added before entry. It is recommended to mention default value 41 | */ 42 | public void addEntry(String name, boolean value, String... comments) { 43 | String stored = preEntries.get(name); 44 | if (stored != null) value = Boolean.parseBoolean(stored); 45 | entries.put(name, new ConfigEntry<>(name, value, List.of(comments))); 46 | order.add(name); 47 | } 48 | 49 | /** 50 | * Add float entry to the config. 51 | * If there is stored value in File it will use it instead 52 | * @param name {@link String} entry name 53 | * @param value {@code float} value 54 | * @param comments Array of strings, comments will be added before entry. It is recommended to mention default value 55 | */ 56 | public void addEntry(String name, float value, String... comments) { 57 | String stored = preEntries.get(name); 58 | if (stored != null) value = Float.parseFloat(stored); 59 | entries.put(name, new ConfigEntry<>(name, value, List.of(comments))); 60 | order.add(name); 61 | } 62 | 63 | /** 64 | * Add int entry to the config. 65 | * If there is stored value in File it will use it instead 66 | * @param name {@link String} entry name 67 | * @param value {@code int} value 68 | * @param comments Array of strings, comments will be added before entry. It is recommended to mention default value 69 | */ 70 | public void addEntry(String name, int value, String... comments) { 71 | String stored = preEntries.get(name); 72 | if (stored != null) value = Integer.parseInt(stored); 73 | entries.put(name, new ConfigEntry<>(name, value, List.of(comments))); 74 | order.add(name); 75 | } 76 | 77 | /** 78 | * Add {@link String} entry to the config. 79 | * If there is stored value in File it will use it instead 80 | * @param name {@link String} entry name 81 | * @param value {@link String} value 82 | * @param comments Array of strings, comments will be added before entry. It is recommended to mention default value 83 | */ 84 | public void addEntry(String name, String value, String... comments) { 85 | String stored = preEntries.get(name); 86 | if (stored != null) value = stored; 87 | entries.put(name, new ConfigEntry<>(name, value, List.of(comments))); 88 | order.add(name); 89 | } 90 | 91 | /** 92 | * Get boolean value from the config 93 | * @param name {@link String} entry name 94 | * @return {@code boolean} 95 | */ 96 | public boolean getBool(String name) { 97 | return (Boolean) entries.get(name).value; 98 | } 99 | 100 | /** 101 | * Get float value from the config 102 | * @param name {@link String} entry name 103 | * @return {@code float} 104 | */ 105 | public float getFloat(String name) { 106 | return (Float) entries.get(name).value; 107 | } 108 | 109 | /** 110 | * Get int value from the config 111 | * @param name {@link String} entry name 112 | * @return {@code int} 113 | */ 114 | public int getInt(String name) { 115 | return (Integer) entries.get(name).value; 116 | } 117 | 118 | /** 119 | * Get {@link String} value from the config 120 | * @param name {@link String} entry name 121 | * @return {@link String} 122 | */ 123 | public String getString(String name) { 124 | return (String) entries.get(name).value; 125 | } 126 | 127 | private void writeFile() { 128 | file.getParentFile().mkdirs(); 129 | int max = entries.size() - 1; 130 | try { 131 | FileWriter writer = new FileWriter(file); 132 | for (int i = 0; i < order.size(); i++) { 133 | entries.get(order.get(i)).append(writer); 134 | if (i < max) writer.append('\n'); 135 | } 136 | writer.flush(); 137 | writer.close(); 138 | } 139 | catch (IOException e) { 140 | e.printStackTrace(); 141 | } 142 | } 143 | 144 | private void load() { 145 | List lines = null; 146 | try { 147 | lines = Files.readAllLines(file.toPath()); 148 | } 149 | catch (IOException e) { 150 | e.printStackTrace(); 151 | } 152 | if (lines == null) return; 153 | lines.stream().filter(line -> line.length() > 2 && line.charAt(0) != '#').forEach(line -> { 154 | int split = line.indexOf('='); 155 | String name = line.substring(0, split).trim(); 156 | String value = line.substring(split + 1).trim(); 157 | preEntries.put(name, value); 158 | }); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/client/rendering/CloudRenderer.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.client.rendering; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.texture.TextureManager; 7 | import net.minecraft.entity.living.LivingEntity; 8 | import net.minecraft.level.Level; 9 | import net.minecraft.util.maths.MCMath; 10 | import net.minecraft.util.maths.Vec2I; 11 | import net.minecraft.util.noise.PerlinNoise; 12 | import net.modificationstation.stationapi.api.util.math.MathHelper; 13 | import org.lwjgl.opengl.GL11; 14 | import paulevs.betterweather.api.WeatherAPI; 15 | import paulevs.betterweather.config.CommonConfig; 16 | import paulevs.betterweather.util.MathUtil; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | import java.util.Random; 22 | import java.util.stream.IntStream; 23 | 24 | @Environment(EnvType.CLIENT) 25 | public class CloudRenderer { 26 | public static final short EMPTY_CLOUD = (short) 0xF000; 27 | 28 | private static final PerlinNoise NOISE = new PerlinNoise(new Random(0)); 29 | private static final short[] CLOUD_DATA = new short[8192]; 30 | 31 | private static final int RADIUS = 9; 32 | private static final int SIDE = RADIUS * 2 + 1; 33 | private static final int CAPACITY = SIDE * SIDE; 34 | public static float fogDistance; 35 | 36 | private final CloudChunk[] chunks = new CloudChunk[CAPACITY]; 37 | private final FrustumCulling culling = new FrustumCulling(); 38 | private final Vec2I[] offsets; 39 | 40 | private CloudTexture cloudTexture; 41 | 42 | public CloudRenderer() { 43 | for (short i = 0; i < chunks.length; i++) { 44 | chunks[i] = new CloudChunk(); 45 | } 46 | 47 | List offsets = new ArrayList<>(CAPACITY); 48 | for (byte x = -RADIUS; x <= RADIUS; x++) { 49 | for (byte z = -RADIUS; z <= RADIUS; z++) { 50 | offsets.add(new Vec2I(x, z)); 51 | } 52 | } 53 | offsets.sort((v1, v2) -> { 54 | int d1 = v1.x * v1.x + v1.z * v1.z; 55 | int d2 = v2.x * v2.x + v2.z * v2.z; 56 | return Integer.compare(d1, d2); 57 | }); 58 | this.offsets = offsets.toArray(Vec2I[]::new); 59 | culling.setFOV((float) Math.toRadians(60F)); 60 | } 61 | 62 | public void update(TextureManager manager) { 63 | if (cloudTexture == null) { 64 | cloudTexture = new CloudTexture(manager); 65 | } 66 | } 67 | 68 | private int getIndex(int x, int y) { 69 | return MathUtil.wrap(x, SIDE) * SIDE + MathUtil.wrap(y, SIDE); 70 | } 71 | 72 | public void render(float delta, Minecraft minecraft) { 73 | LivingEntity entity = minecraft.viewEntity; 74 | double entityX = MathHelper.lerp(delta, entity.prevRenderX, entity.x); 75 | double entityY = MathHelper.lerp(delta, entity.prevRenderY, entity.y); 76 | double entityZ = MathHelper.lerp(delta, entity.prevRenderZ, entity.z); 77 | float height = (float) (minecraft.level.dimension.getCloudHeight() - entityY); 78 | 79 | int centerX = MCMath.floor(entityX / 32); 80 | int centerZ = MCMath.floor(entityZ / 32); 81 | 82 | double moveDelta = ((double) minecraft.level.getLevelTime() + delta) * CommonConfig.getCloudsSpeed(); 83 | int worldOffset = (int) Math.floor(moveDelta); 84 | entityZ -= (moveDelta - worldOffset) * 32; 85 | 86 | GL11.glDisable(GL11.GL_CULL_FACE); 87 | GL11.glDisable(GL11.GL_BLEND); 88 | GL11.glEnable(GL11.GL_TEXTURE_2D); 89 | 90 | cloudTexture.bindAndUpdate(minecraft.level.getSkyColor(delta)); 91 | culling.rotate(entity.yaw, entity.pitch); 92 | 93 | boolean canUpdate = true; 94 | float distance = fogDistance * 1.5F; 95 | distance *= distance; 96 | 97 | for (Vec2I offset : offsets) { 98 | int cx = centerX + offset.x; 99 | int cz = centerZ + offset.z; 100 | int movedZ = cz - worldOffset; 101 | CloudChunk chunk = chunks[getIndex(cx, movedZ)]; 102 | chunk.setRenderPosition(cx, cz); 103 | chunk.checkIfNeedUpdate(cx, movedZ); 104 | if (canUpdate && chunk.needUpdate()) { 105 | updateData(minecraft.level, cx, movedZ); 106 | chunk.update(cx, movedZ, CLOUD_DATA); 107 | canUpdate = false; 108 | } 109 | if (!chunk.needUpdate()) { 110 | chunk.render(entityX, entityZ, height, culling, distance); 111 | } 112 | } 113 | 114 | GL11.glEnable(GL11.GL_CULL_FACE); 115 | GL11.glEnable(GL11.GL_BLEND); 116 | } 117 | 118 | public void updateAll() { 119 | Arrays.stream(chunks).forEach(CloudChunk::forceUpdate); 120 | } 121 | 122 | private void updateData(Level level, int cx, int cz) { 123 | final int posX = cx << 4; 124 | final int posZ = cz << 4; 125 | 126 | IntStream.range(0, 8192).parallel().forEach(index -> { 127 | int x = index & 15; 128 | int y = (index >> 4) & 31; 129 | int z = index >> 9; 130 | 131 | x |= posX; 132 | z |= posZ; 133 | 134 | float rainFront = WeatherAPI.sampleFront(level, x, z, 0.2); 135 | float density = WeatherAPI.getCloudDensity(x << 1, y << 1, z << 1, rainFront); 136 | float coverage = WeatherAPI.getCoverage(rainFront); 137 | 138 | if (density < coverage) { 139 | CLOUD_DATA[index] = EMPTY_CLOUD; 140 | } 141 | else { 142 | CLOUD_DATA[index] = (short) ((byte) (rainFront * 15) << 4); 143 | byte thunder = (byte) (WeatherAPI.sampleThunderstorm(level, x, z, 0.1) * rainFront * 15); 144 | CLOUD_DATA[index] |= (short) (thunder << 8); 145 | } 146 | }); 147 | 148 | IntStream.range(0, 8192).parallel().forEach(index -> { 149 | if (CLOUD_DATA[index] == EMPTY_CLOUD) return; 150 | 151 | int x = index & 15; 152 | int y = (index >> 4) & 31; 153 | int z = index >> 9; 154 | 155 | x |= cx; 156 | z |= cz; 157 | 158 | byte light = 15; 159 | for (byte i = 1; i < 15; i++) { 160 | if (y + i > 31) break; 161 | int index2 = index + (i << 4); 162 | if (CLOUD_DATA[index2] != EMPTY_CLOUD) light--; 163 | } 164 | 165 | if (light > 0) { 166 | light = (byte) (light - NOISE.sample(x * 0.3, y * 0.3, z * 0.3)); 167 | } 168 | 169 | CLOUD_DATA[index] |= light; 170 | }); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/client/rendering/CloudChunk.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.client.rendering; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.client.render.Tessellator; 6 | import net.modificationstation.stationapi.api.util.math.MathHelper; 7 | import net.modificationstation.stationapi.api.util.math.Vec3f; 8 | import org.lwjgl.opengl.GL11; 9 | import paulevs.betterweather.config.ClientConfig; 10 | 11 | import java.util.Random; 12 | 13 | @Environment(EnvType.CLIENT) 14 | public class CloudChunk { 15 | private static final float[] RAIN_COLOR = new float[] { 66F / 255F, 74F / 255F, 74F / 255F }; 16 | private static final float[] DARK_COLOR = new float[] { 150F / 255F, 176F / 255F, 211F / 255F }; 17 | private static final Random RANDOM = new Random(0); 18 | private static final Vec3f POS = new Vec3f(); 19 | 20 | private final int listID; 21 | 22 | private boolean needUpdate = true; 23 | private boolean isEmpty = true; 24 | private int chunkX = Integer.MIN_VALUE; 25 | private int chunkZ = Integer.MIN_VALUE; 26 | private int posX; 27 | private int posZ; 28 | 29 | public CloudChunk() { 30 | listID = GL11.glGenLists(1); 31 | } 32 | 33 | public void checkIfNeedUpdate(int x, int z) { 34 | needUpdate = chunkX != x || chunkZ != z; 35 | } 36 | 37 | public void forceUpdate() { 38 | chunkX = Integer.MIN_VALUE; 39 | chunkZ = Integer.MIN_VALUE; 40 | isEmpty = true; 41 | } 42 | 43 | public boolean needUpdate() { 44 | return needUpdate; 45 | } 46 | 47 | public void setRenderPosition(int chunkX, int chunkZ) { 48 | this.posX = chunkX << 5; 49 | this.posZ = chunkZ << 5; 50 | } 51 | 52 | public void update(int chunkX, int chunkZ, short[] data) { 53 | this.chunkX = chunkX; 54 | this.chunkZ = chunkZ; 55 | isEmpty = true; 56 | 57 | Tessellator tessellator = Tessellator.INSTANCE; 58 | GL11.glNewList(listID, GL11.GL_COMPILE); 59 | tessellator.start(); 60 | 61 | for (short i = 0; i < 8192; i++) { 62 | if (data[i] == CloudRenderer.EMPTY_CLOUD) continue; 63 | 64 | byte x = (byte) (i & 15); 65 | byte y = (byte) ((i >> 4) & 31); 66 | byte z = (byte) (i >> 9); 67 | 68 | boolean canDraw = x == 0 || x == 15 || y == 0 || y == 31 || z == 0 || z == 15; 69 | if (!canDraw) { 70 | canDraw = data[i + 1] == CloudRenderer.EMPTY_CLOUD || data[i - 1] == CloudRenderer.EMPTY_CLOUD || 71 | data[i + 16] == CloudRenderer.EMPTY_CLOUD || data[i - 16] == CloudRenderer.EMPTY_CLOUD || 72 | data[i + 512] == CloudRenderer.EMPTY_CLOUD || data[i - 512] == CloudRenderer.EMPTY_CLOUD; 73 | } 74 | 75 | if (!canDraw) continue; 76 | 77 | RANDOM.setSeed(MathHelper.hashCode(x, y, z)); 78 | float deltaBrightness = ((data[i] & 15) + RANDOM.nextFloat()) / 15F; 79 | float deltaWetness = (((data[i] >> 4) & 15) + RANDOM.nextFloat()) / 15F; 80 | float deltaThunder = ((data[i] >> 8) & 15) / 15F; 81 | deltaBrightness *= (1 - deltaWetness) * 0.5F + 0.5F; 82 | deltaThunder = MathHelper.lerp(deltaThunder, 1F, 0.5F); 83 | 84 | float r = MathHelper.lerp(deltaWetness, RAIN_COLOR[0], DARK_COLOR[0]); 85 | float g = MathHelper.lerp(deltaWetness, RAIN_COLOR[1], DARK_COLOR[1]); 86 | float b = MathHelper.lerp(deltaWetness, RAIN_COLOR[2], DARK_COLOR[2]); 87 | r = MathHelper.lerp(deltaBrightness, r, 1F) * deltaThunder; 88 | g = MathHelper.lerp(deltaBrightness, g, 1F) * deltaThunder; 89 | b = MathHelper.lerp(deltaBrightness, b, 1F) * deltaThunder; 90 | 91 | tessellator.color(r, g, b); 92 | if (ClientConfig.renderFluffy()) { 93 | makeFluffyCloudBlock(tessellator, x, y, z); 94 | } 95 | else { 96 | makeNormalCloudBlock(tessellator, x, y, z, data, i); 97 | } 98 | isEmpty = false; 99 | } 100 | 101 | tessellator.render(); 102 | GL11.glEndList(); 103 | } 104 | 105 | public void render(double entityX, double entityZ, float height, FrustumCulling culling, float distanceSqr) { 106 | if (isEmpty) return; 107 | float dx = (float) (posX - entityX); 108 | float dz = (float) (posZ - entityZ); 109 | POS.set(dx + 16, height + 16, dz + 16); 110 | if (POS.getX() * POS.getX() + POS.getZ() * POS.getZ() > distanceSqr) return; 111 | if (culling.isOutside(POS, 24)) return; 112 | GL11.glPushMatrix(); 113 | GL11.glTranslatef(dx, height, dz); 114 | GL11.glScalef(2, 2, 2); 115 | GL11.glCallList(listID); 116 | GL11.glPopMatrix(); 117 | } 118 | 119 | private void makeFluffyCloudBlock(Tessellator tessellator, int x, int y, int z) { 120 | float px = x + RANDOM.nextFloat() * 0.1F - 0.05F; 121 | float py = y + RANDOM.nextFloat() * 0.1F - 0.05F; 122 | float pz = z + RANDOM.nextFloat() * 0.1F - 0.05F; 123 | 124 | tessellator.vertex(px - 0.207107F, py + 1.207107F, pz + 0.5F, 0.0F, 0.0F); 125 | tessellator.vertex(px + 1.207107F, py - 0.207107F, pz + 0.5F, 1.0F, 0.0F); 126 | tessellator.vertex(px + 1.207107F, py - 0.207107F, pz - 1.5F, 1.0F, 1.0F); 127 | tessellator.vertex(px - 0.207107F, py + 1.207107F, pz - 1.5F, 0.0F, 1.0F); 128 | 129 | tessellator.vertex(px + 1.207107F, py + 1.207107F, pz + 0.5F, 0.0F, 0.0F); 130 | tessellator.vertex(px - 0.207107F, py - 0.207107F, pz + 0.5F, 1.0F, 0.0F); 131 | tessellator.vertex(px - 0.207107F, py - 0.207107F, pz - 1.5F, 1.0F, 1.0F); 132 | tessellator.vertex(px + 1.207107F, py + 1.207107F, pz - 1.5F, 0.0F, 1.0F); 133 | 134 | tessellator.vertex(px + 1.5F, py + 1.207107F, pz + 0.207107F, 0.0F, 0.0F); 135 | tessellator.vertex(px + 1.5F, py - 0.207107F, pz - 1.207107F, 1.0F, 0.0F); 136 | tessellator.vertex(px - 0.5F, py - 0.207107F, pz - 1.207107F, 1.0F, 1.0F); 137 | tessellator.vertex(px - 0.5F, py + 1.207107F, pz + 0.207107F, 0.0F, 1.0F); 138 | 139 | tessellator.vertex(px + 1.5F, py + 1.207107F, pz - 1.207107, 0.0F, 0.0F); 140 | tessellator.vertex(px + 1.5F, py - 0.207107F, pz + 0.207107, 1.0F, 0.0F); 141 | tessellator.vertex(px - 0.5F, py - 0.207107F, pz + 0.207107, 1.0F, 1.0F); 142 | tessellator.vertex(px - 0.5F, py + 1.207107F, pz - 1.207107, 0.0F, 1.0F); 143 | 144 | tessellator.vertex(px + 1.207107F, py - 0.5F, pz + 0.207107, 0.0F, 0.0F); 145 | tessellator.vertex(px - 0.207107F, py - 0.5F, pz - 1.207107, 1.0F, 0.0F); 146 | tessellator.vertex(px - 0.207107F, py + 1.5F, pz - 1.207107, 1.0F, 1.0F); 147 | tessellator.vertex(px + 1.207107F, py + 1.5F, pz + 0.207107, 0.0F, 1.0F); 148 | 149 | tessellator.vertex(px + 1.207107F, py - 0.5F, pz - 1.207107F, 0.0F, 0.0F); 150 | tessellator.vertex(px - 0.207107F, py - 0.5F, pz + 0.207107F, 1.0F, 0.0F); 151 | tessellator.vertex(px - 0.207107F, py + 1.5F, pz + 0.207107F, 1.0F, 1.0F); 152 | tessellator.vertex(px + 1.207107F, py + 1.5F, pz - 1.207107F, 0.0F, 1.0F); 153 | } 154 | 155 | private void makeNormalCloudBlock(Tessellator tessellator, int x, int y, int z, short[] data, int index) { 156 | if (x == 0 || data[index - 1] == CloudRenderer.EMPTY_CLOUD) { 157 | tessellator.vertex(x, y, z, 0.5F, 0.5F); 158 | tessellator.vertex(x, y + 1, z, 0.5F, 0.5F); 159 | tessellator.vertex(x, y + 1, z + 1, 0.5F, 0.5F); 160 | tessellator.vertex(x, y, z + 1, 0.5F, 0.5F); 161 | } 162 | if (x == 15 || data[index + 1] == CloudRenderer.EMPTY_CLOUD) { 163 | tessellator.vertex(x + 1, y, z, 0.5F, 0.5F); 164 | tessellator.vertex(x + 1, y + 1, z, 0.5F, 0.5F); 165 | tessellator.vertex(x + 1, y + 1, z + 1, 0.5F, 0.5F); 166 | tessellator.vertex(x + 1, y, z + 1, 0.5F, 0.5F); 167 | } 168 | 169 | if (y == 0 || data[index - 16] == CloudRenderer.EMPTY_CLOUD) { 170 | tessellator.vertex(x, y, z, 0.5F, 0.5F); 171 | tessellator.vertex(x + 1, y, z, 0.5F, 0.5F); 172 | tessellator.vertex(x + 1, y, z + 1, 0.5F, 0.5F); 173 | tessellator.vertex(x, y, z + 1, 0.5F, 0.5F); 174 | } 175 | if (y == 31 || data[index + 16] == CloudRenderer.EMPTY_CLOUD) { 176 | tessellator.vertex(x, y + 1, z, 0.5F, 0.5F); 177 | tessellator.vertex(x + 1, y + 1, z, 0.5F, 0.5F); 178 | tessellator.vertex(x + 1, y + 1, z + 1, 0.5F, 0.5F); 179 | tessellator.vertex(x, y + 1, z + 1, 0.5F, 0.5F); 180 | } 181 | 182 | if (z == 0 || data[index - 512] == CloudRenderer.EMPTY_CLOUD) { 183 | tessellator.vertex(x, y, z, 0.5F, 0.5F); 184 | tessellator.vertex(x, y + 1, z, 0.5F, 0.5F); 185 | tessellator.vertex(x + 1, y + 1, z, 0.5F, 0.5F); 186 | tessellator.vertex(x + 1, y, z, 0.5F, 0.5F); 187 | } 188 | if (z == 15 || data[index + 512] == CloudRenderer.EMPTY_CLOUD) { 189 | tessellator.vertex(x, y, z + 1, 0.5F, 0.5F); 190 | tessellator.vertex(x, y + 1, z + 1, 0.5F, 0.5F); 191 | tessellator.vertex(x + 1, y + 1, z + 1, 0.5F, 0.5F); 192 | tessellator.vertex(x + 1, y, z + 1, 0.5F, 0.5F); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/api/WeatherAPI.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.api; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.loader.api.FabricLoader; 5 | import net.minecraft.level.Level; 6 | import net.minecraft.level.chunk.Chunk; 7 | import net.minecraft.level.dimension.Dimension; 8 | import net.minecraft.util.maths.MCMath; 9 | import net.minecraft.util.maths.Vec2I; 10 | import net.modificationstation.stationapi.api.block.BlockState; 11 | import net.modificationstation.stationapi.api.registry.DimensionContainer; 12 | import net.modificationstation.stationapi.api.registry.DimensionRegistry; 13 | import net.modificationstation.stationapi.api.registry.RegistryEntry; 14 | import net.modificationstation.stationapi.api.util.math.MathHelper; 15 | import paulevs.betterweather.config.CommonConfig; 16 | import paulevs.betterweather.util.ImageSampler; 17 | import paulevs.betterweather.util.WeatherTags; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.Optional; 22 | 23 | public class WeatherAPI { 24 | private static final ImageSampler MAIN_SHAPE_SAMPLER = new ImageSampler("data/better_weather/clouds/main_shape.png"); 25 | private static final ImageSampler LARGE_DETAILS_SAMPLER = new ImageSampler("data/better_weather/clouds/large_details.png"); 26 | private static final ImageSampler VARIATION_SAMPLER = new ImageSampler("data/better_weather/clouds/variation.png"); 27 | private static final ImageSampler FRONTS_SAMPLER = new ImageSampler("data/better_weather/clouds/rain_fronts.png"); 28 | private static final ImageSampler RAIN_DENSITY = new ImageSampler("data/better_weather/clouds/rain_density.png"); 29 | private static final ImageSampler VANILLA_CLOUDS = new ImageSampler("data/better_weather/clouds/vanilla_clouds.png").setSmooth(true); 30 | private static final ImageSampler THUNDERSTORMS = new ImageSampler("data/better_weather/clouds/thunderstorms.png"); 31 | private static final float[] CLOUD_SHAPE = new float[64]; 32 | private static final Vec2I[] OFFSETS; 33 | 34 | public static boolean isRaining(Level level, int x, int y, int z) { 35 | if (level.dimension.evaporatesWater) return false; 36 | 37 | RegistryEntry> dimension = getDimension(level); 38 | if (dimension != null && dimension.isIn(WeatherTags.NO_RAIN)) return false; 39 | 40 | if (y > getCloudHeight(level.dimension) + 8) return false; 41 | if (y < getRainHeight(level, x, z)) return false; 42 | 43 | z = (int) (z - level.getLevelTime() * CommonConfig.getCloudsSpeed() * 32); 44 | if (CommonConfig.isEternalRain() || (dimension != null && dimension.isIn(WeatherTags.ETERNAL_RAIN))) { 45 | return !CommonConfig.useVanillaClouds() || getCloudDensity(x, 2, z, 1F) > 0.5F; 46 | } 47 | 48 | float rainFront = sampleFront(level, x, z, 0.1); 49 | if (rainFront < 0.2F) return false; 50 | 51 | float coverage = getCoverage(rainFront); 52 | int sampleHeight = CommonConfig.useVanillaClouds() ? 2 : 7; 53 | return getCloudDensity(x, sampleHeight, z, rainFront) > coverage; 54 | } 55 | 56 | public static boolean isThundering(Level level, int x, int y, int z) { 57 | return isRaining(level, x, y, z) && sampleThunderstorm(level, x, z, 0.05) > 0.3F; 58 | } 59 | 60 | public static float inCloud(Level level, double x, double y, double z) { 61 | z -= level.getLevelTime() * CommonConfig.getCloudsSpeed() * 32; 62 | int x1 = MCMath.floor(x / 2.0) << 1; 63 | int y1 = MCMath.floor(y / 2.0) << 1; 64 | int z1 = MCMath.floor(z / 2.0) << 1; 65 | 66 | int x2 = x1 + 2; 67 | int y2 = y1 + 2; 68 | int z2 = z1 + 2; 69 | 70 | float dx = (float) (x - x1) / 2F; 71 | float dy = (float) (y - y1) / 2F; 72 | float dz = (float) (z - z1) / 2F; 73 | 74 | float a = isInCloud(level, x1, y1, z1) ? 1F : 0F; 75 | float b = isInCloud(level, x2, y1, z1) ? 1F : 0F; 76 | float c = isInCloud(level, x1, y2, z1) ? 1F : 0F; 77 | float d = isInCloud(level, x2, y2, z1) ? 1F : 0F; 78 | float e = isInCloud(level, x1, y1, z2) ? 1F : 0F; 79 | float f = isInCloud(level, x2, y1, z2) ? 1F : 0F; 80 | float g = isInCloud(level, x1, y2, z2) ? 1F : 0F; 81 | float h = isInCloud(level, x2, y2, z2) ? 1F : 0F; 82 | 83 | return MathHelper.interpolate3D(dx, dy, dz, a, b, c, d, e, f, g, h); 84 | } 85 | 86 | private static boolean isInCloud(Level level, int x, int y, int z) { 87 | if (level.dimension.evaporatesWater) return false; 88 | int start = (int) getCloudHeight(level.dimension); 89 | if (y < start || y > start + 64) return false; 90 | float rainFront = sampleFront(level, x, z, 0.1); 91 | float coverage = getCoverage(rainFront); 92 | return getCloudDensity(x, y - start, z, rainFront) > coverage; 93 | } 94 | 95 | public static float getCloudDensity(int x, int y, int z, float rainFront) { 96 | if (CommonConfig.useVanillaClouds()) { 97 | if (y > 6) return 0; 98 | float shape = y == 0 || y == 5 ? 1 : 0; 99 | return VANILLA_CLOUDS.sample(x / 16.0, z / 16.0) * 3 - shape; 100 | } 101 | 102 | float density = MAIN_SHAPE_SAMPLER.sample(x * 0.75F, z * 0.75F); 103 | density += LARGE_DETAILS_SAMPLER.sample(x * 2.5F, z * 2.5F); 104 | 105 | density -= VARIATION_SAMPLER.sample(y * 2.5F, x * 2.5F) * 0.05F; 106 | density -= VARIATION_SAMPLER.sample(z * 2.5F, y * 2.5F) * 0.05F; 107 | density -= VARIATION_SAMPLER.sample(z * 2.5F, x * 2.5F) * 0.05F; 108 | 109 | int value = (int) (MathHelper.hashCode(x, y, z) % 3); 110 | density -= value * 0.01F; 111 | 112 | float density1 = density - CLOUD_SHAPE[MathHelper.clamp(y << 1, 0, 63)]; 113 | float density2 = density + MAIN_SHAPE_SAMPLER.sample(x * 1.5F, z * 1.5F) - CLOUD_SHAPE[MathHelper.clamp(y, 0, 63)] * 3F; 114 | 115 | return MathHelper.lerp(rainFront, density1, density2); 116 | } 117 | 118 | public static float sampleFront(Level level, int x, int z, double scale) { 119 | if (CommonConfig.isEternalRain()) return 1F; 120 | 121 | RegistryEntry> dimension = getDimension(level); 122 | if (dimension != null) { 123 | if (dimension.isIn(WeatherTags.NO_RAIN)) return 0F; 124 | if (dimension.isIn(WeatherTags.ETERNAL_RAIN)) return 1F; 125 | } 126 | 127 | float front = FRONTS_SAMPLER.sample(x * scale, z * scale); 128 | if (!CommonConfig.isFrequentRain()) { 129 | scale *= 0.7; 130 | front *= RAIN_DENSITY.sample(x * scale, z * scale); 131 | } 132 | return front; 133 | } 134 | 135 | public static float sampleThunderstorm(Level level, int x, int z, double scale) { 136 | if (CommonConfig.isEternalThunder()) return 1F; 137 | 138 | RegistryEntry> dimension = getDimension(level); 139 | if (dimension != null) { 140 | if (dimension.isIn(WeatherTags.NO_THUNDER)) return 0F; 141 | if (dimension.isIn(WeatherTags.ETERNAL_THUNDER)) return 1F; 142 | } 143 | 144 | return THUNDERSTORMS.sample(x * scale, z * scale); 145 | } 146 | 147 | public static float getCoverage(float rainFront) { 148 | return MathHelper.lerp(rainFront, 1.3F, 0.5F); 149 | } 150 | 151 | public static int getRainHeight(Level level, int x, int z) { 152 | int max = (int) (getCloudHeight(level.dimension) + 4); 153 | int height = level.getHeight(x, z); 154 | if (height >= max) return max; 155 | Chunk chunk = level.getChunkFromCache(x >> 4, z >> 4); 156 | x &= 15; 157 | z &= 15; 158 | for (int y = max; y > height; y--) { 159 | BlockState state = chunk.getBlockState(x, y, z); 160 | if (state.isAir()) continue; 161 | if (state.isOpaque() || state.getBlock().isFullCube() || state.getMaterial().isLiquid()) return y + 1; 162 | } 163 | return height; 164 | } 165 | 166 | public static float getRainDensity(Level level, double x, double y, double z, boolean includeSnow) { 167 | int x1 = MCMath.floor(x); 168 | int y1 = MCMath.floor(y); 169 | int z1 = MCMath.floor(z); 170 | int x2 = x1 + 1; 171 | int z2 = z1 + 1; 172 | 173 | float dx = (float) (x - x1); 174 | float dz = (float) (z - z1); 175 | dz -= (float) ((level.getLevelTime() * CommonConfig.getCloudsSpeed() * 32) % 1.0); 176 | 177 | float a = getRainDensity(level, x1, y1, z1, includeSnow); 178 | float b = getRainDensity(level, x2, y1, z1, includeSnow); 179 | float c = getRainDensity(level, x1, y1, z2, includeSnow); 180 | float d = getRainDensity(level, x2, y1, z2, includeSnow); 181 | 182 | float value = MathHelper.interpolate2D(dx, dz, a, b, c, d); 183 | return MathHelper.clamp(value, 0F, 1F); 184 | } 185 | 186 | private static float getRainDensity(Level level, int x, int y, int z, boolean includeSnow) { 187 | if (level.dimension.evaporatesWater) return 0; 188 | 189 | int count = 0; 190 | for (Vec2I offset : OFFSETS) { 191 | boolean snowCheck = includeSnow || !level.getBiomeSource().getBiome(x, z).canSnow(); 192 | if (snowCheck && isRaining(level, x + offset.x, y, z + offset.z)) { 193 | count++; 194 | if (count >= 64) return 1F; 195 | } 196 | } 197 | 198 | return count / 64F; 199 | } 200 | 201 | static { 202 | for (byte i = 0; i < 16; i++) { 203 | CLOUD_SHAPE[i] = (16 - i) / 16F; 204 | CLOUD_SHAPE[i] *= CLOUD_SHAPE[i]; 205 | } 206 | for (byte i = 16; i < 64; i++) { 207 | CLOUD_SHAPE[i] = (i - 16) / 48F; 208 | CLOUD_SHAPE[i] *= CLOUD_SHAPE[i]; 209 | } 210 | 211 | int radius = 6; 212 | int capacity = radius * 2 + 1; 213 | capacity *= capacity; 214 | 215 | List offsets = new ArrayList<>(capacity); 216 | for (int x = -radius; x <= radius; x++) { 217 | for (int z = -radius; z <= radius; z++) { 218 | if (x * x + z * z <= radius * radius) { 219 | offsets.add(new Vec2I(x, z)); 220 | } 221 | } 222 | } 223 | offsets.sort((v1, v2) -> { 224 | int d1 = v1.x * v1.x + v1.z * v1.z; 225 | int d2 = v2.x * v2.x + v2.z * v2.z; 226 | return Integer.compare(d1, d2); 227 | }); 228 | OFFSETS = offsets.toArray(Vec2I[]::new); 229 | } 230 | 231 | private static RegistryEntry> getDimension(Level level) { 232 | Optional> optional = DimensionRegistry.INSTANCE.getByLegacyId(level.dimension.id); 233 | return optional.map(DimensionRegistry.INSTANCE::getEntry).orElse(null); 234 | } 235 | 236 | private static float getCloudHeight(Dimension dimension) { 237 | if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { 238 | return dimension.getCloudHeight(); 239 | } 240 | return 108F; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/main/java/paulevs/betterweather/client/rendering/WeatherRenderer.java: -------------------------------------------------------------------------------- 1 | package paulevs.betterweather.client.rendering; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.block.Block; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.render.Tessellator; 8 | import net.minecraft.client.texture.TextureManager; 9 | import net.minecraft.entity.living.LivingEntity; 10 | import net.minecraft.level.Level; 11 | import net.minecraft.util.maths.MCMath; 12 | import net.minecraft.util.maths.Vec3D; 13 | import org.lwjgl.opengl.GL11; 14 | import paulevs.betterweather.api.WeatherAPI; 15 | import paulevs.betterweather.config.CommonConfig; 16 | 17 | import java.util.Random; 18 | 19 | @Environment(EnvType.CLIENT) 20 | public class WeatherRenderer { 21 | private static final float TO_RADIANS = (float) (Math.PI / 180); 22 | private final float[] randomOffset; 23 | private final byte[] randomIndex; 24 | private int rainTexture; 25 | private int snowTexture; 26 | private int waterCircles; 27 | 28 | public WeatherRenderer() { 29 | randomOffset = new float[256]; 30 | randomIndex = new byte[256]; 31 | Random random = new Random(0); 32 | for (short i = 0; i < 256; i++) { 33 | randomOffset[i] = random.nextFloat(); 34 | randomIndex[i] = (byte) random.nextInt(4); 35 | } 36 | } 37 | 38 | public void update(TextureManager manager) { 39 | this.rainTexture = manager.getTextureId("/environment/rain.png"); 40 | this.snowTexture = manager.getTextureId("/environment/snow.png"); 41 | this.waterCircles = manager.getTextureId("/assets/better_weather/textures/water_circles.png"); 42 | } 43 | 44 | public void render(float delta, Minecraft minecraft) { 45 | LivingEntity entity = minecraft.viewEntity; 46 | double x = net.modificationstation.stationapi.api.util.math.MathHelper.lerp(delta, entity.prevRenderX, entity.x); 47 | double y = net.modificationstation.stationapi.api.util.math.MathHelper.lerp(delta, entity.prevRenderY, entity.y); 48 | double z = net.modificationstation.stationapi.api.util.math.MathHelper.lerp(delta, entity.prevRenderZ, entity.z); 49 | 50 | int ix = MCMath.floor(entity.x); 51 | int iy = MCMath.floor(entity.y); 52 | int iz = MCMath.floor(entity.z); 53 | 54 | int radius = minecraft.options.fancyGraphics ? 10 : 5; 55 | int radiusCenter = radius / 2 - 1; 56 | float sampleHeight = CommonConfig.useVanillaClouds() ? 2.5F : 8.5F; 57 | Level level = minecraft.level; 58 | int rainTop = (int) (level.dimension.getCloudHeight() + sampleHeight); 59 | 60 | if (iy - rainTop > 40) return; 61 | 62 | float vOffset = (float) (((double) level.getLevelTime() + delta) * 0.05 % 1.0); 63 | Vec3D pos = getPosition(entity); 64 | Vec3D dir = getViewDirection(entity); 65 | 66 | Tessellator tessellator = Tessellator.INSTANCE; 67 | 68 | GL11.glDisable(GL11.GL_CULL_FACE); 69 | GL11.glNormal3f(0.0F, 1.0F, 0.0F); 70 | GL11.glEnable(GL11.GL_BLEND); 71 | GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); 72 | GL11.glAlphaFunc(GL11.GL_GREATER, 0.01F); 73 | GL11.glColor4f(1F, 1F, 1F, 1F); 74 | 75 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, rainTexture); 76 | 77 | tessellator.start(); 78 | tessellator.setOffset(-x, -y, -z); 79 | 80 | for (byte dx = (byte) -radius; dx <= radius; dx++) { 81 | int wx = (ix & -4) + (dx << 2); 82 | for (byte dz = (byte) -radius; dz <= radius; dz++) { 83 | if (Math.abs(dx) < radiusCenter && Math.abs(dz) < radiusCenter) continue; 84 | int wz = (iz & -4) + (dz << 2); 85 | renderLargeSection(level, wx, iy, wz, pos, dir, rainTop, tessellator, vOffset, false); 86 | } 87 | } 88 | 89 | for (byte dx = (byte) -radius; dx <= radius; dx++) { 90 | int wx = ix + dx; 91 | for (byte dz = (byte) -radius; dz <= radius; dz++) { 92 | int wz = iz + dz; 93 | renderNormalSection(level, wx, iy, wz, pos, dir, rainTop, tessellator, vOffset, false); 94 | } 95 | } 96 | 97 | tessellator.render(); 98 | 99 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, waterCircles); 100 | vOffset = (float) (((double) level.getLevelTime() + delta) * 0.07 % 1.0); 101 | 102 | tessellator.start(); 103 | 104 | for (byte dx = (byte) -radius; dx <= radius; dx++) { 105 | int wx = ix + dx; 106 | for (byte dz = (byte) -radius; dz <= radius; dz++) { 107 | int wz = iz + dz; 108 | renderWaterCircles(level, wx, iy, wz, pos, dir, tessellator, vOffset, radius); 109 | } 110 | } 111 | 112 | tessellator.render(); 113 | 114 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, snowTexture); 115 | vOffset = (float) (((double) level.getLevelTime() + delta) * 0.002 % 1.0); 116 | 117 | tessellator.start(); 118 | 119 | for (byte dx = (byte) -radius; dx <= radius; dx++) { 120 | int wx = (ix & -4) + (dx << 2); 121 | for (byte dz = (byte) -radius; dz <= radius; dz++) { 122 | if (Math.abs(dx) < radiusCenter && Math.abs(dz) < radiusCenter) continue; 123 | int wz = (iz & -4) + (dz << 2); 124 | renderLargeSection(level, wx, iy, wz, pos, dir, rainTop, tessellator, vOffset, true); 125 | } 126 | } 127 | 128 | for (byte dx = (byte) -radius; dx <= radius; dx++) { 129 | int wx = ix + dx; 130 | for (byte dz = (byte) -radius; dz <= radius; dz++) { 131 | int wz = iz + dz; 132 | renderNormalSection(level, wx, iy, wz, pos, dir, rainTop, tessellator, vOffset, true); 133 | } 134 | } 135 | 136 | tessellator.setOffset(0.0, 0.0, 0.0); 137 | tessellator.render(); 138 | 139 | GL11.glEnable(GL11.GL_CULL_FACE); 140 | GL11.glDisable(GL11.GL_BLEND); 141 | GL11.glAlphaFunc(GL11.GL_GREATER, 0.1f); 142 | } 143 | 144 | private void renderLargeSection(Level level, int x, int y, int z, Vec3D pos, Vec3D dir, int rainTop, Tessellator tessellator, float vOffset, boolean snow) { 145 | int terrain = WeatherAPI.getRainHeight(level, x, z); 146 | if (terrain - y > 40) return; 147 | 148 | boolean visible = pointIsVisible(pos, dir, x + 0.5, terrain, z + 0.5); 149 | visible |= pointIsVisible(pos, dir, x + 0.5, y, z + 0.5); 150 | visible |= pointIsVisible(pos, dir, x + 0.5, rainTop, z + 0.5); 151 | if (!visible) return; 152 | 153 | if (!WeatherAPI.isRaining(level, x, terrain, z)) return; 154 | if (level.getBiomeSource().getBiome(x, z).canSnow() != snow) return; 155 | 156 | float v1 = randomOffset[(x & 15) << 4 | (z & 15)] + vOffset; 157 | float v2 = ((rainTop - terrain) * 0.0625F + v1); 158 | 159 | float light = level.getBrightness(x, terrain, z); 160 | float alpha = WeatherAPI.sampleFront(level, x, z, 0.1F); 161 | alpha = net.modificationstation.stationapi.api.util.math.MathHelper.clamp((alpha - 0.2F) * 2, 0.5F, 1F); 162 | tessellator.color(light, light, light, alpha); 163 | 164 | float u1 = ((x + z) & 3) * 0.25F; 165 | float u2 = u1 + 0.25F; 166 | 167 | float dx = (float) (pos.x - (x + 0.5)); 168 | float dz = (float) (pos.z - (z + 0.5)); 169 | float l = dx * dx + dz * dz; 170 | if (l > 0) { 171 | l = MCMath.sqrt(l) / 0.5F; 172 | dx /= l; 173 | dz /= l; 174 | float v = dx; 175 | dx = -dz; 176 | dz = v; 177 | } 178 | else { 179 | dx = 0.5F; 180 | dz = 0; 181 | } 182 | 183 | double x1 = x + 0.5 + dx; 184 | double x2 = x + 0.5 - dx; 185 | double z1 = z + 0.5 + dz; 186 | double z2 = z + 0.5 - dz; 187 | 188 | tessellator.vertex(x1, terrain, z1, u1, v1); 189 | tessellator.vertex(x1, rainTop, z1, u1, v2); 190 | tessellator.vertex(x2, rainTop, z2, u2, v2); 191 | tessellator.vertex(x2, terrain, z2, u2, v1); 192 | } 193 | 194 | private void renderNormalSection(Level level, int x, int y, int z, Vec3D pos, Vec3D dir, int rainTop, Tessellator tessellator, float vOffset, boolean snow) { 195 | int terrain = WeatherAPI.getRainHeight(level, x, z); 196 | if (terrain - y > 40) return; 197 | 198 | boolean visible = pointIsVisible(pos, dir, x + 0.5, terrain, z + 0.5); 199 | visible |= pointIsVisible(pos, dir, x + 0.5, y, z + 0.5); 200 | visible |= pointIsVisible(pos, dir, x + 0.5, rainTop, z + 0.5); 201 | if (!visible) return; 202 | 203 | if (!WeatherAPI.isRaining(level, x, terrain, z)) return; 204 | if (level.getBiomeSource().getBiome(x, z).canSnow() != snow) return; 205 | 206 | float v1 = randomOffset[(x & 15) << 4 | (z & 15)] + vOffset; 207 | float v2 = (rainTop - terrain) * 0.0625F + v1; 208 | 209 | float light = level.getBrightness(x, terrain, z); 210 | float alpha = WeatherAPI.sampleFront(level, x, z, 0.1F); 211 | alpha = net.modificationstation.stationapi.api.util.math.MathHelper.clamp((alpha - 0.2F) * 2, 0.5F, 1F); 212 | tessellator.color(light, light, light, alpha); 213 | 214 | float u1 = ((x + z) & 3) * 0.25F; 215 | float u2 = u1 + 0.25F; 216 | 217 | float dx = (float) (pos.x - (x + 0.5)); 218 | float dz = (float) (pos.z - (z + 0.5)); 219 | float l = dx * dx + dz * dz; 220 | if (l > 0) { 221 | l = MCMath.sqrt(l) / 0.5F; 222 | dx /= l; 223 | dz /= l; 224 | float v = dx; 225 | dx = -dz; 226 | dz = v; 227 | } 228 | else { 229 | dx = 0.5F; 230 | dz = 0; 231 | } 232 | 233 | double x1 = x + 0.5 + dx; 234 | double x2 = x + 0.5 - dx; 235 | double z1 = z + 0.5 + dz; 236 | double z2 = z + 0.5 - dz; 237 | 238 | tessellator.vertex(x1, terrain, z1, u1, v1); 239 | tessellator.vertex(x1, rainTop, z1, u1, v2); 240 | tessellator.vertex(x2, rainTop, z2, u2, v2); 241 | tessellator.vertex(x2, terrain, z2, u2, v1); 242 | } 243 | 244 | private void renderWaterCircles(Level level, int x, int y, int z, Vec3D pos, Vec3D dir, Tessellator tessellator, float vOffset, float radius) { 245 | int height = level.getHeight(x, z); 246 | if (height - y > 40 || y - height > 40) return; 247 | if (!pointIsVisible(pos, dir, x + 0.5, height, z + 0.5)) return; 248 | if (!level.getBlockState(x, height - 1, z).isOf(Block.STILL_WATER)) return; 249 | if (!WeatherAPI.isRaining(level, x, height, z)) return; 250 | 251 | float dx = (float) (x - pos.x); 252 | float dy = (float) (y - pos.y); 253 | float dz = (float) (z - pos.z); 254 | float alpha = 1F - MCMath.sqrt(dx * dx + dy * dy + dz * dz) / radius; 255 | alpha = alpha * 4F; 256 | if (alpha <= 0.01F) return; 257 | if (alpha > 1F) alpha = 1F; 258 | 259 | float light = level.getBrightness(x, height, z); 260 | 261 | float u1 = 0; 262 | float u2 = 1; 263 | vOffset += randomOffset[(x & 15) << 4 | (z & 15)]; 264 | float v1 = MCMath.floor(vOffset * 6F) / 6F; 265 | float v2 = v1 + 1F / 6F; 266 | 267 | byte index = randomIndex[(x & 15) << 4 | (z & 15)]; 268 | if ((index & 1) == 0) { 269 | u2 = 0; 270 | u1 = 1; 271 | } 272 | if (index > 1) { 273 | float value = v1; 274 | v1 = v2; 275 | v2 = value; 276 | } 277 | 278 | tessellator.color(light, light, light, alpha); 279 | tessellator.vertex(x, height, z, u1, v1); 280 | tessellator.vertex(x, height, z + 1, u1, v2); 281 | tessellator.vertex(x + 1, height, z + 1, u2, v2); 282 | tessellator.vertex(x + 1, height, z, u2, v1); 283 | } 284 | 285 | private Vec3D getPosition(LivingEntity entity) { 286 | return Vec3D.getFromCacheAndSet(entity.x, entity.y, entity.z); 287 | } 288 | 289 | private Vec3D getViewDirection(LivingEntity entity) { 290 | float yaw = entity.prevYaw + (entity.yaw - entity.prevYaw); 291 | float pitch = entity.prevPitch + (entity.pitch - entity.prevPitch); 292 | 293 | yaw = -yaw * TO_RADIANS - (float) Math.PI; 294 | float cosYaw = MCMath.cos(yaw); 295 | float sinYaw = MCMath.sin(yaw); 296 | float cosPitch = -MCMath.cos(-pitch * TO_RADIANS); 297 | 298 | return Vec3D.getFromCacheAndSet( 299 | sinYaw * cosPitch, 300 | (MCMath.sin(-pitch * ((float) Math.PI / 180))), 301 | cosYaw * cosPitch 302 | ); 303 | } 304 | 305 | private boolean pointIsVisible(Vec3D position, Vec3D normal, double x, double y, double z) { 306 | return normal.x * (x - position.x) + normal.y * (y - position.y) + normal.z * (z - position.z) > 0; 307 | } 308 | } 309 | --------------------------------------------------------------------------------