From 4ba31e106acd820d2728ae657d89c8b72b040b48 Mon Sep 17 00:00:00 2001 From: RealDarkStudios <94769968+RealDarkStudios@users.noreply.github.com> Date: Sat, 19 Jul 2025 21:41:42 -0500 Subject: [PATCH 1/3] 0.15.0.0+rc1 - Update to PMWeather 0.15 - Added ColorMap#getWithBiome - Added ColorMaps#IR - ID Overlay now shows all enabled overlays - Added StormType filtering to NearbyStorms - PixelRenderData now has a Vec3 worldPos parameter - RenderData now has a sizeRenderDiameter property - Added RadarMode#IR - Added new parameters smoothWidth, cycloneWindspeed, and smoothWindspeed to StormBuilder - Added StormType util enum Took 1 hour 55 minutes --- gradle.properties | 4 +- .../pmweatherapi/client/render/IDOverlay.java | 27 +- .../client/render/PixelRenderData.java | 5 +- .../client/render/RenderData.java | 3 +- .../pmweatherapi/config/PMWClientConfig.java | 4 +- .../mixin/RadarRendererMixin.java | 304 +++++++++++++----- .../nullved/pmweatherapi/radar/RadarMode.java | 29 +- .../pmweatherapi/storm/NearbyStorms.java | 100 ++++++ .../pmweatherapi/storm/StormBuilder.java | 84 +++-- .../nullved/pmweatherapi/util/ColorMap.java | 32 +- .../nullved/pmweatherapi/util/ColorMaps.java | 20 +- .../nullved/pmweatherapi/util/StormType.java | 34 ++ 12 files changed, 520 insertions(+), 126 deletions(-) create mode 100644 src/main/java/net/nullved/pmweatherapi/util/StormType.java diff --git a/gradle.properties b/gradle.properties index 99e15c3..c8b93fc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,10 +17,10 @@ parchment_mappings_version=2024.11.17 mod_id=pmweatherapi mod_name=PMWeatherAPI mod_license=GNU GPL 3.0 -mod_version=0.14.16.2 +mod_version=0.15.0.0+rc1 mod_group_id=net.nullved mod_authors=NullVed mod_description=An API for interfacing with ProtoManly's Weather Mod # Dependencies -pmweather_version=0.14.16 \ No newline at end of file +pmweather_version=0.15.0 \ No newline at end of file diff --git a/src/main/java/net/nullved/pmweatherapi/client/render/IDOverlay.java b/src/main/java/net/nullved/pmweatherapi/client/render/IDOverlay.java index 9a0203b..4cd4a29 100644 --- a/src/main/java/net/nullved/pmweatherapi/client/render/IDOverlay.java +++ b/src/main/java/net/nullved/pmweatherapi/client/render/IDOverlay.java @@ -3,6 +3,7 @@ import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Axis; +import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; @@ -10,6 +11,8 @@ import net.nullved.pmweatherapi.config.PMWClientConfig; import net.nullved.pmweatherapi.radar.RadarMode; +import java.util.function.Supplier; + /** * The overlay for {@link RadarMode} IDs. *
@@ -23,16 +26,36 @@ public class IDOverlay implements IRadarOverlay { @Override public void render(boolean canRender, RenderData renderData, BufferBuilder bufferBuilder, Object... args) { if (!PMWClientConfig.showRadarModeId) return; + if (!Minecraft.getInstance().player.isCrouching()) return; + RadarMode mode = getRadarMode(renderData); PoseStack poseStack = renderData.poseStack(); PMWClientConfig.RadarModeIDSide side = PMWClientConfig.radarModeIDSide; - poseStack.translate(side.x, 1.055f, side.z); - poseStack.mulPose(Axis.ZP.rotationDegrees(side.rotation)); + float scale = renderData.sizeRenderDiameter() / 3.0F; + + poseStack.pushPose(); + poseStack.translate((side.x * scale) - 0.5F * (scale - 1), 1.055f, (side.z * scale) - 0.5F * (scale - 1)); + poseStack.mulPose(Axis.YN.rotationDegrees(side.rotation)); poseStack.scale(0.01f, 0.01f, 0.01f); renderText(Component.literal(mode.getId().toString()), renderData, poseStack); + float lineHeight = 8.0f; + float offset = lineHeight; + for (Supplier overlay: RadarOverlays.getOverlays()) { + poseStack.pushPose(); + poseStack.translate(0, 0, offset); + poseStack.scale(0.6f, 0.6f, 0.6f); + + renderText(Component.literal(overlay.get().getID().toString()).withColor(0x888888), renderData, poseStack); + + poseStack.popPose(); + offset += lineHeight * 0.6f; + } + + poseStack.popPose(); + // While I could reset the pose, it is not strictly necessary // poseStack.scale(100f, 100f, 100f); // poseStack.mulPose(Axis.ZP.rotationDegrees(-side.rotation)); diff --git a/src/main/java/net/nullved/pmweatherapi/client/render/PixelRenderData.java b/src/main/java/net/nullved/pmweatherapi/client/render/PixelRenderData.java index a99ff47..32dc116 100644 --- a/src/main/java/net/nullved/pmweatherapi/client/render/PixelRenderData.java +++ b/src/main/java/net/nullved/pmweatherapi/client/render/PixelRenderData.java @@ -1,5 +1,7 @@ package net.nullved.pmweatherapi.client.render; +import net.minecraft.world.phys.Vec3; + /** * Specific rendering data for a pixel on the radar * @param canRender {@code true} if either the server doesn't require WSR-88D or a WSR-88D is complete within 4 chunks of the radar @@ -9,8 +11,9 @@ * @param x The x-position of the pixel (from {@code -resolution} to {@code resolution}) * @param z The z-position of the pixel (from {@code -resolution} to {@code resolution}) * @param resolution The resolution of the radar + * @param worldPos The world position of the pixel * @param renderData The associated {@link RenderData} * @since 0.14.15.6 */ -public record PixelRenderData(boolean canRender, float rdbz, float velocity, float temp, int x, int z, int resolution, RenderData renderData) { +public record PixelRenderData(boolean canRender, float rdbz, float velocity, float temp, int x, int z, int resolution, Vec3 worldPos, RenderData renderData) { } diff --git a/src/main/java/net/nullved/pmweatherapi/client/render/RenderData.java b/src/main/java/net/nullved/pmweatherapi/client/render/RenderData.java index d86554a..2bd1616 100644 --- a/src/main/java/net/nullved/pmweatherapi/client/render/RenderData.java +++ b/src/main/java/net/nullved/pmweatherapi/client/render/RenderData.java @@ -7,6 +7,7 @@ /** * A wrapper class to be passed to {@link RenderData} * @param blockEntity The {@link BlockEntity} associated with the render call + * @param sizeRenderDiameter The size in blocks of the radar * @param partialTicks The time, in partial ticks, since last full tick * @param poseStack The {@link PoseStack} * @param multiBufferSource The {@link MultiBufferSource} @@ -14,5 +15,5 @@ * @param combinedOverlayIn The current overlay of the block entity * @since 0.14.15.2 */ -public record RenderData(BlockEntity blockEntity, float partialTicks, PoseStack poseStack, MultiBufferSource multiBufferSource, int combinedLightIn, int combinedOverlayIn) { +public record RenderData(BlockEntity blockEntity, float sizeRenderDiameter, float partialTicks, PoseStack poseStack, MultiBufferSource multiBufferSource, int combinedLightIn, int combinedOverlayIn) { } diff --git a/src/main/java/net/nullved/pmweatherapi/config/PMWClientConfig.java b/src/main/java/net/nullved/pmweatherapi/config/PMWClientConfig.java index a778d7b..252213a 100644 --- a/src/main/java/net/nullved/pmweatherapi/config/PMWClientConfig.java +++ b/src/main/java/net/nullved/pmweatherapi/config/PMWClientConfig.java @@ -41,9 +41,9 @@ public enum RadarModeIDSide { NORTH(0, -1, -1), EAST(90, 2, -1), SOUTH(180, 2, 2), - WEST(-90, -1, 2),; + WEST(-90, -1, 2); - public int rotation, x, z; + public final int rotation, x, z; RadarModeIDSide(int rotation, int x, int z) { this.rotation = rotation; diff --git a/src/main/java/net/nullved/pmweatherapi/mixin/RadarRendererMixin.java b/src/main/java/net/nullved/pmweatherapi/mixin/RadarRendererMixin.java index 6ca332c..28d4ff3 100644 --- a/src/main/java/net/nullved/pmweatherapi/mixin/RadarRendererMixin.java +++ b/src/main/java/net/nullved/pmweatherapi/mixin/RadarRendererMixin.java @@ -13,19 +13,19 @@ import dev.protomanly.pmweather.render.RadarRenderer; import dev.protomanly.pmweather.util.ColorTables; import dev.protomanly.pmweather.util.Util; -import dev.protomanly.pmweather.weather.Sounding; -import dev.protomanly.pmweather.weather.Storm; -import dev.protomanly.pmweather.weather.ThermodynamicEngine; -import dev.protomanly.pmweather.weather.WindEngine; +import dev.protomanly.pmweather.weather.*; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; import net.minecraft.util.Mth; import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; import net.neoforged.api.distmarker.Dist; @@ -37,41 +37,60 @@ import net.nullved.pmweatherapi.config.PMWClientConfig; import net.nullved.pmweatherapi.data.PMWExtras; import net.nullved.pmweatherapi.radar.RadarMode; +import net.nullved.pmweatherapi.util.StormType; import org.joml.Matrix4fStack; +import org.joml.Vector2f; import org.joml.Vector3f; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import java.awt.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import static dev.protomanly.pmweather.render.RadarRenderer.FBM; + @OnlyIn(Dist.CLIENT) @Mixin(RadarRenderer.class) public class RadarRendererMixin { + @Shadow + public static int RenderedRadars = 0; + @WrapMethod(method = "render") private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseStack, MultiBufferSource multiBufferSource, int combinedLightIn, int combinedOverlayIn, Operation original) { if (!(blockEntity instanceof RadarBlockEntity radarBlockEntity)) return; - if (Minecraft.getInstance().player.position().distanceTo(blockEntity.getBlockPos().getCenter()) > (double)25.0F) return; + if (Minecraft.getInstance().player.position().distanceTo(blockEntity.getBlockPos().getCenter()) > (double) 20.0F || RenderedRadars > 2) return; + ++RenderedRadars; boolean canRender = true; BlockPos pos = radarBlockEntity.getBlockPos(); float sizeRenderDiameter = 3.0F; float simSize = 2048.0F; + + if (radarBlockEntity.hasRangeUpgrade) { + simSize *= 4.0F; + if (!ClientConfig._3X3Radar) { + sizeRenderDiameter = 6.0F; + } + } + int resolution = ClientConfig.radarResolution; Matrix4fStack matrix4fStack = RenderSystem.getModelViewStack(); matrix4fStack.pushMatrix(); matrix4fStack.mul(poseStack.last().pose()); matrix4fStack.translate(0.5F, 1.05F, 0.5F); + RenderSystem.applyModelViewMatrix(); RenderSystem.enableBlend(); RenderSystem.depthMask(true); RenderSystem.enableDepthTest(); RenderSystem.setShader(GameRenderer::getPositionColorShader); RenderSystem.defaultBlendFunc(); + Tesselator tesselator = Tesselator.getInstance(); BufferBuilder bufferBuilder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); - List storms = new ArrayList(radarBlockEntity.storms); + List storms = new ArrayList<>(radarBlockEntity.storms); boolean update = false; ClientConfig.RadarMode clientRadarMode = ClientConfig.radarMode; @@ -114,9 +133,9 @@ private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseS } } - float size = sizeRenderDiameter / (float)resolution; + float size = sizeRenderDiameter / (float) resolution; - RenderData renderData = new RenderData(blockEntity, partialTicks, poseStack, multiBufferSource, combinedLightIn, combinedOverlayIn); + RenderData renderData = new RenderData(blockEntity, sizeRenderDiameter, partialTicks, poseStack, multiBufferSource, combinedLightIn, combinedOverlayIn); RadarMode radarMode = blockEntity.getBlockState().getValue(PMWExtras.RADAR_MODE); if (!PMWClientStorages.RADAR_MODE_COLORS.containsKey(radarMode)) update = true; @@ -134,10 +153,12 @@ private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseS Color color = PMWClientStorages.RADAR_MODE_COLORS.computeIfAbsent(radarMode, rm -> new HashMap<>()).getOrDefault(id, new Color(1.0F, 0, 1.0F)); Color dbg = radarBlockEntity.debugMap.getOrDefault(longID, new Color(0, 0, 0)); - Vector3f pixelPos = (new Vector3f((float)x, 0.0F, (float)z)).mul(1.0F / (float)resolution).mul(sizeRenderDiameter / 2.0F); - Vec3 worldPos = (new Vec3(x, 0.0F, z)).multiply(1.0F / (float)resolution, 0.0F, (1.0F / (float)resolution)).multiply(simSize, 0.0F, simSize).add(pos.getCenter()); + Vector3f pixelPos = (new Vector3f((float) x, 0.0F, (float) z)).mul(1.0F / (float) resolution).mul(sizeRenderDiameter / 2.0F); + Vec3 worldPos = (new Vec3(x, 0.0F, z)).multiply(1.0F / (float) resolution, 0.0F, (1.0F / (float) resolution)).multiply(simSize, 0.0F, simSize).add(pos.getCenter()); if (update) { + float clouds = Clouds.getCloudDensity(GameBusClientEvents.weatherHandler, new Vector2f((float)worldPos.x, (float)worldPos.z), 0.0F); + dbz = 0.0F; temp = 0.0F; Vec2 f = (new Vec2((float)x, (float)z)).normalized(); @@ -147,34 +168,105 @@ private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseS for(Storm storm : storms) { if (!storm.visualOnly) { - double stormSize = ServerConfig.stormSize * (double)2.0F; + double stormSize = ServerConfig.stormSize * (double) 2.0F; if (storm.stormType == 0) { stormSize *= 1.5F; } double scale = stormSize / (double)1200.0F; - double shapeNoise = radarBlockEntity.noise.getValue((float)radarBlockEntity.tickCount / 1200.0F, worldPos.x / ((double)750.0F * scale), worldPos.z / ((double)750.0F * scale)); - double shapeNoise2 = radarBlockEntity.noise.getValue((float)radarBlockEntity.tickCount / 1200.0F, worldPos.z / ((double)750.0F * scale), worldPos.x / ((double)750.0F * scale)); - double shapeNoise4 = radarBlockEntity.noise.getValue((float)radarBlockEntity.tickCount / 1200.0F, worldPos.z / ((double)250.0F * scale), worldPos.x / ((double)250.0F * scale)); - shapeNoise *= 0.5F; - shapeNoise2 *= 0.5F; - shapeNoise4 *= 0.5F; - shapeNoise += 0.5F; - shapeNoise2 += 0.5F; - shapeNoise4 += 0.5F; + if (storm.stormType == 2) { + scale = (float) storm.maxWidth / 3000.0F; + scale *= 0.5F; + } + + double shapeNoise = radarBlockEntity.noise.getValue(((float)radarBlockEntity.tickCount / 8000.0F), worldPos.x / ((double)750.0F * scale), worldPos.z / ((double)750.0F * scale)); + float fineShapeNoise = FBM(radarBlockEntity.noise, new Vec3((float)radarBlockEntity.tickCount / 8000.0F, worldPos.x / ((double)500.0F * scale), worldPos.z / ((double)500.0F * scale)), 10, 2.0F, 0.75F, 1.0F); + double shapeNoise2 = radarBlockEntity.noise.getValue((float)radarBlockEntity.tickCount / 8000.0F, worldPos.z / ((double)750.0F * scale), worldPos.x / ((double)750.0F * scale)); + double shapeNoise3 = radarBlockEntity.noise.getValue(((float)radarBlockEntity.tickCount / 16000.0F), worldPos.x / ((double)4000.0F * scale), worldPos.z / ((double)4000.0F * scale)); + double shapeNoise4 = radarBlockEntity.noise.getValue(((float)radarBlockEntity.tickCount / 8000.0F), worldPos.z / ((double)250.0F * scale), worldPos.x / ((double)250.0F * scale)); + shapeNoise *= (double)0.5F; + shapeNoise2 *= (double)0.5F; + shapeNoise4 *= (double)0.5F; + shapeNoise += (double)0.5F; + shapeNoise2 += (double)0.5F; + shapeNoise4 += (double)0.5F; float localDBZ = 0.0F; float smoothStage = (float)storm.stage + (float)storm.energy / 100.0F; - if (storm.stormType == 1) { - double rawDist = worldPos.distanceTo(storm.position.multiply(1.0F, 0.0F, 1.0F)); - Vec2 v2fWorldPos = new Vec2((float)worldPos.x, (float)worldPos.z); - Vec2 stormVel = new Vec2((float)storm.velocity.x, (float)storm.velocity.z); - Vec2 v2fStormPos = new Vec2((float)storm.position.x, (float)storm.position.z); + if (storm.stormType == StormType.CYCLONE.idx()) { + Vec3 wPos = worldPos; + Vec3 cPos = storm.position.multiply(1.0F, 0.0F, 1.0F); + + for(Vorticy vorticy : storm.vorticies) { + Vec3 vPos = vorticy.getPosition(); + float width = vorticy.getWidth() * 0.35F; + double d = wPos.multiply(1.0F, 0.0F, 1.0F).distanceTo(vPos.multiply(1.0F, 0.0F, 1.0F)); + if (d < (double) width) { + double angle = Math.pow((double) 1.0F - Math.clamp(d / (double) width, 0.0F, 1.0F), 3.75F); + angle *= ((float) Math.PI / 10F); + angle *= Math.min(vorticy.windspeedMult * (float) storm.windspeed, 6.0F); + wPos = Util.rotatePoint(wPos, vPos, angle); + } + } + + double rawDist = wPos.multiply(1.0F, 0.0F, 1.0F).distanceTo(storm.position.multiply(1.0F, 0.0F, 1.0F)); + rawDist *= (double) 1.0F + shapeNoise3 * (double) 0.2F; + float intensity = (float) Math.pow(Math.clamp((float) storm.windspeed / 65.0F, 0.0F, 1.0F), 0.25F); + Vec3 relPos = cPos.subtract(wPos).multiply(scale, 0.0F, scale); + double d = ((float) storm.maxWidth / (3.0F + (float) storm.windspeed / 12.0F)); + double d2 = ((float) storm.maxWidth / (1.15F + (float) storm.windspeed / 12.0F)); + double dE = ((float) storm.maxWidth * 0.65F / (1.75F + (float) storm.windspeed / 12.0F)); + double fac = (double) 1.0F + Math.max((rawDist - (double) ((float) storm.maxWidth * 0.2F)) / (double) storm.maxWidth, 0.0F) * (double) 2.0F; + d *= fac; + d2 *= fac; + double angle = Math.atan2(relPos.z, relPos.x) - rawDist / d; + double angle2 = Math.atan2(relPos.z, relPos.x) - rawDist / d2; + double angleE = Math.atan2(relPos.z, relPos.x) - rawDist / dE; + float weak = 0.0F; + float strong = 0.0F; + float intense = 0.0F; + float staticBands = (float) Math.sin(angle - (Math.PI / 2D)); + staticBands *= (float) Math.pow(Math.clamp(rawDist / (double) ((float) storm.maxWidth * 0.25F), 0.0F, 1.0F), 0.1F); + staticBands *= 1.25F * (float) Math.pow(intensity, 0.75F); + if (staticBands < 0.0F) { + weak += Math.abs(staticBands); + } else { + weak += Math.abs(staticBands) * (float) Math.pow((double) 1.0F - Math.clamp(rawDist / (double) ((float) storm.maxWidth * 0.65F), 0.0F, 1.0F), 0.5F); + weak *= Math.clamp(((float) storm.windspeed - 70.0F) / 40.0F, 0.0F, 1.0F); + } + + float rotatingBands = (float) Math.sin((angle2 + Math.toRadians(((float) storm.tickCount / 8.0F))) * (double) 6.0F); + rotatingBands *= (float) Math.pow(Math.clamp(rawDist / (double) ((float) storm.maxWidth * 0.25F), 0.0F, 1.0F), 0.1F); + rotatingBands *= 1.25F * (float) Math.pow(intensity, 0.75F); + strong += Mth.lerp(0.45F, Math.abs(rotatingBands) * 0.3F + 0.7F, weak); + intense += Mth.lerp(0.3F, Math.abs(rotatingBands) * 0.2F + 0.8F, weak); + weak = (Math.abs(rotatingBands) * 0.3F + 0.6F) * weak; + localDBZ += Mth.lerp(Math.clamp(((float) storm.windspeed - 120.0F) / 60.0F, 0.0F, 1.0F), Mth.lerp(Math.clamp(((float) storm.windspeed - 40.0F) / 90.0F, 0.0F, 1.0F), weak, strong), intense); + float eye = (float) Math.sin((angleE + Math.toRadians(((float) storm.tickCount / 4.0F))) * (double) 2.0F); + float efc = Mth.lerp(Math.clamp(((float) storm.windspeed - 100.0F) / 50.0F, 0.0F, 1.0F), 0.15F, 0.4F); + localDBZ = Math.max((float) Math.pow((double) 1.0F - Math.clamp(rawDist / (double) ((float) storm.maxWidth * efc), 0.0F, 1.0F), 0.5F) * (Math.abs(eye * 0.1F) + 0.9F) * 1.35F * intensity, localDBZ); + localDBZ *= (float) Math.pow((double) 1.0F - Math.clamp(rawDist / (double) storm.maxWidth, 0.0F, 1.0F), 0.5F); + localDBZ *= Mth.lerp(0.5F + Math.clamp(((float) storm.windspeed - 65.0F) / 40.0F, 0.0F, 1.0F) * 0.5F, 1.0F, (float) Math.pow(Math.clamp(rawDist / (double)((float) storm.maxWidth * 0.1F), 0.0F, 1.0F), 2.0F)); + localDBZ *= Mth.lerp(Math.clamp(((float) storm.windspeed - 75.0F) / 50.0F, 0.0F, 1.0F), 0.8F + (float) shapeNoise2 * 0.4F, 1.0F); + localDBZ *= 0.8F + (float) shapeNoise * 0.4F; + localDBZ *= 1.0F + fineShapeNoise * Mth.lerp((float)Math.pow(Math.clamp(rawDist / (double) storm.maxWidth, 0.0F, 1.0F), 1.5F), 0.05F, 0.15F); + localDBZ = (float) Math.pow(localDBZ, 1.75F); + if (localDBZ > 0.8F) { + float dif = (localDBZ - 0.8F) / 1.25F; + localDBZ -= dif; + } + } + + if (storm.stormType == StormType.SQUALL.idx()) { + double rawDist = worldPos.multiply(1.0F, 0.0F, 1.0F).distanceTo(storm.position.multiply(1.0F, 0.0F, 1.0F)); + Vec2 v2fWorldPos = new Vec2((float) worldPos.x, (float) worldPos.z); + Vec2 stormVel = new Vec2((float) storm.velocity.x, (float) storm.velocity.z); + Vec2 v2fStormPos = new Vec2((float) storm.position.x, (float) storm.position.z); Vec2 right = (new Vec2(stormVel.y, -stormVel.x)).normalized(); Vec2 fwd = stormVel.normalized(); - Vec2 le = Util.mulVec2(right, -3000.0F * (float)scale); - Vec2 ri = Util.mulVec2(right, 3000.0F * (float)scale); - Vec2 off = Util.mulVec2(fwd, -((float)Math.pow(Mth.clamp(rawDist / ((double)3000.0F * scale), 0.0F, 1.0F), 2.0F)) * 900.0F * (float)scale); + Vec2 le = Util.mulVec2(right, -3000.0F * (float) scale); + Vec2 ri = Util.mulVec2(right, 3000.0F * (float) scale); + Vec2 off = Util.mulVec2(fwd, -((float) Math.pow(Mth.clamp(rawDist / ((double) 3000.0F * scale), 0.0F, 1.0F), 2.0F)) * 900.0F * (float) scale); le = le.add(off); ri = ri.add(off); le = le.add(v2fStormPos); @@ -182,10 +274,10 @@ private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseS float dist = Util.minimumDistance(le, ri, v2fWorldPos); float intensity = switch (storm.stage) { - case 1 -> 0.1F + (float)storm.energy / 100.0F * 0.7F; - case 2 -> 0.8F + (float)storm.energy / 100.0F * 0.4F; - case 3 -> 1.2F + (float)storm.energy / 100.0F; - default -> (float)storm.energy / 100.0F * 0.1F; + case 1 -> 0.1F + (float) storm.energy / 100.0F * 0.7F; + case 2 -> 0.8F + (float) storm.energy / 100.0F * 0.4F; + case 3 -> 1.2F + (float) storm.energy / 100.0F; + default -> (float) storm.energy / 100.0F * 0.1F; }; if (intensity > 0.8F) { @@ -195,62 +287,59 @@ private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseS Vec2 nearPoint = Util.nearestPoint(le, ri, v2fWorldPos); Vec2 facing = v2fWorldPos.add(nearPoint.negated()); float behind = -facing.dot(fwd); - behind += (float)shapeNoise * 600.0F * (float)scale * 0.2F; - float sze = 600.0F * (float)scale * 1.5F * 3.0F; - behind += (float)stormSize / 2.0F; + behind += (float) shapeNoise * 600.0F * (float) scale * 0.2F; + float sze = 600.0F * (float) scale * 1.5F * 3.0F; + behind += (float) stormSize / 2.0F; if (behind > 0.0F) { sze *= Mth.lerp(Mth.clamp(smoothStage - 1.0F, 0.0F, 1.0F), 1.0F, 4.0F); float p = Mth.clamp(Math.abs(behind) / sze, 0.0F, 1.0F); float start = 0.06F; if (p <= start) { p /= start; - localDBZ += (float)Math.pow(p, 2.0F); + localDBZ += (float) Math.pow(p, 2.0F); } else { p = 1.0F - (p - start) / (1.0F - start); - localDBZ += (float)Math.pow(p, 4.0F); + localDBZ += (float) Math.pow(p, 4.0F); } } localDBZ *= Mth.sqrt(1.0F - Mth.clamp(dist / sze, 0.0F, 1.0F)); if (smoothStage > 3.0F) { float p = Mth.clamp((smoothStage - 3.0F) / 2.0F, 0.0F, 0.5F); - localDBZ *= 0.8F + (float)shapeNoise2 * 0.4F * (1.0F - p); - localDBZ *= 0.8F + (float)shapeNoise * 0.4F * (1.0F - p); + localDBZ *= 0.8F + (float) shapeNoise2 * 0.4F * (1.0F - p); + localDBZ *= 0.8F + (float) shapeNoise * 0.4F * (1.0F - p); localDBZ *= 1.0F + p * 0.25F; } else { - localDBZ *= 0.8F + (float)shapeNoise2 * 0.4F; - localDBZ *= 0.8F + (float)shapeNoise * 0.4F; + localDBZ *= 0.8F + (float) shapeNoise2 * 0.4F; + localDBZ *= 0.8F + (float) shapeNoise * 0.4F; } localDBZ *= Mth.sqrt(intensity); } - if (storm.stormType == 0) { - double dist = worldPos.distanceTo(storm.position.multiply(1.0F, 0.0F, 1.0F)); - if (dist > stormSize * (double)4.0F) { + if (storm.stormType == StormType.SUPERCELL.idx()) { + double dist = worldPos.multiply(1.0F, 0.0F, 1.0F).distanceTo(storm.position.multiply(1.0F, 0.0F, 1.0F)); + if (dist > stormSize * (double) 4.0F) { continue; } - float var178; - switch (storm.stage) { - case 1 -> var178 = 0.1F + (float)storm.energy / 100.0F * 0.7F; - case 2 -> var178 = 0.8F + (float)storm.energy / 100.0F * 0.4F; - case 3 -> var178 = 1.2F + (float)storm.windspeed / 100.0F; - default -> var178 = (float)Math.pow((float)storm.energy / 100.0F, 2.0F) * 0.1F; - } + float intensity = switch (storm.stage) { + case 1 -> 0.1F + (float) storm.energy / 100.0F * 0.7F; + case 2 -> 0.8F + (float) storm.energy / 100.0F * 0.4F; + case 3 -> 1.2F + (float) storm.energy / 100.0F; + default -> (float) Math.pow(storm.energy / 100.0F, 2.0F) * 0.1F; + }; - float intensity = var178; if (intensity > 0.8F) { intensity = 0.8F + (intensity - 0.8F) / 4.0F; } - switch (storm.stage) { - case 2 -> var178 = (float)storm.energy / 100.0F * 40.0F; - case 3 -> var178 = 40.0F + (float)storm.windspeed; - default -> var178 = 0.0F; - } + float windspeed = switch (storm.stage) { + case 2 -> (float) storm.energy / 100.0F * 40.0F; + case 3 -> 40.0F + (float) storm.windspeed; + default -> 0.0F; + }; - float windspeed = var178; if (windspeed > 60.0F) { windspeed -= (windspeed - 60.0F) * 0.2F; } @@ -262,54 +351,61 @@ private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseS xM = 1.0F; } - double coreDist = Math.sqrt(Math.pow((worldPos.x - corePos.x) * (double)xM, 2.0F) + Math.pow((worldPos.z - corePos.z) * (double)1.5F, 2.0F)) / scale; + double coreDist = Math.sqrt(Math.pow((worldPos.x - corePos.x) * (double) xM, 2.0F) + Math.pow((worldPos.z - corePos.z) * (double) 1.5F, 2.0F)) / scale; dist /= scale; coreDist *= 0.9 + shapeNoise * 0.3; Vec3 relPos = torPos.subtract(worldPos).multiply(scale, 0.0F, scale); - double d = (double)150.0F + dist / (double)3.0F; - double d2 = (double)75.0F + dist / (double)3.0F; + double d = (double) 150.0F + dist / (double) 3.0F; + double d2 = (double) 75.0F + dist / (double) 3.0F; double angle = Math.atan2(relPos.z, relPos.x) - dist / d; double angle2 = Math.atan2(relPos.z, relPos.x) - dist / d2; - double angle3 = Math.atan2(relPos.z, relPos.x) - dist / d2 / (double)2.0F; + double angle3 = Math.atan2(relPos.z, relPos.x) - dist / d2 / (double) 2.0F; angle += Math.toRadians(180.0F); angle2 += Math.toRadians(180.0F); angle3 += Math.toRadians(180.0F); - double angleMod = Math.toRadians(40.0F) * ((double)1.0F - Math.clamp(Math.pow((double)windspeed / (double)100.0F, 2.0F), 0.0F, 0.9)); - double noise = (shapeNoise4 - (double)0.5F) * Math.toRadians(10.0F); + double angleMod = Math.toRadians(40.0F) * ((double) 1.0F - Math.clamp(Math.pow((double) windspeed / (double) 100.0F, 2.0F), 0.0F, 0.9)); + double noise = (shapeNoise4 - (double) 0.5F) * Math.toRadians(10.0F); angle += angleMod + noise; angle2 += angleMod + noise; angle3 += angleMod + noise; double inflow = Math.sin(angle - Math.toRadians(15.0F)); inflow = Math.pow(Math.abs(inflow), 0.5F) * Math.sin(inflow); - inflow *= (double)1.0F - Math.clamp(dist / (double)2400.0F, 0.0F, 1.0F); - if (inflow < (double)0.0F) { - localDBZ += (float)(inflow * (double)2.0F * Math.pow(Math.clamp((double)(windspeed - 15.0F) / (double)50.0F, 0.0F, 1.0F), 2.0F)); + inflow *= (double) 1.0F - Math.clamp(dist / (double) 2400.0F, 0.0F, 1.0F); + if (inflow < (double) 0.0F) { + localDBZ += (float) (inflow * (double) 2.0F * Math.pow(Math.clamp((double) (windspeed - 15.0F) / (double) 50.0F, 0.0F, 1.0F), 2.0F)); } double surge = Math.sin(angle2 - Math.toRadians(60.0F)); surge = Math.abs(surge) * Math.sin(surge); - surge *= ((double)1.0F - Math.pow(Math.clamp(dist / (double)1200.0F, 0.0F, 1.0F), 1.5F)) * ((double)1.0F - Math.clamp(dist / (double)200.0F, 0.0F, 0.3)); - if (surge > (double)0.0F) { - double n = 0.8 * ((double)1.0F - Math.clamp(Math.pow((double)windspeed / (double)80.0F, 2.0F), 0.0F, 1.0F)); - double m = (double)1.0F - shapeNoise4 * n; - localDBZ += (float)(surge * (double)1.5F * Math.clamp(dist / (double)500.0F, 0.0F, 1.0F) * Math.sqrt(Math.clamp((double)(windspeed - 20.0F) / (double)50.0F, 0.0F, 1.0F)) * m); + surge *= ((double) 1.0F - Math.pow(Math.clamp(dist / (double) 1200.0F, 0.0F, 1.0F), 1.5F)) * ((double) 1.0F - Math.clamp(dist / (double) 200.0F, 0.0F, 0.3)); + if (surge > (double) 0.0F) { + double n = 0.8 * ((double) 1.0F - Math.clamp(Math.pow((double) windspeed / (double) 80.0F, 2.0F), 0.0F, 1.0F)); + double m = (double) 1.0F - shapeNoise4 * n; + localDBZ += (float) (surge * (double)1.5F * Math.clamp(dist / (double) 500.0F, 0.0F, 1.0F) * Math.sqrt(Math.clamp((double) (windspeed - 20.0F) / (double) 50.0F, 0.0F, 1.0F)) * m); } double shield = Math.sin(angle3 - Math.toRadians(60.0F)); shield = Math.abs(shield) * Math.sin(shield); - shield *= (double)1.0F - Math.pow(Math.clamp(dist / (double)2400.0F, 0.0F, 1.0F), 2.0F); - if (shield > (double)0.0F) { - localDBZ -= (float)(shield * (double)2.0F * Math.clamp(dist / (double)1000.0F, 0.0F, 1.0F) * Math.sqrt(Math.clamp((double)(windspeed - 30.0F) / (double)80.0F, 0.0F, 1.0F))); + shield *= (double) 1.0F - Math.pow(Math.clamp(dist / (double) 2400.0F, 0.0F, 1.0F), 2.0F); + if (shield > (double) 0.0F) { + localDBZ -= (float) (shield * (double) 2.0F * Math.clamp(dist / (double) 1000.0F, 0.0F, 1.0F) * Math.sqrt(Math.clamp((double) (windspeed - 30.0F) / (double) 80.0F, 0.0F, 1.0F))); } - double coreIntensity = ((double)1.0F - Math.clamp(coreDist / (double)1800.0F, 0.0F, 1.0F)) * ((double)1.5F - shapeNoise2 * (double)0.5F) * Math.sqrt(Math.clamp((double)intensity / (double)2.0F, 0.0F, 1.0F)) * Math.clamp(dist / (double)300.0F, 0.5F, 1.0F) * 1.2; - localDBZ += (float)Math.pow(coreIntensity, 0.65); + double coreIntensity = ((double) 1.0F - Math.clamp(coreDist / (double) 1800.0F, 0.0F, 1.0F)) * ((double) 1.5F - shapeNoise2 * (double) 0.5F) * Math.sqrt(Math.clamp((double) intensity / (double) 2.0F, 0.0F, 1.0F)) * Math.clamp(dist / (double) 300.0F, 0.5F, 1.0F) * 1.2; + localDBZ += (float) Math.pow(coreIntensity, 0.65); } dbz = Math.max(dbz, localDBZ); } } + float v = Math.max(clouds - 0.15F, 0.0F) * 4.0F; + if (v > 0.4F) { + float dif = (v - 0.4F) / 2.0F; + v -= dif; + } + + dbz = Math.max(dbz, v); dbz += (PMWeather.RANDOM.nextFloat() - 0.5F) * 5.0F / 60.0F; vel += (PMWeather.RANDOM.nextFloat() - 0.5F) * 3.0F; if (dbz > 1.0F) { @@ -330,21 +426,54 @@ private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseS // PMWeatherAPI: Support custom radar modes if (!PMWClientConfig.disableCustomRadarModeRendering) { - PixelRenderData pixelRenderData = new PixelRenderData(canRender, dbz * 60.0F, vel, temp, x, z, resolution, renderData); + PixelRenderData pixelRenderData = new PixelRenderData(canRender, dbz * 60.0F, vel, temp, x, z, resolution, worldPos, renderData); color = radarMode.getColorForPixel(pixelRenderData); PMWClientStorages.RADAR_MODE_COLORS.get(radarMode).put(id, color); } } float rdbz = dbz * 60.0F; + Color startColor = radarBlockEntity.terrainMap.getOrDefault(id, Color.BLACK); + if (radarBlockEntity.init && update) { + Holder biome = radarBlockEntity.getNearestBiome(new BlockPos((int) worldPos.x, (int) worldPos.y, (int) worldPos.z)); + String rn = biome.getRegisteredName().toLowerCase(); + if (rn.contains("ocean") || rn.contains("river")) startColor = new Color(biome.value().getWaterColor()); + else if (rn.contains("beach") || rn.contains("desert")) { + if (rn.contains("badlands")) startColor = new Color(214, 111, 42); + else startColor = new Color(biome.value().getGrassColor(worldPos.x, worldPos.z)); + } else startColor = new Color(227, 198, 150); + } if (PMWClientConfig.disableCustomRadarModeRendering) { - color = ColorTables.getReflectivity(rdbz); + color = ColorTables.getReflectivity(rdbz, startColor); + + if (rdbz > 5.0F && !radarBlockEntity.hasRangeUpgrade) { + if (temp < 3.0F && temp > -1.0F) { + color = ColorTables.getMixedReflectivity(rdbz); + } else if (temp <= -1.0F) { + color = ColorTables.getSnowReflectivity(rdbz); + } + } + if (radarMode == RadarMode.VELOCITY) { color = new Color(0, 0, 0); vel /= 1.75F; color = ColorTables.lerp(Mth.clamp(Math.max(rdbz, (Mth.abs(vel) - 18.0F) / 0.65F) / 12.0F, 0.0F, 1.0F), color, ColorTables.getVelocity(vel)); } + + if (radarMode == RadarMode.IR) { + float ir = rdbz * 10.0F; + if (rdbz > 10.0F) { + ir = 100.0F + (rdbz - 10.0F) * 2.5F; + } + + if (rdbz > 50.0F) { + ir += (rdbz - 50.0F) * 5.0F; + } + + color = ColorTables.getIR(ir); + } + } if (ClientConfig.radarDebugging && update) { @@ -359,6 +488,15 @@ private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseS } } + if (clientRadarMode == ClientConfig.RadarMode.SST) { + Float t = ThermodynamicEngine.GetSST(GameBusClientEvents.weatherHandler, worldPos, blockEntity.getLevel(), radarBlockEntity, 0); + if (t == null) { + dbg = new Color(0, 0, 0); + } else { + dbg = ColorTables.getSST(t); + } + } + if (clientRadarMode == ClientConfig.RadarMode.WINDFIELDS && GameBusClientEvents.weatherHandler != null) { Vec3 wP = (new Vec3(x, 0.0F, z)).multiply(1.0F / (float)resolution, 0.0F, 1.0F / (float)resolution).multiply(256.0F, 0.0F, 256.0F).add(pos.getCenter()); float wind = 0.0F; @@ -370,6 +508,12 @@ private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseS dbg = ColorTables.getWindspeed(wind); } + if (clientRadarMode == ClientConfig.RadarMode.GLOBALWINDS && GameBusClientEvents.weatherHandler != null) { + int height = GameBusClientEvents.weatherHandler.getWorld().getHeight(Heightmap.Types.MOTION_BLOCKING, (int)worldPos.x, (int)worldPos.z); + float wind = (float)WindEngine.getWind(new Vec3(worldPos.x, (double)height, worldPos.z), GameBusClientEvents.weatherHandler.getWorld(), false, false, false, true).length(); + dbg = ColorTables.getHurricaneWindspeed(wind); + } + if (clientRadarMode == ClientConfig.RadarMode.CAPE) { Sounding sounding = new Sounding(GameBusClientEvents.weatherHandler, worldPos, blockEntity.getLevel(), 500, 12000, radarBlockEntity); Sounding.CAPE CAPE = sounding.getCAPE(sounding.getSBParcel()); @@ -428,7 +572,7 @@ private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseS } } - int color = radarMode.getDotColor().hashCode(); + int color = radarMode.getDotColor().getRGB(); Vector3f topLeft = (new Vector3f(-1.0F, 0.0F, -1.0F)).mul(0.015F).add(0.0F, 0.01F, 0.0F); Vector3f bottomLeft = (new Vector3f(-1.0F, 0.0F, 1.0F)).mul(0.015F).add(0.0F, 0.01F, 0.0F); Vector3f bottomRight = (new Vector3f(1.0F, 0.0F, 1.0F)).mul(0.015F).add(0.0F, 0.01F, 0.0F); diff --git a/src/main/java/net/nullved/pmweatherapi/radar/RadarMode.java b/src/main/java/net/nullved/pmweatherapi/radar/RadarMode.java index dbcdf1d..af90357 100644 --- a/src/main/java/net/nullved/pmweatherapi/radar/RadarMode.java +++ b/src/main/java/net/nullved/pmweatherapi/radar/RadarMode.java @@ -1,9 +1,13 @@ package net.nullved.pmweatherapi.radar; import dev.protomanly.pmweather.PMWeather; +import dev.protomanly.pmweather.block.entity.RadarBlockEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.util.StringRepresentable; +import net.minecraft.world.level.biome.Biome; import net.nullved.pmweatherapi.client.render.PixelRenderData; import net.nullved.pmweatherapi.client.render.RadarOverlays; import net.nullved.pmweatherapi.data.PMWExtras; @@ -53,7 +57,11 @@ public class RadarMode implements StringRepresentable, Comparable { * A Radar Mode that is a copy of PMWeather's Reflectivity * @since 0.14.15.6 */ - public static final RadarMode REFLECTIVITY = create(PMWeather.getPath("reflectivity"), prd -> ColorMaps.REFLECTIVITY.get(prd.rdbz())); + public static final RadarMode REFLECTIVITY = create(PMWeather.getPath("reflectivity"), prd -> { + Holder biome = ((RadarBlockEntity) prd.renderData().blockEntity()).getNearestBiome(new BlockPos((int) prd.worldPos().x, (int) prd.worldPos().y, (int) prd.worldPos().z)); + if (biome != null) return ColorMaps.REFLECTIVITY.getWithBiome(prd.rdbz(), biome, prd.worldPos()); + else return ColorMaps.REFLECTIVITY.get(prd.rdbz()); + }); /** * A Radar Mode that is a copy of PMWeather's Velocity @@ -65,6 +73,25 @@ public class RadarMode implements StringRepresentable, Comparable { return ColorMap.lerp(Mth.clamp(Math.max(prd.rdbz(), (Mth.abs(prd.velocity() / 1.75F) - 18.0F) / 0.65F) / 12.0F, 0.0F, 1.0F), Color.BLACK, velCol); }); + /** + * A Radar Mode that is a copy of PMWeather's IR + * @since 0.15.0.0 + */ + public static final RadarMode IR = create(PMWeather.getPath("ir"), prd -> { + float rdbz = prd.rdbz(); + float ir = rdbz * 10.0F; + + if (rdbz > 10.0F) { + ir = 100.0F + (rdbz - 10.0F) * 2.5F; + } + + if (rdbz > 50.0F) { + ir += (rdbz - 50.0F) * 5.0F; + } + + return ColorMaps.IR.get(ir); + }); + private final ResourceLocation id; private final Function colorFunction; private final Color dotColor; diff --git a/src/main/java/net/nullved/pmweatherapi/storm/NearbyStorms.java b/src/main/java/net/nullved/pmweatherapi/storm/NearbyStorms.java index befd3dc..6440ea9 100644 --- a/src/main/java/net/nullved/pmweatherapi/storm/NearbyStorms.java +++ b/src/main/java/net/nullved/pmweatherapi/storm/NearbyStorms.java @@ -10,6 +10,7 @@ import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; +import net.nullved.pmweatherapi.util.StormType; import java.util.ArrayList; import java.util.HashMap; @@ -152,4 +153,103 @@ public void forStormNearPlayer(Player player, double radius, Consumer con List storms = stormsNearPlayer(player, radius); for (Storm storm: storms) consumer.accept(storm); } + + /** + * Returns a {@link List} of {@link Storm}s that meet the {@link StormType} in a defined radius around the block + * @param block The {@link BlockPos} of the block at the center of the search area + * @param radius The radius of the search area + * @param type The {@link StormType} to match + * @return A {@link List} of {@link Storm}s + * @since 0.15.0.0 + */ + public List stormsNearBlock(BlockPos block, double radius, StormType type) { + List allStorms = handler.getStorms(); + List nearStorms = new ArrayList<>(); + + for (Storm storm: allStorms) { + Vec3 pos = storm.position; + if (pos.distanceTo(block.getCenter()) <= radius && type.matches(storm)) nearStorms.add(storm); + } + + return nearStorms; + } + + /** + * Returns a {@link List} of {@link Storm}s that meet the {@link StormType} in a defined radius around the center of the chunk + * @param chunk The {@link ChunkPos} of the chunk at the center of the search area. + * @param radius The radius of the search area + * @param type The {@link StormType} to match + * @return A {@link List} of {@link Storm}s + * @since 0.15.0.0 + */ + public List stormsNearChunk(ChunkPos chunk, double radius, StormType type) { + List allStorms = handler.getStorms(); + List nearStorms = new ArrayList<>(); + + for (Storm storm: allStorms) { + Vec3 pos = storm.position; + if (pos.distanceTo(chunk.getWorldPosition().getCenter()) <= radius && type.matches(storm)) nearStorms.add(storm); + } + + return nearStorms; + } + + /** + * Returns a {@link List} of {@link Storm}s that meet the {@link StormType} in a defined radius around the given {@link Player} + * @param player The {@link Player} to search around + * @param radius The radius of the search area + * @param type The {@link StormType} to match + * @return A {@link List} of {@link Storm}s + * @since 0.15.0.0 + */ + public List stormsNearPlayer(Player player, double radius, StormType type) { + List allStorms = handler.getStorms(); + List nearStorms = new ArrayList<>(); + + for (Storm storm: allStorms) { + Vec3 pos = storm.position; + if (pos.distanceTo(player.position()) <= radius && type.matches(storm)) nearStorms.add(storm); + } + + return nearStorms; + } + + /** + * Executes the given {@link Consumer} for each {@link Storm} that meets the {@link StormType} in a defined radius around the block + * @param block The {@link BlockPos} of the block at the center of the search area + * @param radius The radius of the search area + * @param type The {@link StormType} to match + * @param consumer The {@link Consumer} to execute for each {@link Storm} + * @since 0.15.0.0 + */ + public void forStormNearBlock(BlockPos block, double radius, StormType type, Consumer consumer) { + List storms = stormsNearBlock(block, radius, type); + for (Storm storm: storms) consumer.accept(storm); + } + + /** + * Executes the given {@link Consumer} for each {@link Storm} that meets the {@link StormType} in a defined radius around the center of the chunk + * @param chunk The {@link ChunkPos} of the chunk at the center of the search area. + * @param radius The radius of the search area + * @param type The {@link StormType} to match + * @param consumer The {@link Consumer} to execute for each {@link Storm} + * @since 0.15.0.0 + */ + public void forStormNearChunk(ChunkPos chunk, double radius, StormType type, Consumer consumer) { + List storms = stormsNearChunk(chunk, radius, type); + for (Storm storm: storms) consumer.accept(storm); + } + + /** + * Executes the given {@link Consumer} for each {@link Storm} that meets the {@link StormType} in a defined radius around the given {@link Player} + * @param player The {@link Player} to search around + * @param radius The radius of the search area + * @param type The {@link StormType} to match + * @param consumer The {@link Consumer} to execute for each {@link Storm} + * @since 0.15.0.0 + */ + public void forStormNearPlayer(Player player, double radius, StormType type, Consumer consumer) { + List storms = stormsNearPlayer(player, radius, type); + for (Storm storm: storms) consumer.accept(storm); + } } diff --git a/src/main/java/net/nullved/pmweatherapi/storm/StormBuilder.java b/src/main/java/net/nullved/pmweatherapi/storm/StormBuilder.java index 0a1ce98..5843c13 100644 --- a/src/main/java/net/nullved/pmweatherapi/storm/StormBuilder.java +++ b/src/main/java/net/nullved/pmweatherapi/storm/StormBuilder.java @@ -10,19 +10,20 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; +import net.nullved.pmweatherapi.util.StormType; /** * A builder for {@link Storm}s that makes it easy to create and spawn them. - * Create a StormBuilder with any constructor or {@link #atPlayer(Type, Player)} + * Create a StormBuilder with any constructor or {@link #atPlayer(StormType, Player)} *
* To spawn the storm in the world, use {@link #buildAndSpawn()}. To only create the storm instance, use {@link #build()} * @since 0.14.15.5 */ public class StormBuilder { private final WeatherHandler weatherHandler; - private final Type type; + private final StormType type; private final Vec3 position; - private float risk = 0.5f, rankineFactor = 4.5F, width = 15.0F; + private float risk = 0.5f, rankineFactor = 4.5F, width = 15.0F, smoothWidth = 15.0F, cycloneWindspeed = 0.0F, smoothWindspeed = 0.0F; private int windspeed = 0, maxWindspeed = 0, stage = 0, maxStage = 2, energy = 0, coldEnergy = 0, maxColdEnergy = 300, maxWidth = 15; private Vec3 velocity = Vec3.ZERO; private boolean visualOnly = false, aimAtAnyPlayer = false; @@ -31,11 +32,11 @@ public class StormBuilder { /** * Create a new {@link StormBuilder} * @param weatherHandler The {@link WeatherHandler} to use - * @param type The {@link Type} of storm + * @param type The {@link StormType} of storm * @param position The {@link Vec3} representing the position of the {@link Storm} * @since 0.14.15.5 */ - public StormBuilder(WeatherHandler weatherHandler, Type type, Vec3 position) { + public StormBuilder(WeatherHandler weatherHandler, StormType type, Vec3 position) { this.weatherHandler = weatherHandler; this.type = type; this.position = position; @@ -44,33 +45,33 @@ public StormBuilder(WeatherHandler weatherHandler, Type type, Vec3 position) { /** * Create a new {@link StormBuilder} * @param level The {@link Level} to spawn in - * @param type The {@link Type} of storm + * @param type The {@link StormType} of storm * @param position The {@link Vec3} representing the position of the {@link Storm} * @since 0.14.15.5 */ - public StormBuilder(Level level, Type type, Vec3 position) { + public StormBuilder(Level level, StormType type, Vec3 position) { this(GameBusEvents.MANAGERS.get(level.dimension()), type, position); } /** * Create a new {@link StormBuilder} * @param dimension The {@link ResourceKey} of the dimension to spawn in - * @param type The {@link Type} of storm + * @param type The {@link StormType} of storm * @param position The {@link Vec3} representing the position of the {@link Storm} * @since 0.14.15.5 */ - public StormBuilder(ResourceKey dimension, Type type, Vec3 position) { + public StormBuilder(ResourceKey dimension, StormType type, Vec3 position) { this(GameBusEvents.MANAGERS.get(dimension), type, position); } /** * Creates a {@link StormBuilder} at a {@link Player}'s dimension and position - * @param type The {@link Type} of storm + * @param type The {@link StormType} of storm * @param player The {@link Player} to grab the dimension and position from * @return A new {@link StormBuilder} * @since 0.14.15.5 */ - public static StormBuilder atPlayer(Type type, Player player) { + public static StormBuilder atPlayer(StormType type, Player player) { return new StormBuilder(player.level(), type, player.position()); } @@ -142,6 +143,17 @@ public StormBuilder width(float width) { return this; } + /** + * Sets the smooth width of the {@link Storm} + * @param smoothWidth The smooth width + * @return The {@link StormBuilder} instance + * @since 0.15.0.0 + */ + public StormBuilder smoothWidth(float smoothWidth) { + this.smoothWidth = smoothWidth; + return this; + } + /** * Sets the max width of the {@link Storm} * @param maxWidth The max width @@ -219,6 +231,28 @@ public StormBuilder windspeed(int windspeed) { return this; } + /** + * Sets the cyclone windspeed of the {@link Storm} + * @param cycloneWindspeed The cyclone windspeed + * @return The {@link StormBuilder} instance + * @since 0.15.0.0 + */ + public StormBuilder cycloneWindspeed(float cycloneWindspeed) { + this.cycloneWindspeed = cycloneWindspeed; + return this; + } + + /** + * Sets the smooth windspeed of the {@link Storm} + * @param smoothWindspeed The smooth windspeed + * @return The {@link StormBuilder} instance + * @since 0.15.0.0 + */ + public StormBuilder smoothWindspeed(float smoothWindspeed) { + this.smoothWindspeed = smoothWindspeed; + return this; + } + /** * Sets the max windspeed of the {@link Storm} * @param maxWindspeed The max windspeed @@ -249,12 +283,12 @@ public StormBuilder velocity(Vec3 velocity) { * @since 0.14.15.5 */ public Storm build() { - Storm storm = new Storm(weatherHandler, weatherHandler.getWorld(), risk, type.ordinal()); + Storm storm = new Storm(weatherHandler, weatherHandler.getWorld(), risk, type.idx()); storm.initFirstTime(); storm.visualOnly = visualOnly; storm.velocity = velocity; if (aimAtPlayer != null) { - if (storm.stormType != Type.SQUALL.ordinal()) { + if (storm.stormType != StormType.SQUALL.idx()) { Vec3 aimPos = aimAtPlayer.position().add(new Vec3((double)(PMWeather.RANDOM.nextFloat() - 0.5F) * ServerConfig.aimAtPlayerOffset, 0.0F, (double)(PMWeather.RANDOM.nextFloat() - 0.5F) * ServerConfig.aimAtPlayerOffset)); if (storm.position.distanceTo(aimPos) >= ServerConfig.aimAtPlayerOffset) { Vec3 toward = storm.position.subtract(new Vec3(aimPos.x, storm.position.y, aimPos.z)).multiply(1.0F, 0.0F, 1.0F).normalize(); @@ -266,7 +300,7 @@ public Storm build() { } } if (aimAtAnyPlayer) { - if (storm.stormType != Type.SQUALL.ordinal()) { + if (storm.stormType != StormType.SQUALL.idx()) { Player nearest = storm.level.getNearestPlayer(this.position.x, this.position.y, this.position.z, 4096.0F, false); if (nearest != null) { Vec3 aimPos = aimAtPlayer.position().add(new Vec3((double) (PMWeather.RANDOM.nextFloat() - 0.5F) * ServerConfig.aimAtPlayerOffset, 0.0F, (double) (PMWeather.RANDOM.nextFloat() - 0.5F) * ServerConfig.aimAtPlayerOffset)); @@ -282,15 +316,18 @@ public Storm build() { } } storm.position = position; - storm.stage = stage; + storm.stage = Math.max(stage, type.stage()); storm.maxStage = maxStage; storm.energy = energy; storm.width = width; + storm.smoothWidth = smoothWidth; storm.maxWidth = maxWidth; storm.rankineFactor = rankineFactor; storm.coldEnergy = coldEnergy; storm.maxColdEnergy = maxColdEnergy; storm.windspeed = windspeed; + storm.cycloneWindspeed = cycloneWindspeed; + storm.smoothWindspeed = smoothWindspeed; storm.maxWindspeed = maxWindspeed; return storm; } @@ -309,21 +346,4 @@ public Storm buildAndSpawn() { } return storm; } - - /** - * An enum with types of {@link Storm}s - * @since 0.14.15.5 - */ - public enum Type { - /** - * Represents both supercells and tornados - * @since 0.14.15.5 - */ - SUPERCELL, - /** - * Represents squall lines - * @since 0.14.15.5 - */ - SQUALL - } } diff --git a/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java b/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java index b088ac1..bf7e50d 100644 --- a/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java +++ b/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java @@ -1,6 +1,9 @@ package net.nullved.pmweatherapi.util; import dev.protomanly.pmweather.util.ColorTables; +import net.minecraft.core.Holder; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.phys.Vec3; import java.awt.Color; import java.util.*; @@ -20,14 +23,14 @@ public class ColorMap { private final NavigableMap overridePoints; private final Color base; - private final float min; - private final float max; + private final float min, max, firstThreshold; private Color[] lookup; private float resolution; private ColorMap(Color base, boolean overrideModeGreater, List segments, NavigableMap overridePoints, float resolution) { this.min = Math.round(segments.getFirst().start / resolution) * resolution; this.max = Math.round(segments.getLast().end / resolution) * resolution; + this.firstThreshold = segments.getFirst().end; this.base = base; this.overrideModeGreater = overrideModeGreater; @@ -90,6 +93,20 @@ public Color get(float val) { return lookup[idx]; } + public Color getWithBiome(float val, Holder biome, Vec3 worldPos) { + Color startColor = Color.BLACK; + String rn = biome.getRegisteredName().toLowerCase(); + if (rn.contains("ocean") || rn.contains("river")) startColor = new Color(biome.value().getWaterColor()); + else if (rn.contains("beach") || rn.contains("desert")) { + if (rn.contains("badlands")) startColor = new Color(214, 111, 42); + else startColor = new Color(biome.value().getGrassColor(worldPos.x, worldPos.z)); + } else startColor = new Color(227, 198, 150); + + if (val < firstThreshold) { + return lerp(Math.clamp(val / (firstThreshold - min), 0.0F, 1.0F), startColor, segments.firstEntry().getValue().to); + } else return get(val); + } + /** * Gets the {@link Color} for the specific value. *
@@ -173,6 +190,15 @@ public static Builder of(Color base) { return new Builder(base); } + /** + * Creates a new {@link ColorMap.Builder} that has a default {@link Color#BLACK} base + * @return The created {@link ColorMap.Builder} + * @since 0.15.0.0 + */ + public static Builder biome() { + return new Builder(Color.BLACK); + } + /** * Sets the step size between each value in the lookup table. * A value too small may be storing the same color multiple times! @@ -235,7 +261,7 @@ public Builder override(Color color, float threshold) { */ public ColorMap build(Color finalColor, float finalThreshold) { if (!segments.isEmpty()) { - LerpSegment first = segments.get(0); + LerpSegment first = segments.getFirst(); if (first.start > 0.0F) { segments.add(0, new LerpSegment(0.0F, base, first.start, first.from)); } diff --git a/src/main/java/net/nullved/pmweatherapi/util/ColorMaps.java b/src/main/java/net/nullved/pmweatherapi/util/ColorMaps.java index 5a6eb93..41a38bc 100644 --- a/src/main/java/net/nullved/pmweatherapi/util/ColorMaps.java +++ b/src/main/java/net/nullved/pmweatherapi/util/ColorMaps.java @@ -11,10 +11,10 @@ */ public class ColorMaps { /** - * A {@link ColorMap} equivalent to {@link ColorTables#getReflectivity(float)} + * A {@link ColorMap} equivalent to {@link ColorTables#getReflectivity(float, Color)} * @since 0.14.15.6 */ - public static final ColorMap REFLECTIVITY = ColorMap.Builder.of(Color.BLACK) + public static final ColorMap REFLECTIVITY = ColorMap.Builder.biome() .addPoint(new Color(0x5C9DAE), 19.0F) .addPoint(new Color(0x0B6409), 27.0F) .addPoint(new Color(0xC5B300), 40.0F) @@ -67,4 +67,20 @@ public class ColorMaps { .addPoint(new Color(246, 53, 53), 200.0F) .addPoint(new Color(240, 53, 246), 250.0F) .build(new Color(255, 255, 255), 300.0F); + + /** + * A {@link ColorMap} equivalent to {@link ColorTables#getIR(float)} + * @since 0.15.0.0 + */ + public static final ColorMap IR = ColorMap.Builder.of(Color.BLACK) + .addPoint(Color.WHITE, 100.0F) + .addPoint(new Color(0, 16, 116), 120.0F) + .addPoint(new Color(105, 248, 251), 140.0F) + .addPoint(new Color(0, 253, 0), 150.0F) + .addPoint(new Color(253, 251, 71), 160.0F) + .addPoint(new Color(235, 55, 23), 180.0F) + .addPoint(new Color(110, 26, 10), 200.0F) + .addPoint(Color.BLACK, 220.0F) + .build(Color.WHITE, 260.0F); + } diff --git a/src/main/java/net/nullved/pmweatherapi/util/StormType.java b/src/main/java/net/nullved/pmweatherapi/util/StormType.java new file mode 100644 index 0000000..4413a57 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/util/StormType.java @@ -0,0 +1,34 @@ +package net.nullved.pmweatherapi.util; + +import dev.protomanly.pmweather.weather.Storm; + +public enum StormType { + SUPERCELL(0), + TORNADO(0, 3), + SQUALL(1), + CYCLONE(2); + + public final int idx, stage; + + StormType(int idx, int stage) { + this.idx = idx; + this.stage = stage; + } + + StormType(int idx) { + this.idx = idx; + this.stage = -1; + } + + public int idx() { + return idx; + } + + public int stage() { + return stage; + } + + public boolean matches(Storm storm) { + return storm.stormType == idx && storm.stage >= stage; + } +} From 4151ffc239a8dc98f797f74eb64f28e2cab6aec7 Mon Sep 17 00:00:00 2001 From: RealDarkStudios <94769968+RealDarkStudios@users.noreply.github.com> Date: Sun, 20 Jul 2025 09:22:21 -0500 Subject: [PATCH 2/3] 0.15.1.0 - Update to PMWeather 0.15.1 - Made 0.15.X.X able to load with any PMWeather 0.15.X version Took 25 minutes --- build.gradle | 3 ++- gradle.properties | 5 +++-- src/main/templates/META-INF/neoforge.mods.toml | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index ff80184..333d29d 100644 --- a/build.gradle +++ b/build.gradle @@ -147,7 +147,8 @@ var generateModMetadata = tasks.register("generateModMetadata", ProcessResources mod_version : mod_version, mod_authors : mod_authors, mod_description : mod_description, - pmweather_version : pmweather_version] + pmweather_version : pmweather_version, + pmweather_version_range: pmweather_version_range] inputs.properties replaceProperties expand replaceProperties from "src/main/templates" diff --git a/gradle.properties b/gradle.properties index c8b93fc..2c38b49 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,10 +17,11 @@ parchment_mappings_version=2024.11.17 mod_id=pmweatherapi mod_name=PMWeatherAPI mod_license=GNU GPL 3.0 -mod_version=0.15.0.0+rc1 +mod_version=0.15.1.0 mod_group_id=net.nullved mod_authors=NullVed mod_description=An API for interfacing with ProtoManly's Weather Mod # Dependencies -pmweather_version=0.15.0 \ No newline at end of file +pmweather_version=0.15.1 +pmweather_version_range=[0.15.0-1.21.1-alpha,0.15.999-1.21.1-alpha) \ No newline at end of file diff --git a/src/main/templates/META-INF/neoforge.mods.toml b/src/main/templates/META-INF/neoforge.mods.toml index 5138c4a..16b6f88 100644 --- a/src/main/templates/META-INF/neoforge.mods.toml +++ b/src/main/templates/META-INF/neoforge.mods.toml @@ -38,7 +38,7 @@ side = "BOTH" [[dependencies."${mod_id}"]] modId = "pmweather" type = "required" -versionRange = "[${pmweather_version}-${minecraft_version}-alpha]" +versionRange = "${pmweather_version_range}" ordering = "BEFORE" side = "BOTH" From 9c803fd65e68c71fafb94527c47ed8cc64a8d26f Mon Sep 17 00:00:00 2001 From: RealDarkStudios <94769968+RealDarkStudios@users.noreply.github.com> Date: Mon, 21 Jul 2025 09:59:25 -0500 Subject: [PATCH 3/3] v0.15.1.0 - Add some missing docs - Add docs for ColorMap#getWithBiome - Add docs for ColorMap.Builder#biome - Add docs for StormType Took 23 minutes --- .../nullved/pmweatherapi/util/ColorMap.java | 13 ++++++++++-- .../nullved/pmweatherapi/util/StormType.java | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java b/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java index bf7e50d..4b9bc58 100644 --- a/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java +++ b/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java @@ -93,8 +93,16 @@ public Color get(float val) { return lookup[idx]; } + /** + * Retrieves the color value using a color derived from the biome + * @param val The value to get a color for + * @param biome The biome to derive the starting color from + * @param worldPos The world position (for grass color checks) + * @return The approximate color for this value + * @since 0.15.0.0 + */ public Color getWithBiome(float val, Holder biome, Vec3 worldPos) { - Color startColor = Color.BLACK; + Color startColor; String rn = biome.getRegisteredName().toLowerCase(); if (rn.contains("ocean") || rn.contains("river")) startColor = new Color(biome.value().getWaterColor()); else if (rn.contains("beach") || rn.contains("desert")) { @@ -191,7 +199,8 @@ public static Builder of(Color base) { } /** - * Creates a new {@link ColorMap.Builder} that has a default {@link Color#BLACK} base + * Creates a new {@link ColorMap.Builder} that has a default {@link Color#BLACK} base. + * This method has no effect unless you use {@link ColorMap#getWithBiome(float, Holder, Vec3)} * @return The created {@link ColorMap.Builder} * @since 0.15.0.0 */ diff --git a/src/main/java/net/nullved/pmweatherapi/util/StormType.java b/src/main/java/net/nullved/pmweatherapi/util/StormType.java index 4413a57..c63135d 100644 --- a/src/main/java/net/nullved/pmweatherapi/util/StormType.java +++ b/src/main/java/net/nullved/pmweatherapi/util/StormType.java @@ -2,6 +2,10 @@ import dev.protomanly.pmweather.weather.Storm; +/** + * An enum holding different storm types + * @since 0.15.0.0 + */ public enum StormType { SUPERCELL(0), TORNADO(0, 3), @@ -20,14 +24,31 @@ public enum StormType { this.stage = -1; } + /** + * Gets the index of the {@link StormType} + * @return The {@link StormType} index + * @since 0.15.0.0 + */ public int idx() { return idx; } + /** + * Gets the minimum stage of the {@link StormType} + * @return The minimum stage of the {@link StormType} + * @since 0.15.0.0 + */ public int stage() { return stage; } + /** + * Determines if the {@link Storm} meets this {@link StormType}'s specification. + * If this {@link StormType} defines a `stage` (such as {@link StormType#TORNADO}), the {@link Storm} must be equal to or above that stage + * @param storm The {@link Storm} to check + * @return {@code true} if this {@link Storm} meets the {@link StormType} specification + * @since 0.15.0.0 + */ public boolean matches(Storm storm) { return storm.stormType == idx && storm.stage >= stage; }