diff --git a/.gitignore b/.gitignore index 34bb2ac0..35ecb533 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ .tmp_dynamictrees/ .tmp_dynamictrees_full/ +/pa_realistic_cloud_renderer_design.md diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 6e1c8b6d..00000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,1256 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index cf338f05..5e874a20 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,7 @@ This repository hosts **Project Atmosphere**, a Minecraft Forge 1.20.1 mod writt - Keep braces on the same line as declarations (`if (...) {`). - Ensure files end with a newline. - When finished, do the summary inside CHANGES.md. if you added functionality, fixed bugs, or made other notable changes. +- For iterative rendering, compatibility, or crash investigations, log each attempted fix and result in a Markdown investigation log before trying another fix. Check that log first so failed approaches are not repeated. ## Build / Checks - The project uses Gradle. The wrapper is not included, so use the system `gradle` command. diff --git a/CHANGES.md b/CHANGES.md index 8b6f32e9..c0e87815 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,261 @@ # Project Atmosphere — Developer Change Log This file records functionality additions/removals made during development sessions, annotated with the current version from `gradle.properties` at the time of change. +## Unreleased - AMD and Intel Simple Clouds compatibility +- Moved Simple Clouds tornado cloud-carving metadata from the `CloudStorms` SSBO to uniform arrays capped at 16 tornadoes, avoiding an extra SSBO dependency for the tornado path on strict AMD and Intel OpenGL drivers. +- Disabled Project Atmosphere's Simple Clouds storm SSBO allocation on GPUs exposing 16 or fewer shader storage buffer bindings, falling back to uniform tornado cloud carving and disabling hurricane cloud shaping instead of crashing. +- Added `storms.tornado.disableSimpleCloudsTornadoSSBO` so users can force the safer no-SSBO Simple Clouds storm integration path when diagnosing GPU driver issues. + +## Unreleased - Ecliptic Seasons forecast integration +- Routed forecast/debug temperature season reads and Simple Clouds rain lifecycle notifications through the shared `SeasonTimeHelper` delegate instead of calling Serene Seasons directly. +- Preferred Ecliptic Seasons when both Ecliptic and Serene are loaded, and fixed the Ecliptic delegate's cycle tick reporting so forecast temperature curves advance from the current solar term position instead of a constant season duration. +- Moved Serene-specific season-change callbacks into the Serene season integration bridge and stopped registering them from the main mod bootstrap. + +## Unreleased - Launcher auth guard +- Ported the Identity2 launcher auth guard into Project Atmosphere with a Forge SimpleChannel challenge/reply flow, client-side TLauncher marker detection, strict offline UUID rejection, timeout kicking, and player/IP ban handling when the marker is reported. + +## Unreleased - Tornado render performance pass +- Made the non-DH tornado downsample composite stamp sampled tornado depth back into the full-resolution cloud target, preventing later terrain/depth composition from cutting through the already-drawn funnel. +- Moved non-DH downsample terrain occlusion from the low-resolution raymarch into the full-resolution composite, preserving the FPS path while preventing one low-res terrain-depth sample from cutting large chunks out of the funnel. +- Re-enabled non-DH tornado downscaling as a color-only resolution reduction: default/shader-support paths now always use copied transparency depth, while the low-resolution intermediate target stores straight alpha before compositing so non-DH funnels do not disappear from pre-weakened alpha. +- Moved the Simple Clouds tornado volume pass onto a configurable low-resolution render target with an upsample composite shader, defaulting to a 2.5x downsample so tornado raymarching shades far fewer pixels on mid-range GPUs. +- Fixed the downsampled tornado pass to set the viewport to the low-resolution target before raymarching and restore the cloud-target viewport before compositing, preventing camera-dependent tornado placement. +- Disabled the tornado downsample path automatically under Distant Horizons so the tornado follows the same full-resolution depth path Simple Clouds uses for its own DH cloud rendering. +- Softened near-terrain tornado depth rejection and biased close dusty intersections in front of the scene depth so ground-contact funnels no longer get visibly cut open by nearby terrain. +- Expanded in-tornado dusty volume/fog when the camera is inside or near the funnel and added a sparse client-only falling dust curtain on the far side of tornadoes. +- Removed camera-dependent funnel widening so tornado visual size stays fixed by tornado strength/radius, then increased ground-contact padding and inside-funnel whiteout so tornadoes touch terrain more reliably and feel dustier from within. +- Corrected inverted ground-contact height math, anchored the visual tornado base near sampled terrain, added a dense low dust skirt at touchdown, and pushed inside-tornado fog toward a short dusty brown-gray whiteout. +- Separated the tornado shader contributions into distinct wallcloud, connection, ground skirt, and main funnel paths, added a `groundskirt` render debug mode, reduced the terrain depth bias to a small local contact helper, and removed the downsample composite's alpha unpremultiply so semi-transparent dark pixels are no longer exaggerated. +- Changed debug-mask tornado modes to render as opaque field overlays so `density`, `wallcloud`, `connection`, and `groundskirt` remain readable even under Distant Horizons' alternate depth path. +- Changed tornado volume rendering to use depth-aware `LEQUAL` proxy rendering, cull proxy backfaces when the camera is outside the volume, and sample scene depth before raymarching so occluded pixels skip the expensive storm loop earlier. +- Reduced tornado raymarch and first-hit refinement work, tightened the shader influence radius, and shrank the Java-side proxy bounds so the shader spends less time marching empty air around the funnel. +- Removed the tornado transparency mixin hooks because the transparency renderer was a no-op but still copied depth and rebound targets every visible tornado frame. + +## Unreleased - Storm spawn and despawn transitions +- Kept tornado command spawns in the forming lifecycle instead of forcing the no-cloud path active immediately, so standalone tornadoes now ease in and the removal command lets them dissipate before cleanup. +- Added a hurricane lifecycle with forming, active, and dissipating phases, then kept cyclone-linked hurricanes alive until the fade-out completes instead of dropping them the moment the cyclone disappears. +- Carried hurricane render intensity through the network snapshots and client cache so the custom hurricane volume can grow in and contract out instead of popping in at full size. +- Kept hurricane block destruction gated behind `enableHurricaneDestruction`, so the new lifecycle does not bypass the existing config-driven protection. +- Added `enableTornadoDestruction` and wired it through the config screen, tornado demolition sweep, and tornado glass break queue so tornado block breaking can be disabled separately from tornado spawning. +- Renamed generic shader `noise3` helpers to Project Atmosphere-specific names in the tornado and hurricane fragment shaders to avoid GPU driver GLSL overload conflicts during shader registration. +- Made the Simple Clouds tornado and hurricane render mixin hooks tolerant of released/custom pipeline differences by targeting the cloud final composite pass for transparency and preventing missing optional hooks from crashing the client. +- Merged tornado and hurricane Simple Clouds compute data into a single lazy `CloudStorms` SSBO so Project Atmosphere no longer exhausts 16-slot SSBO binding limits during Simple Clouds startup. +- Capped Simple Clouds 0.7.4 lightning SSBO buffering to the available positive binding slots and reserved binding `0` for Project Atmosphere's storm compute data on 16-binding GPUs. +- Targeted the Simple Clouds lightning buffer cap at both the named and SRG reload method names so it applies in packaged CurseForge/Forge client jars, not only the development runtime. + +## Unreleased - Hurricane destructive behavior +- Split hurricane server-side interaction into `HurricaneWindField`, `HurricaneDestructionManager`, and `HurricaneBlockBreakRules`, so `HurricaneInstance` now only delegates wind and destruction work during its tick. +- Reworked hurricane entity forces into a rotating wind field with tangential circulation, inward pull toward the eye, light lift, and ambient storm drift scaling from distance to center and storm intensity. +- Added limited tag-driven hurricane block destruction with explicit protection rules: fragile vegetation can break naturally around the eyewall, leaves can strip more often than logs, trees stay optional behind config, and terrain, ores, portals, command blocks, chests, block entities, and protected areas are excluded. +- Added the `projectatmosphere:hurricane_fragile`, `projectatmosphere:hurricane_tree_damage`, and `projectatmosphere:hurricane_never_break` block tags plus four user-facing common config options: `enableHurricaneDestruction`, `hurricaneDestructionStrength`, `hurricaneDropBrokenBlocks`, and `hurricaneDamageTrees`. + +## Unreleased - Distant Horizons storm volume rendering fix +- Fixed the current non-DH no-render regression by disabling the Simple Clouds pipeline frustum as a hard gate for Project Atmosphere tornado volume draws after diagnostics showed valid prepared tornadoes were being rejected before the renderer reached the shader. +- Added rate-limited `[TornadoPath]` diagnostics for the non-DH Simple Clouds tornado hooks and renderer early-return/draw-state decisions so the current non-DH no-render regression can be isolated before further render changes. +- Bound the tornado shader to Simple Clouds' DH-filled cloud target depth in the DH render path so the shader samples the same depth buffer that the cloud framebuffer uses for depth testing. +- Removed the tornado's secondary depth sampler from the DH render path so the shader no longer mixes in cloud-transparency depth while deciding terrain contact. +- Added a `depth` tornado render debug mode that colors shader depth acceptance, scene-depth rejection, low-alpha rejection, and missing-density paths for DH diagnosis. +- Added a `depth_nofb` tornado render debug mode that uses the same shader-side depth colors while disabling framebuffer depth testing, allowing DH tests to distinguish fixed-function depth rejection from shader raymarch/discard rejection. +- Added a `depth_mainfb` tornado render debug mode that temporarily restores Minecraft's main depth attachment for the DH tornado draw, allowing comparison against Simple Clouds' temporary `cloudTarget` depth attachment. +- Added an `occlusion` tornado render debug mode that detaches framebuffer depth and colors whether vanilla main depth, Simple Clouds/DH cloud depth, both, or neither would occlude each tornado pixel. +- Added a `late` tornado render diagnostic mode that skips the Simple Clouds DH hook and draws the tornado at Forge `AFTER_LEVEL`, testing whether terrain is being composited over the earlier DH tornado pass. +- Added a `coverage` tornado render debug mode that colors whether the single late DH pass is missing depth, below the raw-alpha cutoff, inside the DH opacity ramp, solid body coverage, or rejected by scene depth. +- Promoted the late `AFTER_LEVEL` path to the normal Distant Horizons tornado render path after diagnostics showed terrain was compositing over the earlier Simple Clouds DH hook, while keeping explicit depth debug modes on the old hook. +- Made the Distant Horizons tornado render path single-stage by routing debug and normal tornado draws through Forge `AFTER_LEVEL`, removing the old DH post-composite tornado draw, and guarding default/shader Simple Clouds hooks while DH is loaded. +- Limited the late Distant Horizons tornado pass to Minecraft's main scene depth sampler after `coverage` debug showed the remaining seam was shader scene-depth rejection, preventing Simple Clouds' older cloud/DH depth target from falsely cutting the funnel. +- Reintroduced tornado downscaling for the corrected Distant Horizons late pass by raymarching into the low-resolution tornado target and compositing directly back into the same final framebuffer, while keeping debug modes full-resolution. +- Added an alpha-aware 9-tap tornado upsample filter for the downsample composite so aggressive tornado downscaling keeps most of its FPS gain without exposing a blocky low-resolution alpha grid. +- Removed tornado camera whiteout from the fog handler while keeping normal cloud/dynamic fog behavior. +- Split DH and non-DH tornado shader behavior: DH keeps the corrected late main-depth path, while normal non-DH rendering uses the shader's `full` path without enabling Java debug filtering. +- Disabled tornado downscaling for the non-DH forced-full render path so vanilla rendering matches explicit `full` mode more closely, while keeping the confirmed DH late-path downscaling enabled. +- Kept non-DH tornado rendering on the confirmed full-resolution forced-full path after the downsample reintroduction hid the tornado, while preserving DH late-path downscaling. +- Restored the non-DH Simple Clouds hook depth-source behavior from the last known-good state, so the hook still passes cloud-target depth when Simple Clouds reports the tornado path as downsample-capable. +- Fixed DH pipeline selection to use Simple Clouds' active `dhLoaded()` state instead of only checking whether the Distant Horizons mod is installed, preventing the DH pipeline from being forced while the late DH tornado pass is disabled. +- Made tornado debug modes render all prepared tornadoes if no debug storm selection resolves instead of filtering the render order to zero storms. +- Strengthened the DH-only accumulated-body alpha floor so already-rendered far terrain lines do not bleed through dense tornado body pixels after the late render-order fix. +- Tightened the DH-only low-alpha discard and body opacity curve so thin surviving tornado pixels no longer reveal a hard sky/terrain background seam after the late render pass. +- Removed ground-skirt and broad dust-only contributions from the tornado shader's primary cloud density so local ground effects no longer create a large flattened volumetric blob beneath the funnel. +- Added a DH-only tornado alpha floor for depth-accepted body pixels so Simple Clouds' color-only final composite does not let terrain color bleed through the funnel. +- Moved the DH tornado volume draw to Simple Clouds' post-composite main-framebuffer depth-attachment window so it renders with the same DH-filled `cloudTarget` depth Simple Clouds uses for DH-aware world effects, while sampling the detached vanilla main depth texture in the shader so near terrain still occludes the tornado. +- Added tornado shader binding diagnostics that log the live shader name, GL program id, fragment program id/name, sampler names/locations, and requested depth texture ids so DH RenderDoc captures can be matched against the actual `ShaderInstance` state. +- Added GL debug groups around the tornado opaque render pass so RenderDoc and Nsight can isolate the storm frame more easily. +- Added frustum visibility gating to the tornado Simple Clouds pipeline hooks so off-screen tornadoes skip the depth-copy and volume draw work instead of submitting the pass whenever a tornado exists. +- Kept the tornado frustum gate disabled in the Simple Clouds Distant Horizons path because that pipeline's frustum can reject the PA tornado volume even when the storm should render. +- Stopped applying extra ground-contact extension when tornado terrain probing falls back due to missing client samples; in that case the renderer now trusts the synced tornado base instead of inventing a synthetic lower base and opening a visible gap to the touchdown. +- Reworked the non-DH tornado touchdown shaping so extra ground contact comes from dedicated touchdown and lower-stem density terms instead of shifting the main funnel cutoff, avoiding the slab-like lower-body regression while building a denser bridge from the ground contact back into the main funnel. +- Tightened the lower tornado stem profile after the first bridge pass oversized the touchdown, keeping the ground connection continuous while tapering the base back toward a funnel shape instead of a bulb. +- Replaced the separate lower touchdown/stem lobe with a continuous lower-funnel radius profile anchored to the real tornado base, so the column now stays one coherent funnel instead of faking ground contact with a distinct bottom blob. +- Grounded tornado `visualBottomY` from the server terrain heightmap at spawn time and during movement, so the synced funnel base follows actual terrain instead of staying locked to whatever `pos.y` the storm happened to spawn with. +- Removed the tornado shader's touchdown-progress-based lower cutoff lift, so the main funnel body now starts at the grounded base instead of visually hovering well above it even when the synced bottom is correct. +- Removed the tornado upper-body color lift that blended the funnel toward a brighter cloud tint, so the visible column now stays on the same body color instead of picking up a lighter sky-like veil. +- Removed the tornado body color's dependence on `CloudColor`, switching the funnel itself to a neutral dark body tone so thin upper/edge regions no longer inherit a blue sky hue from the cloud tint. +- Removed `BiomeChangeManager`'s nested `RegionTrack` record from the player tick path and replaced it with a direct `Pair` state entry, avoiding the runtime nested-class load that was causing `NoClassDefFoundError: BiomeChangeManager$RegionTrack`. +- Rebalanced the tornado output alpha so thin regions can read less transparent without crushing the whole funnel darker; the final displayed alpha is now boosted separately from the raw accumulated body color normalization. +- Removed the experimental hard alpha clamp after it started rendering nearly empty tornado fragments as a full proxy-volume wall; the tornado now uses a curve-based alpha boost with no synthetic opacity floor, which keeps terrain visible and prevents full-screen slabs. +- Raised the tornado's final raw-alpha discard threshold and tied surviving pixels to real first hits before output, which cuts off the lingering distant proxy-volume sheet while allowing the actual funnel body to use a stronger opacity curve. +- Increased tornado opacity specifically in the upper funnel/wallcloud contribution by scaling per-step alpha from `storm.upper`, so the inverted-cone top reads as a solid storm body without thickening the whole lower funnel. +- Removed repeated hurricane semantic snapshot/shape list allocation from the hot path by caching interpolated client semantic snapshots per tick/partial tick and iterating hurricanes/snapshots directly in `HurricaneSemantics` instead of rebuilding `stream().map(...).toList()` lists on every query. +- Filtered out obviously invalid min-build heightmap samples from tornado terrain contact probing and clamped the resolved funnel base to stay near the sampled surface, preventing intermittent `-65` terrain spikes and keeping the tornado base from being buried below uphill terrain. +- Limited tornado secondary depth sampling to the default Simple Clouds pipeline where the cloud target lacks copied world depth; the DH and shader-support paths now rely on their copied cloud/world depth buffer directly instead of also comparing against the main framebuffer depth. +- Fixed the tornado depth source in the Simple Clouds volume pass by snapshotting cloud/depth state into the transparency target before rendering the tornado and sampling that copied depth texture, instead of sampling the same depth attachment the tornado pass is actively writing to. +- Stopped the Simple Clouds tornado volume renderer from inventing a second ground-contact offset below the synced tornado base; it now uses the server/client `renderBottomY` directly and logs `baseOffsetWorld`, which fixes funnels starting around 11-12 blocks under terrain even when the sampled terrain height is correct. +- Refined tornado first-hit depth writes by binary-searching the entry point of the occupied raymarch segment instead of dropping depth several blocks into the funnel body, restored the explicit scene-depth rejection for the `GL_ALWAYS` proxy pass, and added projected-vs-scene depth diagnostics so DH depth mismatches can be verified from the log. +- Made the tornado client ground-contact sampler ignore unloaded client columns and fall back to the server-synced tornado base when DH-visible terrain is outside vanilla client chunk ownership, preventing bogus `-65` terrain heights from stretching the funnel far below the real ground. +- Hooked the dedicated tornado volume render pass into Simple Clouds' `DhSupportPipeline.afterDistantHorizonsRender`, so PA tornado shaders render into the DH cloud framebuffer path instead of only the default/shader-support pipelines. +- Switched hurricanes back to the Simple Clouds compute-mesh integration path by restoring the `CloudHurricanes` uploader/chunk scheduler and disabling the standalone hurricane volume pipeline hooks. +- Made tornado volume rays screen-space stable and increased optical thickness so funnels read as depth-bearing storm bodies instead of flat hatched overlays, without relying on front-face culling that can hide the proxy volume on some pipelines. +- Synced the active `projectatmosphere` compute shader copies with the Simple Clouds-engine storm shaders, so the redirected cloud-region and cube-mesh programs expose the `CloudTornadoes`/`CloudHurricanes` SSBOs and uniforms they upload. +- Select Simple Clouds' DH support pipeline through `DetermineCloudRenderPipelineEvent` when Distant Horizons is installed, ensuring PA tornado and hurricane rendering use the DH-compatible path before mesh generation and render preparation run. +- Stopped the tornado volume raymarch from hard-clipping to copied scene depth, relying on the shader-written first-hit depth for occlusion instead so ground contact is not camera-dependent. +- Kept hurricane mesh samples opaque near/inside the camera by bypassing Simple Clouds' near-origin fade for hurricane volumes, and rotated the whole hurricane density field slowly around the storm center. +- Let tornado proxy-volume fragments run regardless of proxy-box depth, then manually discard only when the first real funnel hit is behind scene depth, preventing terrain behind the storm from cutting off ground contact. +- Stabilized hurricane visibility during camera movement by disabling Simple Clouds chunk dither-fade while hurricanes are active and generating all exposed hurricane mesh faces instead of camera-origin-selected faces. +- Restored terrain-driven tornado contact extension so the volumetric funnel is anchored back down to the sampled surface instead of floating at the synced storm base. +- Gave the tornado shader separate cloud/DH and main-world depth inputs, allowing it to occlude correctly against both DH LOD depth and vanilla chunk depth in the same frame. + +## Unreleased - Hurricane reservation cache fix +- Cached client-side hurricane reservation `CloudRegion` objects instead of recreating them on every lookup, which should cut the runaway allocation pressure that was showing up after the tornado-to-hurricane merge. +- Moved the client hurricane render hooks over to the cached snapshot path so the renderer stops walking the live hurricane manager state every frame. + +## Unreleased - IDE workspace cleanup +- Stopped tracking `.idea/workspace.xml` in Git so local IntelliJ workspace state stays local instead of showing up as a repo change. + +## Unreleased - Hurricane custom volumetric cloud rewrite +- Abandoned the hurricane fake `CloudRegion` ring path and restored a dedicated one-object hurricane render path, so hurricanes no longer depend on injecting several regular Simple Clouds formations into the world cloud list. +- Added an explicit `HurricaneCloudVolume` representation for the custom hurricane cloud body and re-enabled the dedicated pipeline mixins that render hurricanes as bounded world-space volumetric formations. +- Reworked hurricane shape generation so the cloud body now comes from a torus-based volumetric density field in the hurricane shaders, with spiral band and veil layers built around that single density volume instead of approximating the shape through grouped cloud instances. +- Removed the synthetic hurricane cloud-region helpers and cloud-list injection mixin that were previously faking the donut structure through multiple managed cloud objects. + +## Unreleased - Hurricane visibility logging and tornado log cleanup +- Removed the automatic tornado runtime, demolition, and scheduled tornado-check log spam so tornado debugging stays on-demand instead of constantly filling `latest.log`. +- Added targeted hurricane debug logging for client snapshot receipt, client synthetic-cloud cache state, and final Simple Clouds cloud-list injection, making it easier to see whether hurricanes are syncing, generating synthetic cloud regions, and actually entering the cloud pipeline. +- Removed the recurring forecast/region debug spam from forecast updates, client forecast readiness, and cloud-region sampling/cover updates so region forecasting no longer floods `latest.log` every few ticks. +- Synced managed synthetic hurricane regions back into the base `CloudRegion` position/radius/transform state that Simple Clouds uses during cloud lookup and mesh-generation heuristics, fixing hurricanes that were injected into the cloud list but still resolved as visually empty. +- Removed the remaining automatic tornado render-hook and tornado packet debug logs so `latest.log` stays focused on hurricane visibility diagnostics instead of unrelated tornado spam. +- Changed hurricanes to resolve their inner/outer cloud types from the active Simple Clouds cloud source instead of relying on hardcoded cloud IDs, and extended hurricane debug logs to report the active cloud mode, indexed cloud-type count, and the resolved synthetic cloud types. +- Reworked the hurricane storm-cloud field to use fewer, much larger Simple Clouds formations that better match the engine's coarse chunk-generation heuristics, and prioritized the nearest client hurricane when capping synthetic cloud formations to the Simple Clouds region limit. + +## Unreleased - Hurricane cloud-pipeline refactor +- Replaced the hurricane's standalone PA volume renderer path with a Simple Clouds cloud-data integration, so hurricanes are now injected into `CloudManager.getClouds()` as synthetic storm cloud regions and render through the normal Simple Clouds mesh generator, chunk, shader, fog, depth, and lighting pipeline. +- Added a shared parametric storm-cloud field system with reusable parameters for radius, eye radius, band count, band width, rotation speed, density falloff, vertical thickness, noise scale, and spiral tightness, then used it to generate hurricane eyewall and spiral-band cloud cells instead of hardcoded hurricane mesh volumes. +- Added managed synthetic cloud-region wrappers plus hurricane-side cloud caches on both logical sides, allowing client hurricanes to rebuild matching storm cloud cells from PA snapshots while server queries and client rendering see the same hurricane-shaped cloud field through the normal Simple Clouds cloud getter path. +- Disabled the old hurricane pipeline mixins so the custom hurricane render pass no longer runs, and updated client cloud culling to operate on the combined cloud list that now includes injected hurricane storm cells. + +## Unreleased - Tornado movement refactor +- Replaced the tornado's old per-tick heading recomputation with a persistent route-planning system that stores a waypoint, target heading, target speed, and route duration, so the server now chooses a path ahead of time and commits to it for several seconds instead of zigzagging every tick. +- Refactored `StormMotionModel` so tornado movement planning now happens as a low-frequency route selection pass, while `TornadoInstance` performs only smooth per-tick heading blending, speed blending, and one final authoritative motion application. +- Added leash-aware waypoint planning plus light shield-avoidance shortening, so tornadoes keep a coherent travel path, curve naturally into new routes, and stop stalling or visually shaking from unstable steering corrections. + +## Unreleased - Command and utility fixes +- Refined tornado removal commands so `/pa removetornado` now removes the nearest tornado within a sane default range, supports an optional radius argument, and has a new spaced alias at `/pa remove tornado`; both also support `all` to clear every tornado quickly. +- Added a proper craftable recipe for the storm shield weather deflector, plus the missing blockstate, block model, item model, and lang entries so it can be crafted and shown in inventory without missing-model warnings. +- Kept the storm siren craftable and added the missing display-name lang entry so it shows up correctly in-game. + +## Unreleased - Current systems recap +- Reworked Rainbows and Auroras compatibility so both effects now use Project Atmosphere as the main sky-state authority with shared humidity, cloud-cover, recent-rain, clarity, smoothing, and hysteresis logic. +- Added a humidity-driven fog system with synced atmospheric state, wet-biome heuristics, in-game tuning, and debug commands so fog reacts to PA moisture and rain instead of isolated vanilla checks. +- Rebuilt tornado rendering and gameplay around grounded world-space volumes, longer-lived storm lifecycles, stronger entity interaction, configurable debug tooling, and server-side destruction/debris behavior. +- Added the dedicated hurricane volumetric renderer/mesh work so large storms now render as bounded atmospheric structures instead of flat overlay rings. +## Unreleased - Aurora and Rainbows compatibility refactor +- Replaced the old Rainbows compatibility path that only watched rain-stop timing with a Project Atmosphere-driven client sky controller that evaluates humidity, recent rainfall, cloud breakup, sun visibility, sun angle, and hysteresis before exposing a smoothed rainbow state to the Rainbows renderer. +- Replaced the old Auroras compatibility path that only scaled vanilla brightness with the same shared Project Atmosphere sky controller, now driving aurora visibility from PA cloud cover, atmospheric clarity, humidity haze, night timing, biome temperature, and seasonal context instead of isolated vanilla checks. +- Added shared atmosphere status syncing plus a client atmospheric state cache/smoother so Rainbows, Auroras, and fog all consume the same humidity, rain, and cloud-cover authority from Project Atmosphere rather than maintaining duplicated packet trackers. +- Removed obsolete compatibility duplication by deleting the legacy rain-only rainbow bridge and folding the old rain/fog packets into one atmosphere status packet with thin mod-specific hooks. +## Unreleased - Tornado interaction rebuild +- Replaced the old tornado gameplay force model with a new server-side capture system built around three explicit components: inward suction toward the funnel, tangential orbit around the core, and strong upward lift once a target is captured, so players and entities spiral upward instead of just being shoved sideways. +- Reworked captured-entity handling so nearby mobs and other entities use the same funnel capture/orbit/lift path as players, including deterministic rotation direction, motion damping tuned for spiral ascent, and direct motion packet syncing for server players. +- Replaced the previous tornado destruction sweep with a new active funnel-zone block destruction pass that scans the actual destructive cylinder, tears through tree clusters with connected log/leaf breaking, and more aggressively breaks vegetation, weak terrain, fragile structures, grass, and glass on the server. +- Rebalanced tornado terrain scouring so the sweep now stays at the surface layer instead of digging down into multiple dirt layers, prefers turning grass and soil variants into plain dirt, and only rarely excavates topsoil outright during extreme core-strength hits. +- Added capped tornado falling-block debris spawns for destroyed wood, leaves, surface soil, and loose terrain blocks, so more of the tornado's block damage becomes visible moving debris caught in the funnel instead of disappearing instantly. +- Fixed standalone `spawnTornadoNoClouds` crashes in the Simple Clouds integration by treating cloudless tornadoes as position-based when boosting nearby cloud regions instead of blindly dereferencing a missing `CloudRegion`. +- Removed the old `WindForces` tornado push path for players so the legacy slowdown/side-push model no longer fights the new server-side tornado capture system. +- Standalone no-cloud tornado spawns now begin immediately in the active phase, and tornado interaction/destruction logic now starts during any non-terminal phase once intensity is high enough instead of waiting for the old active-only gate. +## Unreleased - Local Simple Clouds dependency +- Replaced the remote `nonamecrackers2:simpleclouds:0.7.3+1.20.1-forge` dependency with the local `libs/simpleclouds-0.7.4+1.20.1-forge-all.jar` artifact in `build.gradle`. +## Unreleased - Project Atmosphere crash screen +- Added a client-side Project Atmosphere crash interception flow that detects whether a thrown exception or crash report stack involves `net.Gabou.projectatmosphere`, saves a crash report into `crash-reports`, unloads the active client session when possible, and opens a dedicated support screen instead of falling straight into the normal fatal client crash path. +- Added a dedicated crash screen with a clear Discord support message plus buttons to close the game, copy a support summary to the clipboard, and open the Project Atmosphere Discord invite in the browser. +- Hooked the handler into the Forge 1.20.1 client loop via a new Minecraft mixin that wraps `runTick(...)` and intercepts delayed crash reports. +## Unreleased - MCP support bundle +- Added a read-only support bundle under `docs/mods/projectatmosphere/` and `data/mcp/mods/projectatmosphere/`, covering overview/install/troubleshooting/FAQ docs plus structured manifest, features, commands, config schema, compatibility, known issues, versions, and support-focused changelog data derived from repository sources. +- Separated strictly official compatibility notes from code-reviewed runtime behavior so support tooling can answer conservatively when release documentation and current source behavior differ. +## Unreleased - Standalone hurricane cloud renderer tranche +- Added a client tornado render-quality control and moved the tornado shader onto an adaptive LOD path that lowers raymarch step count, trims expensive material/noise work, and skips unnecessary inner-funnel sampling when the storm is far away or the user lowers tornado quality for better FPS. +- Darkened the tornado's upper wall-cloud/connection shading again so the top mass reads denser and less washed out against the surrounding storm canopy. +- Reworked tornado interaction gameplay so active funnels now demolish trees, leaves, vegetation, weak structures, grass, and loose terrain much more aggressively, and strengthened entity suction/capture so nearby mobs and players are pulled harder into the circulation and can take storm damage while trapped in the core. +- Replaced the tornado's deferred block-destruction queueing with a direct server-thread column/cluster sweep around the funnel, and flagged all affected entities for motion sync so non-player mobs and entities now receive the same tornado pull/lift updates instead of only the player visibly reacting. +- Added live tornado runtime instrumentation plus `weatherdebug tornado runtime` and `weatherdebug tornado logging `, exposing real in-game pull force, upward force, eligible/captured entity counts, destruction sweep radius, candidate block counts, and destroyed block counts so tornado physics and block breaking can be debugged from actual runtime data instead of inferred behavior. +- Replaced the fullscreen tornado volume pass with a world-space proxy-box volume draw, so each tornado is now rasterized at a fixed in-world position and the shader uses scene depth only as a march limit instead of as the thing defining the effect. +- Mirrored the same proxy-box world-space volume approach into the hurricane cloud and fringe passes, keeping the existing storm shapes while removing the old fullscreen “mirage/post-process” anchoring behavior from the actual volume render path. +- Added a gated `/pa debug tornado render ...` investigation path with per-storm selection, frozen deterministic funnel sampling, grayscale mask modes (`aabb`, `funnel`, `height`, `radial`, `radius`, `density`, `alpha`, `wallcloud`, `connection`, `full`), and structured CPU-side diagnostics that log the tornado origin, sample position, local-space values, bounds/scaling, and active render state for root-cause analysis. +- Extended the tornado proxy debug path with ordered proof modes for the world-space renderer: `box` draws the proxy volume as a solid opaque color, `hit` shows ray-box intersection success, `fill` renders a constant-density box, and only the later modes reintroduce tornado shaping before normal depth stopping is enabled again. +- Fixed the shared world-space proxy-box draw submission so tornado and hurricane volume boxes now render with the real cloud-pass model-view and projection matrices instead of identity matrices, which could make even forced-opaque `box` mode disappear entirely. +- Darkened the tornado’s upper connection/top shading by about 30% and doubled the existing twist rotation speed in the shader, keeping the current silhouette and animation style while making the top feel denser and the spin read more strongly. +- Removed the client tornado debris particle spawn loop entirely and increased the tornado shader’s existing twist multiplier again, so the funnel keeps the grounded volumetric look without the expensive swirling debris particles and reads with a faster spin. +- Reworked tornado visual motion so the renderer now uses a continuously advancing spin value instead of the old capped twist, and changed client tornado interpolation to smooth toward snapshot targets with light extrapolation instead of directly stepping toward each sync update. +- Fixed `spawnTornadoNoClouds` so it now spawns a real standalone tornado immediately instead of still routing through the cloud-gated cumulonimbus path, and added a no-cloud server spawn mode that does not auto-dissipate just because no Simple Clouds region is attached. +- Corrected the tornado shader’s shaping math to evaluate funnel, wall-cloud, and connection terms in consistent world-unit space instead of mixing cloud-space coordinates with world-sized constants, which was producing oversized dome/ring artifacts near the cloud base. +- Re-integrated tornado opaque rendering with the real cloud-target depth buffer while keeping the horizon-safe raymarch distance path, and thickened the tornado shader density/tint so funnels now read as embedded storm mass instead of a translucent hologram laid over the world. +- Reworked the tornado raymarch from a blind full-range overlay into a scene-depth-clipped world-space volume, and changed the prepared top/bottom bounds to follow actual ground contact plus cloud-base attachment instead of an oversized fallback height band. +- Retuned tornado demolition so active funnels start destroying trees, leaves, wooden lightweight structures, and loose natural terrain much earlier in the wind curve, with stronger inner-core forcing and wider believable damage reach tied to storm intensity. +- Strengthened the dynamic fog application curve so humidity and wet-biome fog compresses view distance and tint more visibly instead of remaining too subtle to notice in normal gameplay. +- Added `/pa fog spawn [strength] [seconds]` and `/pa fog clear`, implemented as a client-side debug fog override packet/state path that lets you force visible fog for render testing without faking server weather. +- Fixed the tornado fullscreen render integration so both Simple Clouds pipelines now render the opaque pass on the cloud target in the correct translated cloud-space stage; this removes the bad extra shader-support translation and stops camera-position-dependent tornado popping/disappearance. +- Increased tornado persistence by extending the lifecycle timings and adding a cloud-detach grace window, so tornadoes no longer collapse almost immediately after a brief cloud-region handoff or wobble. +- Updated the tornado shader/render path so the funnel no longer hard-clips against the copied scene-depth horizon, which previously made tornadoes shear off around camera eye level over water or other long flat surfaces. +- Reworked tornado lifetime scaling to guarantee a roughly 2-10 minute active window based on storm strength, and changed water exposure so it only softens intensity instead of fast-forwarding or force-ending the tornado after a few seconds. +- Added a modular humidity-driven fog system with a lightweight server-to-client fog status sync, a client fog state cache/smoother, wet-biome classification heuristics, and integration into the existing fog event handler so fog now responds to humidity, rain, and moisture-heavy biomes without per-frame heavy sampling. +- Added a new `fog` common-config section plus in-game config screen controls for the main fog tuning values, including humidity thresholds, wet-biome strength, rain boost, fog distance, tint strength, and advanced biome id/keyword overrides in the config file. +- Added `weatherdebug fog` to expose the live humidity, wet-biome weighting, rain contribution, and resulting fog strength using the same shared heuristic that drives the client fog renderer. +- Added a dedicated Simple Clouds-style hurricane renderer and shader path that renders bounded volumetric storm masses as their own cloud body instead of extending the old flat ring overlay path. +- The new hurricane density field now supports a true empty eye, annular eyewall, upper canopy, outer shield, and early spiral-band structure while still reusing the Project Atmosphere / Simple Clouds texture samplers, fog, depth, and cloud-color language. +- Hooked the hurricane renderer into both Simple Clouds default and shader-support pipelines, added hurricane shader registration/resources, and disabled the old `SimpleCloudsRendererMixin` ring path from the active mixin config. +- Extended hurricane client snapshots/interpolation so prepared render data can be cached and reused per frame, and fixed tornado/hurricane motion/constructor mismatches so the current tree builds cleanly again. +## Unreleased - Distant Horizons Simple Clouds compatibility +- Added a DH-aware fallback that forces Simple Clouds onto its `DhSupportPipeline` when Distant Horizons is present, so the shared cloud renderer stays on the post-DH frame path instead of relying on the default after-sky stage. +- Added DH-pipeline diagnostics so the next client run can prove whether the DH cloud pass is reached, how many cloud elements it renders, and whether the geometry is being dropped before or after DH composition. +## Unreleased - Restore ordinary Simple Clouds cloud density +- Restored the working base Simple Clouds cloud profiles for `altocumulus`, `altostratus`, `cumulus_congestus`, `cumulus_humilis`, `cumulus_mediocris`, `custom_cumulonimbus`, and `stratocumulus_opacus` so the shared mesh generator once again gets the same density inputs as the last known good revision. +- Restored the `pattern` cloud type and spawn entry, and returned the ordinary cloud library / weather classification tables to the last known good selection semantics. +- Re-anchored hurricanes against the live Simple Clouds layer instead of a hardcoded Y=64 fallback, so they now sit roughly 200 blocks below the configured cloud height without dropping the volumetric mesh completely out of range. +- Switched the client hurricane cache to request a full Simple Clouds renderer reload when integrated-server hurricane snapshots appear, change, or clear, so local singleplayer hurricanes can force a fresh mesh pass instead of staying hidden with only rain semantics active. +- Simplified the tornado admin/debug command flow so `/pa spawnTornado` and `/pa spawnTornadoNoClouds` no longer sit in cloud-seeding wait loops; if cloud attachment fails, they now fall back immediately to a force-spawned standalone tornado. +- Re-synced `TornadoCommand` and `TornadoManager` to `Dynamic-Forge-1.20.1-Tornado` so the tornado spawn path matches the Tornado branch behavior instead of the newer hurricane-branch force-spawn flow. +- Restored the tornado branch integration points that the hurricane branch had lost: tornado client snapshot packets are registered again, the Simple Clouds tornado renderer mixins/shaders are back, and client tornado overlays/effects now read from the client tornado list instead of the server-only list. +- Fixed the local singleplayer hurricane render path by making `ClientHurricaneStateCache` fall back to live integrated-server hurricane snapshots whenever the synced snapshot cache is empty, which lets debug-spawned hurricanes render again in local worlds. +- Lowered the hurricane anchor by roughly 200 blocks and expanded the outer cumulonimbus storm extent by about 5x, with a broader edge fade and larger hurricane cloud-noise scales so the storm reads as a much larger regional system instead of a compact ring high in the sky. +- Changed the tornado admin/debug spawn commands to force-spawn a visible tornado immediately instead of timing out on cloud seeding, while still attaching to a nearby severe cloud when one is available and broadening cloud lookup to a larger severe-cloud fallback radius. +- Moved the hurricane core-to-cumulonimbus recovery inward so the outer storm body now begins from the eyewall region instead of from far out on the core radius, and added a dedicated inner bridge envelope/noise pass to close the remaining dead ring between the eye wall and the outer storm mass. +- Optimized `StormShieldManager` to stop hammering chunk-load tick time: shield tracking now uses a chunk-aware primitive index, only scans chunk sections whose palettes can actually contain the storm shield block, updates from block place/break events, and queries nearby chunk buckets instead of mutating/iterating a global concurrent boxed set. +- Diagnosed the tornado regression against Dynamic-Forge-1.20.1-Tornado: the dynamic branch diverged from merge base 7c30affb6c9a1f46c5526df5bbb7455e4b14a6c0 and never merged the newer tornado stack, so it had drifted back to the old local tornado implementation. +- Restored the source-of-truth tornado pipeline from Dynamic-Forge-1.20.1-Tornado, including the tornado manager/instance/snapshot/spawner flow, regional storm phase integration, standalone spawn/remove/sync packets, the Simple Clouds tornado renderer/shaders, tornado client effects, and the config/UI hooks needed for render quality and client cleanup on the dynamic branch. +- Reworked hurricanes to intensify out of the existing cyclone system instead of acting like isolated local storms: CycloneManager now exposes active cyclone snapshots, HurricaneManager tracks cyclone formation eligibility over warm ocean plus convective cloud coverage, and cyclone-linked hurricanes inherit the cyclone's regional disruption while adding stronger wind fields, tree/block destruction, entity pushing, eyewall lightning, and native hurricane rendering/sync. +- Stabilized hurricane semantic ownership in the eye by keeping the eye visually empty/dry while still reporting `projectatmosphere:hurricane` to Simple Clouds query paths, which should stop the F3 overlay from flipping to `simpleclouds:empty` when crossing the eye. +- Reworked the hurricane core-to-outer transition again so the inner spiral persists farther out, the outer cumulonimbus mass begins earlier, and new broad outer spiral rainbands give the storm a more cyclone-like top-down silhouette instead of a smooth circular disk. +- Rebalanced `altocumulus`, `altostratus`, `cumulus_humilis`, `cumulus_mediocris`, `cumulus_congestus`, `custom_cumulonimbus`, and `stratocumulus_opacus` to reduce geometric fill and total cube output while improving vertical anisotropy, contour, and layered/puffy sculpting. +- Fixed the command-spawn tornado path so `/pa spawnTornado` now creates a managed tornado instance in addition to attaching the cloud descriptor, and relaxed supporting-cloud lookup to use a nearby fallback cloud when strict intersection misses on the client. +- Added a shared CPU hurricane semantic sampler and wired it into Simple Clouds cloud-type, precipitation, and rain-level queries, so hurricanes now report `projectatmosphere:hurricane`, force visible rain outside the eye, and keep the eye dry without relying on fake cloud regions. +- Added query-only hurricane reservation regions plus spawn/reconciliation hooks in the Simple Clouds generator, preventing normal cloud formations from spawning into or drifting through the hurricane footprint while keeping the hurricane render path native. +- Tightened the hurricane core-to-cumulonimbus blend so the outer storm body starts overlapping before the inner spiral fully fades, removing the remaining visible handoff between the core structure and the outer mass. +- Added per-hurricane vertical anchoring at Y=256, split the preserved eye/core radius from a new world-scale outer storm extent, and updated the Simple Clouds mesh path so hurricane chunks render at the lowered altitude without moving the global cloud layer. +- Reworked the hurricane region mask into a core-to-cumulonimbus blend, keeping the eye/eyewall near the center while expanding the outer storm body into a much larger continuous cloud shield with smoother radial transitions. +- Slowed hurricane rotation to long-period large-storm motion and expanded hurricane weather forcing so nearby atmospheric regions get stronger rain/cloud floors while the server now spawns explicit eyewall lightning near players. +- Slowed hurricane rotation down to large-scale storm pacing, reshaped the mask so the core keeps a clear eye while the outer radius blends into cumulonimbus-style storm mass, and retuned the hurricane cloud profile to borrow a more vertical cumulonimbus volumetric structure instead of a flatter outer shelf. +- Reworked the hurricane eye mask back into a true open center, replaced the oversized flat outer shelf with a tighter cumulonimbus-style outer mass, and added direct hurricane forcing into nearby atmospheric regions so hurricanes now drive rain/thunder conditions instead of only rendering visually. +- Removed the artificial spinning eye-core from the hurricane mask, raised the hurricane cloud body higher above the terrain, and expanded the connected cumulonimbus envelope so the storm spans a much larger continuous cloud mass. +- Fixed the disappearing hurricane regression by bringing projectatmosphere:hurricane back under Simple Clouds' 4-layer noise limit; the outer cavity effect now stays in the hurricane shader mask instead of a fifth cloud noise layer. +- Mapped projectatmosphere:hurricane into the thunderstorm weather path so hurricane clouds count as rainy/thunderous, and thickened the outer hurricane density with cavity-cut cumulonimbus-style mass instead of a cleaner ring shell. +- Slowed hurricane rotation, added rotating inner-core coverage, widened connected outer cumulonimbus mass with blended transitions, and switched the native hurricane cloud type identifier to projectatmosphere:hurricane. +- Moved storm mesh-generator helper DTOs out of the mixin package so RegionUpload/TornadoUpload are no longer loaded as direct mixin-owned classes at runtime. +- Rebalanced the native Simple Clouds hurricane profile so storms render much larger, sit lower in the cloud layer with deeper base offsets, and use smoother band coverage plus softer lower noise to reduce underside streak artifacts. +- Replaced the old hurricane ring overlay with a native Simple Clouds integration path driven by explicit hurricane render snapshots synced from the server to the client. +- Added a client hurricane state cache plus SyncHurricaneStatePacket, so hurricane cloud rendering no longer reaches into server-only hurricane state. +- Extended the overridden cloud_regions.comp compute shader and MultiRegionCloudMeshGenerator mixin with a dedicated hurricane formation primitive, including a true hollow eye, eyewall banding, spiral coverage, and conservative CPU chunk meshing support. +- Added a dedicated simpleclouds:hurricane cloud type for hurricane noise/lighting identity instead of reusing the old custom_cumulonimbus shortcut. +- Removed the fake hurricane render hook/classes and stopped /spawnHurricane from spawning standalone Simple Clouds cumulonimbus regions outside the native hurricane system. +- Forced Simple Clouds mesh and region compute shaders to load Project Atmosphere-owned shader resources directly, so the hurricane/tornado SSBO extensions no longer depend on cross-mod asset override order at runtime. +- Added gated Simple Clouds runtime diagnostics for the shared client pipeline, including player cloud sampling, selected cloud type/profile logging, mesh-region upload counts, chunk-generation decision logs, mesh finalize counts, and per-pass draw counters so a client run can prove whether the failure is in the inputs, the compute path, or the draw path. +- Relaxed the diagnostics gate so the shared Simple Clouds probes now emit a one-time runtime proof line by default, instead of staying silent unless `-Dprojectatmosphere.simpleclouds.debugRender=true` is present. + ## Unreleased - Gradle sync fix - Removed the duplicate mid-script `import groovy.json.JsonOutput` from `build.gradle`, which could stop the Gradle script from compiling during IDE sync. - Replaced legacy archive/version references with Gradle 8-safe values for the jar manifest plus the Modrinth and CurseForge artifact paths. @@ -296,3 +552,31 @@ This file records functionality additions/removals made during development sessi - Forecast cache application now drains in client-side batches across ticks, allowing the overlay to report visible per-loop progress from the actual biome-profile apply path instead of jumping from wait to ready. - Added server-side login preparation stage updates around nearby-region collection and local weather seeding so the overlay advances before the forecast snapshot packet is sent. - Added an integrated-world loading bridge so local world startup can push forecast-design stages from the real server generation loops before the later client sync packet phase begins. +## Unreleased - Hurricane renderer replacement +- Removed the hurricane-specific `custom_cumulonimbus` spawn path so hurricane commands now create only the dedicated hurricane system instead of seeding a Simple Clouds cumulonimbus shell first. +- Added an explicit `HurricaneRenderDescriptor` to server snapshots and client interpolation so eye size, eye-clear radius, eyewall thickness, canopy radius, shield radius, vertical layer factors, and outer band controls travel as storm data instead of being reconstructed only from radius and intensity during rendering. +- Reworked `SimpleCloudsHurricaneRenderer` into a full dedicated hurricane pipeline with bounded opaque raymarching, weighted-transparency fringe rendering, and framebuffer eye-carve passes that clear unrelated ambient Simple Clouds content from both the opaque and transparent cloud targets before hurricane cloud content is added back. +- Replaced the single hurricane shader registration with dedicated opaque, opaque-mask, transparency, and transparency-mask shader programs plus new fragment shaders for the standalone hurricane volume and eye-protection passes. +- Deleted the abandoned flat-ring hurricane scaffold (`SimpleCloudsRendererMixin`, `HurricaneMeshRenderer`, `HurricaneStateProvider`, and `HurricaneState`) so there is no legacy ring renderer left in the active codebase. + +## Unreleased - GPU-native hurricane mesh generation +- Removed the fullscreen hurricane pipeline hooks and their shader registration so hurricanes are no longer rendered as a separate post/cloud-target pass. +- Added `HurricaneMeshField` plus a new `MultiRegionCloudMeshGenerator` mixin that uploads prepared hurricane descriptors to the active Simple Clouds mesh compute shader each mesh-generation cycle. +- Extended chunk scheduling so Simple Clouds now allocates mesh generation work for hurricane volumes even when no ambient cloud region overlaps the chunk. +- Overrode the Simple Clouds `cube_mesh.comp` compute shader with a hurricane-aware density field that emits real mesh cubes for the eye wall, canopy, shield, and outer bands while using the same GPU block generation and face occlusion path as native Simple Clouds clouds. +- Added an eye-carve override directly inside the cube generation density sampling so the hurricane eye removes ambient cloud blocks in the same generated volume instead of relying on a later framebuffer cleanup pass. +- Added a `cloud_regions.comp` override that preserves an explicit "no ambient region" sentinel, preventing hurricane-only chunks from inheriting a bogus default cloud region during compute meshing. + + + + + +# 2026-04-23 + +- Added shared Simple Clouds runtime diagnostics for the client mesh generator base class and both render pipeline branches. +- Split the pass-summary logger state so an early fallback or finalize event cannot suppress draw-pass evidence. +- Added pipeline-entry logs so we can prove whether the active path is `DefaultPipeline` or `ShaderSupportPipeline` at runtime. +- No behavioral render change was made in this step; this is investigation instrumentation to isolate the remaining cloud visibility regression. +- Restored the Tornado-branch hurricane render stack: `HurricaneShaders`, `SimpleCloudsHurricaneRenderer`, the hurricane mixin hooks, and the `hurricane_*` shader assets. +- Added compatibility accessors on `HurricaneInstance` and `HurricaneManager` so the restored branch renderer can consume the current hurricane state model without changing the existing sync path. +- Added a targeted Simple Clouds 0.7.4 compatibility cap for lightning SSBO buffering on low-binding GPUs and moved Project Atmosphere's `CloudStorms` SSBO onto binding 0 there, preventing reload crashes without disabling tornado/hurricane cloud shaping. diff --git a/TORNADO_DH_DEPTH_INVESTIGATION.md b/TORNADO_DH_DEPTH_INVESTIGATION.md new file mode 100644 index 00000000..04b92718 --- /dev/null +++ b/TORNADO_DH_DEPTH_INVESTIGATION.md @@ -0,0 +1,514 @@ +# Tornado DH Depth Investigation + +This log tracks Distant Horizons tornado depth attempts. Check this file before trying another fix. + +## Problem +- With Distant Horizons enabled, terrain color still appears through or over the Project Atmosphere tornado volume. +- Without Distant Horizons, the tornado depth behavior is reported as fine. +- The tornado physical/simulation position appears correct because terrain destruction happens at the expected location. + +## Tried Fixes +- Disabled the tornado downsample path under Distant Horizons. + - Result: Simple Clouds cloud color now renders behind the tornado correctly, but terrain still appears over/through the tornado. +- Used the raw Distant Horizons API depth texture as the tornado shader primary depth sampler. + - Result: did not fix the terrain issue. +- Removed the secondary cloud transparency depth sampler from the DH tornado path. + - Result: did not fix the terrain issue. +- Switched the DH tornado shader depth sampler to Simple Clouds' DH-filled `cloudTarget` depth texture. + - Result: did not fix the terrain issue. +- Added a separate local footing/contact shape in the shader. + - Result: rejected. It changes the tornado shape instead of fixing DH depth. + +## Current Suspects +- The DH path relies on the tornado shader discarding against scene depth before Simple Clouds' final color-only composite. If the scene-depth comparison is wrong, terrain will appear through the tornado. +- The shader ray/depth logic may be comparing ray distance in cloud space against reconstructed depth positions in a different space. +- The render injection timing may be before the correct DH/main depth attachment swap that Simple Clouds uses later for DH-aware world effects. + +## Findings +- Simple Clouds' DH path blits `dhFbo` depth into `cloudTarget` and `cloudTransparencyTarget` before cloud rendering. +- Simple Clouds opaque clouds use normal framebuffer depth testing against that copied depth. +- Simple Clouds final composite is color-only and disables depth testing. Therefore Project Atmosphere tornado pixels must already be correct before final composite. +- Project Atmosphere tornado rendering uses both framebuffer depth testing and a shader-side reconstructed scene-depth discard. +- The shader-side discard is at `tornado_round.fsh`: `nearestT > sceneDistance + SOFT_TERRAIN_OCCLUSION_BIAS`. +- This extra shader-side discard is the most likely DH-only failure point because non-DH rendering does not have the same DH terrain depth in the cloud target at that point. + +## Next Proposal +- Add a DH-only tornado depth debug mode before another visual fix. It should show whether each pixel is rejected by framebuffer depth, shader scene-distance discard, low alpha, or missing volume hit. +- If debug confirms shader scene-distance rejection is the cause, replace the DH path with a depth-buffer based comparison using `firstHitDepth` vs `sceneDepth`, or disable the reconstructed-distance discard under DH and rely on `gl_FragDepth` plus `GL_LEQUAL`. + +## Debug Modes Added +- `/tornado render mode depth` + - Green: tornado pixel accepted by shader depth checks. + - Red: rejected by shader scene-distance depth check. + - Yellow: rejected because accumulated tornado alpha is below the normal discard threshold. + - Blue: ray hit the tornado proxy box, but no tornado density was sampled. + +## Depth Debug Result +- Screenshot below ground: main body is green, edges/base are yellow, surrounding proxy is blue, and there is little to no red. +- Screenshot above ground: main body remains green, lower edge has yellow, surrounding proxy is blue, and there is little to no red. +- Interpretation: the shader scene-distance discard is not the primary failure. Tornado pixels are accepted by the depth checks. +- The visible terrain in normal rendering is more likely coming from final color blending/composite alpha, not from depth rejection. +- Simple Clouds final composite blends `cloudTarget` color over the main scene color using cloud alpha; it does not depth-test during final composite. Therefore accepted tornado pixels with alpha below 1.0 will still show already-rendered terrain color through them. + +## Current Attempt +- Added a DH-only `DistantHorizonsDepthMode` shader uniform. +- When enabled by the DH mixin, accepted tornado pixels receive a higher alpha floor derived from accumulated `rawAlpha`. +- This does not add a separate geometry piece and does not apply to non-DH rendering. +- Hypothesis: accepted tornado pixels need stronger alpha during Simple Clouds' color-only final composite so already-rendered terrain color does not bleed through. +- Result: failed. Terrain is still visible over/through the tornado in DH normal rendering. + +## Updated Suspects +- Since the accepted-pixel alpha floor did not fix the issue, the terrain may be rendered after the tornado cloud target composite in the DH pipeline, or the tornado is composited into a target that is later overwritten by DH/main terrain output. +- The next investigation should inspect whether the tornado should render in the post-composite DH main-framebuffer section where Simple Clouds temporarily attaches `cloudTarget` depth to the main framebuffer for DH-aware world effects. + +## Next Attempt +- Move the DH tornado draw out of the pre-composite `cloudTarget` pass. +- Render it after `SimpleCloudsRenderer.doFinalCompositePass(...)`, directly into Minecraft's main render target while Simple Clouds has temporarily attached `cloudTarget` depth to the main framebuffer. +- Reason: Simple Clouds uses this exact post-composite depth attachment window for DH-aware world effects such as lightning. If terrain is appearing over the tornado because the earlier cloud-target composite is not the final DH color order, this should prove it without adding fake tornado geometry. +- The DH post-composite pass must not sample the same depth texture that is attached as the framebuffer depth attachment. In DH mode, skip the shader-side scene-depth sampler and rely on `GL_LEQUAL` against the attached DH depth plus `gl_FragDepth` from the tornado first hit. +- Implementation status: build passes. In-game DH result is pending user test. +- Result: failed. From less than one block below terrain, the tornado still renders through the ground and far-side grass is visible. + +## Current Finding +- The post-composite hook runs while Simple Clouds has bound Minecraft's main render target for color, but has replaced its depth attachment with `cloudTarget` depth. +- That attached `cloudTarget` depth is DH/Cloud depth, not necessarily the live vanilla near-terrain depth under the camera. +- The shader was also changed to skip depth sampling in DH mode to avoid sampling the attached `cloudTarget` depth texture. +- Combined effect: the pass can respect DH LOD depth while ignoring the vanilla main terrain depth. This matches the below-ground screenshot, where nearby terrain should occlude the tornado but does not. + +## Next Attempt +- Keep rendering in the post-composite main framebuffer window. +- Keep `cloudTarget` depth attached for framebuffer testing against DH LOD depth. +- Pass Minecraft's detached main depth texture to the tornado shader as `DepthSampler`. +- Re-enable shader-side scene-depth sampling in DH mode so vanilla/near terrain can reject tornado pixels while the framebuffer depth test still handles DH LOD terrain. +- Implementation status: build passes. In-game DH result is pending user test. +- Result: failed. The below-ground test still shows the tornado when terrain should fully occlude it. + +## Current Finding +- The failure is no longer safe to treat as a simple source-depth selection problem. +- The observed cutoff/visibility pattern suggests part of the tornado may be rejected before shader output, or the shader is discarding/sampling no density for the region below the camera/player height. +- Existing shader debug modes cannot distinguish fixed-function framebuffer depth rejection from shader rejection because framebuffer depth can kill fragments before the fragment shader runs. + +## Next Diagnostic +- Add `/tornado render mode depth_nofb`. +- This mode uses the same colors as `depth`, but disables framebuffer depth testing for the tornado pass. +- Expected interpretation: + - If the missing lower/below-player tornado region appears in `depth_nofb`, fixed-function framebuffer depth is the culprit. + - If it is still missing or blue, the raymarch/density field is not sampling tornado volume there. + - If it is red, shader-side scene-depth discard is the culprit. + - If it is yellow, alpha thresholding is the culprit. +- Result: `depth_nofb` shows green and yellow from the below-ground/spectator flat-world test. This proves the shader can generate the lower tornado volume when framebuffer depth testing is disabled. + +## Current Finding +- The missing lower/below-player tornado portion is caused by fixed-function framebuffer depth testing or by `gl_FragDepth` compared against the currently attached framebuffer depth. +- It is not primarily caused by the density field being absent. +- In the current DH post-composite hook, that framebuffer depth attachment is Simple Clouds' `cloudTarget` depth, not Minecraft's normal main depth texture. + +## Next Diagnostic +- Add `/tornado render mode depth_mainfb`. +- This keeps the same shader-side debug colors as `depth`, but temporarily restores Minecraft's main depth attachment for the tornado draw during the DH post-composite hook. +- Expected interpretation: + - If the lower/below-player tornado region appears in `depth_mainfb`, the Simple Clouds/DH `cloudTarget` depth attachment is clipping it. + - If it remains missing, the main depth buffer or `gl_FragDepth` projection is also incompatible. + +## Ground Skirt Artifact +- `depth_nofb` showed a large green/yellow flattened shape beneath the funnel. +- Shader inspection found `groundSkirt` is promoted into `outSample.cloud` in both frozen and normal storm sampling. +- Because raymarch opacity is driven by `storm.cloud`, this makes the ground skirt behave like a real volumetric tornado body instead of a light local ground effect. +- Next change: remove `groundSkirt` and broad dust-only terms from the primary `outSample.cloud` field so they cannot create the large under-ground volume. Keep `groundskirt` debug visibility separate. +- Result: fixed. The large flattened under-ground blob is gone in `depth_nofb`. + +## Next Diagnostic +- Add `/tornado render mode occlusion`. +- In this mode, the DH post-composite pass detaches framebuffer depth and disables framebuffer depth testing so both main depth and Simple Clouds/DH cloud depth can be sampled safely in the shader. +- The shader then reports which source would occlude the first real tornado hit: + - Green: accepted by both sampled depths. + - Red: rejected by vanilla main depth. + - Magenta: rejected by Simple Clouds/DH cloud depth. + - White: rejected by both. + - Yellow: low alpha. + - Blue: no tornado density hit. +- This is diagnostic only and does not change normal rendering. +- Result: the pass is green, meaning both sampled depth sources accept the tornado. Terrain still visually cuts/overlays the tornado. + +## Current Finding +- The remaining problem is render order/compositing, not tornado density and not sampled depth rejection. +- If green debug pixels are still visually cut by terrain, a terrain/color pass is being drawn or composited over the tornado after the current Simple Clouds DH hook. + +## Next Diagnostic +- Add `/tornado render mode late`. +- In this mode, skip the current Simple Clouds DH tornado draw and render the tornado from Forge `RenderLevelStageEvent.Stage.AFTER_LEVEL` instead. +- Disable framebuffer depth for this late debug draw and keep shader-side sampled depth available. +- Expected interpretation: + - If terrain stops cutting the tornado, the final fix should move the DH tornado composite to a late level stage. + - If terrain still cuts it, something after `AFTER_LEVEL` or the hand/final post chain is overwriting it. +- Result: major improvement. Terrain no longer broadly renders over the tornado. A thin horizontal terrain line remains near the terrain/horizon contact. + +## Current Finding +- The primary DH bug is render order/compositing. The Simple Clouds DH hook renders too early for the Project Atmosphere tornado; terrain/color can be composed over it afterward. +- The remaining horizontal line is not the same broad terrain-over-tornado failure. It is likely caused by transparent lower-body pixels allowing already-rendered terrain color to show through, or by shader-side contact/depth rejection right at the ground/horizon transition. + +## Next Attempt +- Promote the late `AFTER_LEVEL` path from debug-only to the normal DH tornado render path. +- Keep the old Simple Clouds DH hook skipped while the late path owns DH tornado rendering to avoid double draws. +- Then address the remaining line with a lower-body/contact-only opacity or occlusion adjustment, not a global opacity increase. +- Implementation: normal DH tornado rendering now uses the late `AFTER_LEVEL` path when no explicit tornado debug mode is active. Explicit debug modes other than `late` continue using the old DH hook for diagnostics. +- Implementation: the DH alpha floor now targets accumulated body pixels more strongly (`rawAlpha`-based), reducing already-rendered terrain color bleed through the dense funnel without adding the removed ground-skirt volume back into cloud density. +- Result: major terrain-overdraw issue remains fixed, but a hard horizontal sky/terrain background transition is still visible through low-alpha tornado body pixels. + +## Current Finding +- The remaining seam is background color bleed through translucent tornado pixels, not a depth-order failure. +- Because the late pass renders after terrain, any surviving low-alpha tornado pixel will blend with the already-rendered terrain/sky color behind it. + +## Next Attempt +- Tighten the DH-only low-alpha body curve: + - Raise the DH discard threshold slightly so extremely thin pixels at the horizon/contact boundary do not show as a terrain-colored band. + - Make valid accumulated body pixels reach near-opaque alpha earlier. +- Keep this DH-only and `rawAlpha`-based so it does not reintroduce the removed ground-skirt blob. + +## Next Attempt +- User report: a visible horizontal separation remains and looks like two tornado render stages meeting. +- Checked current draw owners: + - Default Simple Clouds pipeline tornado hook. + - Shader-support Simple Clouds pipeline tornado hook. + - DH support pipeline post-composite tornado hook. + - Forge `AFTER_LEVEL` DH tornado handler. +- Next change: make DH tornado rendering single-owner and single-stage. + - Render DH tornadoes only from `RenderLevelStageEvent.Stage.AFTER_LEVEL`. + - Skip the old DH post-composite tornado draw entirely. + - Guard default/shader-support pipeline tornado hooks when DH is loaded, in case Simple Clouds falls back to those pipelines. + - Let debug modes render through the same `AFTER_LEVEL` path instead of switching back to the old DH hook. +- Implementation: + - Removed the old DH post-composite tornado draw injection body. + - Added DH-loaded guards to the default and shader-support Simple Clouds tornado hooks. + - Changed the `AFTER_LEVEL` handler to render for all DH tornado modes, not only normal/`late`. + - Temporarily detaches the main framebuffer depth attachment while the late pass samples that same depth texture, avoiding read/write feedback. +- Build result: `.\gradlew.bat build` passed. +- Result: failed. The visible horizontal gap/separation is still present after forcing DH tornado rendering to a single `AFTER_LEVEL` owner. + +## Current Finding +- The remaining gap is not caused by the tornado being rendered in two Project Atmosphere stages. +- Since the single-pass late draw still shows the seam, the remaining cause is likely inside the single render pass: + - shader-side depth rejection around terrain/contact pixels, + - the alpha discard/opacity curve creating a hard transition, + - `gl_FragDepth` or first-hit depth being written inconsistently across the lower body, + - or the lower funnel density field itself dropping out near the terrain/horizon line. + +## Next Diagnostic +- Inspect the current tornado fragment shader's DH path before another visual fix. +- Specifically check: + - where `wroteDepth` becomes false, + - DH-only `rawAlpha` discard thresholds, + - terrain/contact depth bias scope, + - final alpha/opacity curve, + - and whether `gl_FragDepth` is skipped for low-alpha lower-body pixels. +- Inspection result: + - The remaining hard DH cutoff is `rawAlpha < 0.045`, which discards pixels before they can write color/depth. + - DH opacity then ramps from `rawAlpha` 0.045 to 0.105 and forces surviving pixels near opaque. + - If the seam aligns with the terrain/sky boundary, it can be caused by low-accumulation rays at the lower funnel edge being discarded or classified as very thin, letting the already-rendered background show through. +- Next change: add a `coverage` debug render mode that does not discard normal low-coverage pixels and instead colors the exact output classification: + - Blue: no storm depth was written. + - Yellow: would be discarded by `rawAlpha < minBodyAlpha`. + - Cyan: survives but is in the DH opacity ramp. + - Green: solid body coverage. + - Red: rejected by scene depth. +- Implementation: added `/tornado render mode coverage`. +- Build result: `.\gradlew.bat build` passed. +- Result: the remaining horizontal line is red in `coverage`, meaning it is rejected by the shader scene-depth check. + +## Current Finding +- The seam is caused by `sceneReject`, not alpha cutoff and not two render stages. +- In the late DH pass, `sampleSceneDepth()` currently uses the nearest of: + - Minecraft main depth texture, + - Simple Clouds/DH `cloudTarget` depth texture. +- A false reject from the secondary Simple Clouds/DH depth can create exactly a horizontal terrain/horizon cut even after the tornado renders late. + +## Next Attempt +- Keep the late single-stage render path. +- For that late path, stop passing the Simple Clouds/DH cloud depth as a secondary scene-depth sampler. +- Reason: the late pass should reject against the main scene depth it is compositing over, not against Simple Clouds' earlier cloud/DH depth target that can contain a different horizon/depth representation. +- Expected result: + - If the red seam disappears, secondary DH/cloud depth was the false reject source. + - If the red seam remains, the main depth reconstruction/comparison is the source. +- Implementation: late DH tornado pass now passes `-1` for `secondaryDepthTextureId`, so `UseSecondaryDepthSampler` is disabled. +- Build result: `.\gradlew.bat build` passed. +- Result: fixed. The red scene-depth seam disappeared when the late DH pass stopped using Simple Clouds' `cloudTarget` depth as a secondary scene-depth sampler. + +## Confirmed Fix +- The final DH tornado render path must be: + - single-stage at Forge `RenderLevelStageEvent.Stage.AFTER_LEVEL`, + - sampling Minecraft's main scene depth only, + - not sampling Simple Clouds' `cloudTarget` depth as secondary depth in the late pass, + - and not drawing from the older Simple Clouds DH post-composite tornado hook. +- The remaining issue was not two-stage rendering after the single-owner fix. It was a false shader scene-depth rejection caused by mixing the late pass with Simple Clouds' earlier DH/cloud depth target. + +## Next Enhancement Reintroduction +- Reintroduce tornado performance enhancements one at a time. +- First target: DH late-path downscaling. +- Constraint: the downscaled pass must preserve the confirmed depth fix by sampling only the main scene depth and compositing only after the late pass, not through the old Simple Clouds cloud target path. +- Planned implementation: + - Allow the `AFTER_LEVEL` target-override path to use the existing low-resolution tornado render target. + - Size that downsample target from the final destination target, not from Simple Clouds' cloud target. + - Composite the downsample result back into the same destination target passed by the late handler. + - Keep debug modes full-resolution so `coverage`, `depth`, and related diagnostics remain readable and exact. +- Implementation: + - `renderOpaqueToTarget(...)` now supports downsampling even when rendering to an override target, but only for the DH late path or the original non-override path. + - The downsample target is sized from the final destination framebuffer. + - The composite pass writes back to that same destination framebuffer. + - Debug modes keep full-resolution rendering. +- Build result: `.\gradlew.bat build` passed. +- Result: worked. FPS improved significantly, but the tornado looks pixelated/low-quality at the current downsample factor. + +## Next Enhancement Attempt +- Add a cheap alpha-aware upsample filter to the tornado composite shader. +- Reason: the expensive part is the tornado raymarch, not a small fullscreen upscale shader. A 9-tap alpha-weighted filter should smooth low-resolution tornado edges and internal blockiness while preserving most of the downscale FPS gain. +- Constraint: + - Do not change depth handling. + - Do not reintroduce the old Simple Clouds DH depth target. + - Keep the filter local to the existing downsample composite pass. +- Implementation: replaced the one-sample tornado composite with a 9-tap alpha-weighted upsample filter. +- Build result: `.\gradlew.bat build` passed. +- Result: pending user test. + +## Result +- Failed. With non-DH downscaling enabled, the tornado is hidden/not visible. +- User also reported switching modes did not recover it. + +## Verification +- The only intentional difference from the last known good non-DH state is `useDownsample == true`. +- Full-resolution non-DH was confirmed working before reintroducing downscaling. +- Debug modes can also render nothing if `resolvedDebugStormIndex` remains `-1`, because the render loop filters all tornadoes when debug mode is active but no storm was resolved. + +## Next Attempt +- Restore last known good non-DH behavior by disabling downscaling for non-DH normal rendering again. +- Keep DH downscaling enabled because it was confirmed working. +- Fix debug-mode fallback so if no selected storm resolves, debug modes render all prepared tornadoes instead of zero tornadoes. +- Implementation: + - Re-added the non-DH forced-full downsample guard. + - Debug render ordering now filters to the selected tornado only if a selected tornado actually resolves. +- Build result: `.\gradlew.bat build` passed. +- Result: failed. User still reports the tornado is broken/not rendering, so the previous downscale changes are still contaminating the non-DH path. + +## Current Decision +- Stop adapting the current DH/target-override downsample path for non-DH. +- Hard-disable non-DH downsample at the renderer level to restore the known-good vanilla path. +- Any future non-DH downscale must be a separate implementation and must not share the DH late-path assumptions. + +## Current Finding +- Verification found another changed variable after the known-good non-DH state: + - The known-good non-DH path still let the pipeline hook think it was "downsampled" through `usesDownsamplePath()`, so it skipped `copyDepthFromCloudsToTransparency()` and passed `cloudTarget` depth. + - Later, the hook was changed to always copy and always pass `cloudTransparencyTarget` depth. +- Since non-DH downscaling is now disabled inside the renderer but the tornado is still broken, restore the previous hook depth-source behavior first. + +## Next Attempt +- Keep renderer-level non-DH downscaling disabled. +- Restore default/shader-support mixins to their prior conditional depth-copy/depth-source logic. +- This should match the state that the user reported as good for non-DH. + +## User Report +- Non-DH still renders no tornadoes. +- User asks to verify the actual facts and stop guessing. + +## Current Suspect To Verify +- The non-DH pipeline hooks now contain broad `SimpleCloudsMod.dhLoaded()` guards. +- If DH is installed/loaded but the active Simple Clouds pipeline is not the DH support pipeline, those guards skip the default/shader-support tornado draw. +- That would produce exactly "no tornadoes without DH" because the non-DH hook returns before rendering. + +## Verified Cause +- Found a DH state mismatch: + - `SimpleCloudsDhPipelineSelector` and `SimpleCloudsRendererDhFallbackMixin` force `DhSupportPipeline` based on `ModList.isLoaded("distanthorizons")`. + - `TornadoLateRenderDiagnostics` only draws the late DH tornado path when `SimpleCloudsMod.dhLoaded()` is true. + - Default/shader-support tornado hooks also skip when `SimpleCloudsMod.dhLoaded()` is true. +- If Distant Horizons is installed but Simple Clouds does not consider DH active, Project Atmosphere can force the DH pipeline while the late DH tornado renderer refuses to run. +- This can produce no tornadoes in a "without DH active" test even when the tornado renderer itself is fine. + +## Next Fix +- Use `SimpleCloudsMod.dhLoaded()` consistently for choosing/forcing the Simple Clouds DH support pipeline. +- Do not force `DhSupportPipeline` based only on the mod being installed. +- Implementation: + - `SimpleCloudsDhPipelineSelector` now checks `SimpleCloudsMod.dhLoaded()`. + - `SimpleCloudsRendererDhFallbackMixin` now checks `SimpleCloudsMod.dhLoaded()`. +- Build result: `.\gradlew.bat build` passed. +- Result: pending user test. + +## New Issue +- User report: + - The inside-tornado fog/whiteout looks bad and should be removed. + - DH rendering now works with the late path and main-depth-only sampling. + - Non-DH/vanilla rendering regressed: the lower tornado is hidden by terrain. + - Non-DH `late` mode does not fix it, but `full` render mode does. +- Interpretation: + - DH and non-DH rendering should not share all behavior. + - DH should keep the confirmed late path. + - Non-DH normal rendering should use the shader behavior that `full` mode triggers, without requiring the user to enable debug mode. + +## Next Attempt +- Remove camera whiteout/fog from the tornado interior effect path. +- Keep DH rendering unchanged. +- For non-DH normal tornado rendering, send the shader the same mode value as `full` while leaving Java debug state inactive so: + - all tornadoes still render, + - downscaling can still work, + - debug filtering is not enabled, + - and the shader uses the full-detail path that fixes the vanilla bottom clipping. +- Implementation: + - Removed tornado whiteout contribution from `SimpleCloudsWhiteoutFogHandler`; cloud and dynamic atmosphere fog remain untouched. + - Non-DH normal rendering now sends the shader `DEBUG_FULL` while Java-side debug mode remains `OFF`. + - DH rendering still uses the corrected late path and does not receive this non-DH shader override. +- Build result: `.\gradlew.bat build` passed. +- Result: failed. Non-DH normal rendering is still broken, while explicit `/tornado render mode full` still works. + +## Current Finding +- Sending the shader `DEBUG_FULL` is not sufficient to reproduce explicit full mode. +- Explicit `full` mode also changes Java-side behavior because debug mode is active: + - it disables the tornado downsample path through `canUseDownsamplePath()`, + - it changes visibility handling through debug-mode behavior, + - and it still renders with the normal non-DH target/depth path. +- Since non-DH normal still has the bottom terrain clipping while explicit full does not, the next most likely difference is the non-DH downsample/composite path. + +## Next Attempt +- Keep DH downscaling because it was confirmed working and fast. +- Disable downscaling for non-DH normal tornado rendering when using the forced full shader behavior. +- This makes non-DH normal closer to explicit `full` mode without forcing Java debug filtering or affecting DH. +- Implementation: non-DH normal forced-full path now bypasses downscaling; DH late path can still downscale. +- Build result: `.\gradlew.bat build` passed. +- Result: fixed. Non-DH normal rendering now matches explicit `full` mode, but FPS is lower because non-DH downscaling is disabled. + +## Next Enhancement Attempt +- User wants non-DH downscaling back because the DH downscaled path has much better FPS. +- Previous non-DH downscaling broke the lower tornado, so do not simply re-enable the old path unchanged. +- Hypothesis: + - The old non-DH downsample render target does not carry the same terrain/cloud framebuffer depth state as the full-resolution cloud target. + - Fixed-function framebuffer depth during the low-resolution raymarch can therefore clip or classify the lower body differently. +- Next change: + - Allow non-DH forced-full normal rendering to use the downsample target again. + - When rendering to the downsample target, disable framebuffer depth and rely on the shader's sampled scene depth instead. + - This matches the safer DH late-path strategy while still compositing the result back into the normal Simple Clouds target. +- Implementation: + - Re-enabled downsampling for non-DH normal forced-full rendering. + - Disabled fixed-function framebuffer depth whenever the tornado renders into the low-resolution downsample target. +- Build result: `.\gradlew.bat build` passed. +- Result: failed. Non-DH tornado is hidden/not visible, and switching debug render modes did not recover the expected view. + +## Current Finding +- The non-DH downsample attempt broke visibility more severely than the previous full-resolution non-DH fix. +- The likely broken part is not the shader `full` behavior itself, because full-resolution non-DH was confirmed fixed before downscaling was reintroduced. +- The non-DH pipeline mixins decide whether to copy depth before calling the renderer: + - when `usesDownsamplePath()` is true, they skip `renderer.copyDepthFromCloudsToTransparency()`; + - they then pass `cloudTarget` depth to the shader. +- The confirmed full-resolution non-DH path used the copied transparency depth instead. + +## Next Attempt +- Keep non-DH downsample enabled. +- But change the default and shader-support pipeline hooks so they still copy depth to the transparency target before tornado rendering. +- Always pass the copied `cloudTransparencyTarget` depth as the shader scene-depth sampler for non-DH tornadoes. +- Reason: downsampling should only change color resolution, not the depth source. +- Result: failed. User still reports no visible non-DH tornado. + +## Current Finding +- The pipeline-selection theory was rejected by the latest user test. +- Stop changing render behavior until the actual failing point is proven. +- The current diagnostic gap is that normal non-DH rendering does not emit path logs unless a tornado render debug mode is active. + +## Next Diagnostic +- Add targeted non-DH path logging around: + - default/shader-support hook entry and early returns, + - prepared tornado count, + - `hasVisibleTornado`, + - hook-selected downsample/depth texture ids, + - renderer early returns, + - renderer-selected shader/debug/downsample/framebuffer-depth state, + - render-order count and submitted draw count. +- Gate these logs behind the existing Tornado Debug Logging config and rate-limit them to once per second. +- Do not change render behavior in this step. +- Result: confirmed root cause for the current non-DH no-render regression. + - Runtime log shows `DefaultPipeline prepared tornadoes=1 hasVisible=false`. + - The hook then exits with `DefaultPipeline skipped: no visible prepared tornado`. + - Therefore the non-DH renderer is not failing in the shader, depth, or downsample composite. It is being skipped before draw because the Simple Clouds pipeline frustum rejects the prepared tornado volume. + +## Next Fix +- Stop using the Simple Clouds pipeline frustum as a hard gate for Project Atmosphere tornado volume rendering in non-DH paths. +- Reason: + - Explicit debug/full mode works because debug mode bypasses frustum visibility by returning true when prepared tornadoes exist. + - DH already needed frustum handling relaxed because the pipeline frustum can reject the PA tornado volume incorrectly. + - The shader proxy and depth checks still constrain actual pixels; correctness is more important than skipping this pass from a bad frustum. +- Implementation plan: + - Add a direct `hasPreparedTornadoes()` check. + - Use that check in default/shader-support hooks. + - Pass `null` as the renderer frustum for those non-DH tornado draws so the renderer does not cull the same valid tornado again inside the draw loop. + +## Reset And Non-DH Downscale Investigation +- Reset local `Dynamic-Forge-1.20.1-Hurricane` to `c08213f`, the origin hurricane commit containing the `tornado-render-findings-1-5` squash. +- Verified the render-mode tests are present again: `density`, `wallcloud`, `connection`, and `groundskirt` modes exist in `TornadoRenderDebugState` and `tornado_round.fsh`. +- Verified DH still has its dedicated late path through `TornadoLateRenderDiagnostics`, rendering after `AFTER_LEVEL` into the main target with main-depth sampling and `distantHorizonsDepthMode=true`. +- Current non-DH downscale issue: + - The non-DH mixins call `usesDownsamplePath()` and can choose cloud-target depth / skip transparency-depth copy based on that prediction. + - The renderer then blocks actual non-DH downscale with `forceNonDhFullPath`, so mixin depth decisions and renderer behavior can disagree. + - When downscale is enabled, the low-resolution target is transparent, but the tornado pass uses normal alpha blending while drawing into it. That weakens alpha before the composite shader's discard. DH can survive because DH mode raises dense-body alpha, while non-DH can become invisible or clipped. +- Next fix: + - Keep DH late rendering behavior intact. + - Make non-DH depth source independent of downscale: always copy and pass `cloudTransparencyTarget` depth before tornado rendering. + - Re-enable non-DH downscale only as a color-resolution change. + - For non-DH downsample renders, disable blending into the intermediate target so the composite receives straight color/alpha instead of already-weakened alpha. +- Implementation: + - Removed the renderer-side `forceNonDhFullPath` block from downsample selection. + - Default and shader-support non-DH hooks now always copy cloud depth to the transparency target and pass that copied depth to the tornado shader, whether or not color downsample is active. + - Non-DH downsample rendering now disables blending while writing the low-resolution intermediate target; the existing composite shader remains responsible for blending into the cloud target. +- Build result: `.\gradlew.bat build` passed. +- Result: pending user test. + +## Non-DH Downscale Still Cut By Terrain +- User log result: + - `dhLoaded=false` + - `downsampled=true` + - `useDownsample=true` + - `deferSceneDepthReject=true` + - `writeDownsampleDepth=true` + - `drawn=1` +- Finding: + - The tornado is not being skipped; the low-resolution pass draws and writes depth into the low-resolution target. + - The composite pass blends color back into the full-resolution cloud target, but it leaves depth writes disabled. + - Therefore the full-resolution cloud target does not receive tornado depth for the composited pixels. + - Later Simple Clouds/world compositing can still use terrain/old depth as the foreground depth, making terrain appear to cut across the composited tornado. +- Next fix: + - For non-DH downsample composite only, enable destination depth writes with `GL_ALWAYS`. + - Have the composite shader write `gl_FragDepth` from the sampled low-resolution tornado depth. + - Keep DH composite behavior unchanged unless its already-working path explicitly enables this later. + +## Non-DH Downscale Log Follow-Up +- User-provided logs confirm the current failing frame still reaches the renderer: + - `prepared tornadoes=1` + - `hasPrepared=true` + - `frustumGate=disabled` + - `useDownsample=true` + - `deferSceneDepthReject=true` + - `writeDownsampleDepth=true` + - `drawn=1` +- Finding: + - The remaining issue is not a skipped hook, frustum rejection, missing shader, or missing low-resolution draw. + - The low-resolution pass produces tornado color and first-hit depth, but the full-resolution composite only blends color back into the destination target. + - Because destination depth stays stale, later Simple Clouds/world composition can still treat terrain depth as the foreground depth and cut through the downsampled tornado. +- Next fix: + - Change the non-DH deferred composite from "full-resolution scene-depth discard only" to "write sampled low-resolution tornado depth into the full-resolution destination depth buffer". + - Use `GL_ALWAYS` during this composite so every surviving tornado color pixel also stamps its corresponding tornado depth. + - Leave DH composite behavior unchanged by keeping this enabled only when the existing non-DH deferred-depth flag is active. + +## Non-DH Downscale Terrain Cut Regression +- User result: + - DH still works. + - Non-DH downscale renders again, but terrain depth cuts a large horizontal/diagonal chunk through the tornado. +- Finding from code inspection: + - The low-resolution tornado pass samples the full-resolution scene depth at one normalized UV per low-res pixel. + - If that one sampled terrain depth is in front of the first tornado hit, the shader discards the entire low-res tornado pixel. + - After upscaling, that discarded low-res pixel covers many screen pixels, so the terrain cut becomes much wider than the real full-resolution terrain edge. + - This is why the problem looks like depth, but the damaging part is the low-resolution pre-discard before composite. +- Next fix: + - For non-DH downscale only, defer scene-depth rejection out of the low-resolution raymarch. + - Still write the tornado first-hit depth into the low-resolution depth attachment. + - During full-resolution composite, sample the low-res tornado color/depth and compare against the full-resolution scene depth per final screen pixel. + - Keep DH late rendering behavior unchanged. +- Implementation: + - Added `DeferSceneDepthReject` to the tornado volume shader and enable it only for non-DH downsample rendering. + - Non-DH downsample now renders the low-resolution tornado with depth test `ALWAYS` and depth writes enabled, so the downsample target stores first-hit tornado depth without letting low-res terrain depth discard the color. + - The composite shader now samples low-res tornado color, low-res tornado depth, and full-resolution scene depth; when enabled, it performs the final scene-depth rejection at full output resolution. + - DH downsample/late behavior keeps the old composite behavior by leaving composite scene-depth testing disabled. +- Build result: `.\gradlew.bat build` passed. +- Result: pending user test. diff --git a/build.gradle b/build.gradle index deae8cb1..4d339797 100644 --- a/build.gradle +++ b/build.gradle @@ -74,7 +74,22 @@ mixin { repositories { - maven { url = 'https://repo.spongepowered.org/repository/maven-public/' } + maven { + name = 'MinecraftForge' + url = 'https://maven.minecraftforge.net/' + content { + includeGroupByRegex('net\\.minecraftforge.*') + includeGroup('cpw.mods') + } + } + maven { + name = 'Sponge' + url = 'https://repo.spongepowered.org/repository/maven-public/' + content { + includeGroupByRegex('org\\.spongepowered.*') + } + } + mavenCentral() maven { url "https://maven.blamejared.com/" } maven { url "https://www.cursemaven.com" @@ -120,7 +135,8 @@ dependencies { annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" //implementation fg.deobf("curse.maven:serene-seasons-plus-1288843:7027745") - implementation fg.deobf("nonamecrackers2:simpleclouds:0.7.3+1.20.1-forge") + //implementation fg.deobf("nonamecrackers2:simpleclouds:0.7.3+1.20.1-forge") + implementation fg.deobf("curse.maven:oculus-for-simpleclouds-1386686:7870940") implementation fg.deobf("nonamecrackers2:crackerslib-forge:${crackerslib_version}") implementation fg.deobf("nonamecrackers2:simplecloudsapi-forge:${api_version}") compileOnly(fg.deobf("curse.maven:legendary-survival-overhaul-840254:6717844")) @@ -140,6 +156,8 @@ dependencies { compileOnly fg.deobf("curse.maven:project-atmosphere-for-tfc-1391548:7261639") compileOnly fg.deobf("curse.maven:dynamictrees-252818:7502488") + compileOnly fg.deobf("curse.maven:simple-clouds-1121215:6928978") + compileOnly "maven.modrinth:protomanlys-weather:0.16.4-1.20.1-alpha" //implementation "curse.maven:nolijium-969602:6264798" @@ -152,6 +170,7 @@ dependencies { runtimeOnly fg.deobf("curse.maven:chloride-931925:6615986") runtimeOnly fg.deobf("curse.maven:embeddium-908741:5681725") + implementation fg.deobf("curse.maven:distant-horizons-508933:7948170") // Local dependency, ensure the file exists in the libs directory // Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings @@ -190,6 +209,8 @@ tasks.named('processResources', ProcessResources).configure { } tasks.named('jar', Jar).configure { + archiveFileName.set("Forge-${mod_id}-pre-${project.version}.jar") + manifest { attributes([ "Specification-Title" : mod_id, @@ -201,6 +222,7 @@ tasks.named('jar', Jar).configure { "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") ]) } + finalizedBy 'reobfJar' } @@ -215,7 +237,7 @@ tasks.withType(JavaCompile).configureEach { ] } -def releaseJarFile = layout.buildDirectory.file("libs/${base.archivesName.get()}-${project.version}.jar") +def releaseJarFile = layout.buildDirectory.file("libs/Forge-${mod_id}-pre-${project.version}.jar") tasks.register("discordWebhook") { doLast { diff --git a/com/seibel/distanthorizons/api/DhApi.java b/com/seibel/distanthorizons/api/DhApi.java new file mode 100644 index 00000000..79f67940 --- /dev/null +++ b/com/seibel/distanthorizons/api/DhApi.java @@ -0,0 +1,213 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.api; + +import com.seibel.distanthorizons.api.interfaces.events.IDhApiEventInjector; +import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory; +import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable; +import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGeneratorOverrideRegister; +import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderObjectFactory; +import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy; +import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper; +import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiAfterDhInitEvent; +import com.seibel.distanthorizons.api.methods.override.DhApiWorldGeneratorOverrideRegister; +import com.seibel.distanthorizons.coreapi.ModInfo; +import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfig; +import com.seibel.distanthorizons.api.interfaces.world.IDhApiWorldProxy; +import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; +import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector; +import com.seibel.distanthorizons.api.interfaces.data.IDhApiTerrainDataRepo; +import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector; + +/** + * This is the masthead of the API, almost everything you could want to do + * can be achieved from here.
+ * For example: you can access singletons which handle the config or event binding.

+ * + * Q: Why should I use this class instead of just getting the API singleton I need?
+ * A: This way there is a lower chance of your code breaking if we change something on our end. + * For example, if we realized there is a much better way of handling dependency injection we would keep the + * interface the same so your code doesn't have to change. Whereas if you were directly referencing + * the concrete object we replaced, there would be issues. + * + * @author James Seibel + * @version 2023-12-16 + * @since API 1.0.0 + */ +public class DhApi +{ + /** + * If you can see this Java Doc, this can be ignored.
+ * This is just to let you know that Javadocs are available and that you should use the API jar + * instead of the full mod jar.

+ * + * Note: Don't use this string in your code. It may change and is only for reference. + */ + public static final String READ_ME = + "If you don't see Javadocs something is wrong. \n" + + "If you are only using the full DH Mod in your build script, you won't have access to our javadocs and could potentially call into unsafe code. \n" + + "\n" + + "Please use the API jar in your build script as a compile time dependency " + + "and the full DH jar as a runtime dependency. \n" + + "\n" + + "Please refer to the example API project or the DH Developer Wiki for additional information " + + "and suggested setup. \n" + // DH Dev note: no links were included to prevent link rot. + ""; + public static String readMe() { return READ_ME; } + + /** + * This is just a humorous way to reference the {@link DhApi#READ_ME} constant string and hopefully peak a few people's attention + * vs the relatively boring "readMe". + */ + public static final String HEY_YOU_YOURE_FINALLY_AWAKE = READ_ME; + /** + * This is just a humorous way to reference the {@link DhApi#READ_ME} constant string and hopefully peak a few people's attention + * vs the relatively boring "readMe". + */ + public static String heyYou_YoureFinallyAwake() { return READ_ME; } + + + + /** + * WARNING: + * All objects in this class will be null until after DH initializes for the first time.

+ * + * Bind a custom {@link DhApiAfterDhInitEvent DhApiAfterDhInitEvent} + * to {@link DhApi#events ApiCoreInjectors.events} in order to be notified when this class can + * be safely used. + * + * @since API 1.0.0 + */ + public static class Delayed + { + /** + * Used to interact with Distant Horizons' Configs. + * @since API 1.0.0 + */ + public static IDhApiConfig configs = null; + + /** + * Used to interact with Distant Horizons' terrain data. + * Designed to be used in conjunction with {@link DhApi.Delayed#worldProxy}. + * @since API 1.0.0 + */ + public static IDhApiTerrainDataRepo terrainRepo = null; + + /** + * Used to interact with Distant Horizons' currently loaded world. + * Designed to be used in conjunction with {@link DhApi.Delayed#terrainRepo}. + * @since API 1.0.0 + */ + public static IDhApiWorldProxy worldProxy = null; + + /** + * Used to interact with Distant Horizons' rendering system. + * @since API 1.0.0 + */ + public static IDhApiRenderProxy renderProxy = null; + + /** + * Used to create wrappers for Minecraft objects needed by other Distant Horizons API methods. + * @since API 2.0.0 + */ + public static IDhApiWrapperFactory wrapperFactory = null; + + /** + * Used to create custom renderable objects.
+ * These objects can be added to the renderer in {@link IDhApiLevelWrapper} + * @since API 3.0.0 + */ + public static IDhApiCustomRenderObjectFactory customRenderObjectFactory = null; + } + + + + //==================// + // always available // + //==================// + + // interfaces // + + /** + * Used to bind/unbind Distant Horizons Api events. + * @since API 1.0.0 + */ + public static final IDhApiEventInjector events = ApiEventInjector.INSTANCE; + + /** + * Used to bind/unbind Distant Horizons Api events. + * @since API 1.0.0 + */ + public static final IDhApiWorldGeneratorOverrideRegister worldGenOverrides = DhApiWorldGeneratorOverrideRegister.INSTANCE; + + /** + * Used to bind overrides to change Distant Horizons' core behavior. + * @since API 1.0.0 + */ + public static final IOverrideInjector overrides = OverrideInjector.INSTANCE; + + + // getters // + + /** + * This version should only be updated when breaking changes are introduced to the Distant Horizons API. + * @since API 1.0.0 + */ + public static int getApiMajorVersion() { return ModInfo.API_MAJOR_VERSION; } + /** + * This version should be updated whenever new methods are added to the Distant Horizons API. + * @since API 1.0.0 + */ + public static int getApiMinorVersion() { return ModInfo.API_MINOR_VERSION; } + /** + * This version should be updated whenever non-breaking fixes are added to the Distant Horizons API. + * @since API 1.0.0 + */ + public static int getApiPatchVersion() { return ModInfo.API_PATH_VERSION; } + + /** + * Returns the mod's semantic version number in the format: Major.Minor.Patch + * with optional extensions "-a" for alpha, "-b" for beta, and -dev for unstable development builds.
+ * Examples: "1.6.9-a", "1.7.0-a-dev", "2.1.0-b", "3.0.0", "3.1.4-dev" + * @since API 1.0.0 + */ + public static String getModVersion() { return ModInfo.VERSION; } + /** + * Returns true if the mod is a development version, false if it is a release version. + * @since API 1.0.0 + */ + public static boolean getIsDevVersion() { return ModInfo.IS_DEV_BUILD; } + + /** + * Returns the network protocol version. + * @since API 1.0.0 + */ + public static int getNetworkProtocolVersion() { return ModInfo.PROTOCOL_VERSION; } + + + // methods // + + /** + * Returns true if the thread this method was called from is owned by Distant Horizons. + * @since API 2.0.0 + */ + public static boolean isDhThread() { return Thread.currentThread().getName().startsWith(ModInfo.THREAD_NAME_PREFIX); } + +} diff --git a/com/seibel/distanthorizons/api/methods/events/sharedParameterObjects/DhApiRenderParam.java b/com/seibel/distanthorizons/api/methods/events/sharedParameterObjects/DhApiRenderParam.java new file mode 100644 index 00000000..e5701ae2 --- /dev/null +++ b/com/seibel/distanthorizons/api/methods/events/sharedParameterObjects/DhApiRenderParam.java @@ -0,0 +1,120 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects; + +import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass; +import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam; +import com.seibel.distanthorizons.api.objects.math.DhApiMat4f; + +/** + * Contains information relevant to Distant Horizons and Minecraft rendering. + * + * @author James Seibel + * @version 2024-1-31 + * @since API 1.0.0 + */ +public class DhApiRenderParam implements IDhApiEventParam +{ + /** Indicates what render pass DH is currently rendering */ + public final EDhApiRenderPass renderPass; + + /** Indicates how far into this tick the frame is. */ + public final float partialTicks; + + /** + * Indicates DH's near clip plane, measured in blocks. + * Note: this may change based on time, player speed, and other factors. + */ + public final float nearClipPlane; + /** + * Indicates DH's far clip plane, measured in blocks. + * Note: this may change based on time, player speed, and other factors. + */ + public final float farClipPlane; + + /** The projection matrix Minecraft is using to render this frame. */ + public final DhApiMat4f mcProjectionMatrix; + /** The model view matrix Minecraft is using to render this frame. */ + public final DhApiMat4f mcModelViewMatrix; + + /** The projection matrix Distant Horizons is using to render this frame. */ + public final DhApiMat4f dhProjectionMatrix; + /** The model view matrix Distant Horizons is using to render this frame. */ + public final DhApiMat4f dhModelViewMatrix; + + public final int worldYOffset; + + + + //==============// + // constructors // + //==============// + + + public DhApiRenderParam(DhApiRenderParam parent) + { + this( + parent.renderPass, + parent.partialTicks, + parent.nearClipPlane, parent.farClipPlane, + parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(), + parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(), + parent.worldYOffset + ); + } + public DhApiRenderParam( + EDhApiRenderPass renderPass, + float newPartialTicks, + float nearClipPlane, float farClipPlane, + DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix, + DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix, + int worldYOffset + ) + { + this.renderPass = renderPass; + + this.partialTicks = newPartialTicks; + + this.farClipPlane = farClipPlane; + this.nearClipPlane = nearClipPlane; + + this.mcProjectionMatrix = newMcProjectionMatrix; + this.mcModelViewMatrix = newMcModelViewMatrix; + + this.dhProjectionMatrix = newDhProjectionMatrix; + this.dhModelViewMatrix = newDhModelViewMatrix; + + this.worldYOffset = worldYOffset; + + } + + + + //================// + // base overrides // + //================// + + @Override + public DhApiRenderParam copy() + { + return new DhApiRenderParam(this); + } + +} diff --git a/data/mcp/mods/projectatmosphere/CHANGELOG.md b/data/mcp/mods/projectatmosphere/CHANGELOG.md new file mode 100644 index 00000000..eee4c23e --- /dev/null +++ b/data/mcp/mods/projectatmosphere/CHANGELOG.md @@ -0,0 +1,51 @@ +# Project Atmosphere support changelog + +This changelog is support-focused. It uses official release notes where available and repository history where detailed public notes are missing. + +## 0.8.0.0 +- Continued the region-first forecast/runtime refactor. +- Added humidity budget instrumentation and cloud-water coupling work. +- Added telemetry for target-vs-live temperature, pressure, and humidity values. +- Fixed a JDK 17 telemetry export crash caused by `java.time.Instant` serialization. +- Official compatibility note: temporarily incompatible with PA x TFC. +- Official compatibility note: Dynamic Trees integration remains work in progress and should stay disabled. + +## 0.6.0.0-pre2 +- Repository history links this version to sunlight-driven temperature calculations. +- Repository history links this version to baseline temperature bounds work. +- Detailed public release notes: `UNKNOWN`. + +## 0.5.5.7 +- Repository history links this version to auroras and rainbows compatibility work. +- Repository history links this version to storm severity tuning. +- Detailed public release notes: `UNKNOWN`. + +## 0.5.5.4 +- Repository history links this version to barometer/humidimeter model updates. +- Repository history links this version to hygrometer additions. +- Detailed public release notes: `UNKNOWN`. + +## 0.5.5.2 +- Repository history shows a version bump and changelog/memory-setting adjustments. +- Detailed public release notes: `UNKNOWN`. + +## 0.5.5.0 +- Repository history describes a beta release. +- Repository history mentions cloud storminess parameter adjustments. +- Detailed public release notes: `UNKNOWN`. + +## 0.5.4.3 +- Repository history mentions cloud region spawn handling improvements. +- Detailed public release notes: `UNKNOWN`. + +## 0.5.4.1 +- Repository history mentions season change handling improvements. +- Detailed public release notes: `UNKNOWN`. + +## 0.5.4.0 +- Repository history mentions sandstorm scheduling changes. +- Detailed public release notes: `UNKNOWN`. + +## 0.3.7 +- Version observed in repository history. +- Detailed public release notes: `UNKNOWN`. diff --git a/data/mcp/mods/projectatmosphere/commands.json b/data/mcp/mods/projectatmosphere/commands.json new file mode 100644 index 00000000..67749b06 --- /dev/null +++ b/data/mcp/mods/projectatmosphere/commands.json @@ -0,0 +1,347 @@ +{ + "commands": [ + { + "name": "/pa temperature forecast", + "syntax": "/pa temperature forecast", + "permissionLevel": "player", + "description": "Shows the weekly temperature forecast for the player's current biome.", + "examples": [ + "/pa temperature forecast" + ], + "notes": "Overworld only." + }, + { + "name": "/pa temperature get", + "syntax": "/pa temperature get ", + "permissionLevel": "player", + "description": "Samples the current temperature for a biome id or the current biome aliases supported by the command helper.", + "examples": [ + "/pa temperature get minecraft:plains", + "/pa temperature get current" + ], + "notes": "Overworld only." + }, + { + "name": "/pa temperature dayprofile", + "syntax": "/pa temperature dayprofile", + "permissionLevel": "player", + "description": "Prints the current biome's daily temperature profile array.", + "examples": [ + "/pa temperature dayprofile" + ], + "notes": "Overworld only." + }, + { + "name": "/pa temperature getseason", + "syntax": "/pa temperature getseason", + "permissionLevel": "2", + "description": "Shows the current season/sub-season as reported by the active season provider.", + "examples": [ + "/pa temperature getseason" + ] + }, + { + "name": "/pa temperature gettemp", + "syntax": "/pa temperature gettemp", + "permissionLevel": "2", + "description": "Prints raw and converted temperature values for the player's position.", + "examples": [ + "/pa temperature gettemp" + ], + "notes": "Overworld only." + }, + { + "name": "/pa temperature regenerate", + "syntax": "/pa temperature regenerate", + "permissionLevel": "2", + "description": "Clears/regenerates forecast cache data.", + "examples": [ + "/pa temperature regenerate" + ], + "notes": "Overworld only." + }, + { + "name": "/pa temperature resetSpikes", + "syntax": "/pa temperature resetSpikes", + "permissionLevel": "2", + "description": "Clears cached temperature spike simulation data.", + "examples": [ + "/pa temperature resetSpikes" + ] + }, + { + "name": "/pa temperature help", + "syntax": "/pa temperature help", + "permissionLevel": "player", + "description": "Prints built-in help for the temperature command group.", + "examples": [ + "/pa temperature help" + ] + }, + { + "name": "/pa humidity get", + "syntax": "/pa humidity get", + "permissionLevel": "player", + "description": "Prints the humidity forecast array for the player's current region.", + "examples": [ + "/pa humidity get" + ] + }, + { + "name": "/pa pressure get", + "syntax": "/pa pressure get", + "permissionLevel": "player", + "description": "Prints the pressure forecast array for the player's current region.", + "examples": [ + "/pa pressure get" + ] + }, + { + "name": "/pa windSpeed get", + "syntax": "/pa windSpeed get", + "permissionLevel": "player", + "description": "Prints the wind forecast data for the player's current region.", + "examples": [ + "/pa windSpeed get" + ], + "notes": "Overworld only." + }, + { + "name": "/pa spawncloud", + "syntax": "/pa spawncloud", + "permissionLevel": "2", + "description": "Spawns a cloud for the executing player through the Simple Clouds integration.", + "examples": [ + "/pa spawncloud" + ], + "notes": "Overworld only." + }, + { + "name": "/pa fog spawn", + "syntax": "/pa fog spawn [strength] [seconds]", + "permissionLevel": "2", + "description": "Applies a client fog debug override to the executing player.", + "examples": [ + "/pa fog spawn", + "/pa fog spawn 0.85 30" + ], + "notes": "Current-source debug command. Overworld only." + }, + { + "name": "/pa fog clear", + "syntax": "/pa fog clear", + "permissionLevel": "2", + "description": "Clears the client fog debug override for the executing player.", + "examples": [ + "/pa fog clear" + ], + "notes": "Current-source debug command. Overworld only." + }, + { + "name": "/pa weatherdebug forecast", + "syntax": "/pa weatherdebug forecast [biome]", + "permissionLevel": "player", + "description": "Prints forecast, humidity, pressure, and wind information for the current position or a biome id argument.", + "examples": [ + "/pa weatherdebug forecast", + "/pa weatherdebug forecast minecraft:plains" + ], + "notes": "Overworld only." + }, + { + "name": "/pa weatherdebug fog", + "syntax": "/pa weatherdebug fog [pos]", + "permissionLevel": "player", + "description": "Prints fog heuristic inputs and tunables for the current position or a target block position.", + "examples": [ + "/pa weatherdebug fog", + "/pa weatherdebug fog 0 64 0" + ], + "notes": "Current-source command. Overworld only." + }, + { + "name": "/pa weatherdebug cpu", + "syntax": "/pa weatherdebug cpu", + "permissionLevel": "player", + "description": "Shows simple CPU/executor profile information used by the mod.", + "examples": [ + "/pa weatherdebug cpu" + ] + }, + { + "name": "/pa weatherdebug rain", + "syntax": "/pa weatherdebug rain [pos] [noThunder] [intensity]", + "permissionLevel": "player", + "description": "Spawns a debug rain cloud near the target position.", + "examples": [ + "/pa weatherdebug rain", + "/pa weatherdebug rain 0 64 0 true 2" + ], + "notes": "Overworld only. Intensity range is 1..2." + }, + { + "name": "/pa weatherdebug thunder", + "syntax": "/pa weatherdebug thunder [pos] [intensity]", + "permissionLevel": "player", + "description": "Spawns a debug thunder cloud near the target position.", + "examples": [ + "/pa weatherdebug thunder", + "/pa weatherdebug thunder 0 64 0 2" + ], + "notes": "Overworld only. Intensity range is 1..2." + }, + { + "name": "/pa weatherdebug snowstorm", + "syntax": "/pa weatherdebug snowstorm [pos] [overwrite]", + "permissionLevel": "player", + "description": "Attempts to spawn a debug snowstorm cloud.", + "examples": [ + "/pa weatherdebug snowstorm", + "/pa weatherdebug snowstorm 0 64 0 true" + ], + "notes": "Currently broken in source: the handler expects an intensity argument that the command tree does not expose." + }, + { + "name": "/pa weatherdebug debugmode", + "syntax": "/pa weatherdebug debugmode ", + "permissionLevel": "player", + "description": "Toggles the in-memory Project Atmosphere debug mode flag.", + "examples": [ + "/pa weatherdebug debugmode true" + ] + }, + { + "name": "/pa weatherdebug cloud", + "syntax": "/pa weatherdebug cloud ", + "permissionLevel": "2", + "description": "Spawns a specific cloud id at the player's position.", + "examples": [ + "/pa weatherdebug cloud cumulonimbus" + ], + "notes": "Overworld only." + }, + { + "name": "/pa weatherdebug tornado risk", + "syntax": "/pa weatherdebug tornado risk", + "permissionLevel": "2", + "description": "Prints the computed tornado risk for the player's current region.", + "examples": [ + "/pa weatherdebug tornado risk" + ], + "notes": "Overworld only." + }, + { + "name": "/pa weatherdebug tornado force", + "syntax": "/pa weatherdebug tornado force ", + "permissionLevel": "2", + "description": "Forces a tornado spawn with the given intensity.", + "examples": [ + "/pa weatherdebug tornado force 0.8" + ], + "notes": "Overworld only. Intensity range is 0.0..1.0." + }, + { + "name": "/pa weatherdebug tornado cooldown reset", + "syntax": "/pa weatherdebug tornado cooldown reset", + "permissionLevel": "2", + "description": "Clears the tornado cooldown for the player's current region.", + "examples": [ + "/pa weatherdebug tornado cooldown reset" + ], + "notes": "Overworld only." + }, + { + "name": "/pa spawnTornado", + "syntax": "/pa spawnTornado", + "permissionLevel": "2", + "description": "Attempts to engage a tornado near the player, seeding or reusing a cumulonimbus cloud if needed.", + "examples": [ + "/pa spawnTornado" + ], + "notes": "Alias: /pa spawntornadoes. Overworld only." + }, + { + "name": "/pa cleartornadoes", + "syntax": "/pa cleartornadoes", + "permissionLevel": "2", + "description": "Clears all active tornadoes.", + "examples": [ + "/pa cleartornadoes" + ] + }, + { + "name": "/pa removetornado", + "syntax": "/pa removetornado", + "permissionLevel": "2", + "description": "Removes a tornado near the executing player.", + "examples": [ + "/pa removetornado" + ], + "notes": "Overworld only." + }, + { + "name": "/pa spawnHurricane", + "syntax": "/pa spawnHurricane ", + "permissionLevel": "2", + "description": "Spawns a hurricane near the player in a valid ocean biome.", + "examples": [ + "/pa spawnHurricane 1", + "/pa spawnHurricanes 5" + ], + "notes": "Alias: /pa spawnHurricanes. Overworld only. Category range is 1..5." + }, + { + "name": "/pa clearhurricanes", + "syntax": "/pa clearhurricanes", + "permissionLevel": "2", + "description": "Clears all active hurricanes.", + "examples": [ + "/pa clearhurricanes" + ] + }, + { + "name": "/pa removehurricane", + "syntax": "/pa removehurricane", + "permissionLevel": "2", + "description": "Removes a hurricane near the executing player.", + "examples": [ + "/pa removehurricane" + ] + }, + { + "name": "/pa debug export", + "syntax": "/pa debug export", + "permissionLevel": "client-only", + "description": "Client debug command that exports telemetry archives when telemetry is enabled.", + "examples": [ + "/pa debug export" + ], + "notes": "Registered on the client command dispatcher." + }, + { + "name": "/pa debug open", + "syntax": "/pa debug open", + "permissionLevel": "client-only", + "description": "Client debug command that opens the telemetry folder when telemetry is enabled.", + "examples": [ + "/pa debug open" + ], + "notes": "Registered on the client command dispatcher." + }, + { + "name": "/dumpcloud", + "syntax": "/dumpcloud", + "permissionLevel": "client-only", + "description": "Client debug command that captures the current cloud render texture.", + "examples": [ + "/dumpcloud" + ], + "notes": "Registered on the client command dispatcher." + } + ], + "notes": [ + "Current server commands are rooted under /pa.", + "The README still mentions /weatherdebug directly, but the current source registers /pa weatherdebug instead.", + "An undocumented /pa weatherdebug path exists in source for cloud violence reporting; it is omitted here because the syntax is unstable and not user-friendly." + ] +} diff --git a/data/mcp/mods/projectatmosphere/compatibility.json b/data/mcp/mods/projectatmosphere/compatibility.json new file mode 100644 index 00000000..f0ec4a8b --- /dev/null +++ b/data/mcp/mods/projectatmosphere/compatibility.json @@ -0,0 +1,88 @@ +{ + "supportedMinecraftVersions": [ + "1.20.1" + ], + "supportedLoaders": [ + "forge" + ], + "requiredDependencies": [ + { + "id": "forge", + "name": "Minecraft Forge", + "notes": "Declared mandatory in mods.toml." + }, + { + "id": "minecraft", + "name": "Minecraft", + "notes": "Declared mandatory in mods.toml with version range [1.20.1,1.21)." + }, + { + "id": "simpleclouds", + "name": "Simple Clouds", + "notes": "Declared mandatory in mods.toml." + }, + { + "id": "gaboulibs", + "name": "Gabou's Libs", + "notes": "Declared mandatory in mods.toml." + } + ], + "optionalDependencies": [ + { + "id": "sereneseasons", + "name": "Serene Seasons", + "notes": "Optional in mods.toml and explicitly documented in README." + }, + { + "id": "sereneseasonsplus", + "name": "Serene Seasons Extended / Serene Seasons Plus", + "notes": "Optional in mods.toml; README calls out Serene Seasons Extended by name." + }, + { + "id": "dynamictrees", + "name": "Dynamic Trees", + "notes": "Optional in mods.toml; latest official note says the module is still work in progress and should be disabled." + }, + { + "id": "sandstorm", + "name": "Sandstorm", + "notes": "Optional in mods.toml. No official support statement beyond optional dependency declaration was found." + }, + { + "id": "prettyrain", + "name": "Pretty Rain", + "notes": "Mentioned as compatible in README." + } + ], + "confirmedCompatible": [ + { + "name": "Serene Seasons", + "scope": "seasonal logic" + }, + { + "name": "Serene Seasons Extended / Serene Seasons Plus", + "scope": "seasonal compatibility" + }, + { + "name": "Simple Clouds", + "scope": "cloud rendering and cloud-driven weather" + }, + { + "name": "Pretty Rain", + "scope": "visual rain enhancements" + } + ], + "confirmedIncompatible": [ + { + "name": "Project Atmosphere for TFC", + "affectedVersions": [ + "0.8.0.0" + ], + "notes": "PAchangelog.md says 0.8.0.0 is temporarily incompatible with PA x TFC." + } + ], + "notes": [ + "This file stays strict: it only includes compatibility directly documented in repository metadata or official release notes.", + "Current code appears to require a season provider at runtime, but the officially supported provider list still needs maintainer confirmation." + ] +} diff --git a/data/mcp/mods/projectatmosphere/config-schema.json b/data/mcp/mods/projectatmosphere/config-schema.json new file mode 100644 index 00000000..9e1938fe --- /dev/null +++ b/data/mcp/mods/projectatmosphere/config-schema.json @@ -0,0 +1,129 @@ +{ + "entries": [ + {"key":"performance.forceSharedExecutor","type":"boolean","default":false,"allowedValues":[true,false],"description":"Force use of a shared executor for async tasks regardless of CPU count.","example":false,"category":"performance"}, + {"key":"performance.cloudRenderDistance","type":"integer","default":2000,"allowedValues":"100..Integer.MAX_VALUE","description":"Maximum cloud render distance in blocks.","example":2000,"category":"performance"}, + {"key":"display.imperialUnits","type":"boolean","default":false,"allowedValues":[true,false],"description":"Show temperatures, wind, and pressure in imperial units instead of metric.","example":false,"category":"display"}, + + {"key":"storms.stormSeverityBooster","type":"number","default":3.2,"allowedValues":"0.5..8.0","description":"Global multiplier used in storm severity calculations.","example":3.2,"category":"storms"}, + {"key":"storms.enableTornadoes","type":"boolean","default":true,"allowedValues":[true,false],"description":"Enables tornado spawning logic and tornado commands.","example":true,"category":"storms"}, + {"key":"storms.enableStormDebris","type":"boolean","default":false,"allowedValues":[true,false],"description":"Enables random debris spawning during storms.","example":false,"category":"storms"}, + {"key":"storms.maxStormDebrisPerChunk","type":"integer","default":10,"allowedValues":"0..Integer.MAX_VALUE","description":"Maximum storm-debris entities/items allowed per chunk.","example":10,"category":"storms"}, + {"key":"storms.autoRepairGlass","type":"boolean","default":true,"allowedValues":[true,false],"description":"Repairs tornado-damaged glass after a quiet period.","example":true,"category":"storms"}, + {"key":"storms.damageGlassOnTornado","type":"boolean","default":true,"allowedValues":[true,false],"description":"Allows tornadoes to damage glass blocks.","example":true,"category":"storms"}, + + {"key":"storms.tornado.checkIntervalSec","type":"number","default":60.0,"allowedValues":"1.0..3600.0","description":"Seconds between tornado spawn checks.","example":60.0,"category":"storms.tornado"}, + {"key":"storms.tornado.baseSpawnRadiusM","type":"number","default":64.0,"allowedValues":"1.0..512.0","description":"Base radius around a biome center used for tornado spawn placement.","example":64.0,"category":"storms.tornado"}, + {"key":"storms.tornado.minTempContrastC","type":"number","default":6.0,"allowedValues":"0.0..100.0","description":"Minimum surface-versus-aloft temperature contrast needed for tornado risk.","example":6.0,"category":"storms.tornado"}, + {"key":"storms.tornado.humidityMinPercent","type":"number","default":65.0,"allowedValues":"0.0..100.0","description":"Minimum humidity percentage needed for tornado risk.","example":65.0,"category":"storms.tornado"}, + {"key":"storms.tornado.pressureGradientGain","type":"number","default":10.0,"allowedValues":"0.0..100.0","description":"Multiplier applied to pressure-gradient contribution.","example":10.0,"category":"storms.tornado"}, + {"key":"storms.tornado.pressureGradientCap","type":"number","default":3.0,"allowedValues":"0.0..100.0","description":"Maximum pressure-gradient contribution.","example":3.0,"category":"storms.tornado"}, + {"key":"storms.tornado.shearMinSpeedDiffMps","type":"number","default":5.0,"allowedValues":"0.0..100.0","description":"Minimum wind-speed difference used for shear checks.","example":5.0,"category":"storms.tornado"}, + {"key":"storms.tornado.shearMinDirDiffDeg","type":"number","default":45.0,"allowedValues":"0.0..360.0","description":"Minimum wind-direction difference used for shear checks.","example":45.0,"category":"storms.tornado"}, + {"key":"storms.tornado.stormMultiplier","type":"number","default":1.5,"allowedValues":"0.0..10.0","description":"Risk multiplier applied during storms.","example":1.5,"category":"storms.tornado"}, + {"key":"storms.tornado.riskMinToConsider","type":"number","default":4.0,"allowedValues":"0.0..100.0","description":"Minimum computed tornado risk before spawning is considered.","example":4.0,"category":"storms.tornado"}, + {"key":"storms.tornado.baseTriggerChance","type":"number","default":0.05,"allowedValues":"0.0..1.0","description":"Base chance per risk point to trigger a tornado.","example":0.05,"category":"storms.tornado"}, + {"key":"storms.tornado.lapseRateCPer100m","type":"number","default":0.65,"allowedValues":"0.0..10.0","description":"Temperature lapse rate per 100 meters for aloft temperature proxying.","example":0.65,"category":"storms.tornado"}, + {"key":"storms.tornado.aloftDeltaHM","type":"number","default":1500.0,"allowedValues":"0.0..10000.0","description":"Height difference used for the aloft temperature proxy.","example":1500.0,"category":"storms.tornado"}, + {"key":"storms.tornado.intensityMin","type":"number","default":0.4,"allowedValues":"0.0..1.0","description":"Minimum tornado intensity.","example":0.4,"category":"storms.tornado"}, + {"key":"storms.tornado.intensityMax","type":"number","default":1.0,"allowedValues":"0.0..1.0","description":"Maximum tornado intensity.","example":1.0,"category":"storms.tornado"}, + {"key":"storms.tornado.cellCooldownMinutes","type":"integer","default":20,"allowedValues":"0..Integer.MAX_VALUE","description":"Cooldown before the same cell can spawn another tornado.","example":20,"category":"storms.tornado"}, + {"key":"storms.tornado.allowLegacyTornadoFallback","type":"boolean","default":false,"allowedValues":[true,false],"description":"Allows a legacy tornado mesh fallback when the shader-driven funnel path is unavailable.","example":false,"category":"storms.tornado"}, + {"key":"storms.tornado.debugTornadoLogging","type":"boolean","default":false,"allowedValues":[true,false],"description":"Enables verbose tornado logging.","example":false,"category":"storms.tornado"}, + + {"key":"wind.baseRetargetSec","type":"number","default":60.0,"allowedValues":"1.0..600.0","description":"Seconds between base-wind retarget operations.","example":60.0,"category":"wind"}, + {"key":"wind.dirRetargetSec","type":"number","default":90.0,"allowedValues":"1.0..600.0","description":"Seconds between wind-direction retarget operations.","example":90.0,"category":"wind"}, + {"key":"wind.gustMeanSec","type":"number","default":15.0,"allowedValues":"1.0..600.0","description":"Average gust duration in seconds.","example":15.0,"category":"wind"}, + {"key":"wind.gustDecayMps","type":"number","default":1.0,"allowedValues":"0.0..100.0","description":"Gust decay speed in meters per second per second.","example":1.0,"category":"wind"}, + {"key":"wind.stormGustMult","type":"number","default":2.0,"allowedValues":"0.0..10.0","description":"Gust multiplier applied during storms.","example":2.0,"category":"wind"}, + {"key":"wind.pushThresholdMps","type":"number","default":6.0,"allowedValues":"0.0..100.0","description":"Minimum wind speed that starts pushing entities.","example":6.0,"category":"wind"}, + {"key":"wind.pushRampMps","type":"number","default":8.0,"allowedValues":"0.0..100.0","description":"Soft-start ramp above the push threshold.","example":8.0,"category":"wind"}, + {"key":"wind.playerPushScale","type":"number","default":0.013333333333,"allowedValues":"0.0..1.0","description":"Push scale applied to players.","example":0.013333333333,"category":"wind"}, + {"key":"wind.entityPushScale","type":"number","default":0.01,"allowedValues":"0.0..1.0","description":"Push scale applied to non-player entities.","example":0.01,"category":"wind"}, + {"key":"wind.particleBendStrength","type":"number","default":0.08,"allowedValues":"0.0..1.0","description":"Blend strength used for wind-bent particles.","example":0.08,"category":"wind"}, + {"key":"wind.playerWindThresholdMps","type":"number","default":11.1,"allowedValues":"0.0..100.0","description":"Minimum base wind speed before player gusts can occur.","example":11.1,"category":"wind"}, + {"key":"wind.playerMaxGustBpt","type":"number","default":0.002,"allowedValues":"0.0..0.2","description":"Maximum per-tick gust impulse for players in blocks per tick.","example":0.002,"category":"wind"}, + {"key":"wind.playerGustChanceScale","type":"number","default":0.03,"allowedValues":"0.0..1.0","description":"Scale used when calculating player gust chance.","example":0.03,"category":"wind"}, + {"key":"wind.playerGustChanceDivider","type":"number","default":15.0,"allowedValues":"1.0..100.0","description":"Divider used for excess-wind gust chance calculations.","example":15.0,"category":"wind"}, + {"key":"wind.playerGustStrengthScale","type":"number","default":0.006666666667,"allowedValues":"0.0..1.0","description":"Scale used for player gust strength from excess wind.","example":0.006666666667,"category":"wind"}, + {"key":"wind.playerGustDurationMin","type":"integer","default":10,"allowedValues":"1..200","description":"Minimum player gust duration in ticks.","example":10,"category":"wind"}, + {"key":"wind.playerGustDurationMax","type":"integer","default":40,"allowedValues":"1..200","description":"Maximum player gust duration in ticks.","example":40,"category":"wind"}, + {"key":"wind.playerGustAngleVarianceDeg","type":"number","default":10.0,"allowedValues":"0.0..45.0","description":"Angle variance applied to gust direction.","example":10.0,"category":"wind"}, + {"key":"wind.playerGustExtremeThresholdMps","type":"number","default":30.0,"allowedValues":"0.0..100.0","description":"Wind speed where extreme gust multipliers begin.","example":30.0,"category":"wind"}, + {"key":"wind.playerGustExtremeChanceMult","type":"number","default":2.5,"allowedValues":"1.0..10.0","description":"Extreme-wind multiplier applied to gust chance.","example":2.5,"category":"wind"}, + {"key":"wind.playerGustExtremeStrengthMult","type":"number","default":2.0,"allowedValues":"1.0..10.0","description":"Extreme-wind multiplier applied to gust strength.","example":2.0,"category":"wind"}, + + {"key":"leafParticles.radiusBlocks","type":"integer","default":32,"allowedValues":"4..128","description":"Horizontal sampling radius for wind-driven leaf particles.","example":32,"category":"leafParticles"}, + {"key":"leafParticles.verticalScanUp","type":"integer","default":12,"allowedValues":"0..64","description":"Vertical scan range above the player for canopy sampling.","example":12,"category":"leafParticles"}, + {"key":"leafParticles.verticalScanDown","type":"integer","default":12,"allowedValues":"0..64","description":"Vertical scan range below the player for canopy sampling.","example":12,"category":"leafParticles"}, + {"key":"leafParticles.attemptsPerTick","type":"integer","default":2,"allowedValues":"0..32","description":"Canopy sampling attempts per client tick.","example":2,"category":"leafParticles"}, + {"key":"leafParticles.chancePerCandidate","type":"number","default":0.03,"allowedValues":"0.0..1.0","description":"Spawn chance per valid canopy candidate.","example":0.03,"category":"leafParticles"}, + {"key":"leafParticles.minFoliageNeighbors","type":"integer","default":6,"allowedValues":"1..27","description":"Minimum nearby foliage blocks required to accept a canopy candidate.","example":6,"category":"leafParticles"}, + {"key":"leafParticles.requireLogBelow","type":"boolean","default":true,"allowedValues":[true,false],"description":"Requires at least one log block below the canopy candidate.","example":true,"category":"leafParticles"}, + {"key":"leafParticles.maxLogSearchDepth","type":"integer","default":8,"allowedValues":"1..32","description":"Maximum search depth for a log below a canopy candidate.","example":8,"category":"leafParticles"}, + + {"key":"seasonalTrees.enabled","type":"boolean","default":false,"allowedValues":[true,false],"description":"Enables seasonal tree leaf transitions and spreading.","example":false,"category":"seasonalTrees"}, + {"key":"seasonalTrees.dynamicTreesEnabled","type":"boolean","default":false,"allowedValues":[true,false],"description":"Enables Dynamic Trees integration.","example":false,"category":"seasonalTrees"}, + {"key":"seasonalTrees.vanillaEnabled","type":"boolean","default":true,"allowedValues":[true,false],"description":"Enables conservative vanilla tree support.","example":true,"category":"seasonalTrees"}, + {"key":"seasonalTrees.leafDropDays","type":"number","default":4.0,"allowedValues":"0.1..40.0","description":"Days for leaves to drop during autumn.","example":4.0,"category":"seasonalTrees"}, + {"key":"seasonalTrees.leafRegrowDays","type":"number","default":4.0,"allowedValues":"0.1..40.0","description":"Days for leaves to regrow during spring.","example":4.0,"category":"seasonalTrees"}, + {"key":"seasonalTrees.transitionCooldownDays","type":"number","default":2.0,"allowedValues":"0.0..40.0","description":"Cooldown before a tree can restart a seasonal transition.","example":2.0,"category":"seasonalTrees"}, + {"key":"seasonalTrees.transitionOffsetDays","type":"number","default":2.0,"allowedValues":"0.0..20.0","description":"Maximum random day offset applied to per-tree seasonal transitions.","example":2.0,"category":"seasonalTrees"}, + {"key":"seasonalTrees.spreadChancePerDay","type":"number","default":0.02,"allowedValues":"0.0..1.0","description":"Chance per in-game day for mature trees to attempt spreading.","example":0.02,"category":"seasonalTrees"}, + {"key":"seasonalTrees.spreadRadiusBlocks","type":"integer","default":12,"allowedValues":"1..128","description":"Baseline local spread radius in blocks.","example":12,"category":"seasonalTrees"}, + {"key":"seasonalTrees.vigorRegenPerDay","type":"number","default":0.05,"allowedValues":"0.0..1.0","description":"Base vigor regeneration per in-game day.","example":0.05,"category":"seasonalTrees"}, + {"key":"seasonalTrees.vigorMinForSpread","type":"number","default":0.55,"allowedValues":"0.0..1.0","description":"Minimum vigor needed before a tree can spread.","example":0.55,"category":"seasonalTrees"}, + {"key":"seasonalTrees.maxActiveSeeds","type":"integer","default":256,"allowedValues":"0..10000","description":"Maximum active wind-transported seed particles per world.","example":256,"category":"seasonalTrees"}, + {"key":"seasonalTrees.seedLifetimeTicks","type":"integer","default":400,"allowedValues":"20..72000","description":"Seed particle lifetime in ticks before attempting to plant.","example":400,"category":"seasonalTrees"}, + {"key":"seasonalTrees.seedBaseSpeed","type":"number","default":0.15,"allowedValues":"0.0..5.0","description":"Base speed multiplier for wind-transported seeds.","example":0.15,"category":"seasonalTrees"}, + {"key":"seasonalTrees.windTransportEnabled","type":"boolean","default":true,"allowedValues":[true,false],"description":"Enables wind-based seed transport.","example":true,"category":"seasonalTrees"}, + {"key":"seasonalTrees.budgetPerTick","type":"integer","default":80,"allowedValues":"1..10000","description":"Maximum tree updates per tick.","example":80,"category":"seasonalTrees"}, + {"key":"seasonalTrees.scanBudgetPerTick","type":"integer","default":120,"allowedValues":"1..20000","description":"Maximum chunk scan steps per tick.","example":120,"category":"seasonalTrees"}, + + {"key":"worldEffects.enabled","type":"boolean","default":true,"allowedValues":[true,false],"description":"Enables Project Atmosphere world effects.","example":true,"category":"worldEffects"}, + {"key":"worldEffects.samplesPerPlayerPerTick","type":"integer","default":12,"allowedValues":"0..128","description":"Random sample count per player per tick for weather world effects.","example":12,"category":"worldEffects"}, + {"key":"worldEffects.sampleRadiusBlocks","type":"integer","default":64,"allowedValues":"8..512","description":"Sampling radius around each player in blocks.","example":64,"category":"worldEffects"}, + {"key":"worldEffects.cloudBurnPreventionThreshold","type":"number","default":0.75,"allowedValues":"0.0..1.0","description":"Cloud-cover threshold that stops sun-burning mobs from igniting.","example":0.75,"category":"worldEffects"}, + {"key":"worldEffects.cloudFireDampThreshold","type":"number","default":0.90,"allowedValues":"0.0..1.0","description":"Cloud-cover threshold that cools burning mobs faster.","example":0.90,"category":"worldEffects"}, + {"key":"worldEffects.cloudFireDampTicks","type":"integer","default":4,"allowedValues":"0..200","description":"Extra fire ticks removed per tick when clouds are very dense.","example":4,"category":"worldEffects"}, + {"key":"worldEffects.fireExtinguishBaseChance","type":"number","default":0.12,"allowedValues":"0.0..1.0","description":"Base per-sample chance to extinguish fires while raining.","example":0.12,"category":"worldEffects"}, + {"key":"worldEffects.cauldronFillBaseChance","type":"number","default":0.06,"allowedValues":"0.0..1.0","description":"Base per-sample chance to fill cauldrons while raining.","example":0.06,"category":"worldEffects"}, + + {"key":"fog.enabled","type":"boolean","default":true,"allowedValues":[true,false],"description":"Enables humidity-driven dynamic fog on the client.","example":true,"category":"fog"}, + {"key":"fog.syncIntervalTicks","type":"integer","default":20,"allowedValues":"1..200","description":"Ticks between server-to-client fog sync updates.","example":20,"category":"fog"}, + {"key":"fog.humidityStartPercent","type":"number","default":72.0,"allowedValues":"0.0..100.0","description":"Humidity percentage where dynamic fog starts to form.","example":72.0,"category":"fog"}, + {"key":"fog.humidityFullPercent","type":"number","default":96.0,"allowedValues":"0.0..100.0","description":"Humidity percentage where humidity fog reaches full contribution.","example":96.0,"category":"fog"}, + {"key":"fog.wetBiomeBaseStrength","type":"number","default":0.18,"allowedValues":"0.0..1.0","description":"Base fog strength added by moisture-heavy biomes.","example":0.18,"category":"fog"}, + {"key":"fog.rainBoost","type":"number","default":0.22,"allowedValues":"0.0..1.0","description":"Additional fog strength contributed by active rain.","example":0.22,"category":"fog"}, + {"key":"fog.nearDistance","type":"number","default":0.5,"allowedValues":"0.0..64.0","description":"Near fog plane when dynamic fog is fully saturated.","example":0.5,"category":"fog"}, + {"key":"fog.farDistance","type":"number","default":72.0,"allowedValues":"4.0..512.0","description":"Far fog plane when dynamic fog is fully saturated.","example":72.0,"category":"fog"}, + {"key":"fog.colorBlend","type":"number","default":0.45,"allowedValues":"0.0..1.0","description":"Color tint blend applied by dynamic fog.","example":0.45,"category":"fog"}, + {"key":"fog.wetBiomeDownfallMin","type":"number","default":0.75,"allowedValues":"0.0..1.0","description":"Biome downfall value where wet-biome fog weighting begins.","example":0.75,"category":"fog"}, + {"key":"fog.wetBiomeIds","type":"string-list","default":["minecraft:swamp","minecraft:mangrove_swamp"],"allowedValues":"Biome id strings","description":"Biome ids that always count as moisture-heavy for fog.","example":["minecraft:swamp","modid:rainforest"],"category":"fog"}, + {"key":"fog.wetBiomeKeywords","type":"string-list","default":["swamp","marsh","bog","fen","rainforest","jungle","mangrove","wetland","bayou"],"allowedValues":"Biome-path keyword strings","description":"Biome path keywords that count as moisture-heavy for fog.","example":["swamp","jungle","wetland"],"category":"fog"}, + + {"key":"telemetry.enableTelemetry","type":"boolean","default":true,"allowedValues":[true,false],"description":"Enables lightweight telemetry collection.","example":true,"category":"telemetry"}, + {"key":"telemetry.telemetryRetentionDays","type":"integer","default":14,"allowedValues":"0..365","description":"Days to retain telemetry archives before pruning.","example":14,"category":"telemetry"}, + + {"key":"debug.debugMode","type":"boolean","default":false,"allowedValues":[true,false],"description":"Enables verbose logging and diagnostics.","example":false,"category":"debug"} + ], + "additionalFiles": [ + { + "path": "config/projectatmosphere/biome_temps.json", + "description": "Optional biome temperature override file created automatically on first run if missing.", + "entries": [ + {"key":"biomes..all","type":"object","default":"UNKNOWN","allowedValues":"Object with min/max Celsius values","description":"Applies one min/max range to all seasons for a biome.","example":{"min":20.0,"max":30.0},"category":"biome_temps"}, + {"key":"biomes...min","type":"number","default":"UNKNOWN","allowedValues":"Any Celsius number","description":"Minimum seasonal temperature in Celsius.","example":-10.0,"category":"biome_temps"}, + {"key":"biomes...max","type":"number","default":"UNKNOWN","allowedValues":"Any Celsius number","description":"Maximum seasonal temperature in Celsius.","example":15.0,"category":"biome_temps"} + ], + "notes": [ + "Valid seasons are winter, spring, summer, and autumn.", + "A biome entry can use either one all-range object or four per-season objects." + ] + } + ], + "notes": [ + "Types are simplified support types, not raw ForgeConfigSpec class names.", + "This schema is based on AtmoCommonConfig.java plus the user biome temperature override file.", + "Forge usually writes the common config to projectatmosphere-common.toml, but confirm the exact path in a modpack if needed." + ] +} diff --git a/data/mcp/mods/projectatmosphere/features.json b/data/mcp/mods/projectatmosphere/features.json new file mode 100644 index 00000000..af802dee --- /dev/null +++ b/data/mcp/mods/projectatmosphere/features.json @@ -0,0 +1,91 @@ +{ + "features": [ + { + "id": "region-forecasting", + "name": "Regional Forecasting", + "shortDescription": "Generates regional forecast data for temperature, humidity, pressure, and wind.", + "examples": [ + "/pa temperature forecast", + "/pa humidity get" + ] + }, + { + "id": "seasonal-temperature", + "name": "Season-Aware Temperatures", + "shortDescription": "Uses biome-specific seasonal temperature ranges and daily curves.", + "examples": [ + "Biome temperature ranges can be overridden in config/projectatmosphere/biome_temps.json" + ] + }, + { + "id": "simple-clouds-integration", + "name": "Simple Clouds Integration", + "shortDescription": "Uses Simple Clouds for cloud spawning and weather-linked cloud visuals.", + "notes": "Simple Clouds is a mandatory dependency." + }, + { + "id": "humidity-pressure-wind", + "name": "Humidity, Pressure, and Wind", + "shortDescription": "Tracks atmospheric state values that can be sampled through commands or API.", + "examples": [ + "/pa pressure get", + "/pa windSpeed get" + ] + }, + { + "id": "world-effects", + "name": "Weather World Effects", + "shortDescription": "Applies weather-driven world effects such as fire suppression and cauldron filling.", + "notes": "Controlled by the worldEffects config section." + }, + { + "id": "telemetry-debugging", + "name": "Telemetry and Debugging", + "shortDescription": "Includes server and client debug tools for inspecting forecasts and exporting telemetry.", + "examples": [ + "/pa weatherdebug forecast", + "/pa debug export" + ] + }, + { + "id": "tornadoes", + "name": "Tornado Systems", + "shortDescription": "Current source includes tornado spawning, risk sampling, and render/debug hooks.", + "examples": [ + "/pa spawnTornado", + "/pa weatherdebug tornado risk" + ], + "notes": "Present in the current source tree. Official release documentation for 0.8.0.0 does not fully document support scope." + }, + { + "id": "hurricanes", + "name": "Hurricane Systems", + "shortDescription": "Current source includes hurricane spawn and clear commands plus runtime handling.", + "examples": [ + "/pa spawnHurricane 1", + "/pa clearhurricanes" + ], + "notes": "Present in the current source tree. Official release documentation for 0.8.0.0 does not fully document support scope." + }, + { + "id": "dynamic-fog", + "name": "Dynamic Fog", + "shortDescription": "Current source includes humidity-driven fog with wet-biome and rain weighting.", + "examples": [ + "/pa fog spawn 0.85 30", + "/pa weatherdebug fog" + ], + "notes": "Present in the current source tree. Official release documentation for 0.8.0.0 does not fully document support scope." + }, + { + "id": "seasonal-trees", + "name": "Seasonal Trees", + "shortDescription": "Includes optional seasonal leaf transition and seed transport systems.", + "notes": "Dynamic Trees integration exists, but the latest official note says to keep the Dynamic Trees module disabled." + } + ], + "notes": [ + "Feature list combines official documentation and currently implemented source features.", + "Entries that mention current-source behavior should not be treated as officially documented release guarantees without maintainer confirmation." + ] +} diff --git a/data/mcp/mods/projectatmosphere/known-issues.json b/data/mcp/mods/projectatmosphere/known-issues.json new file mode 100644 index 00000000..0b404732 --- /dev/null +++ b/data/mcp/mods/projectatmosphere/known-issues.json @@ -0,0 +1,72 @@ +{ + "issues": [ + { + "id": "paxtfc-temporary-incompatibility-0800", + "title": "PA x TFC is temporarily incompatible with 0.8.0.0", + "affectedVersions": [ + "0.8.0.0" + ], + "symptoms": [ + "PA x TFC integration does not work correctly with the latest documented release.", + "Users may need to remove the bridge mod to stabilize startup or gameplay." + ], + "workarounds": [ + "Do not use PA x TFC with 0.8.0.0 until the compatibility update is released." + ], + "status": "open", + "fixedIn": null, + "sourceType": "official" + }, + { + "id": "dynamic-trees-module-should-stay-disabled", + "title": "Dynamic Trees integration is still work in progress", + "affectedVersions": [ + "0.8.0.0" + ], + "symptoms": [ + "Seasonal tree integration with Dynamic Trees is not considered ready." + ], + "workarounds": [ + "Leave the Dynamic Trees integration disabled." + ], + "status": "open", + "fixedIn": null, + "sourceType": "official" + }, + { + "id": "missing-season-provider-startup-failure", + "title": "Startup fails when no season provider is installed", + "affectedVersions": [ + "0.8.0.0", + "UNKNOWN" + ], + "symptoms": [ + "The game or server throws an IllegalStateException at startup asking for Serene Seasons, ProjectAtmosphereForTFC, or Ecliptic Seasons." + ], + "workarounds": [ + "Install a season provider before loading the mod.", + "If you need an officially documented option, prefer Serene Seasons." + ], + "status": "open", + "fixedIn": null, + "sourceType": "reviewed-issues" + }, + { + "id": "weatherdebug-snowstorm-command-broken", + "title": "The weatherdebug snowstorm command is mis-registered", + "affectedVersions": [ + "0.8.0.0", + "UNKNOWN" + ], + "symptoms": [ + "Running /pa weatherdebug snowstorm fails because the handler expects an intensity argument that the command tree does not expose." + ], + "workarounds": [ + "Do not rely on this command until the argument registration is fixed." + ], + "status": "open", + "fixedIn": null, + "sourceType": "reviewed-issues" + } + ] +} diff --git a/data/mcp/mods/projectatmosphere/manifest.json b/data/mcp/mods/projectatmosphere/manifest.json new file mode 100644 index 00000000..50022c60 --- /dev/null +++ b/data/mcp/mods/projectatmosphere/manifest.json @@ -0,0 +1,30 @@ +{ + "slug": "projectatmosphere", + "name": "Project Atmosphere", + "aliases": [ + "projectatmosphere", + "project atmosphere", + "pa" + ], + "latestVersion": "0.8.0.0", + "minecraftVersions": [ + "1.20.1" + ], + "loaders": [ + "forge" + ], + "docs": { + "overview": "docs/mods/projectatmosphere/overview.md", + "install": "docs/mods/projectatmosphere/install.md", + "troubleshooting": "docs/mods/projectatmosphere/troubleshooting.md", + "faq": "docs/mods/projectatmosphere/faq.md" + }, + "links": { + "source": "https://github.com/xGabou/Project-Atmosphere", + "issues": "https://github.com/xGabou/Project-Atmosphere/issues" + }, + "notes": [ + "Alias list is derived from the mod id, display name, and common in-repo shorthand. Confirm against the global mod catalog if that catalog uses different aliases.", + "Latest version is taken from gradle.properties in the current repository." + ] +} diff --git a/data/mcp/mods/projectatmosphere/versions.json b/data/mcp/mods/projectatmosphere/versions.json new file mode 100644 index 00000000..dee4091a --- /dev/null +++ b/data/mcp/mods/projectatmosphere/versions.json @@ -0,0 +1,128 @@ +{ + "versions": [ + { + "version": "0.8.0.0", + "releaseDate": "UNKNOWN", + "mcVersions": [ + "1.20.1" + ], + "loaders": [ + "forge" + ], + "supportStatus": "current", + "notes": "Current version in gradle.properties. Official release notes mention the region-first runtime refactor, telemetry work, a JDK 17 telemetry export fix, temporary PA x TFC incompatibility, and Dynamic Trees remaining work in progress." + }, + { + "version": "0.6.0.0-pre2", + "releaseDate": "UNKNOWN", + "mcVersions": [ + "1.20.1" + ], + "loaders": [ + "forge" + ], + "supportStatus": "UNKNOWN", + "notes": "Observed in repository history. Commit history ties this version to sunlight-driven temperature work and baseline temperature bounds." + }, + { + "version": "0.5.5.7", + "releaseDate": "UNKNOWN", + "mcVersions": [ + "1.20.1" + ], + "loaders": [ + "forge" + ], + "supportStatus": "UNKNOWN", + "notes": "Observed in repository history. Commit history ties this version to auroras/rainbows compatibility and storm severity tuning." + }, + { + "version": "0.5.5.4", + "releaseDate": "UNKNOWN", + "mcVersions": [ + "1.20.1" + ], + "loaders": [ + "forge" + ], + "supportStatus": "UNKNOWN", + "notes": "Observed in repository history. Commit history ties this version to weather instruments and model updates." + }, + { + "version": "0.5.5.2", + "releaseDate": "UNKNOWN", + "mcVersions": [ + "1.20.1" + ], + "loaders": [ + "forge" + ], + "supportStatus": "UNKNOWN", + "notes": "Observed in repository history. Detailed public release notes were not found in this bundle source set." + }, + { + "version": "0.5.5.0", + "releaseDate": "UNKNOWN", + "mcVersions": [ + "1.20.1" + ], + "loaders": [ + "forge" + ], + "supportStatus": "UNKNOWN", + "notes": "Observed in repository history. Commit history describes a beta release and cloud storminess parameter changes." + }, + { + "version": "0.5.4.3", + "releaseDate": "UNKNOWN", + "mcVersions": [ + "1.20.1" + ], + "loaders": [ + "forge" + ], + "supportStatus": "UNKNOWN", + "notes": "Observed in repository history. Commit history mentions cloud region spawn handling improvements." + }, + { + "version": "0.5.4.1", + "releaseDate": "UNKNOWN", + "mcVersions": [ + "1.20.1" + ], + "loaders": [ + "forge" + ], + "supportStatus": "UNKNOWN", + "notes": "Observed in repository history. Commit history mentions season change handling improvements." + }, + { + "version": "0.5.4.0", + "releaseDate": "UNKNOWN", + "mcVersions": [ + "1.20.1" + ], + "loaders": [ + "forge" + ], + "supportStatus": "UNKNOWN", + "notes": "Observed in repository history. Commit history mentions sandstorm scheduling changes." + }, + { + "version": "0.3.7", + "releaseDate": "UNKNOWN", + "mcVersions": [ + "UNKNOWN" + ], + "loaders": [ + "UNKNOWN" + ], + "supportStatus": "UNKNOWN", + "notes": "Observed in repository history only. Additional support details are unknown." + } + ], + "notes": [ + "Only 0.8.0.0 has an official support-focused changelog in this repository.", + "Older versions are included only when they are directly visible in repository history." + ] +} diff --git a/dev/nonamecrackers2/simpleclouds/client/mesh/chunk/MeshChunk.java b/dev/nonamecrackers2/simpleclouds/client/mesh/chunk/MeshChunk.java new file mode 100644 index 00000000..cf1fa73e --- /dev/null +++ b/dev/nonamecrackers2/simpleclouds/client/mesh/chunk/MeshChunk.java @@ -0,0 +1,249 @@ +package dev.nonamecrackers2.simpleclouds.client.mesh.chunk; + +import java.nio.ByteBuffer; +import java.util.Optional; + +import javax.annotation.Nullable; + +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL43; +import org.lwjgl.system.MemoryUtil; + +import com.mojang.blaze3d.platform.MemoryTracker; +import com.mojang.blaze3d.systems.RenderSystem; + +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.PreparedChunk; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.AABB; + +public class MeshChunk +{ + private final PreparedChunk preparedChunk; + private final MeshChunk.BufferSet opaqueBuffers; + private final Optional transparentBuffers; + private float boundsMinX; + private float boundsMinY; + private float boundsMinZ; + private float boundsMaxX; + private float boundsMaxY; + private float boundsMaxZ; + private float minHeight; + private float maxHeight; + private int ticksSinceLastGen; + private boolean fadeEnabled; + private float alpha; + private float alphaO; + + public MeshChunk(PreparedChunk preparedChunk, int opaqueElements, int opaqueElementOffset, int bytesPerOpaqueElement, int transparentElements, int transparentElementOffset, int bytesPerTransparentElement, boolean useTransparency) + { + this.preparedChunk = preparedChunk; + + this.opaqueBuffers = new MeshChunk.BufferSet(opaqueElements, opaqueElementOffset, bytesPerOpaqueElement); + if (useTransparency) + this.transparentBuffers = Optional.of(new MeshChunk.BufferSet(transparentElements, transparentElementOffset, bytesPerTransparentElement)); + else + this.transparentBuffers = Optional.empty(); + + AABB bounds = preparedChunk.bounds(); + this.boundsMinX = (float)bounds.minX; + this.boundsMinY = (float)bounds.minY; + this.boundsMinZ = (float)bounds.minZ; + this.boundsMaxX = (float)bounds.maxX; + this.boundsMaxY = (float)bounds.maxY; + this.boundsMaxZ = (float)bounds.maxZ; + this.minHeight = this.boundsMinY; + this.maxHeight = this.boundsMaxY; + } + + public void tick() + { + this.ticksSinceLastGen++; + + this.alphaO = this.alpha; + if (this.fadeEnabled && this.alpha < 1.0F) + { + this.alpha += SimpleCloudsRenderer.CHUNK_FADE_IN_ALPHA_PER_TICK; + if (this.alpha > 1.0F) + this.alpha = 1.0F; + } + } + + public void setFadeEnabled(boolean flag) + { + this.fadeEnabled = flag; + } + + public void resetAlpha() + { + this.alpha = 0.0F; + this.alphaO = 0.0F; + } + + public PreparedChunk getChunkInfo() + { + return this.preparedChunk; + } + + public MeshChunk.BufferSet getOpaqueBuffers() + { + return this.opaqueBuffers; + } + + public Optional getTransparentBuffers() + { + return this.transparentBuffers; + } + + public void clearChunk() + { + this.opaqueBuffers.setTotalElementCount(0); + this.transparentBuffers.ifPresent(bufferSet -> bufferSet.setTotalElementCount(0)); + } + + public void setBounds(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) + { + this.boundsMinX = minX; + this.boundsMinY = minY; + this.boundsMinZ = minZ; + this.boundsMaxX = maxX; + this.boundsMaxY = maxY; + this.boundsMaxZ = maxZ; + } + + public void setHeights(float minHeight, float maxHeight) + { + this.minHeight = minHeight; + this.maxHeight = maxHeight; + } + + public void resetLastGenTime() + { + this.ticksSinceLastGen = 0; + } + + public int getTicksSinceLastGen() + { + return this.ticksSinceLastGen; + } + + public float getBoundsMinX() + { + return this.boundsMinX; + } + + public float getBoundsMinY() + { + return this.boundsMinY; + } + + public float getBoundsMinZ() + { + return this.boundsMinZ; + } + + public float getBoundsMaxX() + { + return this.boundsMaxX; + } + + public float getBoundsMaxY() + { + return this.boundsMaxY; + } + + public float getBoundsMaxZ() + { + return this.boundsMaxZ; + } + + public float getMinHeight() + { + return this.minHeight; + } + + public float getMaxHeight() + { + return this.maxHeight; + } + + public float getAlpha(float partialTick) + { + return Mth.lerp(partialTick, this.alphaO, this.alpha); + } + + public void destroy() + { + this.opaqueBuffers.destroy(); + this.transparentBuffers.ifPresent(MeshChunk.BufferSet::destroy); + } + + public static class BufferSet + { + private int bufferId = -1; + private @Nullable ByteBuffer buffer; + private int elementCount; + private final int bufferSize; + private final int maxElements; + private final int elementOffset; + + public BufferSet(int maxElements, int elementOffset, int bytesPerElement) + { + this.bufferId = GL15.glGenBuffers(); + this.maxElements = maxElements; + this.elementOffset = elementOffset; + this.bufferSize = maxElements * bytesPerElement; + this.buffer = MemoryTracker.create(this.bufferSize); + GL15.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, this.bufferId); + GL15.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, this.buffer, GL15.GL_DYNAMIC_DRAW); + GL15.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, 0); + } + + public void setTotalElementCount(int count) + { + this.elementCount = count; + } + + public int getElementCount() + { + return this.elementCount; + } + + public int getMaxElements() + { + return this.maxElements; + } + + public int getElementOffset() + { + return this.elementOffset; + } + + public int getBufferSize() + { + return this.bufferSize; + } + + public int getBufferId() + { + return this.bufferId; + } + + public void destroy() + { + this.elementCount = 0; + + if (this.bufferId >= 0) + { + RenderSystem.glDeleteBuffers(this.bufferId); + this.bufferId = -1; + } + + if (this.buffer != null) + { + MemoryUtil.memFree(this.buffer); + this.buffer = null; + } + } + } +} diff --git a/dev/nonamecrackers2/simpleclouds/client/mesh/generator/CloudMeshGenerator.java b/dev/nonamecrackers2/simpleclouds/client/mesh/generator/CloudMeshGenerator.java new file mode 100644 index 00000000..f2ae91fd --- /dev/null +++ b/dev/nonamecrackers2/simpleclouds/client/mesh/generator/CloudMeshGenerator.java @@ -0,0 +1,1149 @@ +package dev.nonamecrackers2.simpleclouds.client.mesh.generator; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Queue; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL31; +import org.lwjgl.opengl.GL41; +import org.lwjgl.opengl.GL42; +import org.lwjgl.opengl.GL43; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Queues; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; + +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import dev.nonamecrackers2.simpleclouds.client.mesh.LevelOfDetailOptions; +import dev.nonamecrackers2.simpleclouds.client.mesh.RendererInitializeResult; +import dev.nonamecrackers2.simpleclouds.client.mesh.chunk.MeshChunk; +import dev.nonamecrackers2.simpleclouds.client.mesh.instancing.InstanceableMesh; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.LevelOfDetailConfig; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.PreparedChunk; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.BindingManager; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.ShaderStorageBufferObject; +import dev.nonamecrackers2.simpleclouds.client.shader.compute.ComputeShader; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudInfo; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.mixin.MixinFrustumAccessor; +import net.minecraft.CrashReportCategory; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.AABB; + +/** + * Abstract mesh generator class that generates a cloud vertex mesh using computer shaders. Implementations are only available on the render thread. + *

+ * Use {@link CloudMeshGenerator#init} to initialize the mesh generator. This will initialize all needed buffers. Note + * that this is an expensive class and having multiple instances in one environment can cause GPU memory to run out quick (including + * available SSBO bindings). + *

+ * Use {@link CloudMeshGenerator#tick} each frame to generate the mesh at a fixed interval of frames + * (defined by {@link CloudMeshGenerator#setMeshGenInterval}) or use {@link CloudMeshGenerator#generateMesh} to generate + * it in a single call. + *

+ * Use {@link CloudMeshGenerator#render} to render the currently generated cloud mesh. + * + * @author nonamecrackers2 + */ +public abstract class CloudMeshGenerator +{ + private static final Logger LOGGER = LogManager.getLogger("simpleclouds/CloudMeshGenerator"); + + public static final ResourceLocation MAIN_CUBE_MESH_GENERATOR = SimpleCloudsMod.id("cube_mesh"); + public static final int MAX_NOISE_LAYERS = 4; + public static final int VERTICAL_CHUNK_SPAN = 8; + public static final int LOCAL_SIZE = 8; + public static final int WORK_SIZE = SimpleCloudsConstants.CHUNK_SIZE / LOCAL_SIZE; + public static final int TICKS_UNTIL_FADE_RESET = 120; + + //Opaque + public static final int BYTES_PER_SIDE_INFO = 24; + public static final int MAX_SIDE_INFO_BUFFER_SIZE = 50331648; + public static final String SIDE_INFO_BUFFER_NAME = "SideInfoBuffer"; + public static final String TOTAL_SIDES_NAME = "TotalSides"; + public static final String SIDES_PER_CHUNK_NAME = "SidesPerChunk"; + //Transparent + public static final int BYTES_PER_CUBE_INFO = 24; + public static final int MAX_TRANSPARENT_CUBE_INFO_BUFFER_SIZE = 50331648; + public static final String TRANSPARENT_CUBE_INFO_BUFFER_NAME = "TransparentCubeInfoBuffer"; + public static final String TRANSPARENT_TOTAL_CUBES_NAME = "TotalTransparentCubes"; + public static final String TRANSPARENT_CUBES_PER_CHUNK_NAME = "TransparentCubesPerChunk"; + + public static final String NOISE_LAYERS_NAME = "NoiseLayers"; + public static final String LAYER_GROUPINGS_NAME = "LayerGroupings"; + + protected final ResourceLocation meshShaderLoc; + protected final int shaderType; + protected final boolean fadeNearOrigin; + protected final boolean shadedClouds; + protected final boolean useTransparency; + protected final LevelOfDetailConfig lodConfig; + protected final boolean useFixedMeshDataSectionSize; + protected @Nullable List chunks; + protected final List completedGenTasks = Lists.newArrayList(); + protected final Queue chunkGenTasks = Queues.newArrayDeque(); + protected final Supplier meshGenIntervalCalculator; + protected int meshGenInterval = 1; + protected int tasksPerTick; + protected @Nullable ComputeShader shader; + + protected @Nullable InstanceableMesh sideMesh; + protected @Nullable InstanceableMesh cubeMesh; + + // Left is for opaque geometry, right is for transparent + protected Pair meshGenStatus = Pair.of(CloudMeshGenerator.MeshGenStatus.NOT_INITIALIZED, CloudMeshGenerator.MeshGenStatus.NOT_INITIALIZED); + protected float scrollX; + protected float scrollY; + protected float scrollZ; + protected boolean testFacesFacingAway; + private float fadeStart; + private float fadeEnd; + private float cullDistance; + private int transparencyDistance; + + private int opaqueBufferSize; + private int opaqueBufferBytesUsed; + private int transparentBufferSize; + private int transparentBufferBytesUsed; + private int opaqueBytesPerChunk; + private int transparentBytesPerChunk; + + /** + * Creates a cloud mesh generator, but does not initialize it for generating (use {@link CloudMeshGenerator#init}) + * + * @param meshShaderLoc + * The location of the cloud mesh generator compute shader + * @param lodConfig + * A level of detail configuration + * @param meshGenInterval + * The frame interval at which the generate the cloud mesh + */ + public CloudMeshGenerator(ResourceLocation meshShaderLoc, int shaderType, boolean fadeNearOrigin, boolean shadedClouds, LevelOfDetailConfig lodConfig, Supplier meshGenIntervalCalculator, boolean useTransparency, boolean fixedMeshDataSectionSize) + { + this.meshShaderLoc = meshShaderLoc; + this.shaderType = shaderType; + this.fadeNearOrigin = fadeNearOrigin; + this.shadedClouds = shadedClouds; + this.useFixedMeshDataSectionSize = fixedMeshDataSectionSize; + + this.lodConfig = lodConfig; + this.meshGenIntervalCalculator = meshGenIntervalCalculator; + this.useTransparency = useTransparency; + + float maxRadius = this.getCloudAreaMaxRadius(); + this.fadeStart = 0.9F * maxRadius; + this.fadeEnd = maxRadius; + this.transparencyDistance = (int)maxRadius / 2; + } + + public boolean fadeNearOriginEnabled() + { + return this.fadeNearOrigin; + } + + public boolean shadedCloudsEnabled() + { + return this.shadedClouds; + } + + public boolean transparencyEnabled() + { + return this.useTransparency; + } + + public boolean usesFixedMeshDataSectionSize() + { + return this.useFixedMeshDataSectionSize; + } + + public LevelOfDetailConfig getLodConfig() + { + return this.lodConfig; + } + + /** + * Specifies if faces not facing the camera should be tested during + * mesh generation on the GPU for whether they should be generated or not. + *

+ * Enabling can improve performance at the cost of some visual artifacts + * or an incomplete cloud mesh + * + * @param flag + * @return + */ + public CloudMeshGenerator setTestFacesFacingAway(boolean flag) + { + this.testFacesFacingAway = flag; + return this; + } + + /** + * Sets the fade start and end distances as decimal percentages + * + * @param fadeStart + * @param fadeEnd + */ + public CloudMeshGenerator setFadeDistances(float fadeStart, float fadeEnd) + { + float fs = fadeStart; + float fe = fadeEnd; + if (fs > fe) + { + fs = fadeEnd; + fe = fadeStart; + } + this.fadeStart = fs * (float)this.getCloudAreaMaxRadius(); + this.fadeEnd = fe * (float)this.getCloudAreaMaxRadius(); + return this; + } + + public CloudMeshGenerator setTransparencyRenderDistance(float percentage) + { + this.transparencyDistance = Mth.floor(percentage * (float)this.getCloudAreaMaxRadius()); + return this; + } + + public float getFadeStart() + { + return this.fadeStart; + } + + public float getFadeEnd() + { + return this.fadeEnd; + } + + public int getCloudAreaMaxRadius() + { + return this.lodConfig.getEffectiveChunkSpan() * WORK_SIZE * LOCAL_SIZE / 2; + } + + public void setCullDistance(float dist) + { + if (dist <= 0.0F) + throw new IllegalArgumentException("Cull distance must be greater than zero"); + this.cullDistance = dist; + } + + public void disableCullDistance() + { + this.cullDistance = 0.0F; + } + + public void setScroll(float x, float y, float z) + { + this.scrollX = x; + this.scrollY = y; + this.scrollZ = z; + } + + public Pair getMeshGenStatus() + { + return this.meshGenStatus; + } + + public @Nullable InstanceableMesh getSideMesh() + { + return this.sideMesh; + } + + public @Nullable InstanceableMesh getCubeMesh() + { + return this.cubeMesh; + } + + public int getOpaqueBufferSize() + { + return this.opaqueBufferSize; + } + + public int getOpaqueBufferBytesUsed() + { + return this.opaqueBufferBytesUsed; + } + + public int getTransparentBufferSize() + { + return this.transparentBufferSize; + } + + public int getTransparentBufferBytesUsed() + { + return this.transparentBufferBytesUsed; + } + + public int getOpaqueBytesPerChunk() + { + return this.opaqueBytesPerChunk; + } + + public int getTransparentBytesPerChunk() + { + return this.transparentBytesPerChunk; + } + + public int getTotalMeshChunks() + { + if (this.chunks == null) + return 0; + return this.chunks.size(); + } + + public int getMeshGenInterval() + { + return this.meshGenInterval; + } + + public void close() + { + RenderSystem.assertOnRenderThreadOrInit(); + + this.opaqueBufferBytesUsed = 0; + this.opaqueBufferSize = 0; + this.opaqueBytesPerChunk = 0; + this.transparentBufferBytesUsed = 0; + this.transparentBufferSize = 0; + this.transparentBytesPerChunk = 0; + + GL42.glMemoryBarrier(GL42.GL_ALL_BARRIER_BITS); + this.chunkGenTasks.clear(); + this.completedGenTasks.clear(); + + if (this.shader != null) + this.shader.close(); + this.shader = null; + + if (this.chunks != null) + { + for (MeshChunk chunk : this.chunks) + chunk.destroy(); + this.chunks = null; + } + + if (this.sideMesh != null) + { + this.sideMesh.destroy(); + this.sideMesh = null; + } + + if (this.cubeMesh != null) + { + this.cubeMesh.destroy(); + this.cubeMesh = null; + } + } + + public boolean canRender() + { + return this.chunks != null; + } + + public final RendererInitializeResult init(ResourceManager manager) + { + RendererInitializeResult.Builder builder = RendererInitializeResult.builder(); + + if (!RenderSystem.isOnRenderThreadOrInit()) + return builder.errorUnknown(new IllegalStateException("Init not called on render thread"), "Mesh Generator; Head").build(); + + this.opaqueBufferBytesUsed = 0; + this.opaqueBufferSize = 0; + this.opaqueBytesPerChunk = 0; + this.transparentBufferBytesUsed = 0; + this.transparentBufferSize = 0; + this.transparentBytesPerChunk = 0; + + GL42.glMemoryBarrier(GL42.GL_ALL_BARRIER_BITS); + this.chunkGenTasks.clear(); + this.completedGenTasks.clear(); + + LOGGER.debug("Beginning mesh generator initialization"); + + if (this.shader != null) + { + LOGGER.debug("Freeing mesh compute shader"); + this.shader.close(); + this.shader = null; + } + + if (this.chunks != null) + { + for (MeshChunk chunk : this.chunks) + chunk.destroy(); + this.chunks = null; + } + + try + { + LOGGER.debug("Creating mesh compute shader..."); + this.shader = this.createShader(manager); + this.setupShader(); + } + catch (IOException e) + { + //LOGGER.warn("Failed to load compute shader", e); + builder.errorCouldNotLoadMeshScript(e, "Mesh Generator; Compute Shader"); + } + catch (Exception e) + { + builder.errorRecommendations(e, "Mesh Generator; Compute Shader"); + } + + try + { + this.initExtra(manager); + } + catch (Exception e) + { + builder.errorUnknown(e, "Init Extra"); + } + + List preparedChunks = this.getLodConfig().getPreparedChunks(); + ImmutableList.Builder meshChunks = ImmutableList.builder(); + int totalPreparedChunks = preparedChunks.size(); + this.opaqueBytesPerChunk = Mth.ceil(this.opaqueBufferSize / totalPreparedChunks); + this.transparentBytesPerChunk = Mth.ceil(this.transparentBufferSize / totalPreparedChunks); + if (!this.useFixedMeshDataSectionSize) + { + this.opaqueBytesPerChunk *= 4; + this.transparentBytesPerChunk *= 4; + } + int maxOpaqueElements = Mth.floor((float)this.opaqueBytesPerChunk / (float)BYTES_PER_SIDE_INFO); + int maxTransparentElements = Mth.floor((float)this.transparentBytesPerChunk / (float)BYTES_PER_CUBE_INFO); + int opaqueElementOffset = 0; + int transparentElementOffset = 0; + for (PreparedChunk chunk : preparedChunks) + { + meshChunks.add(new MeshChunk(chunk, maxOpaqueElements, opaqueElementOffset, BYTES_PER_SIDE_INFO, maxTransparentElements, transparentElementOffset, BYTES_PER_CUBE_INFO, this.useTransparency)); + opaqueElementOffset += maxOpaqueElements; + transparentElementOffset += maxTransparentElements; + } + this.chunks = meshChunks.build(); + + LOGGER.debug("Opaque buffer size: {} bytes, transparent buffer size: {} bytes", this.opaqueBufferSize, this.transparentBufferSize); + + if (this.sideMesh != null) + this.sideMesh.destroy(); + this.sideMesh = InstanceableMesh.defaultSide(); + + if (this.cubeMesh != null) + this.cubeMesh.destroy(); + this.cubeMesh = InstanceableMesh.defaultCube(); + + BindingManager.printDebug(); + + LOGGER.debug("Finished initializing mesh generator"); + + return builder.build(); + } + + protected ComputeShader createShader(ResourceManager manager) throws IOException + { + ImmutableMap parameters = ImmutableMap.of( + "TYPE", String.valueOf(this.shaderType), + "FADE_NEAR_ORIGIN", this.fadeNearOrigin ? "1" : "0", + "STYLE", this.shadedClouds ? "1" : "0", + "TRANSPARENCY", this.useTransparency ? "1" : "0", + "FIXED_SECTION_SIZE", this.useFixedMeshDataSectionSize ? "1" : "0" + ); + return ComputeShader.loadShader(this.meshShaderLoc, manager, LOCAL_SIZE, LOCAL_SIZE, LOCAL_SIZE, parameters); + } + + protected void setupShader() + { + this.opaqueBufferSize = this.createBuffers( + TOTAL_SIDES_NAME, + SIDES_PER_CHUNK_NAME, + SIDE_INFO_BUFFER_NAME, + MAX_SIDE_INFO_BUFFER_SIZE * (this.useFixedMeshDataSectionSize ? 4 : 1) + ); + + if (this.useTransparency) + { + this.transparentBufferSize = this.createBuffers( + TRANSPARENT_TOTAL_CUBES_NAME, + TRANSPARENT_CUBES_PER_CHUNK_NAME, + TRANSPARENT_CUBE_INFO_BUFFER_NAME, + MAX_TRANSPARENT_CUBE_INFO_BUFFER_SIZE * (this.useFixedMeshDataSectionSize ? 4 : 1) + ); + } + + this.shader.forUniform("TotalLodLevels", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, this.lodConfig.getLods().length); + }); + + this.uploadFadeData(); + } + + private void uploadFadeData() + { + if (this.shader == null || !this.shader.isValid()) + return; + + this.shader.forUniform("TransparencyDistance", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, this.transparencyDistance); + }); + this.shader.forUniform("FadeStart", (id, loc) -> { + GL41.glProgramUniform1f(id, loc, this.fadeStart); + }); + this.shader.forUniform("FadeEnd", (id, loc) -> { + GL41.glProgramUniform1f(id, loc, this.fadeEnd); + }); + } + + private int createBuffers(String totalCounterName, String countPerChunkName, String elementInfoBufferName, int maxSize) + { + if (!this.useFixedMeshDataSectionSize) + { + ShaderStorageBufferObject totalCountBuffer = this.shader.createAndBindSSBO(totalCounterName, GL15.GL_DYNAMIC_COPY); + totalCountBuffer.allocateBuffer(4); + totalCountBuffer.writeData(b -> { + b.putInt(0, 0); + }, 4, false); + } + + int bufferSize = this.shader.createAndBindSSBO(elementInfoBufferName, GL15.GL_DYNAMIC_COPY).allocateBuffer(maxSize); + + int totalChunks = this.getLodConfig().getPreparedChunks().size(); + int countPerChunkBufferSize = totalChunks * 4; + ShaderStorageBufferObject countPerChunkBuffer = this.shader.createAndBindSSBO(countPerChunkName, GL15.GL_DYNAMIC_COPY); + countPerChunkBuffer.allocateBuffer(countPerChunkBufferSize); + countPerChunkBuffer.writeData(b -> + { + for (int i = 0; i < totalChunks; i++) + b.putInt(0); + b.rewind(); + }, countPerChunkBufferSize, false); + + return bufferSize; + } + + protected void initExtra(ResourceManager manager) throws IOException {} + + /** + * Generates the entire cloud mesh at the origin at once + */ + public void generateMesh() + { + RenderSystem.assertOnRenderThread(); + + if (this.shader == null || !this.shader.isValid()) + return; + + this.prepareMeshGen(0.0D, 0.0D, 0.0D, 0.0F, 0.0F, null, 1, 1.0F); + + if (!this.chunkGenTasks.isEmpty()) + this.doMeshGenning(this.chunkGenTasks.size()); + + this.meshGenStatus = this.finalizeMeshGen(); + this.completedGenTasks.clear(); + } + + public void worldTick() + { + if (this.chunks != null) + this.chunks.forEach(MeshChunk::tick); + } + + /** + * Generates the cloud mesh on a per-frame basis + * + * @param originX + * @param originY + * @param originZ + * @param frustum + */ + public void genTick(double originX, double originY, double originZ, @Nullable Frustum frustum, float partialTick) + { + RenderSystem.assertOnRenderThread(); + + if (this.shader == null || !this.shader.isValid()) + return; + + float chunkSize = (float)SimpleCloudsConstants.CHUNK_SIZE; + float meshGenOffsetX = (float)Mth.floor(originX / chunkSize) * chunkSize; + float meshGenOffsetZ = (float)Mth.floor(originZ / chunkSize) * chunkSize; + + if (this.chunkGenTasks.isEmpty()) //If we have no chunk gen tasks + { + this.meshGenStatus = this.finalizeMeshGen(); //Split the combined mesh data from the GPU, and store them in the VBOs for each chunk that was generated + this.completedGenTasks.clear(); //Clear the chunk gen tasks + + //Prepare the next batch of chunks to generate meshes for + this.meshGenInterval = this.meshGenIntervalCalculator.get(); + if (this.meshGenInterval <= 0) + throw new RuntimeException("Mesh gen interval is <= 0"); + this.tasksPerTick = this.prepareMeshGen(originX, originY, originZ, meshGenOffsetX, meshGenOffsetZ, frustum, this.meshGenInterval, partialTick); + } + else + { + this.onOffGen(); + } + + //If there are mesh gen tasks, we do mesh genning + if (!this.chunkGenTasks.isEmpty()) + this.doMeshGenning(this.tasksPerTick); + } + + private static CloudMeshGenerator.MeshGenStatus fixedIterateAndCopyToChunkBuffer(int copyBufferId, int copyBufferSizeBytes, Collection chunks, Function byteOffsetPerChunk, Function chunkBufferId, Function bytesToCopyPerChunk, Function bufferSizeBytesPerChunk) + { + CloudMeshGenerator.MeshGenStatus result = CloudMeshGenerator.MeshGenStatus.NORMAL; + + GlStateManager._glBindBuffer(GL31.GL_COPY_READ_BUFFER, copyBufferId); + + for (MeshChunk chunk : chunks) + { + int bytesToCopy = bytesToCopyPerChunk.apply(chunk); + if (bytesToCopy > 0) + { + int maxSize = bufferSizeBytesPerChunk.apply(chunk); + if (bytesToCopy > maxSize) // Too many bytes to go in to the chunk mesh buffer + { + bytesToCopy = maxSize; + result = CloudMeshGenerator.MeshGenStatus.CHUNK_OVERFLOW; + } + + int byteOffset = byteOffsetPerChunk.apply(chunk); + if (byteOffset + bytesToCopy > copyBufferSizeBytes) // TODO: Account for this overflow using mesh gen status + { + //TODO: Make sure this uses multiples of the size of a single element to avoid cutting off a single element + bytesToCopy = copyBufferSizeBytes - byteOffset; + if (bytesToCopy <= 0) + continue; + } + + GlStateManager._glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, chunkBufferId.apply(chunk)); + GL31.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, byteOffset, 0, bytesToCopy); + } + } + + return result; + } + + private static CloudMeshGenerator.MeshGenStatus packedIterateAndCopyToChunkBuffer(int copyBufferId, int copyBufferSizeBytes, Collection chunks, Function chunkBufferId, Function bytesToCopyPerChunk, Function bufferSizeBytesPerChunk) + { + CloudMeshGenerator.MeshGenStatus result = CloudMeshGenerator.MeshGenStatus.NORMAL; + + GlStateManager._glBindBuffer(GL31.GL_COPY_READ_BUFFER, copyBufferId); + + int currentBytes = 0; + for (MeshChunk chunk : chunks) + { + int totalBytes = bytesToCopyPerChunk.apply(chunk); + if (totalBytes > 0) //If the chunk has data that needs copying over + { + int lastBytesOffset = totalBytes; + int maxSize = bufferSizeBytesPerChunk.apply(chunk); + if (lastBytesOffset > maxSize) //Make sure we don't go over the maximum the chunk buffer can hold + { + lastBytesOffset = maxSize; + result = CloudMeshGenerator.MeshGenStatus.CHUNK_OVERFLOW; + } + boolean stop = false; + if (currentBytes + lastBytesOffset > copyBufferSizeBytes) //If the the byte offset will go over the size of the copy buffer, clamp + { + lastBytesOffset = copyBufferSizeBytes - currentBytes; + if (lastBytesOffset <= 0) // If it becomes negative however, we don't want to attempt to copy data over + return CloudMeshGenerator.MeshGenStatus.MESH_POOL_OVERFLOW; + stop = true; // After copying this data over we will stop, since there is no more space in the copy buffer to read data from + } + + GlStateManager._glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, chunkBufferId.apply(chunk)); + GL31.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, currentBytes, 0, lastBytesOffset); + + currentBytes += totalBytes; + + if (stop) + return CloudMeshGenerator.MeshGenStatus.MESH_POOL_OVERFLOW; + } + } + + return result; + } + + protected Pair finalizeMeshGen() + { + if (this.shader == null || !this.shader.isValid() || this.chunks == null) + return Pair.of(CloudMeshGenerator.MeshGenStatus.NOT_INITIALIZED, CloudMeshGenerator.MeshGenStatus.NOT_INITIALIZED); + + if (this.completedGenTasks.isEmpty()) + return Pair.of(CloudMeshGenerator.MeshGenStatus.NO_TASKS, CloudMeshGenerator.MeshGenStatus.NO_TASKS); + + RenderSystem.assertOnRenderThread(); + + GL42.glMemoryBarrier(GL43.GL_SHADER_STORAGE_BARRIER_BIT); + + CloudMeshGenerator.MeshGenStatus opaqueResult = CloudMeshGenerator.MeshGenStatus.NORMAL; + CloudMeshGenerator.MeshGenStatus transparentResult = CloudMeshGenerator.MeshGenStatus.NORMAL; + + opaqueResult = this.copyMeshData( + TOTAL_SIDES_NAME, + SIDES_PER_CHUNK_NAME, + SIDE_INFO_BUFFER_NAME, + MeshChunk::getOpaqueBuffers, + BYTES_PER_SIDE_INFO, + this.opaqueBufferSize + ); + + if (this.useTransparency) + { + transparentResult = this.copyMeshData( + TRANSPARENT_TOTAL_CUBES_NAME, + TRANSPARENT_CUBES_PER_CHUNK_NAME, + TRANSPARENT_CUBE_INFO_BUFFER_NAME, + c -> c.getTransparentBuffers().get(), + BYTES_PER_CUBE_INFO, + this.transparentBufferSize + ); + } + + this.opaqueBufferBytesUsed = 0; + this.transparentBufferBytesUsed = 0; + for (MeshChunk chunk : this.chunks) + { + this.opaqueBufferBytesUsed += chunk.getOpaqueBuffers().getElementCount() * BYTES_PER_SIDE_INFO; + chunk.getTransparentBuffers().ifPresent(bufferSet -> { + this.transparentBufferBytesUsed += bufferSet.getElementCount() * BYTES_PER_CUBE_INFO; + }); + } + + return Pair.of(opaqueResult, transparentResult); + } + + private CloudMeshGenerator.MeshGenStatus copyMeshData(String totalCountBufferName, String countPerChunkBufferName, String elementBufferName, Function bufferSetFunction, int bytesPerElement, int elementBufferSize) + { + CloudMeshGenerator.MeshGenStatus status = CloudMeshGenerator.MeshGenStatus.NORMAL; + + if (!this.useFixedMeshDataSectionSize) + { + //Get the total amount of sides and indices across all chunks and reset + this.shader.getShaderStorageBuffer(totalCountBufferName).writeData(b -> { + b.putInt(0, 0); + }, 4, true); + } + + //Get the amount of total sides each chunk has and reset each counter + this.shader.getShaderStorageBuffer(countPerChunkBufferName).readWriteData(buffer -> + { + for (CloudMeshGenerator.ChunkGenTask gennedChunk : this.completedGenTasks) + { + MeshChunk.BufferSet bufferSet = bufferSetFunction.apply(gennedChunk.chunk()); + int index = gennedChunk.index() * 4; + int count = buffer.getInt(index); + bufferSet.setTotalElementCount(count); + buffer.putInt(index, 0); + } + }, this.chunks.size() * 4); + + List completedChunks = this.completedGenTasks.stream().map(CloudMeshGenerator.ChunkGenTask::chunk).toList(); + + int elementBufferId = this.shader.getShaderStorageBuffer(elementBufferName).getId(); + if (this.useFixedMeshDataSectionSize) + status = fixedIterateAndCopyToChunkBuffer(elementBufferId, elementBufferSize, completedChunks, bufferSetFunction.andThen(b -> b.getElementOffset() * bytesPerElement), bufferSetFunction.andThen(MeshChunk.BufferSet::getBufferId), bufferSetFunction.andThen(c -> c.getElementCount() * bytesPerElement), bufferSetFunction.andThen(MeshChunk.BufferSet::getBufferSize)); + else + status = packedIterateAndCopyToChunkBuffer(elementBufferId, elementBufferSize, completedChunks, bufferSetFunction.andThen(MeshChunk.BufferSet::getBufferId), bufferSetFunction.andThen(c -> c.getElementCount() * bytesPerElement), bufferSetFunction.andThen(MeshChunk.BufferSet::getBufferSize)); + + GlStateManager._glBindBuffer(GL31.GL_COPY_READ_BUFFER, 0); + GlStateManager._glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, 0); + + return status; + } + + /** + * Queues a list of chunk gen tasks for each chunk in this mesh generator + * + * @param meshGenOffsetX + * @param meshGenOffsetZ + * @param frustum + * Culling frustum, null for no culling + * @param genInterval + * How many frames mesh genning should take + * @return + */ + protected int prepareMeshGen(double originX, double originY, double originZ, float meshGenOffsetX, float meshGenOffsetZ, @Nullable Frustum frustum, int genInterval, float partialTick) + { + this.shader.forUniform("Scroll", (id, loc) -> { + GL41.glProgramUniform3f(id, loc, this.scrollX, this.scrollY, this.scrollZ); + }); + this.shader.forUniform("Wiggle", (id, loc) -> { + GL41.glProgramUniform1f(id, loc, (this.scrollX + this.scrollY + this.scrollZ) / 5.0F); + }); + this.shader.forUniform("Origin", (id, loc) -> { + GL41.glProgramUniform3f(id, loc, (float)originX, (float)originY, (float)originZ); + }); + this.shader.forUniform("TestFacesFacingAway", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, this.testFacesFacingAway ? 1 : 0); + }); + this.uploadFadeData(); + + int chunkCount = 0; + for (int i = 0; i < this.chunks.size(); i++) + { + if (this.queueChunkMeshGenTaskOrClear(this.chunks.get(i), i, meshGenOffsetX, meshGenOffsetZ, frustum)) + chunkCount++; + } + return Mth.ceil((float)chunkCount / (float)genInterval); + } + + protected void onOffGen() + { + //We read these SSBOs here to avoid weird frame spikes when in fullscreen V-Sync, not sure why it happens + if (!this.useFixedMeshDataSectionSize) + this.shader.getShaderStorageBuffer(TOTAL_SIDES_NAME).readWriteData(b -> {}, 4); + this.shader.getShaderStorageBuffer(SIDES_PER_CHUNK_NAME).readWriteData(buffer -> {}, this.chunks.size() * 4); + if (this.useTransparency) + { + if (!this.useFixedMeshDataSectionSize) + this.shader.getShaderStorageBuffer(TRANSPARENT_TOTAL_CUBES_NAME).readWriteData(b -> {}, 4); + this.shader.getShaderStorageBuffer(TRANSPARENT_CUBES_PER_CHUNK_NAME).readWriteData(buffer -> {}, this.chunks.size() * 4); + } + } + + /** + * Queues a given chunk for mesh genning or clears it if empty + * + * @param chunk + * The given {@link MeshChunk} to generate a mesh for + * @param chunkIndex + * The index of the mesh chunk in {@code this.chunks} + * @param meshGenOffsetX + * @param meshGenOffsetZ + * @param frustum + * For frustum culling, null for no culling + * @return + */ + protected boolean queueChunkMeshGenTaskOrClear(MeshChunk chunk, int chunkIndex, float meshGenOffsetX, float meshGenOffsetZ, @Nullable Frustum frustum) + { + PreparedChunk chunkInfo = chunk.getChunkInfo(); + AABB bounds = chunkInfo.bounds(); + float minX = (float)bounds.minX + meshGenOffsetX; + float minZ = (float)bounds.minZ + meshGenOffsetZ; + float maxX = (float)bounds.maxX + meshGenOffsetX; + float maxZ = (float)bounds.maxZ + meshGenOffsetZ; + + if (frustum == null || ((MixinFrustumAccessor)frustum).simpleclouds$cubeInFrustum(minX, bounds.minY, minZ, maxX, bounds.maxY, maxZ)) + { + double nearestCornerX = Math.max(Math.max(bounds.minX, -bounds.maxX), 0.0D); + double nearestCornerZ = Math.max(Math.max(bounds.minZ, -bounds.maxZ), 0.0D); + double dist = Math.sqrt(nearestCornerX * nearestCornerX + nearestCornerZ * nearestCornerZ); + + if (this.cullDistance <= 0.0F || dist < this.cullDistance) + { + CloudMeshGenerator.ChunkGenSettings settings = this.determineChunkGenSettings(minX, minZ, maxX, maxZ); + if (settings.skipChunk()) + { + chunk.clearChunk(); + return false; + } + this.chunkGenTasks.add(new CloudMeshGenerator.ChunkGenTask(chunk, minX, (float)bounds.minY, minZ, maxX, (float)bounds.maxY, maxZ, chunkIndex, minX, 0.0F, minZ, settings.minimumHeight(), settings.maximumHeight())); + return true; + } + } + return false; + } + + protected abstract CloudMeshGenerator.ChunkGenSettings determineChunkGenSettings(float minX, float minZ, float maxX, float maxZ); + + /** + * Does mesh generating for a given amount of chunks defined by tasksPerTick + * + * @param tasksPerTick + */ + protected void doMeshGenning(int tasksPerTick) + { + for (int i = 0; i < tasksPerTick; i++) + { + CloudMeshGenerator.ChunkGenTask task = this.chunkGenTasks.poll(); + if (task != null) + { + this.generateChunk(task); + this.updateMeshChunkAfterGeneration(task.chunk(), task); + this.completedGenTasks.add(task); + } + else + { + break; + } + } + } + + protected void updateMeshChunkAfterGeneration(MeshChunk chunk, CloudMeshGenerator.ChunkGenTask task) + { + chunk.setBounds(task.minX(), task.minY(), task.minZ(), task.maxX(), task.maxY(), task.maxZ()); + chunk.setHeights(task.startY(), task.endY()); + chunk.resetLastGenTime(); + } + + /** + * Generates a given chunk, or completes a chunk gen task + * + * @param task + * @param scale + * @param globalOffsetX + * @param globalOffsetZ + */ + protected void generateChunk(CloudMeshGenerator.ChunkGenTask task) + { + PreparedChunk chunkInfo = task.chunk().getChunkInfo(); + + int lodScale = chunkInfo.lodScale(); + int lowestY = task.startY(); + int height = Mth.ceil((float)(task.endY() - lowestY) / (float)lodScale); + int localHeightInvocations = Mth.ceil((float)height / (float)LOCAL_SIZE); + + if (localHeightInvocations > 0) + { + this.shader.forUniform("ChunkIndex", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, task.index()); + }); + this.shader.forUniform("LodLevel", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, chunkInfo.lodLevel()); + }); + this.shader.forUniform("RenderOffset", (id, loc) -> { + GL41.glProgramUniform3f(id, loc, task.x(), task.y() + lowestY, task.z()); + }); + this.shader.forUniform("Scale", (id, loc) -> { + GL41.glProgramUniform1f(id, loc, lodScale); + }); + this.shader.forUniform("DoNotOccludeSide", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, chunkInfo.noOcclusionDirectionIndex()); + }); + if (this.useFixedMeshDataSectionSize) + { + this.shader.forUniform("OpaqueMeshDataOffset", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, task.chunk().getOpaqueBuffers().getElementOffset()); + }); + + task.chunk().getTransparentBuffers().ifPresent(bufferSet -> + { + this.shader.forUniform("TransparentMeshDataOffset", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, bufferSet.getElementOffset()); + }); + }); + } + + this.shader.dispatch(WORK_SIZE, localHeightInvocations, WORK_SIZE, false); + if (!this.useFixedMeshDataSectionSize) + GL42.glMemoryBarrier(GL43.GL_SHADER_STORAGE_BARRIER_BIT); + } + } + + public void forRenderableMeshChunks(@Nullable Frustum frustum, Function bufferSetFunction, BiConsumer function) + { + this.forRenderableMeshChunks(frustum, bufferSetFunction, function, false); + } + + public void forRenderableMeshChunks(@Nullable Frustum frustum, Function bufferSetFunction, BiConsumer function, boolean updateFade) + { + for (MeshChunk chunk : this.chunks) + { + MeshChunk.BufferSet bufferSet = bufferSetFunction.apply(chunk); + if (bufferSet.getElementCount() > 0) + { + if (updateFade && chunk.getTicksSinceLastGen() > TICKS_UNTIL_FADE_RESET) + { + chunk.resetAlpha(); + chunk.setFadeEnabled(false); + } + + boolean render = true; + if (frustum != null) + render = ((MixinFrustumAccessor)frustum).simpleclouds$cubeInFrustum(chunk.getBoundsMinX(), chunk.getBoundsMinY(), chunk.getBoundsMinZ(), chunk.getBoundsMaxX(), chunk.getBoundsMaxY(), chunk.getBoundsMaxZ()); + + if (render) + { + PreparedChunk chunkInfo = chunk.getChunkInfo(); + AABB bounds = chunkInfo.bounds(); + double nearestCornerX = Math.max(Math.max(bounds.minX, -bounds.maxX), 0.0D); + double nearestCornerZ = Math.max(Math.max(bounds.minZ, -bounds.maxZ), 0.0D); + double dist = Math.sqrt(nearestCornerX * nearestCornerX + nearestCornerZ * nearestCornerZ); + if (this.cullDistance <= 0.0F || this.cullDistance > dist) + { + if (updateFade) + chunk.setFadeEnabled(true); + function.accept(chunk, bufferSet); + } + } + } + } + } + + public void fillReport(CrashReportCategory category) + { + category.setDetail("Shader Type", this.shaderType); + category.setDetail("Shaded Clouds", this.shadedClouds); + category.setDetail("Transparency Enabled", this.useTransparency); + category.setDetail("Fade Near Origin", this.fadeNearOrigin); + category.setDetail("Compute Shader", this.shader); + category.setDetail("Level Of Details", 1 + this.lodConfig.getLods().length); + category.setDetail("Generation Frame Interval", this.meshGenInterval); + category.setDetail("Total Prepared Chunks", this.lodConfig.getPreparedChunks().size()); + category.setDetail("Tasks Per Frame", this.tasksPerTick); + category.setDetail("Scroll", String.format("X: %s, Y: %s, Z: %s", this.scrollX, this.scrollY, this.scrollZ)); + category.setDetail("Total Mesh Chunks", this.chunks != null ? this.chunks.size() : "null"); + category.setDetail("Mesh Gen Status", this.meshGenStatus); + category.setDetail("Test Occluded Faces", this.testFacesFacingAway); + } + + @Override + public String toString() + { + return String.format("%s[shader_name=%s]", this.getClass().getSimpleName(), this.meshShaderLoc); + } + + protected static CloudMeshGenerator.ChunkGenSettings skip() + { + return new CloudMeshGenerator.ChunkGenSettings(true, 0, 0); + } + + protected static CloudMeshGenerator.ChunkGenSettings heights(int min, int max) + { + return new CloudMeshGenerator.ChunkGenSettings(false, min, max); + } + + public static CloudMeshGenerator.Builder builder() + { + return new CloudMeshGenerator.Builder(); + } + + protected static record ChunkGenSettings(boolean skipChunk, int minimumHeight, int maximumHeight) {} + + protected static record ChunkGenTask(MeshChunk chunk, float minX, float minY, float minZ, float maxX, float maxY, float maxZ, int index, float x, float y, float z, int startY, int endY) {} + + public static enum MeshGenStatus + { + NOT_INITIALIZED("Not initialized", true), + NO_TASKS("No tasks", false), + NORMAL("Normal", false), + MESH_POOL_OVERFLOW("Mesh pool overflow", true), + CHUNK_OVERFLOW("Chunk overflow", true); + + private String name; + private boolean isErroneous; + + private MeshGenStatus(String name, boolean isErroneous) + { + this.name = name; + this.isErroneous = isErroneous; + } + + public String getName() + { + return this.name; + } + + public boolean isErroneous() + { + return this.isErroneous; + } + } + + public static class Builder + { + private boolean fadeNearOrigin; + private boolean shadedClouds = true; + private LevelOfDetailConfig lodConfig = LevelOfDetailOptions.HIGH.getConfig(); + private Supplier meshGenIntervalCalculator = () -> 5; + private boolean useTransparency = true; + private boolean fixedMeshDataSectionSize; + private float fadeStart = 0.5F; + private float fadeEnd = 1.0F; + private boolean testFacesFacingAway = false; + + private Builder() {} + + public Builder fadeNearOrigin(boolean flag) + { + this.fadeNearOrigin = flag; + return this; + } + + public Builder shadedClouds(boolean flag) + { + this.shadedClouds = flag; + return this; + } + + public Builder meshGenInterval(int interval) + { + if (interval <= 0) + throw new IllegalArgumentException("Mesh gen interval must be greater than 0"); + this.meshGenIntervalCalculator = () -> interval; + return this; + } + + public Builder meshGenInterval(Supplier calculator) + { + this.meshGenIntervalCalculator = calculator; + return this; + } + + public Builder lodConfig(LevelOfDetailConfig config) + { + this.lodConfig = config; + return this; + } + + public Builder useTransparency(boolean flag) + { + this.useTransparency = flag; + return this; + } + + public Builder fixedMeshDataSectionSize(boolean flag) + { + this.fixedMeshDataSectionSize = flag; + return this; + } + + public Builder fadeStart(float fadeStart) + { + this.fadeStart = fadeStart; + return this; + } + + public Builder fadeEnd(float fadeEnd) + { + this.fadeEnd = fadeEnd; + return this; + } + + public Builder testFacesFacingAway(boolean flag) + { + this.testFacesFacingAway = flag; + return this; + } + + private T applyExtraSettings(T generator) + { + generator.setFadeDistances(this.fadeStart, this.fadeEnd); + generator.setTestFacesFacingAway(this.testFacesFacingAway); + return generator; + } + + public MultiRegionCloudMeshGenerator createMultiRegion() + { + return this.applyExtraSettings(new MultiRegionCloudMeshGenerator(this.fadeNearOrigin, this.shadedClouds, this.lodConfig, this.meshGenIntervalCalculator, this.useTransparency, this.fixedMeshDataSectionSize)); + } + + public SingleRegionCloudMeshGenerator createSingleRegion(CloudInfo type) + { + return this.applyExtraSettings(new SingleRegionCloudMeshGenerator(this.shadedClouds, this.lodConfig, this.meshGenIntervalCalculator, this.useTransparency, this.fixedMeshDataSectionSize, type)); + } + } +} diff --git a/dev/nonamecrackers2/simpleclouds/client/mesh/generator/MultiRegionCloudMeshGenerator.java b/dev/nonamecrackers2/simpleclouds/client/mesh/generator/MultiRegionCloudMeshGenerator.java new file mode 100644 index 00000000..8226742f --- /dev/null +++ b/dev/nonamecrackers2/simpleclouds/client/mesh/generator/MultiRegionCloudMeshGenerator.java @@ -0,0 +1,404 @@ +package dev.nonamecrackers2.simpleclouds.client.mesh.generator; + +import java.io.IOException; +import java.nio.IntBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.joml.Matrix2f; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL41; +import org.lwjgl.opengl.GL42; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.mojang.blaze3d.platform.TextureUtil; +import com.mojang.blaze3d.systems.RenderSystem; + +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.LevelOfDetail; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.LevelOfDetailConfig; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.PreparedChunk; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.BindingManager; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.ShaderStorageBufferObject; +import dev.nonamecrackers2.simpleclouds.client.shader.compute.ComputeShader; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudInfo; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudType; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudGetter; +import dev.nonamecrackers2.simpleclouds.common.noise.AbstractNoiseSettings; +import dev.nonamecrackers2.simpleclouds.common.noise.NoiseSettings; +import net.minecraft.CrashReportCategory; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; + +public final class MultiRegionCloudMeshGenerator extends CloudMeshGenerator +{ + private static final Logger LOGGER = LogManager.getLogger("simpleclouds/MultiRegionCloudMeshGenerator"); + + private static final ResourceLocation REGION_GENERATOR_LOC = SimpleCloudsMod.id("cloud_regions"); + private static final String LOD_SCALES_NAME = "LodScales"; + private static final String CLOUD_REGIONS_NAME = "CloudRegions"; + public static final int MAX_CLOUD_TYPES = 64; + public static final int MAX_CLOUD_FORMATIONS = 10; + private static final int BYTES_PER_REGION = 32; + private int requiredRegionTexSize; + private CloudGetter cloudGetter = CloudGetter.EMPTY; + private CloudInfo[] cachedTypes = new CloudInfo[0]; + private @Nullable ComputeShader regionTextureGenerator; + private int cloudRegionTextureId = -1; + private int cloudRegionImageBinding = -1; + private boolean updateCloudTypes; + private int currentCloudFormationCount; + + protected MultiRegionCloudMeshGenerator(boolean fadeNearOrigin, boolean shadedClouds, LevelOfDetailConfig lodConfig, Supplier meshGenIntervalCalculator, boolean useTransparency, boolean fixedMeshDataSectionSize) + { + super(CloudMeshGenerator.MAIN_CUBE_MESH_GENERATOR, 0, fadeNearOrigin, shadedClouds, lodConfig, meshGenIntervalCalculator, useTransparency, fixedMeshDataSectionSize); + } + + public void setCloudGetter(CloudGetter getter) + { + this.cloudGetter = Objects.requireNonNull(getter, "Cloud getter cannot be null"); + this.updateCloudTypes(); + } + + public int getCloudRegionTextureId() + { + return this.cloudRegionTextureId; + } + + public void updateCloudTypes() + { + this.updateCloudTypes = true; + } + + public int getTotalCloudTypes() + { + return this.cachedTypes.length; + } + + public int getCloudFormationCount() + { + return this.currentCloudFormationCount; + } + + @Override + protected void setupShader() + { + super.setupShader(); + + this.cachedTypes = new CloudInfo[0]; + this.updateCloudTypes = false; + + this.shader.createAndBindSSBO(NOISE_LAYERS_NAME, GL15.GL_STATIC_DRAW).allocateBuffer(AbstractNoiseSettings.Param.values().length * 4 * MAX_NOISE_LAYERS * MAX_CLOUD_TYPES); + this.shader.createAndBindSSBO(LAYER_GROUPINGS_NAME, GL15.GL_STATIC_DRAW).allocateBuffer(CloudInfo.BYTES_PER_TYPE * MAX_CLOUD_TYPES); + + this.uploadCloudTypeData(); + } + + @Override + protected void initExtra(ResourceManager manager) throws IOException + { + // Cloud region texture generator compute shader + // This texture is a 2D array texture, with a texture for each level of detail. + // The red channel contains the index for a cloud type in the main mesh compute shader, and + // the green channel contains an "edge fade" value for smooth cloud region boundaries. + // When generating the cloud mesh, the main mesh compute shader samples this array texture + // depending on what LOD it is generating for to determine what cloud type to construct + + // Create the compute shader + + this.currentCloudFormationCount = 0; + this.requiredRegionTexSize = 0; + + if (this.regionTextureGenerator != null) + this.regionTextureGenerator.close(); + + var params = ImmutableMap.of("EDGE_FADE_FACTOR", String.valueOf(SimpleCloudsConstants.REGION_EDGE_FADE_FACTOR)); + this.regionTextureGenerator = ComputeShader.loadShader(REGION_GENERATOR_LOC, manager, 16, 16, this.lodConfig.getLods().length + 1, params); + + ShaderStorageBufferObject lodScales = this.regionTextureGenerator.createAndBindSSBO(LOD_SCALES_NAME, GL15.GL_STATIC_READ); + int lodScalesSize = this.lodConfig.getLods().length * 4 + 4; + lodScales.allocateBuffer(lodScalesSize); + lodScales.writeData(b -> { + b.putFloat(1.0F); // Primary chunk scale + for (LevelOfDetail l : this.lodConfig.getLods()) + b.putFloat((float)l.chunkScale()); + b.rewind(); + }, lodScalesSize, false); + + // Data for the cloud regions in world + this.regionTextureGenerator.createAndBindSSBO(CLOUD_REGIONS_NAME, GL15.GL_STATIC_READ).allocateBuffer(MAX_CLOUD_FORMATIONS * BYTES_PER_REGION); + + // Create the cloud region 2D array texture + + // Here we calculate the maximum size we need for this array texture, + // ensuring each block in the mesh will have a value to read in this texture + // when doing mesh generation + int prevSpan = this.lodConfig.getPrimaryChunkSpan(); + int prevScale = 1; + int largestSpan = prevSpan; + for (LevelOfDetail config : this.lodConfig.getLods()) + { + int scale = config.chunkScale(); + int div = scale / prevScale; + prevScale = scale; + prevSpan = prevSpan / div + config.spread() * 2; + if (prevSpan > largestSpan) + largestSpan = prevSpan; + } + this.requiredRegionTexSize = largestSpan * SimpleCloudsConstants.CHUNK_SIZE; + + if (this.cloudRegionTextureId != -1) + { + TextureUtil.releaseTextureId(this.cloudRegionTextureId); + this.cloudRegionTextureId = -1; + } + + this.cloudRegionTextureId = TextureUtil.generateTextureId(); + GL11.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cloudRegionTextureId); + GL11.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE); + GL11.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE); + GL11.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); + GL11.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + GL12.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RG32F, this.requiredRegionTexSize, this.requiredRegionTexSize, this.lodConfig.getLods().length + 1, 0, GL30.GL_RG, GL11.GL_FLOAT, (IntBuffer)null); + GL11.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, 0); + + // Assign an image unit to it so any shader can access it + if (this.cloudRegionImageBinding != -1) + BindingManager.freeImageUnit(this.cloudRegionImageBinding); + this.cloudRegionImageBinding = BindingManager.getAvailableImageUnit(); + BindingManager.useImageUnit(this.cloudRegionImageBinding); + GL42.glBindImageTexture(this.cloudRegionImageBinding, this.cloudRegionTextureId, 0, true, 0, GL15.GL_WRITE_ONLY, GL30.GL_RG32F); + this.regionTextureGenerator.setImageUnit("regionTexture", this.cloudRegionImageBinding); + + this.runRegionGenerator(0.0F, 0.0F, 1.0F); + + // Update the main mesh shader to use this texture + this.shader.setSampler2DArray("RegionsSampler", this.cloudRegionTextureId, 0); + this.shader.forUniform("RegionsTexSize", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, this.requiredRegionTexSize); + }); + + LOGGER.debug("Created cloud region texture generator with size {}x{}x{}", this.requiredRegionTexSize, this.requiredRegionTexSize, this.lodConfig.getLods().length + 1); + } + + @Override + protected CloudMeshGenerator.ChunkGenSettings determineChunkGenSettings(float minX, float minZ, float maxX, float maxZ) + { + float[][] positions = new float[][] { {minX, minZ}, {minX, maxZ}, {maxX, minZ}, {maxX, maxZ} }; + int smallestStartHeight = 0; + int largestEndHeight = 0; + boolean empty = true; + for (int i = 0; i < positions.length; i++) + { + float[] pos = positions[i]; + Pair typeAt = this.cloudGetter.getCloudTypeAtPosition(pos[0], pos[1]); + if (typeAt.getRight() < 1.0F) + empty = false; + NoiseSettings config = typeAt.getLeft().noiseConfig(); + int startHeight = config.getStartHeight(); + int endHeight = config.getEndHeight(); + if (i == 0 || smallestStartHeight > startHeight) + smallestStartHeight = startHeight; + if (i == 0 || largestEndHeight < endHeight) + largestEndHeight = endHeight; + } + if (empty || smallestStartHeight == largestEndHeight) + return skip(); + else + return heights(smallestStartHeight, largestEndHeight); + } + + @Override + protected void generateChunk(CloudMeshGenerator.ChunkGenTask task) + { + this.shader.forUniform("RegionSampleOffset", (id, loc) -> + { + PreparedChunk chunk = task.chunk().getChunkInfo(); + GL41.glProgramUniform2f(id, loc, chunk.x() * (float)SimpleCloudsConstants.CHUNK_SIZE + (float)this.requiredRegionTexSize / 2.0F, chunk.z() * (float)SimpleCloudsConstants.CHUNK_SIZE + (float)this.requiredRegionTexSize / 2.0F); + }); + this.shader.setSampler2DArray("RegionsSampler", this.cloudRegionTextureId, 0); + + super.generateChunk(task); + } + + private void runRegionGenerator(float meshOffsetX, float meshOffsetZ, float partialTick) + { + if (this.regionTextureGenerator == null || !this.regionTextureGenerator.isValid()) + return; + + this.uploadCloudRegionData(partialTick); + this.regionTextureGenerator.forUniform("Offset", (id, loc) -> { + GL41.glProgramUniform2f(id, loc, meshOffsetX, meshOffsetZ); + }); + this.regionTextureGenerator.dispatchAndWait(this.requiredRegionTexSize / 16, this.requiredRegionTexSize / 16, 1); + } + + private void uploadCloudRegionData(float partialTick) + { + if (this.regionTextureGenerator == null || !this.regionTextureGenerator.isValid()) + return; + + // Converts the cloud regions into data we can then easily pack into + // the SSBO. This method also checks and excludes cloud regions that + // reference cloud types that are not set up in this mesh generator + // to avoid errors. + List regionData = this.cloudGetter.getClouds().stream().map(region -> + { + Matrix2f transform = region.createTransform(partialTick); + float[] data = new float[] { + region.getPosX(partialTick), + region.getPosZ(partialTick), + (float)ArrayUtils.indexOf(this.cachedTypes, this.cloudGetter.getCloudTypeForId(region.getCloudTypeId())), + region.getRadius(partialTick), + transform.m00, + transform.m01, + transform.m10, + transform.m11 + }; + return data; + }).filter(data -> data[2] >= 0.0F).toList(); + + int regionDataSize = regionData.size(); + int count = Math.min(MAX_CLOUD_FORMATIONS, regionDataSize); + if (regionDataSize != this.currentCloudFormationCount) + { + if (regionDataSize > MAX_CLOUD_FORMATIONS && regionDataSize > this.currentCloudFormationCount) + LOGGER.warn("Cloud formations {}/{}. Maximum count has been exceeded; some cloud formations will be ignored. Please ensure cloud formation count does not exceed the maximum of {}.", regionData.size(), MAX_CLOUD_FORMATIONS, MAX_CLOUD_FORMATIONS); + this.currentCloudFormationCount = regionDataSize; + } + + if (count > 0) + { + ShaderStorageBufferObject regionsBuffer = this.regionTextureGenerator.getShaderStorageBuffer("CloudRegions"); + regionsBuffer.writeData(b -> + { + for (int i = 0; i < count; i++) + { + float[] data = regionData.get(i); + for (float f : data) + b.putFloat(f); + } + b.rewind(); + }, count * BYTES_PER_REGION, false); + } + + this.regionTextureGenerator.forUniform("TotalCloudRegions", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, count); + }); + } + + private void uploadCloudTypeData() + { + RenderSystem.assertOnRenderThreadOrInit(); + + if (this.shader != null && this.shader.isValid()) + { + var toCopy = this.cloudGetter.getIndexedCloudTypes(); + if (toCopy.length > MAX_CLOUD_TYPES) + LOGGER.warn("Cloud type count exceeds the maximum. Not all cloud types will render."); + int copySize = Math.min(MAX_CLOUD_TYPES, toCopy.length); + this.cachedTypes = Arrays.copyOf(toCopy, copySize); + + LOGGER.debug("Uploading cloud type noise data..."); + + this.shader.getShaderStorageBuffer(LAYER_GROUPINGS_NAME).writeData(b -> + { + int previousLayerIndex = 0; + for (int i = 0; i < this.cachedTypes.length; i++) + { + CloudInfo type = this.cachedTypes[i]; + previousLayerIndex = type.packToBuffer(b, previousLayerIndex); + } + b.rewind(); + }, CloudInfo.BYTES_PER_TYPE * this.cachedTypes.length, false); + + this.shader.getShaderStorageBuffer(NOISE_LAYERS_NAME).writeData(b -> + { + for (int i = 0; i < this.cachedTypes.length; i++) + { + NoiseSettings settings = this.cachedTypes[i].noiseConfig(); + float[] packed = settings.packForShader(); + for (int j = 0; j < packed.length && j < AbstractNoiseSettings.Param.values().length * MAX_NOISE_LAYERS; j++) + b.putFloat(packed[j]); + } + b.rewind(); + }, AbstractNoiseSettings.Param.values().length * 4 * MAX_NOISE_LAYERS * this.cachedTypes.length, false); + } + } + + @Override + protected int prepareMeshGen(double originX, double originY, double originZ, float meshGenOffsetX, float meshGenOffsetZ, @Nullable Frustum frustum, int interval, float partialTick) + { + if (this.updateCloudTypes) + { + this.uploadCloudTypeData(); + this.updateCloudTypes = false; + } + + this.runRegionGenerator(meshGenOffsetX, meshGenOffsetZ, partialTick); + + return super.prepareMeshGen(originX, originY, originZ, meshGenOffsetX, meshGenOffsetZ, frustum, interval, partialTick); + } + + @Override + protected void onOffGen() + { + super.onOffGen(); + + if (this.regionTextureGenerator != null) + this.regionTextureGenerator.getShaderStorageBuffer("CloudRegions").readData(buf -> {}, BYTES_PER_REGION * MAX_CLOUD_FORMATIONS); + } + + @Override + public void close() + { + super.close(); + + this.currentCloudFormationCount = 0; + this.requiredRegionTexSize = 0; + this.updateCloudTypes = false; + this.cloudGetter = CloudGetter.EMPTY; + this.cachedTypes = new CloudInfo[0]; + + if (this.regionTextureGenerator != null) + { + this.regionTextureGenerator.close(); + this.regionTextureGenerator = null; + } + + if (this.cloudRegionTextureId != -1) + { + TextureUtil.releaseTextureId(this.cloudRegionTextureId); + this.cloudRegionTextureId = -1; + } + + if (this.cloudRegionImageBinding != -1) + { + BindingManager.freeImageUnit(this.cloudRegionImageBinding); + this.cloudRegionImageBinding = -1; + } + } + + @Override + public void fillReport(CrashReportCategory category) + { + category.setDetail("Cloud Types", "(" + this.cachedTypes.length + ") " + Joiner.on(", ").join(this.cachedTypes)); + category.setDetail("Cloud Regions", this.cloudGetter.getClouds().size()); + category.setDetail("Cloud Formations", this.currentCloudFormationCount); + super.fillReport(category); + } +} diff --git a/dev/nonamecrackers2/simpleclouds/client/mesh/instancing/InstanceableMesh.java b/dev/nonamecrackers2/simpleclouds/client/mesh/instancing/InstanceableMesh.java new file mode 100644 index 00000000..49a38c40 --- /dev/null +++ b/dev/nonamecrackers2/simpleclouds/client/mesh/instancing/InstanceableMesh.java @@ -0,0 +1,219 @@ +package dev.nonamecrackers2.simpleclouds.client.mesh.instancing; + +import java.nio.ByteBuffer; +import java.util.function.Consumer; +import java.util.function.Function; + +import javax.annotation.Nullable; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL31; +import org.lwjgl.system.MemoryUtil; + +import com.mojang.blaze3d.platform.MemoryTracker; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.VertexFormat; + +public class InstanceableMesh +{ + private int arrayObjectId = -1; + private int vertexBufferId = -1; + private int indexBufferId = -1; + private @Nullable ByteBuffer vertexBuffer; + private @Nullable ByteBuffer indexBuffer; + private int totalIndices; + + public InstanceableMesh(int vertexBufferSize, int indexBufferSize, VertexFormat format, Consumer vertexBufferGenerator, Function indexBufferGenerator) + { + RenderSystem.assertOnRenderThread(); + + this.arrayObjectId = GL30.glGenVertexArrays(); + this.vertexBufferId = GL15.glGenBuffers(); + this.indexBufferId = GL15.glGenBuffers(); + + GL30.glBindVertexArray(this.arrayObjectId); + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.vertexBufferId); + this.vertexBuffer = MemoryTracker.create(vertexBufferSize); + vertexBufferGenerator.accept(this.vertexBuffer); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, this.vertexBuffer, GL15.GL_STATIC_DRAW); + format.setupBufferState(); + GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, this.indexBufferId); + this.indexBuffer = MemoryTracker.create(indexBufferSize); + this.totalIndices = indexBufferGenerator.apply(this.indexBuffer); + GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer, GL15.GL_STATIC_DRAW); + + GL30.glBindVertexArray(0); + } + + public static InstanceableMesh defaultSide() + { + return new InstanceableMesh(48, 24, DefaultVertexFormat.POSITION, buffer -> + { + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); + buffer.rewind(); + }, buffer -> + { + buffer.putInt(0); + buffer.putInt(1); + buffer.putInt(2); + buffer.putInt(0); + buffer.putInt(2); + buffer.putInt(3); + buffer.rewind(); + return 6; + }); + } + + public static InstanceableMesh defaultNonCulledSide() + { + return new InstanceableMesh(48, 48, DefaultVertexFormat.POSITION, buffer -> + { + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); + buffer.rewind(); + }, buffer -> + { + buffer.putInt(0); + buffer.putInt(1); + buffer.putInt(2); + buffer.putInt(0); + buffer.putInt(2); + buffer.putInt(3); + + buffer.putInt(2); + buffer.putInt(1); + buffer.putInt(0); + buffer.putInt(3); + buffer.putInt(2); + buffer.putInt(0); + + buffer.rewind(); + return 12; + }); + } + +// public static PreparedMesh defaultCube() +// { +// return new PreparedMesh(576, 144, SimpleCloudsShaders.POSITION_NORMAL, buffer -> { +// //-x +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// //+x +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// //-y +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); +// //+y +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); +// //-z +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); +// //-z +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); +// +// buffer.rewind(); +// }, buffer -> { +// buffer.putInt(0); buffer.putInt(1); buffer.putInt(2); buffer.putInt(0); buffer.putInt(2); buffer.putInt(3); // -x +// buffer.putInt(4); buffer.putInt(5); buffer.putInt(6); buffer.putInt(4); buffer.putInt(6); buffer.putInt(7); // +x +// buffer.putInt(8); buffer.putInt(9); buffer.putInt(10); buffer.putInt(8); buffer.putInt(10); buffer.putInt(11); // -y +// buffer.putInt(12); buffer.putInt(13); buffer.putInt(14); buffer.putInt(12); buffer.putInt(14); buffer.putInt(15); // +y +// buffer.putInt(16); buffer.putInt(17); buffer.putInt(18); buffer.putInt(16); buffer.putInt(18); buffer.putInt(19); // -z +// buffer.putInt(20); buffer.putInt(21); buffer.putInt(22); buffer.putInt(20); buffer.putInt(22); buffer.putInt(23); // +z +// buffer.rewind(); +// return 36; +// }); +// } + + public static InstanceableMesh defaultCube() + { + return new InstanceableMesh(96, 144, DefaultVertexFormat.POSITION, buffer -> + { + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); + buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); + buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); + buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); + buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); + buffer.rewind(); + }, buffer -> + { + buffer.putInt(0); buffer.putInt(1); buffer.putInt(2); buffer.putInt(0); buffer.putInt(2); buffer.putInt(3); // -z + buffer.putInt(4); buffer.putInt(7); buffer.putInt(6); buffer.putInt(4); buffer.putInt(6); buffer.putInt(5); // +z + buffer.putInt(7); buffer.putInt(0); buffer.putInt(3); buffer.putInt(7); buffer.putInt(3); buffer.putInt(6); // -x + buffer.putInt(1); buffer.putInt(4); buffer.putInt(5); buffer.putInt(1); buffer.putInt(5); buffer.putInt(2); // +x + buffer.putInt(1); buffer.putInt(0); buffer.putInt(7); buffer.putInt(1); buffer.putInt(7); buffer.putInt(4); // -y + buffer.putInt(5); buffer.putInt(6); buffer.putInt(3); buffer.putInt(5); buffer.putInt(3); buffer.putInt(2); // +y + buffer.rewind(); + return 36; + }); + } + + public void drawInstanced(int count) + { + RenderSystem.assertOnRenderThread(); + + GL30.glBindVertexArray(this.arrayObjectId); + GL31.glDrawElementsInstanced(GL11.GL_TRIANGLES, this.totalIndices, GL11.GL_UNSIGNED_INT, 0L, count); + } + + public void destroy() + { + this.totalIndices = 0; + + if (this.arrayObjectId >= 0) + { + RenderSystem.glDeleteVertexArrays(this.arrayObjectId); + this.arrayObjectId = -1; + } + + if (this.vertexBufferId >= 0) + { + RenderSystem.glDeleteBuffers(this.vertexBufferId); + this.vertexBufferId = -1; + } + + if (this.vertexBuffer != null) + { + MemoryUtil.memFree(this.vertexBuffer); + this.vertexBuffer = null; + } + + if (this.indexBufferId >= 0) + { + RenderSystem.glDeleteBuffers(this.indexBufferId); + this.indexBufferId = -1; + } + + if (this.indexBuffer != null) + { + MemoryUtil.memFree(this.indexBuffer); + this.indexBuffer = null; + } + } +} diff --git a/dev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer.java b/dev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer.java new file mode 100644 index 00000000..7bd37397 --- /dev/null +++ b/dev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer.java @@ -0,0 +1,1497 @@ +package dev.nonamecrackers2.simpleclouds.client.renderer; + +import java.awt.Color; +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.joml.Matrix4f; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL14; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL40; +import org.lwjgl.opengl.GL43; + +import com.google.common.collect.Lists; +import com.google.gson.JsonSyntaxException; +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.pipeline.TextureTarget; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.BufferUploader; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.mojang.math.Axis; + +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import dev.nonamecrackers2.simpleclouds.api.client.event.ModifyCloudRenderDistanceEvent; +import dev.nonamecrackers2.simpleclouds.api.common.cloud.CloudMode; +import dev.nonamecrackers2.simpleclouds.client.cloud.ClientSideCloudTypeManager; +import dev.nonamecrackers2.simpleclouds.client.compat.SimpleCloudsCompatHelper; +import dev.nonamecrackers2.simpleclouds.client.event.impl.DetermineCloudRenderPipelineEvent; +import dev.nonamecrackers2.simpleclouds.client.framebuffer.CloudRenderTarget; +import dev.nonamecrackers2.simpleclouds.client.framebuffer.ShadowMapBuffer; +import dev.nonamecrackers2.simpleclouds.client.framebuffer.WeightedBlendingTarget; +import dev.nonamecrackers2.simpleclouds.client.mesh.RendererInitializeResult; +import dev.nonamecrackers2.simpleclouds.client.mesh.chunk.MeshChunk; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.MultiRegionCloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.SingleRegionCloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.LevelOfDetailConfig; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.PreparedChunk; +import dev.nonamecrackers2.simpleclouds.client.renderer.lightning.LightningBolt; +import dev.nonamecrackers2.simpleclouds.client.renderer.pipeline.CloudsRenderPipeline; +import dev.nonamecrackers2.simpleclouds.client.renderer.settings.CloudsRendererSettings; +import dev.nonamecrackers2.simpleclouds.client.shader.SimpleCloudsShaders; +import dev.nonamecrackers2.simpleclouds.client.shader.SingleSSBOShaderInstance; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.BindingManager; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.ShaderStorageBufferObject; +import dev.nonamecrackers2.simpleclouds.client.world.ClientCloudManager; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudType; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudGetter; +import dev.nonamecrackers2.simpleclouds.common.config.SimpleCloudsConfig; +import dev.nonamecrackers2.simpleclouds.mixin.MixinPostChain; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.EffectInstance; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.PostChain; +import net.minecraft.client.renderer.PostPass; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.ResourceManagerReloadListener; +import net.minecraft.util.FastColor; +import net.minecraft.util.Mth; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.StartupMessageManager; +import net.minecraftforge.fml.loading.ImmediateWindowHandler; +import nonamecrackers2.crackerslib.common.compat.CompatHelper; + +public class SimpleCloudsRenderer implements ResourceManagerReloadListener +{ + private static final Logger LOGGER = LogManager.getLogger("simpleclouds/SimpleCloudsRenderer"); + private static final Vector3f DIFFUSE_LIGHT_0 = (new Vector3f(0.2F, 1.0F, -0.7F)).normalize(); + private static final Vector3f DIFFUSE_LIGHT_1 = (new Vector3f(-0.2F, 1.0F, 0.7F)).normalize(); + private static final ResourceLocation STORM_POST_PROCESSING_LOC = SimpleCloudsMod.id("shaders/post/storm_post.json"); + private static final ResourceLocation BLUR_POST_PROCESSING_LOC = SimpleCloudsMod.id("shaders/post/blur_post.json"); + private static final ResourceLocation SCREEN_SPACE_WORLD_FOG_LOC = SimpleCloudsMod.id("shaders/post/screen_space_world_fog.json"); + private static final ResourceLocation CLOUD_SHADOWS_LOC = SimpleCloudsMod.id("shaders/post/cloud_shadows.json"); + public static final ResourceLocation FINAL_COMPOSITE_LOC = SimpleCloudsMod.id("shaders/post/final_composite.json"); + public static final ResourceLocation FINAL_COMPOSITE_NO_TRANSPARENCY_LOC = SimpleCloudsMod.id("shaders/post/final_composite_no_transparency.json"); + private static final ResourceLocation DITHER_TEXTURE = SimpleCloudsMod.id("textures/shader/bayer_matrix.png"); + private static final ArtifactVersion REQUIRED_OPENGL_VERSION = new DefaultArtifactVersion("4.3"); + public static final int SHADOW_MAP_SIZE = 1024; + public static final int SHADOW_MAP_SPAN = 10000; + public static final int MAX_LIGHTNING_BOLTS = 16; + public static final int BYTES_PER_LIGHTNING_BOLT = 16; + public static final float CHUNK_FADE_IN_ALPHA_PER_TICK = 0.2F; + public static final float DITHER_SCALE = 0.05F; + private static @Nullable SimpleCloudsRenderer instance; + private final CloudsRendererSettings settings; + private final Minecraft mc; + private final WorldEffects worldEffectsManager; + private final AtmosphericCloudsRenderHandler atmoshpericClouds; + private @Nullable ClientCloudManager cloudManager; + private ArtifactVersion openGlVersion; + private CloudMeshGenerator meshGenerator; + private @Nullable CloudsRenderPipeline renderPipelineThisPass; + private @Nullable RenderTarget cloudTarget; + private @Nullable WeightedBlendingTarget cloudTransparencyTarget; + private @Nullable RenderTarget stormFogTarget; + private int stormFogResolutionDivisor = 4; + private @Nullable RenderTarget blurTarget; + private final List postChains = Lists.newArrayList(); + private @Nullable PostChain finalComposite; + private @Nullable PostChain stormPostProcessing; + private @Nullable PostChain blurPostProcessing; + private @Nullable PostChain screenSpaceWorldFog; + private @Nullable PostChain cloudShadows; + private @Nullable ShaderStorageBufferObject lightningBoltPositions; + private @Nullable ShadowMapBuffer stormFogShadowMap; + private Optional shadowMap = Optional.empty(); + private @Nullable Frustum cullFrustum; + private float fogStart; + private float fogEnd; + private @Nullable PoseStack stormFogShadowMapStack; + private @Nullable PoseStack shadowMapStack; + private boolean failedToCopyDepthBuffer; + private boolean needsReload; + private @Nullable RendererInitializeResult initialInitializationResult; + + private SimpleCloudsRenderer(CloudsRendererSettings settings, Minecraft mc) + { + this.settings = settings; + this.mc = mc; + this.worldEffectsManager = new WorldEffects(mc, this); + this.atmoshpericClouds = new AtmosphericCloudsRenderHandler(mc); + } + + public String getClientCloudManagerString() + { + return this.cloudManager != null ? this.cloudManager.toString() : "null"; + } + + public CloudMeshGenerator getMeshGenerator() + { + return this.meshGenerator; + } + + public CloudsRenderPipeline getRenderPipeline() + { + return Objects.requireNonNull(this.renderPipelineThisPass, "Pipeline not determined"); + } + + public WorldEffects getWorldEffectsManager() + { + return this.worldEffectsManager; + } + + public AtmosphericCloudsRenderHandler getAtmosphericCloudRenderer() + { + return this.atmoshpericClouds; + } + + public CloudsRendererSettings getSettings() + { + return this.settings; + } + + public @Nullable RendererInitializeResult getInitialInitializationResult() + { + return this.initialInitializationResult; + } + + public ShadowMapBuffer getStormFogShadowMap() + { + return this.stormFogShadowMap; + } + + public Optional getShadowMap() + { + return this.shadowMap; + } + + public @Nullable PoseStack getStormFogShadowMapStack() + { + return this.stormFogShadowMapStack; + } + + public @Nullable PoseStack getShadowMapStack() + { + return this.shadowMapStack; + } + + public RenderTarget getBlurTarget() + { + return this.blurTarget; + } + + public RenderTarget getStormFogTarget() + { + return this.stormFogTarget; + } + + public RenderTarget getCloudTarget() + { + return this.cloudTarget; + } + + public WeightedBlendingTarget getCloudTransparencyTarget() + { + return this.cloudTransparencyTarget; + } + + public float getFogStart() + { + return this.fogStart; + } + + public float getFogEnd() + { + return this.fogEnd; + } + + public float getFadeFactorForDistance(float distance) + { + return 1.0F - Math.min(Math.max(distance - this.fogStart, 0.0F) / (this.fogEnd - this.fogStart), 1.0F); + } + + public @Nullable Frustum getCullFrustum() + { + return this.cullFrustum; + } + + public void onCloudManagerChange(ClientCloudManager manager) + { + this.cloudManager = manager; + if (this.meshGenerator instanceof MultiRegionCloudMeshGenerator generator) + generator.setCloudGetter(manager); + } + + private void prepareMeshGenerator(float partialTicks) + { + if (this.meshGenerator instanceof SingleRegionCloudMeshGenerator generator) + generator.setFadeDistances((float)SimpleCloudsConfig.CLIENT.singleModeFadeStartPercentage.get() / 100.0F, (float)SimpleCloudsConfig.CLIENT.singleModeFadeEndPercentage.get() / 100.0F); + this.meshGenerator.setTransparencyRenderDistance((float)SimpleCloudsConfig.CLIENT.transparencyRenderDistancePercentage.get() / 100.0F); + this.meshGenerator.setTestFacesFacingAway(SimpleCloudsConfig.CLIENT.testSidesThatAreOccluded.get()); + if (this.mc.level != null) + { + this.meshGenerator.setScroll(this.cloudManager.getScrollX(partialTicks), this.cloudManager.getScrollY(partialTicks), this.cloudManager.getScrollZ(partialTicks)); + } + } + + public boolean needsReinitialization() + { + return this.settings.needsReinitialization(this.meshGenerator); + } + + public void requestReload() + { + LOGGER.debug("Requesting reload..."); + this.needsReload = true; + } + + @Override + public void onResourceManagerReload(ResourceManager manager) + { + RenderSystem.assertOnRenderThreadOrInit(); + + this.initialInitializationResult = null; + + // --- Check OpenGL version --- + + ArtifactVersion openGlVersion = this.openGlVersion; + if (openGlVersion == null) + openGlVersion = new DefaultArtifactVersion(ImmediateWindowHandler.getGLVersion()); + if (openGlVersion.compareTo(REQUIRED_OPENGL_VERSION) < 0) + { + LOGGER.error("Simple Clouds renderer could not initialize. OpenGL version is {}, minimum required is {}", openGlVersion, REQUIRED_OPENGL_VERSION); + this.initialInitializationResult = RendererInitializeResult.builder().errorOpenGL().build(); + this.openGlVersion = openGlVersion; + return; + } + + if (!SimpleCloudsShaders.areShadersInitialized()) + { + LOGGER.error("Simple Clouds renderer could not initialize. Core shaders are not initialized."); + this.initialInitializationResult = RendererInitializeResult.builder().coreShadersNotInitialized(SimpleCloudsShaders.getError()).build(); + saveAndPrintCrashReports(this.mc, this.initialInitializationResult); + return; + } + + RendererInitializeResult compatError = SimpleCloudsCompatHelper.findCompatErrors(); + if (compatError.getState() == RendererInitializeResult.State.ERROR) + { + LOGGER.error("Simple Clouds renderer could not initialize due to compat error(s): {}", compatError.getErrors().stream().map(e -> e.text().getString()).toList()); + this.initialInitializationResult = compatError; + saveAndPrintCrashReports(this.mc, this.initialInitializationResult); + return; + } + + StartupMessageManager.addModMessage("Initializing Simple Clouds renderer"); + + LOGGER.debug("OpenGL {}", openGlVersion); + + Instant started = Instant.now(); + + LOGGER.debug("Beginning Simple Clouds renderer initialization"); + + this.failedToCopyDepthBuffer = false; + + // --- Render Targets --- + + boolean highPrecisionDepth = SimpleCloudsMod.dhLoaded(); + + RenderTarget main = SimpleCloudsCompatHelper.getMainRenderTarget(); + if (main == null) + { + this.initialInitializationResult = RendererInitializeResult.builder().errorUnknown(new NullPointerException("Main framebuffer is null"), "Simple Clouds Renderer").build(); + saveAndPrintCrashReports(this.mc, this.initialInitializationResult); + return; + } + + if (this.cloudTarget != null) + this.cloudTarget.destroyBuffers(); + this.cloudTarget = new CloudRenderTarget(main.width, main.height, Minecraft.ON_OSX, highPrecisionDepth); + this.cloudTarget.setClearColor(0.0F, 0.0F, 0.0F, 0.0F); + + if (this.cloudTransparencyTarget != null) + this.cloudTransparencyTarget.destroyBuffers(); + this.cloudTransparencyTarget = new WeightedBlendingTarget(main.width, main.height, Minecraft.ON_OSX, highPrecisionDepth); + + this.stormFogResolutionDivisor = SimpleCloudsCompatHelper.getStormFogResolutionDivisor(); + if (this.stormFogTarget != null) + this.stormFogTarget.destroyBuffers(); + this.stormFogTarget = new TextureTarget(main.width / this.stormFogResolutionDivisor, main.height / this.stormFogResolutionDivisor, false, Minecraft.ON_OSX); + this.stormFogTarget.setClearColor(0.0F, 0.0F, 0.0F, 0.0F); + this.stormFogTarget.setFilterMode(GL11.GL_LINEAR); + + if (this.blurTarget != null) + this.blurTarget.destroyBuffers(); + this.blurTarget = new TextureTarget(main.width, main.height, false, Minecraft.ON_OSX); + this.blurTarget.setClearColor(0.0F, 0.0F, 0.0F, 0.0F); + this.blurTarget.setFilterMode(GL11.GL_LINEAR); + + // --- Mesh Generator --- + + this.setupMeshGenerator(); // Create/setup the generator + this.prepareMeshGenerator(0.0F); // Prepare it + + RendererInitializeResult result = this.meshGenerator.init(manager); // Initialize + if (this.initialInitializationResult == null) + this.initialInitializationResult = result; + + // --- Shadow Map --- + + if (this.stormFogShadowMap != null) + { + this.stormFogShadowMap.close(); + this.stormFogShadowMap = null; + } + + this.shadowMap.ifPresent(buffer -> { + buffer.close(); + }); + + int span = this.meshGenerator.getLodConfig().getEffectiveChunkSpan() * SimpleCloudsConstants.CHUNK_SIZE * SimpleCloudsConstants.CLOUD_SCALE; + this.stormFogShadowMap = new ShadowMapBuffer(span, span, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 0.0F, 10000.0F, true, false); + + if (SimpleCloudsConfig.CLIENT.distantShadows.get() && SimpleCloudsMod.dhLoaded()) + { + int distantShadowSpan = SimpleCloudsConfig.CLIENT.shadowDistance.get() * 2; + distantShadowSpan = Math.min(distantShadowSpan, span); + this.shadowMap = Optional.of(new ShadowMapBuffer(distantShadowSpan, distantShadowSpan, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 0.0F, 10000.0F, false, true)); + } + else + { + this.shadowMap = Optional.empty(); + } + + // --- Post Processing Shaders --- + + this.destroyPostChains(); + + if (this.lightningBoltPositions != null) + { + BindingManager.freeSSBO(this.lightningBoltPositions); + this.lightningBoltPositions = null; + } + + this.lightningBoltPositions = BindingManager.createSSBO(GL15.GL_DYNAMIC_DRAW); + this.lightningBoltPositions.allocateBuffer(MAX_LIGHTNING_BOLTS * BYTES_PER_LIGHTNING_BOLT); + + this.stormPostProcessing = this.createPostChain(manager, STORM_POST_PROCESSING_LOC, this.stormFogTarget, pass -> + { + EffectInstance effect = pass.getEffect(); + effect.setSampler("ShadowMap", () -> this.stormFogShadowMap.getDepthTexId()); + effect.setSampler("ShadowMapColor", () -> this.stormFogShadowMap.getColorTexId()); + effect.setSampler("DepthSampler", () -> this.cloudTarget.getDepthTextureId()); + this.lightningBoltPositions.optionalBindToProgram("LightningBolts", effect.getId()); + }); + + this.blurPostProcessing = this.createPostChain(manager, BLUR_POST_PROCESSING_LOC, this.blurTarget); + this.blurPostProcessing.getTempTarget("swap").setFilterMode(GL11.GL_LINEAR); + + this.screenSpaceWorldFog = this.createPostChain(manager, SCREEN_SPACE_WORLD_FOG_LOC, main, pass -> + { + EffectInstance effect = pass.getEffect(); + effect.setSampler("StormFogSampler", () -> this.blurTarget.getColorTextureId()); + effect.setSampler("CloudDepthSampler", () -> this.cloudTarget.getDepthTextureId()); + }); + + this.finalComposite = this.createPostChain(manager, this.settings.useTransparency() ? FINAL_COMPOSITE_LOC : FINAL_COMPOSITE_NO_TRANSPARENCY_LOC, main, pass -> + { + EffectInstance effect = pass.getEffect(); + if (this.settings.useTransparency()) + { + effect.setSampler("AccumTexture", () -> this.cloudTransparencyTarget.getColorTextureId()); + effect.setSampler("RevealageTexture", () -> this.cloudTransparencyTarget.getRevealageTextureId()); + } + effect.setSampler("CloudsTexture", () -> this.cloudTarget.getColorTextureId()); + }); + + if (this.shadowMap.isPresent()) + { + ShadowMapBuffer map = this.shadowMap.get(); + this.cloudShadows = this.createPostChain(manager, CLOUD_SHADOWS_LOC, main, pass -> + { + EffectInstance effect = pass.getEffect(); + effect.setSampler("ShadowMap", () -> this.shadowMap.get().getDepthTexId()); + effect.safeGetUniform("ShadowSpan").set((float)Math.min(map.getViewWidth(), map.getViewHeight())); + }); + } + + this.atmoshpericClouds.init(manager); + + // --- Final debug --- + + long duration = Duration.between(started, Instant.now()).toMillis(); + LOGGER.info("Finished initialization, took {} ms", duration); + + LOGGER.debug("Total LODs: {}", this.meshGenerator.getLodConfig().getLods().length + 1); + LOGGER.debug("Highest detail (primary) chunk span: {}", this.meshGenerator.getLodConfig().getPrimaryChunkSpan()); + LOGGER.debug("Effective chunk span with LODs (total viewable area): {}", this.meshGenerator.getLodConfig().getEffectiveChunkSpan()); + LOGGER.debug("Total span in blocks: {}", this.meshGenerator.getLodConfig().getEffectiveChunkSpan() * SimpleCloudsConstants.CHUNK_SIZE * SimpleCloudsConstants.CLOUD_SCALE); + + //Print crash reports if needed + saveAndPrintCrashReports(this.mc, result); + } + + private static void saveAndPrintCrashReports(Minecraft mc, RendererInitializeResult result) + { + switch (result.getState()) + { + case ERROR: + { + List reports = result.createCrashReports(); + LOGGER.error("---------CRASH REPORT BEGIN---------"); + for (CrashReport report : reports) + { + mc.fillReport(report); + LOGGER.error("{}", report.getFriendlyReport()); + } + LOGGER.error("---------CRASH REPORT END---------"); + result.saveCrashReports(mc.gameDirectory); + break; + } + default: + } + } + + private void setupMeshGenerator() + { + if (this.settings.checkAndOrBeginInitialization(this.meshGenerator)) + { + if (this.meshGenerator != null) + { + this.meshGenerator.close(); //Close the current generator + this.meshGenerator = null; + } + + CloudMode mode = this.settings.getCurrentCloudMode(); + boolean isAmbientMode = mode == CloudMode.AMBIENT; + boolean useMultiRegion = isAmbientMode || mode == CloudMode.DEFAULT; + boolean shadedClouds = this.settings.shadedClouds(); + boolean useFixedMeshDataSectionSize = this.settings.useFixedMeshDataSectionSize(); + boolean useTransparency = this.settings.useTransparency(); + LevelOfDetailConfig lod = this.settings.getCurrentLod().getConfig(); + + var builder = CloudMeshGenerator.builder() + .fadeNearOrigin(isAmbientMode) + .shadedClouds(shadedClouds) + .fixedMeshDataSectionSize(useFixedMeshDataSectionSize) + .meshGenInterval(SimpleCloudsRenderer::calculateMeshGenInterval) + .lodConfig(lod) + .useTransparency(useTransparency); + + if (useMultiRegion) //Use the multi-region generator for DEFAULT or AMBIENT cloud mode + { + if (isAmbientMode) + { + builder.fadeStart(SimpleCloudsConstants.AMBIENT_MODE_FADE_START) + .fadeEnd(SimpleCloudsConstants.AMBIENT_MODE_FADE_END); + } + this.meshGenerator = builder.createMultiRegion(); + } + else if (mode == CloudMode.SINGLE) + { + float fadeStart = (float)SimpleCloudsConfig.CLIENT.singleModeFadeStartPercentage.get() / 100.0F; + float fadeEnd = (float)SimpleCloudsConfig.CLIENT.singleModeFadeEndPercentage.get() / 100.0F; + this.meshGenerator = builder.fadeStart(fadeStart).fadeEnd(fadeEnd).createSingleRegion(SimpleCloudsConstants.EMPTY); + } + else + { + throw new IllegalArgumentException("Not sure how to handle cloud mode " + mode); + } + } + + if (this.meshGenerator instanceof MultiRegionCloudMeshGenerator multiRegionGenerator) + { + multiRegionGenerator.setCloudGetter(this.cloudManager != null ? this.cloudManager : CloudGetter.EMPTY); + } + else if (this.meshGenerator instanceof SingleRegionCloudMeshGenerator singleRegionGenerator) + { + //Find the desired single mode cloud type, either from the client-side only context or + //from the synced cloud types from the server + CloudType type = this.settings.getSingleModeCloudType(); + if (!ClientCloudManager.isAvailableServerSide() && !ClientSideCloudTypeManager.isValidClientSideSingleModeCloudType(type)) + type = SimpleCloudsConstants.EMPTY; + if (type == null) + type = SimpleCloudsConstants.EMPTY; + singleRegionGenerator.setCloudType(type); + } + else + { + throw new IllegalArgumentException("Not sure how to handle generator: " + this.meshGenerator); + } + } + + private void destroyPostChains() + { + this.postChains.forEach(PostChain::close); + this.postChains.clear(); + } + + private @Nullable PostChain createPostChain(ResourceManager manager, ResourceLocation loc, RenderTarget target) + { + return this.createPostChain(manager, loc, target, effect -> {}); + } + + private @Nullable PostChain createPostChain(ResourceManager manager, ResourceLocation loc, RenderTarget target, Consumer passConsumer) + { + try + { + PostChain chain = new PostChain(this.mc.getTextureManager(), manager, target, loc); + chain.resize(target.width, target.height); + for (PostPass pass : ((MixinPostChain)chain).simpleclouds$getPostPasses()) + passConsumer.accept(pass); + this.postChains.add(chain); + return chain; + } + catch (JsonSyntaxException e) + { + LOGGER.warn("Failed to parse post shader: {}", loc, e); + } + catch (IOException e) + { + LOGGER.warn("Failed to load post shader: {}", loc, e); + } + + return null; + } + + public void onMainWindowResize(int width, int height) + { + this.atmoshpericClouds.onResize(width, height); + + RenderTarget main = SimpleCloudsCompatHelper.getMainRenderTarget(); + if (main == null) + return; + + width = main.width; + height = main.height; + + if (this.cloudTarget != null) + this.cloudTarget.resize(width, height, Minecraft.ON_OSX); + + if (this.cloudTransparencyTarget != null) + this.cloudTransparencyTarget.resize(width, height, Minecraft.ON_OSX); + + this.stormFogResolutionDivisor = SimpleCloudsCompatHelper.getStormFogResolutionDivisor(); + + if (this.stormFogTarget != null) + { + this.stormFogTarget.resize(width / this.stormFogResolutionDivisor, height / this.stormFogResolutionDivisor, Minecraft.ON_OSX); + this.stormFogTarget.setFilterMode(GL11.GL_LINEAR); + } + + if (this.blurTarget != null) + { + this.blurTarget.resize(width, height, Minecraft.ON_OSX); + this.blurTarget.setFilterMode(GL11.GL_LINEAR); + } + + for (PostChain chain : this.postChains) + { + RenderTarget chainTarget = ((MixinPostChain)chain).simpleclouds$getScreenTarget(); + chain.resize(chainTarget.width, chainTarget.height); + } + + if (this.blurPostProcessing != null) + this.blurPostProcessing.getTempTarget("swap").setFilterMode(GL11.GL_LINEAR); + } + + public void shutdown() + { + if (this.cloudTarget != null) + this.cloudTarget.destroyBuffers(); + if (this.cloudTransparencyTarget != null) + this.cloudTransparencyTarget.destroyBuffers(); + if (this.stormFogTarget != null) + this.stormFogTarget.destroyBuffers();; + if (this.blurTarget != null) + this.blurTarget.destroyBuffers(); + + this.cloudTarget = null; + this.cloudTransparencyTarget = null; + this.stormFogTarget = null; + this.blurTarget = null; + + this.destroyPostChains(); + + if (this.meshGenerator != null) + this.meshGenerator.close(); + + if (this.stormFogShadowMap != null) + { + this.stormFogShadowMap.close(); + this.stormFogShadowMap = null; + } + + if (this.shadowMap.isPresent()) + { + this.shadowMap.get().close(); + this.shadowMap = Optional.empty(); + } + + if (this.lightningBoltPositions != null) + { + BindingManager.freeSSBO(this.lightningBoltPositions); + this.lightningBoltPositions = null; + } + + this.atmoshpericClouds.close(); + } + + public void baseTick() + { + if (this.needsReload) + { + this.onResourceManagerReload(this.mc.getResourceManager()); + this.needsReload = false; + } + } + + public void tick() + { + this.worldEffectsManager.tick(); + + if (this.cloudManager != null) + this.atmoshpericClouds.setWindDirection(this.cloudManager.calculateWindDirection()); + this.atmoshpericClouds.tick(); + + if (this.meshGenerator != null) + this.meshGenerator.worldTick(); + } + + public static void renderCloudsOpaque(CloudMeshGenerator generator, PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, @Nullable Frustum frustum) + { + renderCloudsOpaque(generator, stack, projMat, fogStart, fogEnd, partialTick, r, g, b, frustum, true); + } + + public static void renderCloudsOpaque(CloudMeshGenerator generator, PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, @Nullable Frustum frustum, boolean ditherFade) + { + RenderSystem.assertOnRenderThread(); + + BufferUploader.reset(); + + if (!generator.canRender()) + return; + + RenderSystem.disableBlend(); + RenderSystem.enableDepthTest(); + RenderSystem.disableCull(); + + SingleSSBOShaderInstance shader = SimpleCloudsShaders.getCloudsShader(); + RenderSystem.setShader(() -> shader); + + TextureManager manager = Minecraft.getInstance().getTextureManager(); + AbstractTexture ditherTexture = manager.getTexture(DITHER_TEXTURE); + shader.setSampler("BayerMatrixSampler", ditherTexture); + shader.safeGetUniform("DitherScale").set(DITHER_SCALE); + + SimpleCloudsRenderer.prepareShader(shader, stack.last().pose(), projMat, fogStart, fogEnd); + shader.apply(); + + generator.forRenderableMeshChunks(frustum, MeshChunk::getOpaqueBuffers, (chunk, opaqueBuffers) -> + { + if (ditherFade) + { + RenderSystem.setShaderColor(r, g, b, chunk.getAlpha(partialTick)); + shader.COLOR_MODULATOR.set(RenderSystem.getShaderColor()); + shader.COLOR_MODULATOR.upload(); + } + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), opaqueBuffers.getBufferId()); + generator.getSideMesh().drawInstanced(opaqueBuffers.getElementCount()); + }, ditherFade); + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), 0); + + shader.clear(); + + GL30.glBindVertexArray(0); + + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.enableCull(); + } + + public static void renderCloudsTransparency(CloudMeshGenerator generator, PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, @Nullable Frustum frustum) + { + renderCloudsTransparency(generator, stack, projMat, fogStart, fogEnd, partialTick, r, g, b, frustum, true); + } + + public static void renderCloudsTransparency(CloudMeshGenerator generator, PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, @Nullable Frustum frustum, boolean ditherFade) + { + RenderSystem.assertOnRenderThread(); + + BufferUploader.reset(); + + if (!generator.canRender() || !generator.transparencyEnabled()) + return; + + RenderSystem.enableDepthTest(); + RenderSystem.depthMask(false); + + SingleSSBOShaderInstance shader = SimpleCloudsShaders.getCloudsTransparencyShader(); + RenderSystem.setShader(() -> shader); + + TextureManager manager = Minecraft.getInstance().getTextureManager(); + AbstractTexture ditherTexture = manager.getTexture(DITHER_TEXTURE); + shader.setSampler("BayerMatrixSampler", ditherTexture); + shader.safeGetUniform("DitherScale").set(DITHER_SCALE); + + SimpleCloudsRenderer.prepareShader(shader, stack.last().pose(), projMat, fogStart, fogEnd); + + shader.apply(); + + GL30.glEnablei(GL11.GL_BLEND, 0); + GL30.glEnablei(GL11.GL_BLEND, 1); + GL40.glBlendEquationi(0, GL14.GL_FUNC_ADD); + GL40.glBlendEquationi(1, GL14.GL_FUNC_ADD); + GL40.glBlendFunci(0, GL11.GL_ONE, GL11.GL_ONE); + GL40.glBlendFunci(1, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_COLOR); + + generator.forRenderableMeshChunks(frustum, c -> c.getTransparentBuffers().get(), (chunk, transparentBuffers) -> + { + if (ditherFade) + { + RenderSystem.setShaderColor(r, g, b, chunk.getAlpha(partialTick)); + shader.COLOR_MODULATOR.set(RenderSystem.getShaderColor()); + shader.COLOR_MODULATOR.upload(); + } + + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), transparentBuffers.getBufferId()); + generator.getCubeMesh().drawInstanced(transparentBuffers.getElementCount()); + }, ditherFade); + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), 0); + + shader.clear(); + + GL30.glDisablei(GL11.GL_BLEND, 0); + GL30.glDisablei(GL11.GL_BLEND, 1); + GL40.glBlendFuncSeparatei(0, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + GL40.glBlendFuncSeparatei(1, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + + GL30.glBindVertexArray(0); + + RenderSystem.depthMask(true); + + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + } + + private PoseStack createShadowMapStack(ShadowMapBuffer shadowMap, double camX, double camY, double camZ, Consumer transformApplier) + { + PoseStack stack = new PoseStack(); + stack.setIdentity(); + double depthCenter = ((double)shadowMap.getNear() + (double)shadowMap.getFar()) * -0.5D; + stack.translate((double)shadowMap.getViewWidth() / 2.0D, (double)shadowMap.getViewHeight() / 2.0D, depthCenter); + transformApplier.accept(stack); + float chunkSizeUpscaled = (float)SimpleCloudsConstants.CHUNK_SIZE * (float)SimpleCloudsConstants.CLOUD_SCALE; + float camOffsetX = ((float)Mth.floor(camX / chunkSizeUpscaled) * chunkSizeUpscaled); + float camOffsetZ = ((float)Mth.floor(camZ / chunkSizeUpscaled) * chunkSizeUpscaled); + stack.translate(-camOffsetX, -(double)this.cloudManager.getCloudHeight(), -camOffsetZ); + return stack; + } + + private void renderShadowMap(ShadowMapBuffer shadowMap, PoseStack stack, SingleSSBOShaderInstance shader, @Nullable Frustum frustum) + { + RenderSystem.assertOnRenderThread(); + + stack.pushPose(); + this.translateClouds(stack, 0.0D, 0.0D, 0.0D); + + RenderSystem.setShader(() -> shader); + prepareShader(shader, stack.last().pose(), shadowMap.getProjMatrix(), this.fogStart, this.fogEnd); + shader.apply(); + + shadowMap.bind(); + shadowMap.clear(Minecraft.ON_OSX); + + this.meshGenerator.forRenderableMeshChunks(frustum, MeshChunk::getOpaqueBuffers, (chunk, opaqueBuffers) -> + { + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), opaqueBuffers.getBufferId()); + this.meshGenerator.getSideMesh().drawInstanced(opaqueBuffers.getElementCount()); + }); + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), 0); + GL30.glBindVertexArray(0); + + shadowMap.unbind(); + + shader.clear(); + + stack.popPose(); + } + + private float determineShadowMapAngle(float partialTick) + { + float timeOfDay = this.mc.level.getTimeOfDay(partialTick); + return 45.0F * Mth.sin(2.0F * (float)Math.PI * timeOfDay); + } + + private void renderShadowMaps(double camX, double camY, double camZ, float partialTick) + { + RenderSystem.assertOnRenderThread(); + + BufferUploader.reset(); + + RenderSystem.disableBlend(); + RenderSystem.enableDepthTest(); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.disableCull(); + + this.stormFogShadowMapStack = this.createShadowMapStack(this.stormFogShadowMap, camX, camY, camZ, s -> + { + Vector2f direction = this.cloudManager.calculateWindDirection(); + float yaw = (float)Mth.atan2((double)direction.x, (double)direction.y); + s.mulPose(Axis.XP.rotationDegrees(SimpleCloudsConfig.CLIENT.stormFogAngle.get().floatValue())); + s.mulPose(Axis.YP.rotation(yaw)); + }); + this.renderShadowMap(this.stormFogShadowMap, this.stormFogShadowMapStack, SimpleCloudsShaders.getStormFogShadowMapShader(), this.cullFrustum); + + this.shadowMapStack = this.shadowMap.map(buffer -> + { + PoseStack stack = this.createShadowMapStack(buffer, camX, camY, camZ, s -> { + s.mulPose(Axis.XP.rotationDegrees(90.0F)); + s.mulPose(Axis.ZN.rotationDegrees(this.determineShadowMapAngle(partialTick))); + }); + this.renderShadowMap(buffer, stack, SimpleCloudsShaders.getCloudsShadowMapShader(), null); + return stack; + }).orElse(null); + + RenderSystem.enableCull(); + + this.mc.getMainRenderTarget().bindWrite(true); + } + + public static void renderCloudsDebug(CloudMeshGenerator generator, PoseStack stack, Matrix4f projMat, float partialTick, float fogStart, float fogEnd, @Nullable Frustum frustum, boolean chunkBoundaries, boolean noiseBoundaries) + { + RenderSystem.assertOnRenderThread(); + + if (!generator.canRender()) + return; + + BufferUploader.reset(); + + RenderSystem.disableBlend(); + RenderSystem.enableDepthTest(); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.disableCull(); + + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder builder = tesselator.getBuilder(); + builder.begin(VertexFormat.Mode.LINES, DefaultVertexFormat.POSITION_COLOR_NORMAL); + + generator.forRenderableMeshChunks(frustum, MeshChunk::getOpaqueBuffers, (chunk, bufferSet) -> + { + PreparedChunk preparedChunk = chunk.getChunkInfo(); + if (chunkBoundaries) + { + int color = Color.HSBtoRGB((float)preparedChunk.lodLevel() / ((float)generator.getLodConfig().getLods().length + 1), 1.0F, 1.0F); + float r = (float)FastColor.ARGB32.red(color) / 255.0F; + float g = (float)FastColor.ARGB32.green(color) / 255.0F; + float b = (float)FastColor.ARGB32.blue(color) / 255.0F; + LevelRenderer.renderLineBox(builder, chunk.getBoundsMinX() + 1.0F, chunk.getBoundsMinY() + 1.0F, chunk.getBoundsMinZ() + 1.0F, chunk.getBoundsMaxX() - 1.0F, chunk.getBoundsMaxY() - 1.0F, chunk.getBoundsMaxZ() - 1.0F, r, g, b, 1.0F); + } + if (noiseBoundaries) + LevelRenderer.renderLineBox(builder, chunk.getBoundsMinX() + 1.0F, chunk.getMinHeight() + 1.0F, chunk.getBoundsMinZ() + 1.0F, chunk.getBoundsMaxX() - 1.0F, chunk.getMaxHeight() - 1.0F, chunk.getBoundsMaxZ() - 1.0F, 1.0F, 1.0F, 0.0F, 1.0F); + }); + + RenderSystem.setShader(GameRenderer::getRendertypeLinesShader); + ShaderInstance shader = RenderSystem.getShader(); + SimpleCloudsRenderer.prepareShader(shader, stack.last().pose(), projMat, fogStart, fogEnd); + shader.LINE_WIDTH.set(2.5F); + shader.FOG_START.set(Float.MAX_VALUE); + shader.apply(); + BufferUploader.draw(builder.end()); + shader.clear(); + + RenderSystem.enableCull(); + + RenderSystem.defaultBlendFunc(); + RenderSystem.enableBlend(); + + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + generator.forRenderableMeshChunks(frustum, MeshChunk::getOpaqueBuffers, (chunk, bufferSet) -> + { + PreparedChunk preparedChunk = chunk.getChunkInfo(); + if (chunkBoundaries) + { + int color = Color.HSBtoRGB((float)preparedChunk.lodLevel() / ((float)generator.getLodConfig().getLods().length + 1), 1.0F, 1.0F); + float r = (float)FastColor.ARGB32.red(color) / 255.0F; + float g = (float)FastColor.ARGB32.green(color) / 255.0F; + float b = (float)FastColor.ARGB32.blue(color) / 255.0F; + renderChunkBox(builder, chunk.getBoundsMinX() + 1.0F, chunk.getBoundsMinY() + 1.0F, chunk.getBoundsMinZ() + 1.0F, chunk.getBoundsMaxX() - 1.0F, chunk.getBoundsMaxY() - 1.0F, chunk.getBoundsMaxZ() - 1.0F, r, g, b, 0.4F); + } + if (noiseBoundaries) + renderChunkBox(builder, chunk.getBoundsMinX() + 1.0F, chunk.getMinHeight() + 1.0F, chunk.getBoundsMinZ() + 1.0F, chunk.getBoundsMaxX() - 1.0F, chunk.getMaxHeight() - 1.0F, chunk.getBoundsMaxZ() - 1.0F, 1.0F, 1.0F, 0.0F, 0.4F); + }); + + RenderSystem.setShader(GameRenderer::getPositionColorShader); + shader = RenderSystem.getShader(); + SimpleCloudsRenderer.prepareShader(shader, stack.last().pose(), projMat, fogStart, fogEnd); + shader.apply(); + BufferUploader.draw(builder.end()); + shader.clear(); + + RenderSystem.disableBlend(); + } + + public float[] getCloudColor(float partialTick) + { + Vec3 cloudCol = this.mc.level.getCloudColor(partialTick); + float factor = this.worldEffectsManager.getDarkenFactor(partialTick, 0.8F); + float skyFlashFactor = Math.max(0.0F, ((float)this.mc.level.getSkyFlashTime() - partialTick) * SimpleCloudsConstants.LIGHTNING_FLASH_STRENGTH); + factor += skyFlashFactor; + float r = Mth.clamp((float)cloudCol.x * factor, 0.0F, 1.0F); + float g = Mth.clamp((float)cloudCol.y * factor, 0.0F, 1.0F); + float b = Mth.clamp((float)cloudCol.z * factor, 0.0F, 1.0F); + return new float[] { r, g, b }; + } + + public void translateClouds(PoseStack stack, double camX, double camY, double camZ) + { + stack.translate(-camX, -camY + (double)this.cloudManager.getCloudHeight(), -camZ); + stack.scale((float)SimpleCloudsConstants.CLOUD_SCALE, (float)SimpleCloudsConstants.CLOUD_SCALE, (float)SimpleCloudsConstants.CLOUD_SCALE); + } + + public void renderWeather(LightTexture texture, float partialTick, double camX, double camY, double camZ) + { + if (SimpleCloudsCompatHelper.renderCustomRain()) + this.worldEffectsManager.renderRain(texture, partialTick, camX, camY, camZ); + if (!SimpleCloudsMod.dhLoaded()) + this.worldEffectsManager.renderLightning(partialTick, camX, camY, camZ); + } + + public void renderBeforeLevel(PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ) + { + if (!SimpleCloudsCompatHelper.renderThisPass()) + return; + + CloudsRenderPipeline pipeline = CompatHelper.areShadersRunning() ? CloudsRenderPipeline.SHADER_SUPPORT : CloudsRenderPipeline.DEFAULT; + DetermineCloudRenderPipelineEvent pipelineEvent = new DetermineCloudRenderPipelineEvent(pipeline); + MinecraftForge.EVENT_BUS.post(pipelineEvent); + this.renderPipelineThisPass = pipeline; + if (pipelineEvent.getOverridenPipeline() != null) + this.renderPipelineThisPass = pipelineEvent.getOverridenPipeline(); + + float factor = this.worldEffectsManager.getDarkenFactor(partialTick); + float renderDistance = (float)this.meshGenerator.getCloudAreaMaxRadius() * (float)SimpleCloudsConstants.CLOUD_SCALE * factor; + if (renderDistance < 2867.0F) + renderDistance = 2867.0F; + ModifyCloudRenderDistanceEvent renderDistEvent = new ModifyCloudRenderDistanceEvent(renderDistance); + MinecraftForge.EVENT_BUS.post(renderDistEvent); + renderDistance = renderDistEvent.getRenderDistance(); + this.fogStart = renderDistance / 4.0F; + this.fogEnd = renderDistance; + + Entity cameraEntity = this.mc.gameRenderer.getMainCamera().getEntity(); + if (cameraEntity instanceof LivingEntity living) + { + var map = living.getActiveEffectsMap(); + if (map.containsKey(MobEffects.BLINDNESS)) + { + MobEffectInstance instance = map.get(MobEffects.BLINDNESS); + float effectFactor = instance.isInfiniteDuration() ? 5.0F : Mth.lerp(Math.min(1.0F, (float)instance.getDuration() / 20.0F), renderDistance, 5.0F); + this.fogStart = 0.0F; + this.fogEnd = effectFactor * 0.8F; + } + else if (map.containsKey(MobEffects.DARKNESS)) + { + MobEffectInstance instance = map.get(MobEffects.DARKNESS); + if (instance.getFactorData().isPresent()) + { + float f = Mth.lerp(instance.getFactorData().get().getFactor(living, partialTick), renderDistance, 15.0F); + this.fogStart = 0.0F; + this.fogEnd = f; + } + } + } + + this.meshGenerator.setCullDistance(this.fogEnd / (float)SimpleCloudsConstants.CLOUD_SCALE); + + this.mc.getProfiler().push("simple_clouds_prepare"); + + this.cullFrustum = new Frustum(stack.last().pose(), projMat); + float scale = (float)SimpleCloudsConstants.CLOUD_SCALE; + double originX = camX / scale; + double originY = (camY - (double)this.cloudManager.getCloudHeight()) / scale; + double originZ = camZ / scale; + this.cullFrustum.prepare(originX, originY, originZ); + + ProfilerFiller p = this.mc.getProfiler(); + + if (SimpleCloudsConfig.CLIENT.generateMesh.get() && SimpleCloudsCompatHelper.isPrimaryPass()) + { + p.push("mesh_generation"); + this.prepareMeshGenerator(partialTick); + this.meshGenerator.genTick(originX, originY, originZ, SimpleCloudsConfig.CLIENT.frustumCulling.get() ? this.cullFrustum : null, partialTick); + p.pop(); + } + + if (SimpleCloudsConfig.CLIENT.renderClouds.get() && SimpleCloudsCompatHelper.isPrimaryPass()) + { + p.push("shadow_map"); + this.renderShadowMaps(camX, camY, camZ, partialTick); + this.getRenderPipeline().prepare(this.mc, this, stack, projMat, partialTick, camX, camY, camZ, this.cullFrustum); + p.pop(); + } + + this.mc.getProfiler().pop(); + } + + public void renderAfterSky(PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ) + { + if (!SimpleCloudsCompatHelper.renderThisPass()) + return; + + this.mc.getProfiler().push("simple_clouds_after_sky"); + this.getRenderPipeline().afterSky(this.mc, this, stack, projMat, partialTick, camX, camY, camZ, this.cullFrustum); + this.mc.getProfiler().pop(); + } + + public void renderBeforeWeather(PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ) + { + if (!SimpleCloudsCompatHelper.renderThisPass()) + return; + + this.mc.getProfiler().push("simple_clouds_before_weather"); + this.getRenderPipeline().beforeWeather(this.mc, this, stack, projMat, partialTick, camX, camY, camZ, this.cullFrustum); + this.mc.getProfiler().pop(); + } + + public void renderAfterLevel(PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ) + { + if (!SimpleCloudsCompatHelper.renderThisPass()) + return; + + this.mc.getProfiler().push("simple_clouds"); + this.getRenderPipeline().afterLevel(this.mc, this, stack, projMat, partialTick, camX, camY, camZ, this.cullFrustum); + this.mc.getProfiler().pop(); + + this.mc.getProfiler().push("world_effects"); + this.worldEffectsManager.renderPost(stack, partialTick, camX, camY, camZ, (float)SimpleCloudsConstants.CLOUD_SCALE); + this.mc.getProfiler().pop(); + } + + public void doBlurPostProcessing(float partialTick) + { + if (this.blurPostProcessing != null) + { + RenderSystem.disableDepthTest(); + RenderSystem.resetTextureMatrix(); + RenderSystem.disableBlend(); + RenderSystem.depthMask(false); + this.blurPostProcessing.process(partialTick); + RenderSystem.depthMask(true); + } + } + + public void doScreenSpaceWorldFog(PoseStack stack, Matrix4f projMat, float partialTick) + { + if (this.screenSpaceWorldFog != null) + { + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + RenderSystem.resetTextureMatrix(); + RenderSystem.depthMask(false); + + Matrix4f invertedProjMat = new Matrix4f(projMat).invert(); + Matrix4f invertedModelViewMat = new Matrix4f(stack.last().pose()).invert(); + for (PostPass pass : ((MixinPostChain)this.screenSpaceWorldFog).simpleclouds$getPostPasses()) + { + EffectInstance effect = pass.getEffect(); + effect.safeGetUniform("InverseWorldProjMat").set(invertedProjMat); + effect.safeGetUniform("InverseModelViewMat").set(invertedModelViewMat); + effect.safeGetUniform("FogStart").set(RenderSystem.getShaderFogStart()); + effect.safeGetUniform("FogEnd").set(RenderSystem.getShaderFogEnd()); + float[] fogCol = RenderSystem.getShaderFogColor(); + effect.safeGetUniform("FogColor").set(fogCol[0], fogCol[1], fogCol[2]); + effect.safeGetUniform("FogShape").set(RenderSystem.getShaderFogShape().getIndex()); + } + + this.screenSpaceWorldFog.process(partialTick); + + RenderSystem.depthMask(true); + } + } + + public void doFinalCompositePass(PoseStack stack, float partialTick, Matrix4f projMat) + { + if (this.finalComposite != null) + { + RenderSystem.disableDepthTest(); + RenderSystem.resetTextureMatrix(); + RenderSystem.depthMask(false); + + this.finalComposite.process(partialTick); + + RenderSystem.depthMask(true); + } + } + + public void doStormPostProcessing(PoseStack stack, float partialTick, Matrix4f projMat, double camX, double camY, double camZ, float r, float g, float b) + { + if (this.stormPostProcessing == null || this.stormFogShadowMapStack == null || this.stormFogShadowMapStack == null) + return; + + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + RenderSystem.resetTextureMatrix(); + RenderSystem.depthMask(false); + + this.stormFogTarget.clear(Minecraft.ON_OSX); + this.stormFogTarget.bindWrite(true); + + MutableInt size = new MutableInt(); + boolean flag = SimpleCloudsConfig.CLIENT.stormFogLightningFlashes.get(); + if (flag) + { + List lightningBolts = this.worldEffectsManager.getLightningBolts(); + size.setValue(Math.min(lightningBolts.size(), MAX_LIGHTNING_BOLTS)); + if (size.getValue() > 0) + { + this.lightningBoltPositions.writeData(buffer -> + { + for (int i = 0; i < size.getValue(); i++) + { + LightningBolt bolt = lightningBolts.get(i); + Vector3f pos = bolt.getPosition(); + buffer.putFloat(pos.x); + buffer.putFloat(pos.y); + buffer.putFloat(pos.z); + buffer.putFloat(bolt.getFade(partialTick)); + } + buffer.rewind(); + }, size.getValue() * BYTES_PER_LIGHTNING_BOLT, false); + } + } + + Matrix4f invertedProjMat = new Matrix4f(projMat).invert(); + Matrix4f invertedModelViewMat = new Matrix4f(stack.last().pose()).invert(); + for (PostPass pass : ((MixinPostChain)this.stormPostProcessing).simpleclouds$getPostPasses()) + { + EffectInstance effect = pass.getEffect(); + effect.safeGetUniform("InverseWorldProjMat").set(invertedProjMat); + effect.safeGetUniform("InverseModelViewMat").set(invertedModelViewMat); + effect.safeGetUniform("ShadowProjMat").set(this.stormFogShadowMap.getProjMatrix()); + effect.safeGetUniform("ShadowModelViewMat").set(this.stormFogShadowMapStack.last().pose()); + effect.safeGetUniform("CameraPos").set((float)camX, (float)camY, (float)camZ); + effect.safeGetUniform("FogStart").set(this.fogEnd / 2.0F); + effect.safeGetUniform("FogEnd").set(this.fogEnd); + effect.safeGetUniform("ColorModulator").set(r, g, b, 1.0F); + float factor = this.worldEffectsManager.getDarkenFactor(partialTick); + effect.safeGetUniform("CutoffDistance").set(1000.0F * factor); + effect.safeGetUniform("TotalLightningBolts").set(size.getValue()); + } + + this.stormPostProcessing.process(partialTick); + + RenderSystem.depthMask(true); + } + + public void doCloudShadowProcessing(PoseStack stack, float partialTick, Matrix4f projMat, double camX, double camY, double camZ, int depthBufferId) + { + if (this.cloudShadows == null || this.shadowMap.isEmpty() || this.shadowMapStack == null) + return; + + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + RenderSystem.resetTextureMatrix(); + RenderSystem.depthMask(false); + + Matrix4f invertedProjMat = new Matrix4f(projMat).invert(); + Matrix4f invertedModelViewMat = new Matrix4f(stack.last().pose()).invert(); + float minimumRadius = this.mc.gameRenderer.getRenderDistance(); + for (PostPass pass : ((MixinPostChain)this.cloudShadows).simpleclouds$getPostPasses()) + { + EffectInstance effect = pass.getEffect(); + effect.setSampler("DepthSampler", () -> depthBufferId); + effect.safeGetUniform("InverseWorldProjMat").set(invertedProjMat); + effect.safeGetUniform("InverseModelViewMat").set(invertedModelViewMat); + effect.safeGetUniform("ShadowProjMat").set(this.shadowMap.get().getProjMatrix()); + effect.safeGetUniform("ShadowModelViewMat").set(this.shadowMapStack.last().pose()); + effect.safeGetUniform("CameraPos").set((float)camX, (float)camY, (float)camZ); + effect.safeGetUniform("MinimumRadius").set(minimumRadius); + } + + this.cloudShadows.process(partialTick); + + RenderSystem.depthMask(true); + } + + public static void prepareShader(ShaderInstance shader, Matrix4f modelView, Matrix4f projMat, float fogStart, float fogEnd) + { + for (int i = 0; i < 12; ++i) + { + int j = RenderSystem.getShaderTexture(i); + shader.setSampler("Sampler" + i, j); + } + + if (shader.MODEL_VIEW_MATRIX != null) + shader.MODEL_VIEW_MATRIX.set(modelView); + + if (shader.PROJECTION_MATRIX != null) + shader.PROJECTION_MATRIX.set(projMat); + + if (shader.INVERSE_VIEW_ROTATION_MATRIX != null) + shader.INVERSE_VIEW_ROTATION_MATRIX.set(RenderSystem.getInverseViewRotationMatrix()); + + if (shader.COLOR_MODULATOR != null) + shader.COLOR_MODULATOR.set(RenderSystem.getShaderColor()); + + if (shader.GLINT_ALPHA != null) + shader.GLINT_ALPHA.set(RenderSystem.getShaderGlintAlpha()); + + if (shader.FOG_START != null) + shader.FOG_START.set(fogStart); + + if (shader.FOG_END != null) + shader.FOG_END.set(fogEnd); + + if (shader.FOG_COLOR != null) + shader.FOG_COLOR.set(RenderSystem.getShaderFogColor()); + + if (shader.FOG_SHAPE != null) + shader.FOG_SHAPE.set(RenderSystem.getShaderFogShape().getIndex()); + + if (shader.TEXTURE_MATRIX != null) + shader.TEXTURE_MATRIX.set(RenderSystem.getTextureMatrix()); + + if (shader.GAME_TIME != null) + shader.GAME_TIME.set(RenderSystem.getShaderGameTime()); + + if (shader.SCREEN_SIZE != null) + { + Window window = Minecraft.getInstance().getWindow(); + shader.SCREEN_SIZE.set((float) window.getWidth(), (float) window.getHeight()); + } + + shader.safeGetUniform("UseNormals").set(SimpleCloudsConfig.CLIENT.cubeNormals.get() ? 1 : 0); + + RenderSystem.setShaderLights(DIFFUSE_LIGHT_0, DIFFUSE_LIGHT_1); + RenderSystem.setupShaderLights(shader); + } + + public void copyDepthFromCloudsToMain() + { + this._copyDepthSafe(this.mc.getMainRenderTarget(), this.cloudTarget); + } + + public void copyDepthFromMainToClouds() + { + this._copyDepthSafe(this.cloudTarget, this.mc.getMainRenderTarget()); + } + + public void copyDepthFromCloudsToTransparency() + { + this._copyDepthSafe(this.cloudTransparencyTarget, this.cloudTarget); + } + + private void _copyDepthSafe(RenderTarget to, RenderTarget from) + { + RenderSystem.assertOnRenderThread(); + GlStateManager._getError(); //Clear old error + if (!this.failedToCopyDepthBuffer) + { + to.bindWrite(false); + to.copyDepthFrom(from); + if (GlStateManager._getError() != GL11.GL_INVALID_OPERATION) + return; + boolean enabledStencil = false; + if (to.isStencilEnabled() && !from.isStencilEnabled()) + { + from.enableStencil(); + enabledStencil = true; + } + else if (from.isStencilEnabled() && !to.isStencilEnabled()) + { + to.enableStencil(); + enabledStencil = true; + } + if (enabledStencil) + { + to.copyDepthFrom(from); + if (GlStateManager._getError() == GL11.GL_INVALID_OPERATION) + { + LOGGER.error("Unable to copy depth between the main and clouds frame buffers, even after enabling stencil. Please note that the clouds may not render properly."); + this.failedToCopyDepthBuffer = true; + } + else + { + LOGGER.info("NOTE: Please ignore the above OpenGL error. Simple Clouds had to toggle stencil in order to copy the depth buffer between the main and clouds frame buffers."); + } + } + else + { + LOGGER.error("Unable to copy depth between the main and clouds frame buffers. Please note that the clouds may not render properly."); + this.failedToCopyDepthBuffer = true; + } + } + } + + public void fillReport(CrashReport report) + { + CrashReportCategory category = report.addCategory("Simple Clouds Renderer"); + category.setDetail("Cloud Mode", this.settings.getCurrentCloudMode()); + category.setDetail("Cloud Target Available", this.cloudTarget != null); + category.setDetail("Storm Fog Target Active", this.stormFogTarget != null); + category.setDetail("Blur Target Active", this.blurTarget != null); + category.setDetail("Transparency Target Active", this.cloudTransparencyTarget != null); + category.setDetail("Post Chains", this.postChains.toString()); + category.setDetail("Lightning Bolt SSBO", this.lightningBoltPositions); + category.setDetail("Clouds Shadow Map", this.stormFogShadowMap); + category.setDetail("Storm Fog Shadow Map", this.stormFogShadowMap); + category.setDetail("Failed to copy depth buffer", this.failedToCopyDepthBuffer); + category.setDetail("Needs Reload", this.needsReload); + + CrashReportCategory meshGenCategory = report.addCategory("Cloud Mesh Generator"); + if (this.meshGenerator != null) + { + meshGenCategory.setDetail("Type", this.meshGenerator.toString()); + this.meshGenerator.fillReport(meshGenCategory); + } + else + { + meshGenCategory.setDetail("Type", "Mesh generator is not initialized"); + } + } + + public static void initialize(CloudsRendererSettings settings) + { + RenderSystem.assertOnRenderThread(); + if (instance != null) + throw new IllegalStateException("Simple Clouds renderer is already initialized"); + instance = new SimpleCloudsRenderer(settings, Minecraft.getInstance()); + LOGGER.debug("Clouds render initialized"); + } + + public static SimpleCloudsRenderer getInstance() + { + return Objects.requireNonNull(instance, "Renderer not initialized!"); + } + + public static Optional getOptionalInstance() + { + return Optional.ofNullable(instance); + } + + public static boolean canRenderInDimension(@Nullable ClientLevel level) + { + if (level == null) + return false; + + List whitelist; + boolean useAsBlacklist; + if (ClientCloudManager.isAvailableServerSide() && SimpleCloudsConfig.SERVER_SPEC.isLoaded()) + { + whitelist = SimpleCloudsConfig.SERVER.dimensionWhitelist.get(); + useAsBlacklist = SimpleCloudsConfig.SERVER.whitelistAsBlacklist.get(); + } + else + { + whitelist = SimpleCloudsConfig.CLIENT.dimensionWhitelist.get(); + useAsBlacklist = SimpleCloudsConfig.CLIENT.whitelistAsBlacklist.get(); + } + + boolean flag = whitelist.stream().anyMatch(val -> { + return level.dimension().location().toString().equals(val); + }); + + return useAsBlacklist ? !flag : flag; + } + + private static void renderChunkBox(VertexConsumer consumer, float minX, float minY, float minZ, float maxX, float maxY, float maxZ, float r, float g, float b, float a) + { + //-X + consumer.vertex(minX, minY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, maxY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, minY, minZ).color(r, g, b, a).endVertex(); + + //+X + consumer.vertex(maxX, minY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, minY, maxZ).color(r, g, b, a).endVertex(); + + //-Y + consumer.vertex(maxX, minY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, minY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, minY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, minY, minZ).color(r, g, b, a).endVertex(); + + //+Y + consumer.vertex(minX, maxY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, minZ).color(r, g, b, a).endVertex(); + + //-Z + consumer.vertex(minX, minY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, maxY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, minY, minZ).color(r, g, b, a).endVertex(); + + //+Z + consumer.vertex(maxX, minY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, minY, maxZ).color(r, g, b, a).endVertex(); + } + + private static int calculateMeshGenInterval() + { + int fps = Minecraft.getInstance().getFps(); + switch (SimpleCloudsConfig.CLIENT.generationInterval.get()) + { + case STATIC: + { + return SimpleCloudsConfig.CLIENT.framesToGenerateMesh.get(); + } + case DYNAMIC: + { + return Math.max(Mth.ceil((130.0F - (float)fps) / 30.0F) + 5, 1); + } + case TARGET_FPS: + { + return Math.max(Mth.ceil((float)fps / SimpleCloudsConfig.CLIENT.targetMeshGenFps.get()), 1); + } + default: + return 5; + } + } +} diff --git a/dev/nonamecrackers2/simpleclouds/client/renderer/pipeline/DefaultPipeline.java b/dev/nonamecrackers2/simpleclouds/client/renderer/pipeline/DefaultPipeline.java new file mode 100644 index 00000000..9c89df20 --- /dev/null +++ b/dev/nonamecrackers2/simpleclouds/client/renderer/pipeline/DefaultPipeline.java @@ -0,0 +1,155 @@ +package dev.nonamecrackers2.simpleclouds.client.renderer.pipeline; + +import org.joml.Matrix4f; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexSorting; + +import dev.nonamecrackers2.simpleclouds.client.framebuffer.FrameBufferUtils; +import dev.nonamecrackers2.simpleclouds.client.framebuffer.WeightedBlendingTarget; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import dev.nonamecrackers2.simpleclouds.client.world.FogRenderMode; +import dev.nonamecrackers2.simpleclouds.common.config.SimpleCloudsConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.material.FogType; +import nonamecrackers2.crackerslib.common.compat.CompatHelper; + +public class DefaultPipeline implements CloudsRenderPipeline +{ + protected DefaultPipeline() {} + + @Override + public void prepare(Minecraft mc, SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ, Frustum frustum) {} + + @Override + public void afterSky(Minecraft mc, SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ, Frustum frustum) + { + ProfilerFiller p = mc.getProfiler(); + + float[] cloudCol = renderer.getCloudColor(partialTick); + float cloudR = (float)cloudCol[0]; + float cloudG = (float)cloudCol[1]; + float cloudB = (float)cloudCol[2]; + + if (SimpleCloudsConfig.CLIENT.atmosphericClouds.get()) + { + p.push("atmospheric_clouds"); + renderer.getAtmosphericCloudRenderer().render(stack, projMat, partialTick, camX, camY, camZ, cloudR, cloudG, cloudB); + mc.getMainRenderTarget().bindWrite(false); + p.pop(); + } + + // Clouds + + p.push("clouds"); + + stack.pushPose(); + + renderer.translateClouds(stack, camX, camY, camZ); // Prepare render for origin of camera + + // Render opaque cloud geometry + p.push("clouds_opaque"); + + // Clears the cloud framebuffer and sets it as the current one + RenderTarget cloudTarget = renderer.getCloudTarget(); + cloudTarget.clear(Minecraft.ON_OSX); + cloudTarget.bindWrite(false); + + // Renders the clouds on to the cloud frame buffer + CloudMeshGenerator generator = renderer.getMeshGenerator(); + SimpleCloudsRenderer.renderCloudsOpaque(generator, stack, projMat, renderer.getFogStart(), renderer.getFogEnd(), partialTick, cloudR, cloudG, cloudB, SimpleCloudsConfig.CLIENT.frustumCulling.get() ? frustum : null); + + // Here we copy the depth from the cloud frame buffer to the main one, so we can have correct depth information with the + // rest of the Minecraft world + renderer.copyDepthFromCloudsToMain(); + + // Render transparent cloud geometry + p.popPush("clouds_transparent"); + + WeightedBlendingTarget transparencyTarget = renderer.getCloudTransparencyTarget(); + transparencyTarget.clear(Minecraft.ON_OSX); + + if (generator.transparencyEnabled()) + { + // We use weighted order independent transparency as we cannot easily sort the cloud mesh + // More info here https://jcgt.org/published/0002/02/09/paper.pdf and http://casual-effects.blogspot.com/2015/03/implemented-weighted-blended-order.html + renderer.copyDepthFromCloudsToTransparency(); // Copy the depth data from the cloud framebuffer so we don't get weird depth issues + transparencyTarget.bindWrite(false); + + // Render the transparent geometry to the transparency framebuffer + SimpleCloudsRenderer.renderCloudsTransparency(generator, stack, projMat, renderer.getFogStart(), renderer.getFogEnd(), partialTick, cloudR, cloudG, cloudB, SimpleCloudsConfig.CLIENT.frustumCulling.get() ? frustum : null); + } + + p.pop(); + + stack.popPose(); + + // Render everything on to the main screen using a final composite shader + p.push("clouds_composite"); + renderer.doFinalCompositePass(stack, partialTick, projMat); + p.pop(); + + p.pop(); + + // Storm Fog + + if (SimpleCloudsConfig.CLIENT.renderStormFog.get()) + { + p.push("storm_fog"); + + // Renders the storm fog at a lower resolution + renderer.doStormPostProcessing(stack, partialTick, projMat, camX, camY, camZ, cloudR, cloudG, cloudB); + + // Next we blit the storm fog to a higher resolution texture and apply a box blur + RenderTarget target = renderer.getBlurTarget(); + target.clear(Minecraft.ON_OSX); // Clear old contents on the blur framebuffer + target.bindWrite(true); // Bind write and resize viewport + // Here we blit the contents of the storm fog framebuffer on to the blur framebuffer. A special function is used here + // to preserve the alpha channel when rendering + FrameBufferUtils.blitTargetPreservingAlpha(renderer.getStormFogTarget(), mc.getWindow().getWidth(), mc.getWindow().getHeight()); + // Blurs the storm fog + renderer.doBlurPostProcessing(partialTick); + // Renders the storm fog to the screen + mc.getMainRenderTarget().bindWrite(false); + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ZERO, GlStateManager.DestFactor.ONE); + renderer.getBlurTarget().blitToScreen(mc.getWindow().getWidth(), mc.getWindow().getHeight(), false); + RenderSystem.disableBlend(); + RenderSystem.defaultBlendFunc(); + // Need to do this here because blitToScreen messes up the projection matrix and doesn't set it back + RenderSystem.setProjectionMatrix(projMat, VertexSorting.DISTANCE_TO_ORIGIN); + + p.pop(); + } + + // Set the frame buffer back to the main one so everything else can render normally + mc.getMainRenderTarget().bindWrite(CompatHelper.isVrActive()); + } + + @Override + public void beforeWeather(Minecraft mc, SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ, Frustum frustum) + { + if (SimpleCloudsConfig.CLIENT.fogMode.get() == FogRenderMode.SCREEN_SPACE && mc.gameRenderer.getMainCamera().getFluidInCamera() == FogType.NONE) + { + renderer.doScreenSpaceWorldFog(stack, projMat, partialTick); + mc.getMainRenderTarget().bindWrite(false); + } + } + + @Override + public void afterLevel(Minecraft mc, SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ, Frustum frustum) + { +// mc.getProfiler().push("clouds_debug"); +// stack.pushPose(); +// renderer.translateClouds(stack, camX, camY, camZ); +// SimpleCloudsRenderer.renderCloudsDebug(renderer.getMeshGenerator(), stack, projMat, partialTick, renderer.getFogStart(), renderer.getFogEnd(), frustum, false, true); +// stack.popPose(); +// mc.getProfiler().pop(); + } +} diff --git a/dev/nonamecrackers2/simpleclouds/client/renderer/pipeline/ShaderSupportPipeline.java b/dev/nonamecrackers2/simpleclouds/client/renderer/pipeline/ShaderSupportPipeline.java new file mode 100644 index 00000000..5ea1293d --- /dev/null +++ b/dev/nonamecrackers2/simpleclouds/client/renderer/pipeline/ShaderSupportPipeline.java @@ -0,0 +1,121 @@ +package dev.nonamecrackers2.simpleclouds.client.renderer.pipeline; + +import org.joml.Matrix4f; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexSorting; + +import dev.nonamecrackers2.simpleclouds.client.framebuffer.FrameBufferUtils; +import dev.nonamecrackers2.simpleclouds.client.framebuffer.WeightedBlendingTarget; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import dev.nonamecrackers2.simpleclouds.common.config.SimpleCloudsConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.util.profiling.ProfilerFiller; +import nonamecrackers2.crackerslib.common.compat.CompatHelper; + +public class ShaderSupportPipeline implements CloudsRenderPipeline +{ + protected ShaderSupportPipeline() {} + + @Override + public void prepare(Minecraft mc, SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ, Frustum frustum) {} + + @Override + public void afterSky(Minecraft mc, SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ, Frustum frustum) {} + + @Override + public void beforeWeather(Minecraft mc, SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ, Frustum frustum) {} + + // To make Iris shaders work at a bare minimum, we render the clouds after the Iris render pipeline + @Override + public void afterLevel(Minecraft mc, SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ, Frustum frustum) + { + ProfilerFiller p = mc.getProfiler(); + + float[] cloudCol = renderer.getCloudColor(partialTick); + float cloudR = (float)cloudCol[0]; + float cloudG = (float)cloudCol[1]; + float cloudB = (float)cloudCol[2]; + + if (CompatHelper.areShadersRunning()) + GlStateManager._depthMask(true); + + // Render opaque cloud geometry + p.push("clouds_opaque"); + + stack.pushPose(); + + renderer.translateClouds(stack, camX, camY, camZ); + + RenderTarget cloudTarget = renderer.getCloudTarget(); + cloudTarget.clear(Minecraft.ON_OSX); + renderer.copyDepthFromMainToClouds(); // Copy depth from main framebuffer + cloudTarget.bindWrite(false); + + // Renders the clouds on to the cloud frame buffer + CloudMeshGenerator generator = renderer.getMeshGenerator(); + SimpleCloudsRenderer.renderCloudsOpaque(renderer.getMeshGenerator(), stack, projMat, renderer.getFogStart(), renderer.getFogEnd(), partialTick, cloudR, cloudG, cloudB, SimpleCloudsConfig.CLIENT.frustumCulling.get() ? frustum : null); + + // Render transparent cloud geometry + p.popPush("clouds_transparent"); + + WeightedBlendingTarget transparencyTarget = renderer.getCloudTransparencyTarget(); + transparencyTarget.clear(Minecraft.ON_OSX); + + if (generator.transparencyEnabled()) + { + // We use weighted order independent transparency as we cannot easily sort the cloud mesh + // More info here https://jcgt.org/published/0002/02/09/paper.pdf and http://casual-effects.blogspot.com/2015/03/implemented-weighted-blended-order.html + renderer.copyDepthFromCloudsToTransparency(); // Copy the depth data from the cloud framebuffer so we don't get weird depth issues + transparencyTarget.bindWrite(false); + + // Render the transparent geometry to the transparency framebuffer + SimpleCloudsRenderer.renderCloudsTransparency(generator, stack, projMat, renderer.getFogStart(), renderer.getFogEnd(), partialTick, cloudR, cloudG, cloudB, SimpleCloudsConfig.CLIENT.frustumCulling.get() ? frustum : null); + } + + p.pop(); + + stack.popPose(); + + // Render everything on to the main screen using a final composite shader + p.push("clouds_composite"); + renderer.doFinalCompositePass(stack, partialTick, projMat); + p.pop(); + + mc.getMainRenderTarget().bindWrite(false); + + if (SimpleCloudsConfig.CLIENT.renderStormFog.get()) + { + p.push("storm_fog"); + + // Renders the storm fog at a lower resolution + renderer.doStormPostProcessing(stack, partialTick, projMat, camX, camY, camZ, cloudR, cloudG, cloudB); + + // Next we blit the storm fog to a higher resolution texture and apply a box blur + RenderTarget target = renderer.getBlurTarget(); + target.clear(Minecraft.ON_OSX); // Clear old contents on the blur framebuffer + target.bindWrite(true); // Bind write and resize viewport + // Here we blit the contents of the storm fog framebuffer on to the blur framebuffer. A special function is used here + // to preserve the alpha channel when rendering + FrameBufferUtils.blitTargetPreservingAlpha(renderer.getStormFogTarget(), mc.getWindow().getWidth(), mc.getWindow().getHeight()); + // Blurs the storm fog + renderer.doBlurPostProcessing(partialTick); + // Renders the storm fog to the screen + mc.getMainRenderTarget().bindWrite(false); + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ZERO, GlStateManager.DestFactor.ONE); + renderer.getBlurTarget().blitToScreen(mc.getWindow().getWidth(), mc.getWindow().getHeight(), false); + RenderSystem.disableBlend(); + RenderSystem.defaultBlendFunc(); + // Need to do this here because blitToScreen messes up the projection matrix and doesn't set it back + RenderSystem.setProjectionMatrix(projMat, VertexSorting.DISTANCE_TO_ORIGIN); + + p.pop(); + } + } +} diff --git a/dev/nonamecrackers2/simpleclouds/common/cloud/spawning/CloudGenerator.java b/dev/nonamecrackers2/simpleclouds/common/cloud/spawning/CloudGenerator.java new file mode 100644 index 00000000..a65053c4 --- /dev/null +++ b/dev/nonamecrackers2/simpleclouds/common/cloud/spawning/CloudGenerator.java @@ -0,0 +1,413 @@ +package dev.nonamecrackers2.simpleclouds.common.cloud.spawning; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.mutable.MutableObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.joml.Vector2f; +import org.joml.Vector2i; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import dev.nonamecrackers2.simpleclouds.api.SimpleCloudsAPI; +import dev.nonamecrackers2.simpleclouds.api.common.cloud.spawning.CreateRegionFunction; +import dev.nonamecrackers2.simpleclouds.api.common.cloud.spawning.SpawnInfo; +import dev.nonamecrackers2.simpleclouds.api.common.event.CloudRegionNaturallySpawnEvent; +import dev.nonamecrackers2.simpleclouds.api.common.event.CloudRegionRemovedEvent; +import dev.nonamecrackers2.simpleclouds.common.api.ScAPICloudGeneratorImplHelper; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudType; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudTypeSource; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; +import dev.nonamecrackers2.simpleclouds.common.world.SpawnRegion; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec2; +import net.minecraftforge.common.MinecraftForge; + +public abstract class CloudGenerator implements ScAPICloudGeneratorImplHelper +{ + private static final Logger LOGGER = LogManager.getLogger("simpleclouds/CloudGenerator"); + private List spawnRegions = Lists.newArrayList(); + private final List clouds = Lists.newArrayList(); + protected RandomSource random = RandomSource.create(); + protected final Supplier spawnConfig; + protected int ticksTillNextGen; + protected final CloudTypeSource cloudGetter; + + public CloudGenerator(CloudTypeSource cloudGetter, Supplier spawnConfig) + { + this.cloudGetter = cloudGetter; + this.spawnConfig = spawnConfig; + } + + public int getTicksTillNextGen() + { + return this.ticksTillNextGen; + } + + public Supplier getSpawnConfig() + { + return this.spawnConfig; + } + + @Override + public final List getClouds() + { + return ImmutableList.copyOf(this.clouds); + } + + @Override + public final List getSpawnRegions() + { + return ImmutableList.copyOf(this.spawnRegions); + } + + @Override + public List getCloudsInRegion(SpawnRegion region) + { + List clouds = Lists.newArrayList(); + for (CloudRegion cloud : this.clouds) + { + if (cloud.intersects(region)) + clouds.add(cloud); + } + return clouds; + } + + @Override + public @Nullable CloudRegion getCloudAtWorldPosition(float worldX, float worldZ) + { + return this.getCloudAtPosition(worldX / (float)SimpleCloudsConstants.CLOUD_SCALE, worldZ / (float)SimpleCloudsConstants.CLOUD_SCALE); + } + + @Override + public @Nullable CloudRegion getCloudAtPosition(float x, float z) + { + return CloudRegion.calculateAt(this.getClouds(), x, z).getLeft(); + } + + @Override + public List getRegionsThatOccupyCloud(CloudRegion cloud) + { + List regions = Lists.newArrayList(); + for (SpawnRegion region : this.spawnRegions) + { + if (cloud.intersects(region)) + regions.add(region); + } + return regions; + } + + @Override + public final int getTotalCloudRegions() + { + return this.clouds.size(); + } + + @Override + public void setClouds(Collection clouds) + { + this.removeAllClouds(); + clouds.forEach(r -> { + this.clouds.add(r); + }); + } + + @Override + public boolean removeAllClouds() + { + return this.removeClouds(r -> true); + } + + @Override + public boolean removeClouds(Predicate predicate) + { + return this.removeCloudsCount(predicate) > 0; + } + + @Override + public int removeCloudsCount(Predicate predicate) + { + int count = 0; + var iterator = this.clouds.iterator(); + while (iterator.hasNext()) + { + CloudRegion region = iterator.next(); + if (predicate.test(region)) + { + iterator.remove(); + MinecraftForge.EVENT_BUS.post(new CloudRegionRemovedEvent(null, region, CloudRegionRemovedEvent.Reason.MANUALLY)); + count++; + } + } + return count; + } + + @Override + public boolean addCloud(CloudRegion region, CloudGenerator.Order order) + { + if (!this.cloudGetter.doesCloudTypeExist(region.getCloudTypeId())) + { + LOGGER.warn("Attempted to spawn a cloud formation: unknown id '{}'", region.getCloudTypeId()); + return false; + } + + if (this.clouds.contains(region)) + return false; + + // Ensures we wont go over the maximum cloud formations for all regions that would include + // this cloud formation + for (SpawnRegion spawnRegion : this.getRegionsThatOccupyCloud(region)) + { + int totalCount = 0; + for (CloudRegion cloud : this.clouds) + { + if (cloud.intersects(spawnRegion)) + totalCount++; + } + if (totalCount >= SimpleCloudsConstants.MAX_CLOUD_FORMATIONS) + { +// System.out.println("refusing cloud region, too many"); + return false; + } + } + + order.appender.accept(this.clouds, region); + +// System.out.println(this.clouds.stream().map(CloudRegion::getOrderWeight).toList()); + + return true; + } + + public void initialize(RandomSource random, Level level) + { + this.random = RandomSource.create(); + this.spawnRegions = this.determineValidSpawnRegions(this.random, level); + this.removeAllClouds(); + CloudSpawningConfig config = this.spawnConfig.get(); + this.ticksTillNextGen = config.getSpawnInterval().sample(this.random); + } + + public void tick(@Nullable Level level, float speed) + { + this.spawnRegions = this.determineValidSpawnRegions(this.random, level); + + var iterator = this.clouds.iterator(); + while (iterator.hasNext()) + { + CloudRegion region = iterator.next(); + + //NOTE: If a cloud formation (region) is on the edge of a spawn region and is not visible, if the player moves even a slightly bit they can make that + //formation visible again causing it to tick. It could then move outside the region again, then the player can move and make it become + //visible again causing a cycle. This shouldn't happen as often since cloud formations shrink and will shrink extra fast when no longer visible, + //making it so they will shrink farther away from the edge of a spawn region preventing this, but it is behavior to note + boolean isVisible = SpawnRegion.doesCircleIntersect(this.spawnRegions, region.getWorldX(), region.getWorldZ(), region.getWorldRadius() / region.getStretch() + (float)SimpleCloudsConstants.CLOUD_SCALE / SimpleCloudsConstants.REGION_EDGE_FADE_FACTOR); + if (isVisible != region.wasPriorVisible()) + this.onRegionVisibilityChange(region, isVisible); + region.tick(this.random, level, isVisible, speed); + + if (!this.cloudGetter.doesCloudTypeExist(region.getCloudTypeId())) + { + LOGGER.warn("Cloud type with id {} no longer exists, removing cloud region", region.getCloudTypeId()); + iterator.remove(); + MinecraftForge.EVENT_BUS.post(new CloudRegionRemovedEvent(level, region, CloudRegionRemovedEvent.Reason.CLOUD_TYPE_NO_LONGER_EXISTS)); + } + + if (region.isDead()) + { + iterator.remove(); + CloudRegionRemovedEvent.Reason reason = CloudRegionRemovedEvent.Reason.NATURALLY; + if (!region.wasPriorVisible()) + reason = CloudRegionRemovedEvent.Reason.NO_LONGER_VISIBLE; + MinecraftForge.EVENT_BUS.post(new CloudRegionRemovedEvent(level, region, reason)); +// if (level != null && !level.isClientSide) +// System.out.println("cloud region died, was visible: " + isVisible + ", total: " + this.getTotalCloudRegions()); + } + } + + if (this.ticksTillNextGen > 0) + this.ticksTillNextGen -= Math.max(1, Mth.ceil(speed)); + + CloudSpawningConfig config = this.spawnConfig.get(); + + // In case the spawning config changes and the spawn interval is very different + int maxSpawnInterval = config.getSpawnInterval().getMaxValue(); + if (this.ticksTillNextGen > maxSpawnInterval) + this.ticksTillNextGen = maxSpawnInterval; + + if (!SimpleCloudsAPI.getApi().getHooks().isExternalWeatherControlEnabled()) + { + if (!config.isEmpty() && this.shouldGenerateCloud(config, this.random, level)) + this.spawnCloud(config, level); + } + } + + protected boolean shouldGenerateCloud(CloudSpawningConfig config, RandomSource random, Level level) + { + return this.ticksTillNextGen <= 0; + } + + public Optional spawnCloud(CloudSpawningConfig config, Level level) + { + return this.spawnCloud(() -> config.getRandom(this.random).orElse(null), config.getSpawnInterval().sample(this.random), config.getMaxRegions(), level); + } + + @Override + public Optional spawnCloud(Supplier infoGetter, int nextSpawnInterval, int maxRegions, Level level) + { + return this.spawnCloud(infoGetter, nextSpawnInterval, maxRegions, level, this::createRegion); + } + + @Override + public Optional spawnCloud(Supplier infoGetter, int nextSpawnInterval, int maxRegions, Level level, CreateRegionFunction regionFunc) + { + this.ticksTillNextGen = nextSpawnInterval; +// System.out.println("next spawn attempt: " + this.ticksTillNextGen); + + MutableObject spawnedCloud = new MutableObject<>(); + + SpawnRegion.randomPointForEachRegion(this.spawnRegions, this.random, SimpleCloudsConstants.SPAWN_ATTEMPTS, (r, p) -> + { + if (this.getCloudsInRegion(r).size() >= maxRegions) + return true; + + float x = (float)p.x + 0.5F; + float z = (float)p.y + 0.5F; + + SpawnInfo info = infoGetter.get(); + + if (info == null) + return false; + + CloudType type = this.cloudGetter.getCloudTypeForId(info.cloudType()); + if (type == null) + { + LOGGER.warn("Spawn config has unknown cloud type with id '{}'", info.cloudType()); + return false; + } + + return regionFunc.create(infoGetter.get(), (float)r.x() + 0.5F, (float)r.z() + 0.5F, x, z, this.random, true).map(apiRegion -> + { + CloudRegion region = (CloudRegion)apiRegion; + if (this.addCloud(region, CloudGenerator.Order.USE_WEIGHT)) + { + spawnedCloud.setValue(region); + MinecraftForge.EVENT_BUS.post(new CloudRegionNaturallySpawnEvent(level, apiRegion)); + return true; + } + else + { + return false; + } + }).orElse(false); + }); + + return Optional.ofNullable(spawnedCloud.getValue()); + } + + @Override + public Optional createRegion(SpawnInfo info, float playerX, float playerZ, float x, float z, RandomSource random, boolean growTime) + { + for (CloudRegion region : this.getClouds()) + { + float dist = Vector2f.distance(x, z, region.getWorldX(), region.getWorldZ()) - region.getWorldRadius(); + if (dist <= SimpleCloudsConstants.MIN_SPAWN_DIST_BETWEEN_REGIONS) + return Optional.empty(); + } + + float deltaAdj = info.movesToPlayer() ? 0.1F : 1.0F; + float deltaX = (playerX - x) * (1.0F + random.nextFloat() * deltaAdj); + float deltaZ = (playerZ - z) * (1.0F + random.nextFloat() * deltaAdj); + float rotation = (float)Math.atan2(deltaX, deltaZ) + (float)Math.PI; + Vec2 direction; + if (random.nextInt(5) == 0) + direction = new Vec2(random.nextFloat() * 2.0F - 1.0F, random.nextFloat() * 2.0F - 1.0F).normalized(); + else + direction = new Vec2(deltaX, deltaZ).normalized(); + + float radius = (float)info.determineRadius(random); + float maxSpeed = info.determineSpeed(random); + float accelerationFactor = 0.01F; + int existTicks = info.determineExistTicks(random); + int growTicks = growTime ? info.determineGrowTicks(random) : 0; + float stretchFactor = info.determineStretchFactor(random); + + return Optional.of(new CloudRegion(info.cloudType(), direction, maxSpeed, accelerationFactor, x / (float)SimpleCloudsConstants.CLOUD_SCALE, z / (float)SimpleCloudsConstants.CLOUD_SCALE, radius / (float)SimpleCloudsConstants.CLOUD_SCALE, rotation, stretchFactor, existTicks, growTicks, info.orderWeight())); + } + + public void doInitialGen(int x, int z, Level level, boolean ignoreOtherRegions) + { + SpawnRegion region = new SpawnRegion(x, z, SimpleCloudsConstants.SPAWN_RADIUS); + + CloudSpawningConfig config = this.spawnConfig.get(); + + if (this.getCloudsInRegion(region).size() > config.getMaxInitialRegions()) + return; + + for (int i = 0; i < config.getMaxInitialRegions(); i++) + { + for (int j = 0; j < SimpleCloudsConstants.SPAWN_ATTEMPTS; j++) + { + Vector2i pos = SpawnRegion.getRandomPointInRegion(region, this.random); + if (this.getCloudsInRegion(region).size() >= config.getMaxInitialRegions()) + continue; + if (!ignoreOtherRegions && this.spawnRegions.stream().anyMatch(r -> r.includesPoint(pos.x, pos.y))) + continue; + CloudRegion cloudFormation = this.createRegion(config.getRandom(this.random).orElse(null), (float)x + 0.5F, (float)z + 0.5F, (float)pos.x + 0.5F, (float)pos.y + 0.5F, this.random, false).orElse(null); + if (cloudFormation == null) + continue; + this.addCloud(cloudFormation, CloudGenerator.Order.USE_WEIGHT); + break; + } + } + } + + protected void onRegionVisibilityChange(CloudRegion region, boolean nowVisible) {} + + protected abstract List determineValidSpawnRegions(RandomSource random, @Nullable Level level); + + public static enum Order + { + TOP((l, r) -> + { + l.add(r); + }), + BOTTOM((l, r) -> + { + l.add(0, r); + }), + USE_WEIGHT((l, r) -> + { + int prevWeight = 0; + for (int i = 0; i < l.size(); i++) + { + CloudRegion region = l.get(i); + if (r.getOrderWeight() >= prevWeight && r.getOrderWeight() <= region.getOrderWeight()) + { + l.add(i, r); + prevWeight = region.getOrderWeight(); + return; + } + } + l.add(r); + }); + + private final BiConsumer, CloudRegion> appender; + + private Order(BiConsumer, CloudRegion> appender) + { + this.appender = appender; + } + } +} diff --git a/dev/nonamecrackers2/simpleclouds/common/world/CloudManager.java b/dev/nonamecrackers2/simpleclouds/common/world/CloudManager.java new file mode 100644 index 00000000..f7c74bb7 --- /dev/null +++ b/dev/nonamecrackers2/simpleclouds/common/world/CloudManager.java @@ -0,0 +1,427 @@ +package dev.nonamecrackers2.simpleclouds.common.world; + +import java.util.List; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +import dev.nonamecrackers2.simpleclouds.common.api.SimpleCloudsHooks; +import org.apache.commons.lang3.tuple.Pair; +import org.joml.Vector2f; + +import dev.nonamecrackers2.simpleclouds.api.SimpleCloudsAPI; +import dev.nonamecrackers2.simpleclouds.api.common.cloud.CloudMode; +import dev.nonamecrackers2.simpleclouds.api.common.cloud.weather.WeatherType; +import dev.nonamecrackers2.simpleclouds.api.common.event.ModifyCloudSpeedEvent; +import dev.nonamecrackers2.simpleclouds.api.common.world.ScAPICloudManager; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudType; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudTypeSource; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudGetter; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; +import dev.nonamecrackers2.simpleclouds.common.cloud.spawning.CloudGenerator; +import dev.nonamecrackers2.simpleclouds.common.cloud.spawning.CloudSpawningConfig; +import dev.nonamecrackers2.simpleclouds.common.config.SimpleCloudsConfig; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraftforge.common.MinecraftForge; + +public abstract class CloudManager implements CloudGetter, ScAPICloudManager +{ + public static final int CLOUD_HEIGHT_MAX = 2048; + public static final int CLOUD_HEIGHT_MIN = 0; + public static final int UPDATE_INTERVAL = 200; + public static final float RANDOM_SPREAD = 10000.0F; + public static final float SCROLL_OFFSET = 100.0F; + protected final T level; + protected final CloudTypeSource cloudSource; + protected final CloudGenerator cloudGenerator; + private long seed; + protected @Nullable RandomSource random; + protected float scrollAngle; + protected float scrollXO; + protected float scrollYO; + protected float scrollZO; + protected float scrollX; + protected float scrollY; + protected float scrollZ; + protected float speed = 1.0F; + protected int cloudHeight = 128; + protected int tickCount; + protected int nextLightningStrike = 60; + protected boolean useVanillaWeather; + + @SuppressWarnings("unchecked") + public static CloudManager get(T level) + { + return Objects.requireNonNull(((CloudManagerHolder)level).getCloudManager(), "Cloud manager is not available, this shouldn't happen!"); + } + + public CloudManager(T level, CloudTypeSource source, Supplier configGetter, BiFunction, CloudGenerator> generatorFunc) + { + this.level = level; + this.cloudSource = source; + this.cloudGenerator = generatorFunc.apply(this, configGetter); + this.useVanillaWeather = this.determineUseVanillaWeather(); + } + + @Override + public CloudGenerator getCloudGenerator() + { + return this.cloudGenerator; + } + + @Override + public List getClouds() + { + return this.cloudGenerator.getClouds(); + } + + @Override + public CloudType getCloudTypeForId(ResourceLocation id) + { + return this.cloudSource.getCloudTypeForId(id); + } + + @Override + public CloudType[] getIndexedCloudTypes() + { + return this.cloudSource.getIndexedCloudTypes(); + } + + @Override + public boolean isCloudGeneratorActive() { + return this.getCloudMode() != CloudMode.SINGLE; + } + + + public void onPlayerJoin(Player player) + { + if (this.isCloudGeneratorActive() && !SimpleCloudsAPI.getApi().getHooks().isExternalWeatherControlEnabled()) + this.cloudGenerator.doInitialGen(player.getBlockX(), player.getBlockZ(), this.level, false); + } + + @Override + public Pair getCloudTypeAtPosition(float x, float z) + { + if (this.getCloudMode() != CloudMode.SINGLE) + { + Pair result = CloudRegion.calculateAt(this.getClouds(), x, z); + CloudType type = null; + if (result.getLeft() != null) + type = this.getCloudTypeForId(result.getLeft().getCloudTypeId()); + if (type == null) + type = SimpleCloudsConstants.EMPTY; + return Pair.of(type, 1.0F - result.getRight()); + } + else + { + String rawId = this.getSingleModeCloudTypeRawId(); + ResourceLocation id = ResourceLocation.tryParse(rawId); + if (id != null) + { + CloudType type = this.getCloudTypeForId(id); + if (type != null) + return Pair.of(type, 0.0F); + } + return Pair.of(SimpleCloudsConstants.EMPTY, 0.0F); + } + } + + public Pair getPrecipitationAt(BlockPos pos) + { + if (!this.level.canSeeSky(pos) || this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos).getY() > pos.getY()) + return Pair.of(false, Biome.Precipitation.NONE); + + Biome.Precipitation precipitation = this.level.getBiome(pos).value().getPrecipitationAt(pos); + + var info = this.getCloudTypeAtWorldPos((float)pos.getX() + 0.5F, (float)pos.getZ() + 0.5F); + CloudType type = info.getLeft(); + if ((float)pos.getY() + 0.5F > type.stormStart() * SimpleCloudsConstants.CLOUD_SCALE + 128.0F) + return Pair.of(false, Biome.Precipitation.NONE); + + if (info.getLeft().weatherType().includesRain() && info.getRight() < SimpleCloudsConstants.RAIN_THRESHOLD - 0.01F) + return Pair.of(true, precipitation); + else + return Pair.of(false, Biome.Precipitation.NONE); + } + + //For API calls, use Level#isRainingAt + public boolean isRainingAt(BlockPos pos) + { + Pair val = this.getPrecipitationAt(pos); + return val.getLeft() && val.getRight() != Biome.Precipitation.RAIN; + } + + public boolean isSnowingAt(BlockPos pos) + { + Pair val = this.getPrecipitationAt(pos); + return val.getLeft() && val.getRight() == Biome.Precipitation.SNOW; + } + + public boolean hasPrecipitationAt(BlockPos pos) + { + Pair val = this.getPrecipitationAt(pos); + return val.getLeft() && val.getRight() != Biome.Precipitation.NONE; + } + + @Override + public float getRainLevel(float x, float y, float z) + { + var info = this.getCloudTypeAtWorldPos(x, z); + CloudType type = info.getLeft(); + + if (!type.weatherType().includesRain()) + return 0.0F; + + float fade = info.getRight(); + float verticalFade = 1.0F - Mth.clamp((y - (type.stormStart() * SimpleCloudsConstants.CLOUD_SCALE + this.getCloudHeight())) / SimpleCloudsConstants.RAIN_VERTICAL_FADE, 0.0F, 1.0F); + return Math.min(1.0F, Math.max(0.0F, SimpleCloudsConstants.RAIN_THRESHOLD - fade) / SimpleCloudsConstants.RAIN_FADE) * verticalFade; + } + + public void init(long seed) + { + RandomSource random = this.setSeed(seed); + this.random = random; + this.speed = 1.0F; + this.cloudGenerator.initialize(random, this.level); + } + + @Override + public int getCloudHeight() + { + return this.cloudHeight; + } + + @Override + public void setCloudHeight(int height) + { + this.cloudHeight = height; + } + + public void tick() + { + MinecraftServer server = this.level.getServer(); + if (server instanceof DedicatedServer && server.getPlayerCount() == 0) + return; + + this.tickCount++; + + this.scrollXO = this.scrollX; + this.scrollYO = this.scrollY; + this.scrollZO = this.scrollZ; + float speed = this.getCloudSpeed(); + speed = this.modifyCloudSpeed(speed); + + if (this.isCloudGeneratorActive()) + this.cloudGenerator.tick(this.level, speed); + + speed *= 0.0001F; + this.scrollAngle += speed; + this.scrollX = (float)Math.cos(this.scrollAngle) * SCROLL_OFFSET; + this.scrollY = 0.0F;//(float)Math.sin(this.scrollAngle + (float)Math.PI / 4.0F) * SCROLL_OFFSET * 0.5F; + this.scrollZ = (float)Math.sin(this.scrollAngle) * SCROLL_OFFSET; + + boolean flag = this.determineUseVanillaWeather(); + if (flag != this.useVanillaWeather) + { + this.useVanillaWeather = flag; + this.resetVanillaWeather(); + } + + if (!this.useVanillaWeather) + this.tickLightning(); + } + + protected void resetVanillaWeather() {} + + protected void tickLightning() + { + if (this.nextLightningStrike <= 0 || --this.nextLightningStrike > 0) + return; + this.attemptToSpawnLightning(); + int minInterval = SimpleCloudsConfig.COMMON.lightningSpawnIntervalMin.get(); + int maxInterval = Math.max(minInterval, SimpleCloudsConfig.COMMON.lightningSpawnIntervalMax.get()); + this.nextLightningStrike = Mth.randomBetweenInclusive(this.random, minInterval, maxInterval); + } + + protected boolean determineUseVanillaWeather() + { + return useVanillaWeather(this.level, this); + } + + @Override + public final boolean shouldUseVanillaWeather() + { + return this.useVanillaWeather; + } + + protected abstract void attemptToSpawnLightning(); + + protected abstract void spawnLightning(CloudType type, float fade, int x, int z, boolean soundOnly); + + @Override + public abstract CloudMode getCloudMode(); + + @Override + public abstract String getSingleModeCloudTypeRawId(); + + @Override + public void spawnLightning(int x, int z, boolean soundOnly) + { + var info = this.getCloudTypeAtWorldPos((float)x + 0.5F, (float)z + 0.5f); + this.spawnLightning(info.getLeft(), info.getRight(), x, z, soundOnly); + } + + @Override + public Vector2f calculateWindDirection() + { + float dirX = Mth.cos(this.scrollAngle); + float dirZ = Mth.sin(this.scrollAngle); + return new Vector2f(dirX, dirZ); + } + + @Override + public int getTickCount() + { + return this.tickCount; + } + + @Override + public long getSeed() + { + return this.seed; + } + + public RandomSource setSeed(long seed) + { + this.seed = seed; + return RandomSource.create(seed); + } + + protected float modifyCloudSpeed(float speed) + { + ModifyCloudSpeedEvent event = new ModifyCloudSpeedEvent(this.level, this, speed); + MinecraftForge.EVENT_BUS.post(event); + return event.getCurrentSpeed(); + } + + @Override + public float getCloudSpeed() + { + return this.speed; + } + + @Override + public void setCloudSpeed(float speed) + { + this.speed = Math.max(0.0F, speed); + } + + @Override + public float getScrollAngle() + { + return this.scrollAngle; + } + + @Override + public void setScrollAngle(float angle) + { + this.scrollAngle = angle; + } + + @Override + public float getScrollX() + { + return this.scrollX; + } + + @Override + public float getScrollY() + { + return this.scrollY; + } + + @Override + public float getScrollZ() + { + return this.scrollZ; + } + + @Override + public float getScrollX(float partialTicks) + { + return Mth.lerp(partialTicks, this.scrollXO, this.scrollX); + } + + @Override + public float getScrollY(float partialTicks) + { + return Mth.lerp(partialTicks, this.scrollYO, this.scrollY); + } + + @Override + public float getScrollZ(float partialTicks) + { + return Mth.lerp(partialTicks, this.scrollZO, this.scrollZ); + } + + public static boolean isValidLightning(CloudType type, float fade, RandomSource random) + { + return type.weatherType().includesThunder() && fade < 0.8F;// && (fade > 0.7F || random.nextInt(3) == 0); + } + + public static boolean useVanillaWeather(Level level, CloudTypeSource source) + { + if (!SimpleCloudsConfig.SERVER_SPEC.isLoaded()) + return false; + + boolean flag = SimpleCloudsConfig.SERVER.dimensionWhitelist.get().stream().anyMatch(val -> { + return level.dimension().location().toString().equals(val); + }); + + if (SimpleCloudsConfig.SERVER.whitelistAsBlacklist.get() ? flag : !flag) + return true; + + CloudMode mode = SimpleCloudsConfig.SERVER.cloudMode.get(); + + switch (mode) + { + case AMBIENT: + { + return true; + } + case SINGLE: + { + String rawId = SimpleCloudsConfig.SERVER.singleModeCloudType.get(); + ResourceLocation id = ResourceLocation.tryParse(rawId); + if (id != null) + { + CloudType type = source.getCloudTypeForId(id); + if (type != null && type.weatherType() == WeatherType.NONE) + return true; + } + } + default: + { + return false; + } + } + } + + @Override + public String toString() + { + return this.getClass().getSimpleName() + "[level=" + this.level.dimension().location() + "]"; + } +} diff --git a/docs/mods/projectatmosphere/faq.md b/docs/mods/projectatmosphere/faq.md new file mode 100644 index 00000000..711c40ac --- /dev/null +++ b/docs/mods/projectatmosphere/faq.md @@ -0,0 +1,34 @@ +# FAQ + +## Which Minecraft version and loader are supported? +Repository metadata currently targets Minecraft `1.20.1` on Forge. + +## Does Project Atmosphere require Simple Clouds? +Yes. `simpleclouds` is a mandatory dependency in `mods.toml`. + +## Does it require Serene Seasons? +Official docs describe Serene Seasons support, but current code actually requires some season provider at startup. The code names Serene Seasons, PA x TFC, and Ecliptic Seasons as accepted providers. Official support for the non-Serene options still needs confirmation. + +## Does `0.8.0.0` work with PA x TFC? +Not officially. The `0.8.0.0` changelog says it is temporarily incompatible. + +## Does it support Dynamic Trees? +There is integration code, but the latest official note says the module is still work in progress and should always be disabled. + +## How do I inspect current weather data? +Use the `/pa` commands. The most useful support commands are: +- `/pa temperature forecast` +- `/pa humidity get` +- `/pa pressure get` +- `/pa windSpeed get` +- `/pa weatherdebug forecast` +- `/pa weatherdebug fog` + +## How do I force fog for testing? +In the current source tree, use `/pa fog spawn [strength] [seconds]` and `/pa fog clear`. + +## How do I override biome temperatures? +Edit `config/projectatmosphere/biome_temps.json`. You can set either one `all` range for a biome or explicit `winter`, `spring`, `summer`, and `autumn` min/max ranges. + +## Does it support Fabric or NeoForge? +No official support for Fabric or NeoForge is documented in this bundle. diff --git a/docs/mods/projectatmosphere/install.md b/docs/mods/projectatmosphere/install.md new file mode 100644 index 00000000..5f38de53 --- /dev/null +++ b/docs/mods/projectatmosphere/install.md @@ -0,0 +1,34 @@ +# Install Project Atmosphere + +## Prerequisites +- Minecraft `1.20.1` +- Forge `47+` +- Project Atmosphere `0.8.0.0` + +## Required mods +- Project Atmosphere +- Simple Clouds +- Gabou's Libs + +## Season provider note +Official documentation explicitly mentions Serene Seasons support. Current code also refuses to start without a season provider and names these accepted providers in the startup check: +- `sereneseasons` +- `projectatmospherefortfc` +- `eclipticseasons` + +Official support for the non-Serene options still needs maintainer confirmation, so treat them as observed runtime behavior, not confirmed public compatibility. + +## Installation order +1. Install Forge for Minecraft `1.20.1`. +2. Add the required dependency mods. +3. Add a season provider. +4. Add Project Atmosphere. +5. Add optional compatibility mods only after the core setup works. + +No special jar ordering is required once the correct files are present in the `mods` folder. + +## Basic verification +1. Start the game or server and confirm it reaches the main menu/world load without dependency errors. +2. Check that no startup error mentions missing `simpleclouds`, `gaboulibs`, or a missing season provider. +3. In an Overworld test world, run `/pa temperature forecast`. +4. If you want to test current-source fog commands, run `/pa fog spawn`. diff --git a/docs/mods/projectatmosphere/overview.md b/docs/mods/projectatmosphere/overview.md new file mode 100644 index 00000000..265ac2d6 --- /dev/null +++ b/docs/mods/projectatmosphere/overview.md @@ -0,0 +1,30 @@ +# Project Atmosphere overview + +## What it does +Project Atmosphere is a Minecraft Forge 1.20.1 weather and climate mod. It simulates regional temperature, humidity, pressure, wind, and cloud-driven weather instead of relying only on vanilla weather flags. + +## Main features +- Season-aware temperature forecasting by biome/region. +- Regional humidity, pressure, and wind sampling. +- Simple Clouds integration for cloud-driven weather visuals. +- Weather world effects such as rain fire suppression and cauldron filling. +- Admin/debug commands for forecast inspection and weather testing. +- Optional biome temperature overrides through `config/projectatmosphere/biome_temps.json`. + +## Current source features +The current source tree also contains severe-weather and fog systems such as tornadoes, hurricanes, and dynamic fog. Their exact release/support status for `0.8.0.0` is not fully documented in official release notes, so treat them as current-source behavior rather than fully documented release guarantees. + +## Who it is for +- Players using climate or realism-focused Forge 1.20.1 packs. +- Pack/server admins who want weather diagnostics and configurable climate behavior. +- Mod integrators who want region-based weather data through the public API. + +## High-level dependencies +- Required by `mods.toml`: Forge, Minecraft, Simple Clouds, Gabou's Libs. +- Officially documented optional integrations: Serene Seasons, Serene Seasons Extended, Pretty Rain. +- Observed current runtime note: the code will abort startup if no season provider is present. See the install and troubleshooting guides for details. + +## Important compatibility notes +- Official latest note: `0.8.0.0` is temporarily incompatible with PA x TFC. +- Official latest note: Dynamic Trees integration remains work in progress and should stay disabled. +- Official loader support in repository metadata is Forge only. diff --git a/docs/mods/projectatmosphere/troubleshooting.md b/docs/mods/projectatmosphere/troubleshooting.md new file mode 100644 index 00000000..396a4338 --- /dev/null +++ b/docs/mods/projectatmosphere/troubleshooting.md @@ -0,0 +1,46 @@ +# Troubleshooting + +## Startup or mod loading failure + +### Wrong game version or loader +Project Atmosphere is currently documented for Minecraft `1.20.1` on Forge. Using Fabric, NeoForge, or another Minecraft version is not confirmed here. + +### Missing required dependency +If the log mentions `simpleclouds` or `gaboulibs`, install those mods first. They are declared as mandatory in `mods.toml`. + +### Missing season provider +Current code throws an error if no season provider is loaded. If startup fails with a message about installing Serene Seasons, ProjectAtmosphereForTFC, or Ecliptic Seasons, add one of those mods and retry. + +### PA x TFC incompatibility on 0.8.0.0 +The official `0.8.0.0` changelog marks PA x TFC as temporarily incompatible. If that bridge is installed and weather logic behaves incorrectly, remove it or wait for a compatibility update. + +## Commands do not work + +### Use the `/pa` root +Current server commands are registered under `/pa`. If older docs mention `/weatherdebug`, use `/pa weatherdebug` instead. + +### Wrong dimension +Many forecast and debug commands only work in the Overworld. + +### Missing permission level +Admin/debug commands such as cloud spawning, tornado spawning, hurricane spawning, and fog forcing require permission level `2`. + +## Config problems + +### Common config values +Use the mod's Forge common config entries for weather tuning. The schema in `data/mcp/mods/projectatmosphere/config-schema.json` maps the real keys and default values. + +### Biome temperature overrides +Custom biome temperature overrides live in `config/projectatmosphere/biome_temps.json`. Invalid biome ids or malformed season ranges are skipped instead of applied. + +### Dynamic Trees integration +Latest official notes say the Dynamic Trees module is still work in progress and should remain disabled. + +## Known command issue +`/pa weatherdebug snowstorm` is currently mis-registered in the source tree: the handler expects an `intensity` argument that the command does not expose. Treat that command as broken until fixed. + +## When to check known issues +Check `data/mcp/mods/projectatmosphere/known-issues.json` when: +- a compatibility problem appears after updating to `0.8.0.0` +- an admin/debug command behaves inconsistently +- an integration is mentioned in old docs but not in current release notes diff --git a/gradle.properties b/gradle.properties index 06e132e5..694f7915 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ mixin.gradle.disableDiffplug=true # Minecraft + Forge versions minecraft_version=1.20.1 minecraft_version_range=[1.20.1,1.21) -forge_version=47.4.13 +forge_version=47.4.20 forge_version_range=[47,) loader_version_range=[47,) @@ -19,7 +19,7 @@ mod_id=projectatmosphere mod_name=Project Atmosphere mod_group_id=net.Gabou.projectatmosphere mod_license=All Rights Reserved -mod_version=0.8.0.0 +mod_version=0.9.0.0 mod_authors=Gabou mod_description=Dynamic seasonal temperature and weather simulation with support for Serene Seasons and shaders. mod_mixins=projectatmosphere.mixins.json diff --git a/libs/simpleclouds-0.7.4+1.20.1-forge-all.jar b/libs/simpleclouds-0.7.4+1.20.1-forge-all.jar new file mode 100644 index 00000000..bc8addb6 Binary files /dev/null and b/libs/simpleclouds-0.7.4+1.20.1-forge-all.jar differ diff --git a/libs/simpleclouds-0.7.4+1.20.1-forge-sources.jar b/libs/simpleclouds-0.7.4+1.20.1-forge-sources.jar new file mode 100644 index 00000000..15af3950 Binary files /dev/null and b/libs/simpleclouds-0.7.4+1.20.1-forge-sources.jar differ diff --git a/pmweather_tornado_extraction_export.zip b/pmweather_tornado_extraction_export.zip new file mode 100644 index 00000000..38b29bb8 Binary files /dev/null and b/pmweather_tornado_extraction_export.zip differ diff --git a/pmweather_tornado_extraction_export/README.md b/pmweather_tornado_extraction_export/README.md new file mode 100644 index 00000000..5ff2ed15 --- /dev/null +++ b/pmweather_tornado_extraction_export/README.md @@ -0,0 +1,111 @@ +# PMWeather Tornado Extraction + +This folder contains the PMWeather tornado code path that matters most if you want to port the behavior into another mod. + +Important: PMWeather does not render the tornado as a normal model or mesh. + +- The close-range visual motion comes from particles and debris being advected by the tornado wind field. +- The large visible funnel and wall cloud are generated in the volumetric cloud shader. +- The tornado backend is mainly `Storm.java` plus `WindEngine.java`. + +## Core Backend + +- `dev/protomanly/pmweather/weather/Storm.java` + - Storm lifecycle, widening, intensification, dying, chunk force-loading, pulling entities/particles, block damage, debris spawning. + - Key methods: + - `tick()` + - `doDamage(...)` + - `getRankine(...)` + - `getWind(...)` + - `pull(Particle, ...)` + - `pull(Entity, ...)` +- `dev/protomanly/pmweather/weather/Vorticy.java` + - Secondary subvortices orbiting the tornado. +- `dev/protomanly/pmweather/weather/WindEngine.java` + - Blends ambient wind, storm inflow/rotation, and the tornado-specific wind field. + - This is what drives particles and debris into a funnel-looking motion. +- `dev/protomanly/pmweather/weather/WeatherHandler.java` +- `dev/protomanly/pmweather/weather/WeatherHandlerClient.java` + - Storm management and client sync for debris particles. + +## Visual Funnel + +- `dev/protomanly/pmweather/shaders/ModShaders.java` + - Sends storm uniforms to the volumetric shader: + - position + - width + - windspeed + - touchdown speed + - random tornado shape + - spin + - occlusion +- `assets/pmweather/shaders/program/clouds.fsh` + - This is the actual funnel shape logic. + - Search these sections: + - `tornadic` + - `torPerc` + - `tornadoHeight` + - `torShape` + - `ropeMod` + - `dust` +- `assets/pmweather/shaders/program/clouds.json` +- `assets/pmweather/shaders/post/clouds.json` +- `dev/protomanly/pmweather/render/RenderEvents.java` + - Hooks the shader render pass into the level render. +- `dev/protomanly/pmweather/event/ModBusClientEvents.java` + - Reload listener and client-side registration that the shader/debris path depends on. +- `dev/protomanly/pmweather/mixin/PostChainMixin.java` + - Accessor mixin used by `ModShaders` to reach the post-pass list. +- `dev/protomanly/pmweather/interfaces/PostChainData.java` +- `dev/protomanly/pmweather/compat/DistantHorizons.java` +- `dev/protomanly/pmweather/compat/DistantHorizonsHandler.java` + - Optional compatibility layer used by the shader when Distant Horizons is present. +- `pmweather.mixins.json` + - Needed if you want the `PostChainMixin` path to work as PMWeather does it. + +## Particle And Debris Layer + +- `dev/protomanly/pmweather/event/GameBusClientEvents.java` + - Ticks custom particle managers and applies `WindEngine.getWind(...)` to particles every client tick. +- `dev/protomanly/pmweather/particle/ParticleManager.java` + - Custom particle render manager. +- `dev/protomanly/pmweather/particle/EntityRotFX.java` + - Base particle implementation with sorted translucent and block render types. +- `dev/protomanly/pmweather/particle/ParticleCube.java` + - Renders spinning cube debris using block textures. +- `dev/protomanly/pmweather/particle/ParticleTexFX.java` +- `dev/protomanly/pmweather/particle/ParticleTexExtraRender.java` +- `dev/protomanly/pmweather/particle/ParticleRegistry.java` +- `dev/protomanly/pmweather/particle/behavior/ParticleBehavior.java` +- `dev/protomanly/pmweather/entity/MovingBlock.java` +- `dev/protomanly/pmweather/entity/client/MovingBlockRenderer.java` + +## Assets Included + +- `assets/pmweather/shaders/...` +- `assets/pmweather/textures/particle/...` +- `assets/minecraft/textures/effect/pmweather/...` +- `assets/minecraft/atlases/particles.json` + +## What To Port First + +1. Port `Storm.getRankine(...)`, `Storm.getWind(...)`, and the two `pull(...)` methods. +2. Port the `Vorticy` system if you want the small satellite swirls. +3. Port the `WindEngine.getWind(...)` blending, because PMWeather uses that everywhere for visual motion. +4. Port `ModShaders.java` plus `clouds.fsh` if you want the same volumetric funnel. +5. Port `ParticleCube` plus the client particle tick path if you want the same debris behavior. + +## Expect Missing Dependencies + +These extracted files are the tornado stack, not a standalone compile-ready module. + +You will need to adapt references to PMWeather-specific infrastructure such as: + +- config classes +- utility helpers +- networking sync +- sound registration +- NeoForge event wiring +- PMWeather random/logger helpers + +If you want, I can do a second pass and turn this into a cleaner drop-in package for your own mod namespace. diff --git a/pmweather_tornado_extraction_export/assets/minecraft/atlases/blocks.json b/pmweather_tornado_extraction_export/assets/minecraft/atlases/blocks.json new file mode 100644 index 00000000..f244cf32 --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/minecraft/atlases/blocks.json @@ -0,0 +1,8 @@ +{ + "sources": [ + { + "type": "minecraft:single", + "resource": "pmweather:block/anemometer" + } + ] +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/minecraft/atlases/particles.json b/pmweather_tornado_extraction_export/assets/minecraft/atlases/particles.json new file mode 100644 index 00000000..61eaa406 --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/minecraft/atlases/particles.json @@ -0,0 +1,36 @@ +{ + "sources": [ + { + "type": "minecraft:single", + "resource": "pmweather:particle/rain" + }, + { + "type": "minecraft:single", + "resource": "pmweather:particle/mist" + }, + { + "type": "minecraft:single", + "resource": "pmweather:particle/splash" + }, + { + "type": "minecraft:single", + "resource": "pmweather:particle/snow" + }, + { + "type": "minecraft:single", + "resource": "pmweather:particle/snow1" + }, + { + "type": "minecraft:single", + "resource": "pmweather:particle/snow2" + }, + { + "type": "minecraft:single", + "resource": "pmweather:particle/snow3" + }, + { + "type": "minecraft:single", + "resource": "pmweather:particle/sleet" + } + ] +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noise.png b/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noise.png new file mode 100644 index 00000000..db96f6d0 Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noise.png differ diff --git a/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noisex.png b/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noisex.png new file mode 100644 index 00000000..3d7b9530 Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noisex.png differ diff --git a/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noisey.png b/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noisey.png new file mode 100644 index 00000000..b21d05e3 Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noisey.png differ diff --git a/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noisez.png b/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noisez.png new file mode 100644 index 00000000..ad680492 Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/minecraft/textures/effect/pmweather/noisez.png differ diff --git a/pmweather_tornado_extraction_export/assets/pmweather/shaders/include/noise.glsl b/pmweather_tornado_extraction_export/assets/pmweather/shaders/include/noise.glsl new file mode 100644 index 00000000..bfcca816 --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/pmweather/shaders/include/noise.glsl @@ -0,0 +1,95 @@ +#version 150 + +vec3 mod289(vec3 x) +{ + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec4 mod289(vec4 x) +{ + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec4 permute(vec4 x) +{ + return mod289(((x*34.0)+1.0)*x); +} + +vec4 taylorInvSqrt(vec4 r) +{ + return 1.79284291400159 - 0.85373472095314 * r; +} + +vec3 fade(vec3 t) { + return t*t*t*(t*(t*6.0-15.0)+10.0); +} + +// Classic Perlin noise +float cnoise(vec3 P) +{ + vec3 Pi0 = floor(P); // Integer part for indexing + vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 + Pi0 = mod289(Pi0); + Pi1 = mod289(Pi1); + vec3 Pf0 = fract(P); // Fractional part for interpolation + vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 + vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); + vec4 iy = vec4(Pi0.yy, Pi1.yy); + vec4 iz0 = Pi0.zzzz; + vec4 iz1 = Pi1.zzzz; + + vec4 ixy = permute(permute(ix) + iy); + vec4 ixy0 = permute(ixy + iz0); + vec4 ixy1 = permute(ixy + iz1); + + vec4 gx0 = ixy0 * (1.0 / 7.0); + vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; + gx0 = fract(gx0); + vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); + vec4 sz0 = step(gz0, vec4(0.0)); + gx0 -= sz0 * (step(0.0, gx0) - 0.5); + gy0 -= sz0 * (step(0.0, gy0) - 0.5); + + vec4 gx1 = ixy1 * (1.0 / 7.0); + vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; + gx1 = fract(gx1); + vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); + vec4 sz1 = step(gz1, vec4(0.0)); + gx1 -= sz1 * (step(0.0, gx1) - 0.5); + gy1 -= sz1 * (step(0.0, gy1) - 0.5); + + vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); + vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); + vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); + vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); + vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); + vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); + vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); + vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); + + vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); + g000 *= norm0.x; + g010 *= norm0.y; + g100 *= norm0.z; + g110 *= norm0.w; + vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); + g001 *= norm1.x; + g011 *= norm1.y; + g101 *= norm1.z; + g111 *= norm1.w; + + float n000 = dot(g000, Pf0); + float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); + float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); + float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); + float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); + float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); + float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); + float n111 = dot(g111, Pf1); + + vec3 fade_xyz = fade(Pf0); + vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); + vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); + float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); + return 2.2 * n_xyz; +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/pmweather/shaders/post/clouds.json b/pmweather_tornado_extraction_export/assets/pmweather/shaders/post/clouds.json new file mode 100644 index 00000000..00d4b672 --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/pmweather/shaders/post/clouds.json @@ -0,0 +1,34 @@ +{ + "targets": [ + "clouds", + "final" + ], + "passes": [ + { + "name": "pmweather:clouds", + "intarget": "minecraft:main", + "outtarget": "clouds", + "auxtargets":[ + {"name": "DepthSampler", "id": "minecraft:main:depth"}, + {"name": "NoiseSampler", "id": "pmweather/noise", "width": 512, "height": 512, "bilinear": true}, + {"name": "NoiseSamplerX", "id": "pmweather/noisex", "width": 512, "height": 512, "bilinear": true}, + {"name": "NoiseSamplerY", "id": "pmweather/noisey", "width": 512, "height": 512, "bilinear": true}, + {"name": "NoiseSamplerZ", "id": "pmweather/noisez", "width": 512, "height": 512, "bilinear": true} + ] + }, + { + "name": "pmweather:blur", + "intarget": "minecraft:main", + "outtarget": "final", + "auxtargets":[ + {"name": "DepthSampler", "id": "minecraft:main:depth"}, + {"name": "CloudSampler", "id": "clouds"} + ] + }, + { + "name": "blit", + "intarget": "final", + "outtarget": "minecraft:main" + } + ] +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/blur.fsh b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/blur.fsh new file mode 100644 index 00000000..1292dd7a --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/blur.fsh @@ -0,0 +1,115 @@ +#version 330 + +uniform sampler2D DiffuseSampler; +uniform sampler2D DownsampledDepthSampler; +uniform sampler2D DepthSampler; +uniform sampler2D CloudSampler; +uniform float downsample; +uniform float glowFix; +uniform float doBlur; + +uniform vec2 OutSize; + +in vec2 texCoord; + +out vec4 fragColor; + +#define near 0.05 +#define far 6000.0 +float linearizeDepth(float depth) { + float z = depth * 2.0 - 1.0; + return (2.0 * near * far) / (far + near - z * (far - near)); +} + +vec4 textureBilinear(sampler2D s, vec2 uv){ + vec4 tl = texture(s, uv); + vec4 tr = textureOffset(s, uv, ivec2(1,0)); + vec4 bl = textureOffset(s, uv, ivec2(0,1)); + vec4 br = textureOffset(s, uv, ivec2(1,1)); + + vec2 f = fract(uv*OutSize); + + vec4 ta = mix(tl, tr, f.x); + vec4 tb = mix(bl, br, f.x); + + return mix(ta, tb, f.y); +} + +vec4 textureBilinearOffset(sampler2D s, vec2 uv, ivec2 offset){ + vec2 perc = vec2(1.0)/OutSize; + uv += perc*vec2(offset); + vec4 tl = texture(s, uv); + vec4 tr = textureOffset(s, uv, ivec2(1,0)); + vec4 bl = textureOffset(s, uv, ivec2(0,1)); + vec4 br = textureOffset(s, uv, ivec2(1,1)); + + vec2 f = fract(uv*OutSize); + + vec4 ta = mix(tl, tr, f.x); + vec4 tb = mix(bl, br, f.x); + + return mix(ta, tb, f.y); +} + +void main(){ + vec4 texel = texture(DiffuseSampler, texCoord); + vec4 cloud = textureBilinear(CloudSampler, texCoord/downsample); + float odepth = linearizeDepth(texture(DepthSampler, texCoord).r); + + ivec2 off = ivec2(0,0); + if(downsample > 1.0 && glowFix > 0.5){ + vec4 a = textureBilinearOffset(CloudSampler, texCoord/downsample, ivec2(1,0)); + if(a.r > cloud.r){ + off = ivec2(1,0); + } + cloud = max(cloud, a); + + a = textureBilinearOffset(CloudSampler, texCoord/downsample, ivec2(-1,0)); + if(a.r > cloud.r){ + off = ivec2(-1,0); + } + cloud = max(cloud, a); + + a = textureBilinearOffset(CloudSampler, texCoord/downsample, ivec2(0,1)); + if(a.r > cloud.r){ + off = ivec2(0,1); + } + cloud = max(cloud, a); + + a = textureBilinearOffset(CloudSampler, texCoord/downsample, ivec2(0,-1)); + if(a.r > cloud.r){ + off = ivec2(0,-1); + } + cloud = max(cloud, a); + } + + int res = 3; + float size = 0.0012; + int count = 1; + + if(glowFix < 0.5 && doBlur > 0.5){ + for(int x=-res; x <= res; x++){ + for(int y=-res; y <= res; y++){ + if(x != 0 || y != 0){ + vec2 offset = (vec2(float(x), float(y))/float(res))*size; + + float depth = linearizeDepth(texture(DepthSampler, texCoord + offset).r); + + if(abs(depth-odepth) < 1){ + cloud += textureBilinear(CloudSampler, (texCoord/downsample) + offset); + count++; + } + } + } + } + } + + cloud /= count; + + vec3 col = texel.rgb; + vec4 renderOut = cloud; + + col = col * (1.0-renderOut.a) + renderOut.rgb; + + fragColor = vec4(col, 1.0); +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/blur.json b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/blur.json new file mode 100644 index 00000000..6eb05586 --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/blur.json @@ -0,0 +1,23 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "blit", + "fragment": "pmweather:blur", + "attributes": ["Position"], + "samplers": [ + {"name": "DiffuseSampler"}, + {"name": "DepthSampler"}, + {"name": "CloudSampler"} + ], + "uniforms": [ + {"name": "downsample", "type": "float", "count": 1, "values": [0.1]}, + {"name": "glowFix", "type": "float", "count": 1, "values": [0]}, + {"name": "doBlur", "type": "float", "count": 1, "values": [1]}, + + {"name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]}, + {"name": "OutSize", "type": "float", "count": 2, "values": [ 1.0, 1.0 ]} + ] +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/clouds.fsh b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/clouds.fsh new file mode 100644 index 00000000..59865b80 --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/clouds.fsh @@ -0,0 +1,1168 @@ +#version 330 + +#define PI 3.1415926535897932384626433832795 + +struct CloudReturn{ + float cloudDensity; + float rainDensity; + float dustDensity; + float brighten; +}; + +struct Render{ + vec4 col; + float lightEnergy; + float lightningEnergy; + float depth; +}; + +float hash( float p ) { + p = fract(p * .1031); + p *= p + 33.33; + p *= p + p; + return fract(p); +} + +float onoise(vec3 pos) { + // The noise function returns a value in the range -1.0f -> 1.0f + + vec3 x = pos * 2.0; + vec3 p = floor(x); + vec3 f = fract(x); + + f = f*f*(3.0-2.0*f); + float n = p.x + p.y*57.0 + 113.0*p.z; + + return mix(mix(mix( hash(n+0.0), hash(n+1.0),f.x), + mix( hash(n+57.0), hash(n+58.0),f.x),f.y), + mix(mix( hash(n+113.0), hash(n+114.0),f.x), + mix( hash(n+170.0), hash(n+171.0),f.x),f.y),f.z); +} + +uniform sampler2D DiffuseSampler; +uniform sampler2D DepthSampler; +uniform sampler2D NoiseSampler; +uniform sampler2D NoiseSamplerX; +uniform sampler2D NoiseSamplerY; +uniform sampler2D NoiseSamplerZ; + +uniform sampler2D dhDepthTex0; +uniform sampler2D dhDepthTex1; +uniform float hasDHDepth; +uniform float dhNearPlane; +uniform float dhFarPlane; +uniform float dhRenderDistance; +uniform mat4 dhProjection; +uniform mat4 dhProjectionInverse; + +float noise2d(vec2 x){ + x /= 512.0; + return (texture(NoiseSamplerX, x, -1.0).r-0.5)*2.0; +} + +float noise(vec3 x){ + x /= vec3(100.0,180.0,100.0)*3.0; + + x.y = fract(x.y)*512.0; + float iz = floor(x.y); + float fz = fract(x.y); + vec2 a_off = vec2(23.0, 29.0)*(iz)/512.0; + vec2 b_off = vec2(23.0, 29.0)*(iz+1.0)/512.0; + float a = texture(NoiseSampler, x.xz + a_off, -1.0).r; + float b = texture(NoiseSampler, x.xz + b_off, -1.0).r; + return (mix(a,b,fz)-0.5)*2.0; +} + +in vec2 texCoord; + +uniform float layer0height; +uniform float layerCheight; +uniform float stormSize; +uniform float rain; +uniform float snow; +uniform float rainStrength; +uniform int stormCount; +uniform float stormPositions[48]; +uniform float stormVelocities[32]; +uniform float stormStages[16]; +uniform float stormEnergies[16]; +uniform float stormTypes[16]; +uniform float stormOcclusions[16]; +uniform float tornadoWindspeeds[16]; +uniform float tornadoWidths[16]; +uniform float tornadoTouchdownSpeeds[16]; +uniform float visualOnlys[16]; +uniform float stormDyings[16]; +uniform float stormSpins[16]; +uniform float tornadoShapes[16]; +uniform int lightningCount; +uniform float lightningStrikes[192]; +uniform float lightningBrightness[64]; + +uniform vec2 OutSize; +uniform int maxSteps; +uniform float downsample; +uniform float stepSize; +uniform float time; +uniform float worldTime; +uniform float overcastPerc; + +out vec4 fragColor; + +uniform vec3 pos; +uniform vec2 scroll; +uniform vec3 sunDir; +uniform vec3 lightingColor; +uniform vec3 skyColor; +uniform mat4 proj; +uniform mat4 viewmat; +uniform mat4 vmat; +uniform float lightIntensity; +uniform float simpleLighting; + +uniform int quality; + +uniform float fogStart; +uniform float fogEnd; + +uniform float _FOV; + +const float inf = uintBitsToFloat(0x7F800000u); + +uniform float nearPlane; +uniform float farPlane; +uniform float renderDistance; + +float linearizeDepth(float depth) { + float z = depth * 2.0 - 1.0; + return (2.0 * nearPlane * farPlane) / (farPlane + nearPlane - z * (farPlane - nearPlane)); +} + +float fbm(vec3 x, int octaves, float lacunarity, float gain, float amplitude){ + float y = 0.0; + + octaves -= int(floor(pow(4.0-quality, 0.75))); + + for(int i = 0; i < max(octaves, 1); i++){ + y += amplitude * noise(x); + x *= lacunarity; + amplitude *= gain; + } + return y; +} + +vec3 getRayDir(vec2 screenUV){ + vec2 uv = (screenUV*2.0)-1.0; + vec4 n = vec4(uv, 0.0, 1.0); + vec4 f = vec4(uv, 1.0, 1.0); + n = proj * n; + f = proj * f; + n.xyz /= n.w; + f.xyz /= f.w; + return normalize((viewmat*f).xyz - (viewmat*n).xyz); +} + +mat2 spin(float angle){ + return mat2(cos(angle),-sin(angle),sin(angle),cos(angle)); +} + +float distanceSqr(vec2 a, vec2 b){ + vec2 c = a-b; + return dot(c,c); +} + +vec2 nearestPoint(vec2 v, vec2 w, vec2 p){ + float l2 = distanceSqr(v, w); + float t = clamp(dot(p-v, w-v) / l2, 0, 1); + return v + t * (w - v); +} + +float minimumDistance(vec2 v, vec2 w, vec2 p){ + float l2 = distanceSqr(v, w); + if(l2 == 0.0) return distance(p, v); + + vec2 proj = nearestPoint(v,w,p); + return distance(p, proj); +} + +float atan2(float y, float x){ + return x == 0.0 ? sign(y)*PI/2 : atan(y, x); +} + +CloudReturn getClouds(vec3 position, int octaveReduction){ + float totalCloud = 0.0; + float totalBGCloud = 0.0; + float totalRain = 0.0; + float totalDust = 0.0; + float upperLevel = 0.0; + + float nearestStorm = inf; + + vec2 s = vec2(worldTime); + + vec3 noisePos = position + vec3(s.x, -worldTime/2.0, s.y); + vec3 cloudNoisePos = position + vec3(worldTime*0.5, 0, worldTime*0.5); + + vec3 noisePos2 = position + vec3(s.x*4.0, -worldTime/2.0, s.y*4.0); + vec3 cloudNoisePos2 = position + vec3(worldTime*1.5, 0, worldTime*1.5); + + vec3 noisePosIT = position + vec3(s.x, worldTime/2.0, s.y); + + float noise1 = -10.0;//fbm(noisePos/90.0, 5-octaveReduction, 2.0, 0.5, 1.0); + float noise2 = -10.0;//fbm(noisePosIT/120.0, 2-octaveReduction, 2.0, 0.3, 1.0); + float noise3 = -10.0;//noise2d(noisePos.xz/25.0)+(noise1*0.4); + + if(octaveReduction == 0 && quality == 4){ + octaveReduction = -2; + } + + float bdensityNoise = min(noise2d(cloudNoisePos.xz/400.0), 1.0); + float bcloudNoise = min(noise2d(noisePos.xz/30.0), 1.0); + float bbaseNoise = noise2d(noisePos.xz/90.0); + float bheightNoise = noise2d(noisePos.zx/90.0); + + float bgc = 0.0; + float bv = clamp(bdensityNoise-(1.0-overcastPerc), 0.0, 1.0); + bgc += max(pow(bv, 0.25), 0.0); + float bgCloudBase = mix(mix(0.0, 150.0, clamp((bbaseNoise+1)*0.5, 0.0, 1.0)), 0.0, bv*bv*bv)+layer0height; + float bgCloudHeight = mix(300.0, 850.0, clamp((bheightNoise+1)*0.5, 0.0, 1.0)); + bgc *= mix(clamp((bcloudNoise-0.1)+bv, 0, 1), 1, bv); + bgc = pow(bgc, 0.5)*0.5; + bgc *= mix(bgCloudHeight/850.0, 1.0, sqrt(bv)); + + if(position.y > layer0height && position.y < layer0height+1000 && overcastPerc > 0){ + float bgClouds = 0; + float detailNoise = fbm(noisePos/90.0, 5-octaveReduction, 2.0, 0.5, 1.0); + float densityNoise = min(bdensityNoise+(detailNoise*0.1), 1.0); + float cloudNoise = min(bcloudNoise+(detailNoise*0.3), 1.0); + float baseNoise = bbaseNoise+(detailNoise*0.1); + float heightNoise = bheightNoise+(detailNoise*0.1); + + float v = clamp(densityNoise-(1.0-overcastPerc), 0.0, 1.0); + bgClouds += max(pow(v, 0.25), 0.0); + + float base = mix(mix(0.0, 150.0, clamp((baseNoise+1)*0.5, 0.0, 1.0)), 0.0, v*v*v)+layer0height; + float height = mix(300.0, 850.0, clamp((heightNoise+1)*0.5, 0.0, 1.0)); + + bgClouds *= mix(clamp((cloudNoise-0.1)+(v*v), 0, 1), 1, v*v*v); + + bgClouds *= 1.0 + detailNoise*0.2; + + v = clamp((position.y-base)/height, 0.0, 1.0); + bgClouds -= v*v; + bgClouds *= 1-clamp((base-position.y)/50.0, 0.0, 1.0); + + bgClouds = pow(bgClouds, 0.1)*0.5; + + totalBGCloud = max(totalBGCloud, bgClouds); + upperLevel = max(upperLevel, bgClouds); + } + + if(position.y > layerCheight && position.y < layerCheight+2000 && overcastPerc > 0){ + float bgClouds = 0.0; + float detailNoise = fbm(noisePos2/150.0, 3-octaveReduction, 2.0, 0.5, 1.0); + float densityNoise = min(noise2d(cloudNoisePos2.xz/800.0)+(detailNoise*0.1), 1.0); + float warpNoiseX = noise2d(noisePos2.zx/400.0); + float warpNoiseY = noise2d(noisePos2.xz/400.0); + float cloudNoise = min(noise2d((noisePos2.xz/200.0) + (vec2(warpNoiseX, warpNoiseY)*100.0))+(detailNoise*0.2), 1.0); + float baseNoise = noise2d(noisePos2.xz/400.0)+(detailNoise*0.1); + float heightNoise = noise2d(noisePos2.zx/150.0)+(detailNoise*0.1); + + float bandNoise = noise2d(noisePos2.xz/800.0)+(detailNoise*0.1); + float bandNoise2 = noise2d(noisePos2.zx/400.0)+(detailNoise*0.1); + float bandNoiseX = noise2d(noisePos2.xz/150.0)+(detailNoise*0.1); + float bandNoiseZ = noise2d(noisePos2.zx/150.0)+(detailNoise*0.1); + + float v = clamp(densityNoise-(1.0-overcastPerc), 0.0, 1.0); + bgClouds += max(pow(v, 0.25), 0.0); + + float base = mix(mix(0, 1000.0, clamp((baseNoise+1)*0.5, 0.0, 1.0)), 0.0, v*v*v)+layerCheight; + float height = mix(200.0, 500.0, clamp((heightNoise+1)*0.5, 0.0, 1.0)); + + bgClouds *= mix(clamp(sqrt(cloudNoise+0.3)+(v*v), 0.0, 1.0), 1, v*v*v); + + bgClouds *= 1.0 + detailNoise*0.2; + + vec3 bandingPos = noisePos2 + vec3(bandNoiseX, 0, bandNoiseZ)*1200.0; + float banding = sin((bandingPos.x+bandingPos.z)/(600.0 * (1 + bandNoise2*0.3))); + banding = abs(banding * banding * banding * banding); + + bgClouds = mix(bgClouds, bgClouds*banding, clamp((bandNoise+1)*0.5, 0.0, 1.0)); + + v = clamp((position.y-base)/height, 0.0, 1.0); + bgClouds -= v*v; + bgClouds *= 1-clamp((base-position.y)/50.0, 0.0, 1.0); + + v = clamp(densityNoise, 0.0, 1.0); + bgClouds = pow(bgClouds, 0.25)*mix(0.35, 0.0, v*v*v*v*v); + + totalBGCloud = max(totalBGCloud, bgClouds); + } + + for(int i = 0; i < stormCount; i++){ + float clouds = 0.0; + float rainam = 0.0; + vec3 pos = vec3(stormPositions[i*3], stormPositions[(i*3)+1], stormPositions[(i*3)+2]); + vec2 vel = vec2(stormVelocities[i*2], stormVelocities[(i*2)+1]); + float stage = stormStages[i]; + float energy = stormEnergies[i]; + float windspeed = tornadoWindspeeds[i]; + float width = max(tornadoWidths[i], 15.0); + float touchdownSpeed = tornadoTouchdownSpeeds[i]; + float tornadoShape = tornadoShapes[i]; + float stormSpin = stormSpins[i]; + float stormType = stormTypes[i]; + float occlusion = stormOcclusions[i]; + bool visualOnly = false; + bool dying = false; + bool tornadic = false; + + if(visualOnlys[i] > 0.0){ + visualOnly = true; + } + + if(stormDyings[i] > 0.0){ + dying = true; + } + + float smoothStage = stage + (energy/100.0); + + // Hurricane + if(abs(stormType-2) < 0.1 && windspeed > 0){ + float hHeight = layer0height+1000.0; + float heightP = (position.y-layer0height)/(hHeight-layer0height); + float sze = width; + pos += vec3(pow(heightP, 2.0)*sze*0.1, 0, -pow(heightP, 2.0)*sze*0.05); + float dist = distance(position.xz, pos.xz); + + sze *= 1.0+(pow(heightP, 2.0)*0.4*(1.0+(clamp(dist/sze, 0.0, 1.0)*1.0))); + + float eyeSize = 0.1; + float eyeCutSize = 0.1 + (heightP*0.2); + + if(dist > sze*1.2 || position.y > hHeight){ + continue; + } + + if(noise1 < -9.0 && (position.y <= hHeight)){ + noise1 = fbm(noisePos/90.0, 4-octaveReduction, 2.0, 0.5, 1.0); + noise2 = fbm(noisePosIT/120.0, 2-octaveReduction, 2.0, 0.3, 1.0); + noise3 = noise2d(noisePos.xz/25.0)+(noise1*0.4); + } + + if(noise1 > -9.0){ + dist *= 1.0+(((noise3+1.0)/2.0)*0.2); + } + + float intensity = pow(clamp(windspeed/65.0, 0.0, 1.0), 0.85); + + vec2 relPos = pos.xz-position.xz; + + float d = sze/(3.0+(windspeed/12.0)); + float d2 = sze/(1.15+(windspeed/12.0)); + float dE = (sze*0.3)/(1.75+(windspeed/12.0)); + + float fac = 1.0+(max((dist-(sze*(eyeSize*2.0)))/sze, 0.0)*2.0); + d *= fac; + d2 *= fac; + + float angle = ((atan2(relPos.y, relPos.x)-(dist/d))); + float angle2 = ((atan2(relPos.y, relPos.x)-(dist/d2))); + float angleE = ((atan2(relPos.y, relPos.x)-(dist/dE))); + + float weak = 0.0; + float strong = 0.0; + float intense = 0.0; + + float staticBands = sin(angle-(PI/2.0)); + staticBands *= pow(clamp(dist/(sze*0.25), 0.0, 1.0), 0.1); + staticBands *= 1.25*pow(intensity, 0.75); + if(staticBands < 0){ + weak += abs(staticBands); + }else{ + weak += abs(staticBands) * pow(1.0-clamp(dist/(sze*0.65), 0.0, 1.0), 0.5); + weak *= clamp((windspeed-70.0)/40.0, 0.0, 1.0); + } + + float rotatingBands = sin((angle2+radians(worldTime/8.0))*6.0); + rotatingBands *= pow(clamp(dist/(sze*0.25), 0.0, 1.0), 0.1); + rotatingBands *= 1.25*pow(intensity, 0.75); + strong += mix((abs(rotatingBands)*0.3)+0.7, weak, 0.45); + intense += mix((abs(rotatingBands)*0.2)+0.8, weak, 0.3); + weak = ((abs(rotatingBands)*0.3)+0.6)*weak; + + float localCloud = 0.0; + + localCloud += mix(mix(weak, strong, clamp((windspeed-40.0)/90.0, 0.0, 1.0)), intense, clamp((windspeed-120.0)/60.0, 0.0, 1.0)); + + float eye = sin((angleE+radians(worldTime/4.0))*2.0); + eye = mix(eye, 1.0, 1.0-clamp(dist/(sze*eyeSize), 0, 1)); + localCloud = max(pow(1.0-clamp(dist/(sze*eyeSize*2.0), 0.0, 1.0), 0.5)*(abs(eye*0.3)+0.7)*1.4*intensity, localCloud); + + localCloud *= pow(1.0-clamp(dist/sze, 0.0, 1.0), 0.5); + localCloud *= mix(1.0, pow(clamp(dist/(sze*eyeCutSize), 0.0, 1.0), 2.0), 0.5+(clamp((windspeed-65.0)/60.0, 0.0, 1.0)*0.5)); + + float noiseMain = (1.0+noise1)/2.0; + localCloud *= mix(0.8 + noiseMain*0.4, 1.0, clamp((windspeed-75.0)/50.0, 0.0, 1.0)); + localCloud *= 0.8 + noiseMain*0.4; + + float eyeBG = mix(1.0, pow(clamp(dist/(sze*eyeCutSize), 0, 1), 2), clamp((windspeed-65.0)/60.0, 0.0, 1.0)); + totalBGCloud *= eyeBG; + bgc *= eyeBG; + + if(localCloud > 0.7){ + float dif = (localCloud-0.7)/3.0; + localCloud -= dif; + } + + if(heightP >= 0.0 && heightP <= 1.0){ + clouds += localCloud-(0.4*(1.0-pow((1.4*heightP)-0.4, 2.0))); + clouds *= pow(1.0-heightP, 0.5); + } + + if(heightP <= 1.0){ + float rain = max(localCloud-0.15, 0.0)*2.0; + rain *= pow(1.0-clamp(dist/width, 0.0, 1.0), 0.35); + rain *= 0.6 + noise2*0.5; + rain *= pow(1.0-heightP, 0.5); + rainam += rain*rainStrength; + } + + clouds = sqrt(clouds)*0.8; + } + + // Squall + if(abs(stormType-1) < 0.1){ + if(stage >= 2.99){ + smoothStage = 3.0; + } + + float subcellular = clamp(smoothStage, 0.0, 1.0); + float cellular = clamp(smoothStage-0.5, 0.0, 1.0); + + float stormHeight = mix(400.0, 1600.0, cellular); + float baseHeight = layer0height; + + float v = clamp((position.y-layer0height)/1600.0, 0, 1); + v *= v; + vec2 right = normalize(vel.yx*vec2(1,-1)); + vec2 fwd = normalize(vel.xy); + vec3 right3 = vec3(right.x, 0, right.y); + + float rawDist = distance(position.xz, pos.xz); + + vec3 l = right3*-(stormSize*5); + vec3 r = right3*(stormSize*5); + + vec3 offset = vec3(-fwd.x, 0.0, -fwd.y)*pow(clamp(rawDist/(stormSize*5.0), 0.0, 1.0), 2.0)*(stormSize*1.5); + l += offset; + r += offset; + + l += vec3(2000.0*v, 0, -900.0*v); + r += vec3(2000.0*v, 0, -900.0*v); + + l += pos; + r += pos; + + float dist = minimumDistance(l.xz, r.xz, position.xz); + + float sze = stormSize*10.0; + + if(position.y > layer0height+1000.0){ + sze *= 8.0; + } + + if(dist > sze){ + continue; + } + + if(noise1 < -9.0 && (dist < stormSize*1.6 || smoothStage > 1) && (position.y < stormHeight*1.5)){ + noise1 = fbm(noisePos/90.0, 4-octaveReduction, 2.0, 0.5, 1.0); + noise2 = fbm(noisePosIT/120.0, 2-octaveReduction, 2.0, 0.3, 1.0); + noise3 = noise2d(noisePos.xz/25.0)+(noise1*0.4); + } + + if(noise1 > -9){ + baseHeight += noise1*15.0; + } + + float noiseMain = noise1; + + v = clamp((position.y-layer0height)/1600.0, 0, 1); + noiseMain = mix(noiseMain, noise3+(noise1*0.25), cellular*v); + + float size = stormSize*1.5; + stormHeight *= 1 + noiseMain*0.1; + + offset = vec3(fwd.x, 0, fwd.y)*stormSize*0.5; + l += offset; + r += offset; + vec2 nearPoint = nearestPoint(l.xz, r.xz, position.xz); + vec2 facing = position.xz-nearPoint; + float behind = -dot(facing, fwd); + behind += noise3*stormSize*0.2; + + if(behind > 0.0){ + baseHeight *= 1.0 - (pow(clamp(1.0-((behind-20.0)/stormSize), 0, 1), 4.0)*0.45*clamp(smoothStage-1.0, 0, 1.0)); + + float heightFromBase = position.y-baseHeight; + + if(position.y < layer0height){ + clouds -= clamp((heightFromBase-30.0)/30.0, 0, 1)*2.0; + } + + float baseChange = pow(clamp((behind-stormSize)/(stormSize*4.0), 0, 1), 0.4)*300.0; + baseHeight += baseChange; + stormHeight -= baseChange; + } + + float heightFromBase = position.y-baseHeight; + float heightPerc = clamp(heightFromBase/stormHeight, 0.0, 1.0); + float currentHeight = heightPerc*stormHeight; + float heightFromTop = stormHeight-currentHeight; + + v = clamp(currentHeight/max(stormHeight, 1600.0), 0, 1.0); + v *= v; + pos += vec3(2000.0*v, 0, -900.0*v); + + size = mix(size, mix(mix(size, size/2.0, cellular), size, sqrt(1.0-clamp(currentHeight/300.0, 0.0, 1.0))), pow(heightPerc, 0.25)); + + v = 1-clamp(heightFromTop/1600.0, 0.0, 1.0); + size = mix(size, mix(size, size*8.0, cellular), v*v*v*v*v); + size = mix(size, size/4.0, clamp(1.0-smoothStage, 0.0, 1.0)); + + size = mix(size, size*4, clamp(behind/(stormSize/4), 0.0, 1.0)*clamp(smoothStage-1.0, 0.0, 1.0)); + + size *= 1.0 + noiseMain*0.4; + + float distPerc = clamp(dist/size, 0.0, 1.0); + + if(distPerc >= 0.999){ + continue; + } + + if(position.y < baseHeight){ + totalBGCloud *= distPerc*distPerc*distPerc; + } + + float distBased = 1.0-distPerc; + float cloudField = (noise3-clamp(1.0-(smoothStage-1.0), 0.0, 1.0))*clamp(heightFromTop/65.0, 0.0, 1.0)*pow(1-distPerc, 0.25); + cloudField *= clamp(rawDist/mix(stormSize, stormSize*5.0, clamp(smoothStage/1.25, 0.0, 1.0)), 0.0, 1.0); + + clouds += mix(0.0, mix(cloudField, distBased, clamp(smoothStage/2.0, 0.0, 1.0)), step(baseHeight, position.y)*step(position.y, baseHeight+stormHeight)); + + clouds *= clamp(heightFromTop/mix(100.0, 300.0, clamp(smoothStage*0.8, 0.0, 1.0)), 0.0, 1.0); + + size = stormSize*0.75*clamp(smoothStage, 0.0, 1.0); + size = mix(size, size*8.0, clamp(behind/(stormSize/4.0), 0.0, 1.0)*clamp(smoothStage-1.0, 0.0, 1.0)); + size *= 1.0 + noise2*0.4; + distPerc = clamp(dist/size, 0.0, 1.0); + + clouds = sqrt(clouds)*0.8; + + float rain = step(position.y, baseHeight); + rain *= 1.0-pow(distPerc, 1.35); + rain *= 0.6 + noise2*0.5; + rain *= clamp(smoothStage-0.5, 0.0, 1.0); + + if(behind > 0.0){ + rain = pow(rain, 2.5); + } + + behind -= stormSize/3; + + if(behind < 0.0){ + rain *= 1.0-clamp(abs(behind)/(stormSize/3), 0.0, 1.0); + } + + rainam += rain*rainStrength; + } + + // Supercell + if(abs(stormType) < 0.1){ + if(stage >= 2.99){ + tornadic = true; + smoothStage = 3.0; + } + + float visualOnlyStep = step(float(visualOnly), 0.5); + float subceullular = clamp(smoothStage-0.5, 0.0, visualOnlyStep); + float supercellular = clamp(smoothStage-1.5, 0.0, visualOnlyStep); + + float stormHeight = mix(400.0, 1600.0, subceullular); + float baseHeight = layer0height; + + float v = clamp((position.y-layer0height)/1600.0, 0, 1); + v *= v; + float dist = distance(position.xz, pos.xz + vec2(2000.0*v, -900.0*v)); + float coreDist = distance(position.xz, pos.xz + vec2(2000.0, -900.0)); + float sze = stormSize*2.0; + + float clearBG = mix(1.0, pow(clamp(dist/(sze*2.0), 0.0, 1.0), 2.0), clamp(smoothStage, 0.0, 1.0)); + totalBGCloud *= clearBG; + bgc *= clearBG; + + if(position.y > layer0height+1000.0){ + sze *= 8.0; + }else if(coreDist <= sze*2.0){ + sze *= 2.5; + } + + if(min(coreDist, dist) > sze){ + continue; + } + + if(noise1 < -9.0 && (dist < mix(stormSize*1.6, stormSize*0.85, 1-visualOnlyStep) || smoothStage > 1.0) && (position.y < stormHeight*1.5)){ + noise1 = fbm(noisePos/90.0, 4-octaveReduction, 2.0, 0.5, 1.0); + noise2 = fbm(noisePosIT/120.0, 2-octaveReduction, 2.0, 0.3, 1.0); + noise3 = noise2d(noisePos.xz/25.0)+(noise1*0.4); + } + + if(noise1 > -9.0){ + baseHeight += noise1*15.0; + } + + vec3 localPos = position-pos; + mat2 speen = spin((-time/(20.0*50.0))+(dist/(stormSize*2.0))); + mat2 speen2 = spin((-time/(20.0*15.0))+(dist/(stormSize/2.0))); + + vec3 spinNoisePos = vec3(speen*localPos.xz, position.y-time); + vec3 spinNoisePos2 = vec3(speen2*localPos.xz, position.y-time); + + float spinNoise1 = 0.0;//fbm(spinNoisePos/120.0, 5-octaveReduction, 2.0, 0.5, 1.0); + float spinNoise2 = 0.0;//fbm(spinNoisePos2/80.0, 5-octaveReduction, 2.0, 0.5, 1.0); + + float heightFromBase = position.y-baseHeight; + + if(position.y < baseHeight && smoothStage > 1.75 && position.y > baseHeight-120.0 && dist < stormSize){ + spinNoise2 = fbm(spinNoisePos2/80.0, 4-octaveReduction, 2.0, 0.5, 1.0); + }else if(position.y > baseHeight && heightFromBase < 200.0 && smoothStage > 1.75 && dist < stormSize*2.5){ + spinNoise1 = fbm(spinNoisePos/120.0, 4-octaveReduction, 2.0, 0.5, 1.0); + } + + float noiseMain = mix(noise1, mix(spinNoise1, spinNoise2, step(position.y, baseHeight)), clamp((smoothStage-1.75)*3.0, 0.0, 1.0)*clamp(1.0-(heightFromBase/200.0), 0.0, 1.0)); + + v = clamp((position.y-layer0height)/1600.0, 0, 1); + noiseMain = mix(noiseMain, noise3+(noise1*0.25), supercellular*v); + + float size = stormSize*1.5; + stormHeight *= 1.0 + noiseMain*0.1; + + float heightPerc = clamp(heightFromBase/stormHeight, 0.0, 1.0); + float currentHeight = heightPerc*stormHeight; + float heightFromTop = stormHeight-currentHeight; + + v = clamp(currentHeight/max(stormHeight, 1600.0), 0.0, 1.0); + v *= v; + pos += vec3(2000.0*v, 0, -900.0*v); + dist = distance(position.xz, pos.xz); + + //float rawDistPerc = max(dist/size, 0); + + size = mix(size, mix(mix(size, size/2.0, supercellular), size, sqrt(1-clamp(currentHeight/300, 0.0, 1.0))), pow(heightPerc, 0.25)); + + v = 1-clamp(heightFromTop/1600.0, 0.0, 1.0); + size = mix(size, mix(size, size*8.0, supercellular), v*v*v*v*v); + size = mix(size, size/4.0, clamp(1.0-smoothStage, 0.0, 1.0)*visualOnlyStep); + size = mix(size, size/2.0, 1.0-visualOnlyStep); + + size *= 1.0 + noiseMain*0.4; + float distPerc = clamp(dist/size, 0.0, 1.0); + float coreDistPerc = clamp(coreDist/(size*4.0), 0.0, 1.0); + + if(min(distPerc, coreDistPerc) >= 0.999){ + continue; + } + + float distBased = 1.0-distPerc; + float cloudField = (noise3-clamp(1.0-smoothStage, 0.0, 1.0))*clamp(heightFromTop/65.0, 0.0, 1.0)*pow(1.0-distPerc, 0.25); + + clouds += mix(0, mix(cloudField, distBased, clamp(smoothStage*1.2, 0.0, visualOnlyStep)), step(baseHeight, position.y)*step(position.y, baseHeight+stormHeight)); + + clouds *= clamp(heightFromTop/mix(100.0, 300.0, clamp(smoothStage*0.8, 0.0, 1.0)), 0.0, 1.0); + + //clouds *= 1 + clamp(noiseMain*2*clamp(1-smoothStage, 1-visualOnlyStep, 1), -1, 0.4); + //clouds -= clamp(1-smoothStage, 0, 1)*2; + + size *= 0.35; + distPerc = clamp(dist/size, 0.0, 1.0); + + float wallcloudLower = 120.0*pow(1.0-distPerc, 0.25)*clamp((smoothStage-2.0)*3.0, 0.0, 1.0); + float wallcloud = mix(0.0, 1.0-distPerc, step(position.y, baseHeight)*step(baseHeight-wallcloudLower, position.y)); + wallcloud *= clamp((smoothStage-2.0)*5.0, 0.0, 1.0); + clouds += wallcloud; + + float fnlTop = max(baseHeight-105.0, pos.y+30.0); + float torPerc = clamp(windspeed/touchdownSpeed, 0.0, 1.0); + float tornadoHeight = mix(fnlTop, pos.y-50.0, torPerc); + + if(tornadic && position.y < baseHeight-wallcloudLower && position.y > pos.y-50.0 && dist < max(width*4.5, stormSize/3.0)){ + float tornado = 1.0; + float percFnlHeight = clamp((position.y-pos.y)/(fnlTop-pos.y), 0.0, 1.0); + float percCos = (-cos(percFnlHeight*3.141592)+1.0)*0.5; + + float torShape = mix(tornadoShape, 20.0, pow(clamp(width/500.0, 0.0, 1.0), 1.75)); + + float wid = (width/2.5) + ((width/2.5)*percFnlHeight*torPerc) + ((stormSize/mix(torShape+2.0, torShape, torPerc)) * percFnlHeight*percFnlHeight*percFnlHeight*percFnlHeight); + wid = mix(wid, 0.0, (1.0-percFnlHeight)*(1.0-torPerc)); + float th = 1-clamp((position.y-tornadoHeight)/30.0, 0.0, 1.0); + wid = mix(wid, 0.0, th*th*th); + float maxWid = (width/4.0) + ((width/4.0)*torPerc) + ((stormSize/8.0) * torPerc); + vec3 torPos = pos; + + float ropeMod = mix(3.0, 1.0, clamp(width/30.0, 0.0, 1.0)); + ropeMod = mix(ropeMod, 1.0, clamp((windspeed-65.0)/30.0, 0.0, 1.0)); + ropeMod = mix(0.1, ropeMod, clamp((windspeed/touchdownSpeed)*1.35, 0.0, 1.0)); + + float nx = onoise(vec3(pos.xz/500.0, time/200.0))*40.0*ropeMod; + float nz = onoise(vec3(time/200.0, pos.zx/500.0))*40.0*ropeMod; + vec3 attachmentPoint = vec3(nx, 0.0, nz); + + float xAdd = onoise(vec3(pos.xz/250.0, (time/200.0)+((position.y*ropeMod)/50.0)))*20.0*ropeMod; + float zAdd = onoise(vec3((time/200.0)+((position.y*ropeMod)/50.0), pos.zx/250.0))*20.0*ropeMod; + + float a = pow(percFnlHeight, 0.75); + xAdd *= a; + zAdd *= a; + + torPos += mix(vec3(0), vec3(attachmentPoint.x, 0, attachmentPoint.z), percCos); + torPos += vec3(xAdd, 0, zAdd); + + float torDist = distance(torPos.xz, position.xz); + vec3 localTorPos = position-torPos; + + float widPerc = 1-clamp(torDist/wid, 0.0, 1.0); + float widMaxPerc = clamp(wid/maxWid, 0.0, 1.0); + float rotation = -stormSpin*3; + float rotation2 = -stormSpin/1.5; + + mat2 torSpin = spin(rotation+(torDist/50.0)); + mat2 torSpin2 = spin(rotation2+(torDist/150.0)); + mat2 torSpin3 = spin(rotation2+(torDist/60.0)); + vec3 torSpinPos = vec3(torSpin*localTorPos.xz, position.y-(time/2.0)); + vec3 torSpinPos2 = vec3(torSpin2*localTorPos.xz, position.y-(time/2.0)); + vec3 torSpinPos3 = vec3(torSpin3*localTorPos.xz, position.y-(time/2.0)); + + float nComp1 = fbm(torSpinPos/20.0, 3-octaveReduction, 2.0, 0.5, 1.0); + float nComp2 = fbm(torSpinPos2/40.0, 3-octaveReduction, 2.0, 0.5, 1.0); + + float torNoise1 = mix(nComp1, nComp2, sqrt(widMaxPerc)); + + wid *= mix(0.8 + (torNoise1*0.2), 0.9, clamp(width/1000, 0.0, 1.0)*0.9); + + widPerc = 1-clamp(torDist/wid, 0.0, 1.0); + + tornado *= widPerc; + tornado = pow(tornado, 1.5)*4.0; + tornado *= clamp((position.y-tornadoHeight)/20.0, 0, 1.0); + tornado *= 0.8 + (torNoise1*0.2); + + float dust = 1.0; + float dcNoise1 = fbm(torSpinPos3/20.0, 3-octaveReduction, 2.0, 0.5, 1.0); + + float dcPerc = clamp((windspeed-45.0)/30.0, 0.0, 1.0); + float h = 40.0 + (dcNoise1*15.0); + float dcTop = pos.y+(max(dcPerc, 0.35)*h); + float percDCHeight = clamp((position.y-(pos.y-10.0))/(dcTop-pos.y), 0.0, 1.0); + + wid = ((width/1.5) + ((width/1.5)*percFnlHeight*torPerc) + 25.0) + (25.0*pow(percDCHeight, 1.5)*pow(dcPerc, 0.75)); + wid *= mix(0.6 + (dcNoise1*0.5), 0.85, clamp(width/500, 0.0, 1.0)*0.9); + widPerc = 1-clamp(torDist/wid, 0.0, 1.0); + widPerc = pow(widPerc, 0.25); + v = clamp(torDist/(wid*0.9), 0.0, 1.0); + widPerc *= v*v*v; + dust *= widPerc; + dust = pow(dust, 1.5)*0.15; + dust *= clamp((dcTop-position.y)/20, 0.0, 1.0); + dust *= clamp((position.y-(pos.y-20))/20, 0.0, 1.0); + dust *= 0.8 + (dcNoise1*0.2); + dust *= dcPerc; + dust *= 1.0-clamp((width-50.0)/200.0, 0.0, 1.0); + tornado = max(tornado, dust); + totalDust = max(totalDust, dust); + + clouds = max(clouds, tornado); + } + + size = stormSize*0.75*clamp(smoothStage, 0.0, 1.0); + size *= 1.0 + noise2*0.4; + distPerc = clamp(dist/size, 0.0, 1.0); + coreDistPerc = clamp(coreDist/(size*4.0), 0.0, 1.0); + + clouds = sqrt(clouds)*0.8; + + float rain = step(position.y, baseHeight)*visualOnlyStep; + rain *= 1.0-pow(distPerc, 1.35); + rain *= 0.6 + noise2*0.5; + rain *= clamp(smoothStage, 0.0, 1.0); + rainam += rain*rainStrength*mix(1.0, (occlusion*0.5)+0.5, clamp(smoothStage-2.0, 0.0, 1.0)); + + rain = step(position.y, baseHeight+1300.0)*visualOnlyStep; + rain *= 1.0-pow(coreDistPerc, 1.35); + rain *= 0.6 + noise2*0.5; + rain *= clamp((smoothStage-2.0)*2.0, 0.0, 1.0); + rainam = max(rainam, rain*rainStrength); + } + + totalCloud = max(totalCloud, clouds); + totalRain = max(totalRain, rainam); + } + + if(position.y <= bgCloudBase && bgc > 0.15){ + if(noise2 < -9.0){ + noise2 = fbm(noisePosIT/120.0, 2-octaveReduction, 2.0, 0.3, 1.0); + } + + float rain = (bgc-0.15)*1.65; + rain *= 0.6 + noise2*0.5; + totalRain = max(totalRain, rain*rainStrength); + } + + return CloudReturn(max(max(totalCloud, totalBGCloud), 0.0), max(totalRain, 0.0), max(totalDust, 0.0), max(upperLevel, 0.0)); +} + +vec3 worldPos(vec2 uv, float depth, mat4 invProj){ + vec4 ndc; + ndc.xy = (uv-0.5)*2.0; + ndc.z = (depth-0.5)*2.0; + ndc.w = 1.0; + + vec4 clip = invProj*ndc; + vec4 view = viewmat*(clip/clip.w); + vec3 result = view.xyz; + + return result; +} + +float BeersLaw(float dist, float absorbtion){ + return exp(-dist * absorbtion); +} + +float HenyeyGreenstein(float g, float mu){ + float denom = 1.0 + g * (g - 2.0 * mu); + denom = sqrt(denom * denom * denom); + float heyey = (1.0 - g * g) / (4.0 * 3.14 * denom); + return mix(heyey, 1.0, 0.1); +} + +float lightmarch(vec3 ro, vec3 dir, int steps, float marchSize){ + float totalDensity = 0.0; + + float d0 = 0.0; + for(int i = 0; i < steps; i++){ + vec3 p = ro+(dir*d0); + + CloudReturn d = getClouds(p, 8); + totalDensity += d.cloudDensity*0.005*marchSize; + d0 += marchSize; + marchSize += marchSize/4.0; + + if(BeersLaw(totalDensity, 0.9) < 0.05){ + break; + } + } + + return BeersLaw(totalDensity, 0.9); +} + +Render render(vec2 uv, float maxDepth){ + float light = clamp((sunDir.y+0.1)/0.3, 0, 1); + vec3 ro = pos; + vec3 rd = getRayDir(uv); + float offset = onoise(vec3(uv*800.0, 0))*1; + float density = 0; + float totalRain = 0; + float rainDampen = 0; + + vec4 res = vec4(0.0); + + float d0 = 1.0;//nearPlane; + + float ms = float(maxSteps); + + bool inCloud = false; + int cyclesNotInCloud = 0; + int count = 0; + + float totalTransmittance = 1.0; + float lightEnergy = 0.0; + float lightningEnergy = 0.0; + + float phase = HenyeyGreenstein(0.3, dot(rd, sunDir)); + float moonLight = 0.0; + float moonPhase = 0.0; + + float depth = maxDepth; + + if(sunDir.y < 0.1){ + moonPhase = HenyeyGreenstein(0.3, dot(rd, sunDir*-1.0)); + moonLight = clamp((sunDir.y-0.1)/-0.1, 0.0, 1.0)*0.25; + moonPhase = clamp(moonPhase*moonPhase*moonPhase*4.0, 0.0, 1.0); + } + + for(int i = 0; i < ms; i++){ + vec3 p = ro+(rd*d0); + + if(d0 >= maxDepth || p.y > max(layerCheight+1000.0, layer0height+2000.0) || p.y < -64.0){ + break; + } + + float v = (count+5.0)/60.0; + + float stepMult = v*v*v; + float multiplier = 1.0; + + stepMult /= 2.0; + + if(p.y > layer0height+500.0){ + //stepMult *= 4; + } + + if(quality == 0){ + stepMult *= 4.0; + multiplier = 3.0; + }else if(quality == 1){ + stepMult *= 2.0; + multiplier = 1.5; + }else if(quality == 4){ + stepMult /= 2.0; + multiplier = 0.75; + } + + if(!inCloud){ + stepMult *= 4.0; + } + + float smult = 1.0; + float s = max(((8.0+offset)*smult*stepMult), 0.5); + + float rDist = renderDistance*2.0; + + if(d0 < rDist){ + CloudReturn clouds = getClouds(p, 0); + + clouds.cloudDensity *= multiplier; + clouds.rainDensity *= multiplier; + clouds.dustDensity *= multiplier; + + float sf = (snow*1.5*clamp(1.0-(d0/256.0), 0.0, 1.0)); + float d = clouds.cloudDensity+sf; + float r = clouds.rainDensity; + + d *= clamp(d0/25.0, 0.0, 1.0); + r *= clamp(d0/35.0, 0.0, 1.0); + + d *= 1-clamp((d0-(rDist-1000.0))/1000.0, 0.0, 1.0); + r *= 1-clamp((d0-(rDist-1000.0))/1000.0, 0.0, 1.0); + + float rd = d+(r*0.25*(s/8.0)); + + count++; + + if(rd > 0){ + if(d0 < depth){ + depth = d0; + } + + cyclesNotInCloud = 0; + + if(!inCloud && d > 0){ + inCloud = true; + d0 -= s; + count--; + continue; + } + + density += rd*step(0.1, rd); + totalRain += r; + + if(density > 5.0){ + rd = max(rd, 2.0); + if(r > d){ + r = 1.0; + } + } + + if(d > 0.0 && totalRain < 0.01){ + rainDampen += sqrt(d)*1.25; + } + + float transmittance = mix(1.0, max(light, moonLight)*0.3, clamp(rd*4, 0.0, 1.0)); + + if(light > 0 && d-sf > 0 && totalTransmittance > 0.015 && simpleLighting < 0.5){ + int steps = 0; + + switch(quality){ + case 0: + steps = 2; + break; + + case 1: + steps = 4; + break; + + case 2: + steps = 6; + break; + + case 3: + steps = 8; + break; + + case 4: + steps = 10; + break; + + default: + steps = 4; + break; + } + + transmittance = lightmarch(p, sunDir, steps, (((10.0-float(steps))/10.0)*20.0)+4.0); + } + + if(moonLight > 0 && light <= 0 && d-sf > 0 && totalTransmittance > 0.015 && simpleLighting < 0.5){ + int steps = 0; + + switch(quality){ + case 0: + steps = 2; + break; + + case 1: + steps = 4; + break; + + case 2: + steps = 6; + break; + + case 3: + steps = 8; + break; + + case 4: + steps = 10; + break; + + default: + steps = 4; + break; + } + + transmittance = lightmarch(p, sunDir*-1, steps, (((10.0-float(steps))/10.0)*20.0)+4.0); + } + + if(lightningCount > 0 && totalTransmittance > 0.015){ + for(int j = 0; j < lightningCount; j++){ + vec3 lPos = vec3(lightningStrikes[j*3], layer0height, lightningStrikes[(j*3)+2]); + float bright = lightningBrightness[j]; + float dist = distance(lPos.xz, p.xz); + + float l = pow(1-clamp(dist/750.0, 0.0, 1.0), 2.0)*bright; + + l *= 1-clamp((p.y-lPos.y)/150.0, 0.0, 1.0); + l *= rd; + + lightningEnergy += totalTransmittance * l; + } + } + + float luminance = 1.25 * (pow(d, 0.65)*(clamp(s, 10.0, 35.0)/35.0)) * max(phase, moonPhase); + + vec3 cloudColor = mix(vec3(mix(0.5, 0.2, clamp(rain, 0.0, 1.0))), vec3(0.22, 0.302, 0.278), clamp(r, 0.0, 1.0)); + vec3 dustColor = vec3(0.2, 0.125, 0.071); + float dustP = clamp(pow(clouds.dustDensity, 0.3), 0.0, 1.0); + dustColor = mix(dustColor*2.5, dustColor*0.5, hash(dustP*100.015)); + + cloudColor = mix(cloudColor, dustColor, clamp(pow(clouds.dustDensity, 0.1), 0.0, 1.0)); + cloudColor = mix(cloudColor, skyColor*0.5, 0.6); + if(clouds.dustDensity <= 0.0){ + cloudColor = mix(lightingColor*max(light, moonLight), cloudColor, clamp(pow(d, 0.65)+(r*5.0)+snow, 0, 1)); + } + vec4 col = vec4(mix(cloudColor, vec3(0.0), clamp(rd, 0.0, 1.0))*light, clamp(rd, 0.0, 1.0)); + + col.rgb *= col.a; + + res += col * (1.0-res.a); + + lightEnergy += totalTransmittance * luminance; + totalTransmittance *= transmittance; + + if(totalTransmittance < 0.2){ + totalTransmittance = 0.0; + } + }else{ + if(inCloud){ + cyclesNotInCloud++; + } + + if(cyclesNotInCloud >= 10.0){ + inCloud = false; + cyclesNotInCloud = 0; + } + } + + if(density > 4.0 || rd >= 1.5){ + break; + } + } + + d0 += s; + } + + totalRain -= rainDampen; + + float rv = clamp(1.0-rain, 0.0, 1.0); + return Render(res, clamp(lightEnergy, mix(0.3, 0.1, clamp(max(rain*4, totalRain*0.15), 0, 1)), clamp(density, 0, 1))*max(light, moonLight)*clamp(density, 0, 1)*rv*rv, lightningEnergy, depth); +} + +float getDistFromDepth(float depth, vec2 uv, mat4 invProj) { + if (depth >= 1.0) return 1e10; // for sky + vec3 viewPos = worldPos(uv, depth, invProj); + return length(viewPos); +} + +void main(){ + vec2 uv = texCoord*downsample; + if(uv.x > 1.0 || uv.y > 1.0){ + discard; + } + + float maxTotalRenderDist = hasDHDepth > 0.5 ? max(renderDistance, dhRenderDistance) : renderDistance; + float sceneDistance = maxTotalRenderDist; + float vanillaDepth = texture(DepthSampler, uv).r; + + if(vanillaDepth < 1.0){ + sceneDistance = getDistFromDepth(vanillaDepth, uv, proj); + } + + if(hasDHDepth > 0.5){ + float dhDepth = texture(dhDepthTex0, uv).r; + if(dhDepth < 1.0){ + float dhDistance = getDistFromDepth(dhDepth, uv, inverse(dhProjection)); + sceneDistance = min(sceneDistance, dhDistance); + } + + if(vanillaDepth >= 1.0 && dhDepth >= 1.0){ + sceneDistance = maxTotalRenderDist*4.0; + } + }else{ + if(vanillaDepth >= 1.0){ + sceneDistance = maxTotalRenderDist*4.0; + } + } + + /*vec3 wPos = worldPos(uv, rawDepth); + float depthCirc = min(length(wPos), farPlane); + + if(rawDepth >= 1.0){ + depthCirc = renderDistance*4.0; + }*/ + + sceneDistance = min(sceneDistance, maxTotalRenderDist*4.0); + + Render renderOut = render(uv, sceneDistance); + + fragColor = mix(mix(renderOut.col, vec4(lightingColor, renderOut.col.a), renderOut.lightEnergy*renderOut.col.a), vec4(vec3(1.0), renderOut.col.a), renderOut.lightningEnergy); +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/clouds.json b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/clouds.json new file mode 100644 index 00000000..148f5dca --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/clouds.json @@ -0,0 +1,79 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "blit", + "fragment": "pmweather:clouds", + "attributes": ["Position"], + "samplers": [ + {"name": "DiffuseSampler"}, + {"name": "DepthSampler"}, + {"name": "DHDepthSampler"}, + {"name": "NoiseSampler"}, + {"name": "NoiseSamplerX"}, + {"name": "NoiseSamplerY"}, + {"name": "NoiseSamplerZ"} + ], + "uniforms": [ + {"name": "dhDepthTex0", "type": "int", "count": 1, "values": [7]}, + {"name": "dhDepthTex1", "type": "int", "count": 1, "values": [8]}, + {"name": "hasDHDepth", "type": "float", "count": 1, "values": [0.0]}, + {"name": "dhNearPlane", "type": "float", "count": 1, "values": [0.05]}, + {"name": "dhFarPlane", "type": "float", "count": 1, "values": [1024.0]}, + {"name": "dhRenderDistance", "type": "float", "count": 1, "values": [4096.0]}, + {"name": "dhProjection", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]}, + {"name": "dhProjectionInverse", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]}, + {"name": "dhViewmat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]}, + {"name": "stormCount", "type": "int", "count": 1, "values": [0]}, + {"name": "lightningCount", "type": "int", "count": 1, "values": [0]}, + {"name": "quality", "type": "int", "count": 1, "values": [2]}, + {"name": "maxSteps", "type": "int", "count": 1, "values": [100]}, + {"name": "stepSize", "type": "float", "count": 1, "values": [0.1]}, + {"name": "rainStrength", "type": "float", "count": 1, "values": [1.0]}, + {"name": "nearPlane", "type": "float", "count": 1, "values": [0.05]}, + {"name": "farPlane", "type": "float", "count": 1, "values": [256]}, + {"name": "renderDistance", "type": "float", "count": 1, "values": [8000]}, + {"name": "time", "type": "float", "count": 1, "values": [0.1]}, + {"name": "worldTime", "type": "float", "count": 1, "values": [0.1]}, + {"name": "layer0height", "type": "float", "count": 1, "values": [0.1]}, + {"name": "layerCheight", "type": "float", "count": 1, "values": [0.1]}, + {"name": "rain", "type": "float", "count": 1, "values": [0.1]}, + {"name": "snow", "type": "float", "count": 1, "values": [0.1]}, + {"name": "overcastPerc", "type": "float", "count": 1, "values": [0]}, + {"name": "stormSize", "type": "float", "count": 1, "values": [0.1]}, + {"name": "lightIntensity", "type": "float", "count": 1, "values": [0.1]}, + {"name": "fogStart", "type": "float", "count": 1, "values": [0.1]}, + {"name": "downsample", "type": "float", "count": 1, "values": [0.1]}, + {"name": "fogEnd", "type": "float", "count": 1, "values": [0.1]}, + {"name": "simpleLighting", "type": "float", "count": 1, "values": [0]}, + {"name": "pos", "type": "float", "count": 3, "values": [0.0, 0.0, 0.0]}, + {"name": "sunDir", "type": "float", "count": 3, "values": [0.0, 0.0, 0.0]}, + {"name": "lightingColor", "type": "float", "count": 3, "values": [1.0, 1.0, 1.0]}, + {"name": "skyColor", "type": "float", "count": 3, "values": [1.0, 1.0, 1.0]}, + {"name": "scroll", "type": "float", "count": 2, "values": [0.0, 0.0]}, + {"name": "lightningStrikes", "type": "float", "count": 192, "values": []}, + {"name": "lightningBrightness", "type": "float", "count": 64, "values": []}, + {"name": "stormPositions", "type": "float", "count": 48, "values": []}, + {"name": "stormVelocities", "type": "float", "count": 32, "values": []}, + {"name": "stormStages", "type": "float", "count": 16, "values": []}, + {"name": "visualOnlys", "type": "float", "count": 16, "values": []}, + {"name": "tornadoShapes", "type": "float", "count": 16, "values": []}, + {"name": "stormEnergies", "type": "float", "count": 16, "values": []}, + {"name": "stormOcclusions", "type": "float", "count": 16, "values": []}, + {"name": "stormTypes", "type": "float", "count": 16, "values": []}, + {"name": "tornadoWindspeeds", "type": "float", "count": 16, "values": []}, + {"name": "tornadoWidths", "type": "float", "count": 16, "values": []}, + {"name": "tornadoTouchdownSpeeds", "type": "float", "count": 16, "values": []}, + {"name": "stormSpins", "type": "float", "count": 16, "values": []}, + + {"name": "viewmat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]}, + {"name": "vmat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]}, + {"name": "proj", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]}, + + {"name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]}, + {"name": "OutSize", "type": "float", "count": 2, "values": [ 1.0, 1.0 ]}, + { "name": "_FOV", "type": "float", "count": 1, "values": [ 70.0 ] } + ] +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/downsample.fsh b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/downsample.fsh new file mode 100644 index 00000000..29bc0750 --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/downsample.fsh @@ -0,0 +1,18 @@ +#version 330 + +uniform sampler2D DiffuseSampler; +uniform sampler2D MainSampler; +uniform float downsample; + +in vec2 texCoord; +out vec4 fragColor; + +void main(){ + vec2 uv = texCoord*downsample; + if(uv.x > 1 || uv.y > 1){ + discard; + } + + vec4 texel = texture(MainSampler, uv); + fragColor = vec4(texel.rgb, 1.0); +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/downsample.json b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/downsample.json new file mode 100644 index 00000000..01ebef06 --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/downsample.json @@ -0,0 +1,20 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "blit", + "fragment": "pmweather:blur", + "attributes": ["Position"], + "samplers": [ + {"name": "DiffuseSampler"}, + {"name": "MainSampler"} + ], + "uniforms": [ + {"name": "downsample", "type": "float", "count": 1, "values": [0.1]}, + + {"name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]}, + {"name": "OutSize", "type": "float", "count": 2, "values": [ 1.0, 1.0 ]} + ] +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/smoothing.fsh b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/smoothing.fsh new file mode 100644 index 00000000..e4f04856 --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/smoothing.fsh @@ -0,0 +1,16 @@ +#version 330 + +uniform sampler2D DiffuseSampler; +uniform sampler2D PreviousSampler; + +uniform vec2 OutSize; + +in vec2 texCoord; +out vec4 fragColor; + +void main() { + vec4 texel = texture(DiffuseSampler, texCoord); + vec4 lastTexel = texture(PreviousSampler, texCoord); + + fragColor = mix(lastTexel, texel, 0.3); +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/smoothing.json b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/smoothing.json new file mode 100644 index 00000000..bcb24a1a --- /dev/null +++ b/pmweather_tornado_extraction_export/assets/pmweather/shaders/program/smoothing.json @@ -0,0 +1,18 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "blit", + "fragment": "pmweather:smoothing", + "attributes": ["Position"], + "samplers": [ + {"name": "DiffuseSampler"}, + {"name": "PreviousSampler"} + ], + "uniforms": [ + {"name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]}, + {"name": "OutSize", "type": "float", "count": 2, "values": [ 1.0, 1.0 ]} + ] +} \ No newline at end of file diff --git a/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/mist.png b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/mist.png new file mode 100644 index 00000000..51b50233 Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/mist.png differ diff --git a/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/rain.png b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/rain.png new file mode 100644 index 00000000..ac8b8730 Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/rain.png differ diff --git a/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/sleet.png b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/sleet.png new file mode 100644 index 00000000..2b856e57 Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/sleet.png differ diff --git a/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow.png b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow.png new file mode 100644 index 00000000..c2104c5f Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow.png differ diff --git a/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow1.png b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow1.png new file mode 100644 index 00000000..aa83c3c6 Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow1.png differ diff --git a/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow2.png b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow2.png new file mode 100644 index 00000000..c7c1c85c Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow2.png differ diff --git a/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow3.png b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow3.png new file mode 100644 index 00000000..9fd8a0d3 Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/snow3.png differ diff --git a/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/splash.png b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/splash.png new file mode 100644 index 00000000..dbbf1904 Binary files /dev/null and b/pmweather_tornado_extraction_export/assets/pmweather/textures/particle/splash.png differ diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/compat/DistantHorizons.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/compat/DistantHorizons.java new file mode 100644 index 00000000..e8ad76ea --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/compat/DistantHorizons.java @@ -0,0 +1,70 @@ +package dev.protomanly.pmweather.compat; + +import dev.protomanly.pmweather.PMWeather; +import org.joml.Matrix4f; + +public class DistantHorizons { + private static boolean initialized = false; + private static boolean dhPresent = false; + private static DistantHorizonsHandler handler = null; + private static final int DEFAULT_DEPTH_TEXTURE_ID = -1; + private static final Matrix4f DEFAULT_MATRIX = new Matrix4f(); + private static final float DEFAULT_NEAR_PLANE = 0.05F; + private static final float DEFAULT_FAR_PLANE = 1024.0F; + private static final int DEFAULT_RENDER_DISTANCE = 256; + + public DistantHorizons() { + super(); + } + + public static void initialize() { + if (!initialized) { + initialized = true; + + try { + Class.forName("com.seibel.distanthorizons.api.DhApi"); + handler = new DistantHorizonsHandler(); + handler.initialize(); + dhPresent = true; + PMWeather.LOGGER.info("Distant Horizons compatibility initialized"); + } catch (NoClassDefFoundError | ClassNotFoundException var1) { + PMWeather.LOGGER.info("Distant Horizons not found, skipping integration"); + dhPresent = false; + handler = null; + } catch (Exception e) { + PMWeather.LOGGER.error("Failed to initialize Distant Horizons compatibility", e); + dhPresent = false; + handler = null; + } + + } + } + + public static boolean isAvailable() { + return dhPresent && handler != null && handler.isReady(); + } + + public static int getDepthTextureId() { + return isAvailable() ? handler.getDepthTextureId() : -1; + } + + public static Matrix4f getDhProjectionMatrix() { + return isAvailable() ? handler.getDhProjectionMatrix() : new Matrix4f(DEFAULT_MATRIX); + } + + public static Matrix4f getDhModelViewMatrix() { + return isAvailable() ? handler.getDhModelViewMatrix() : new Matrix4f(DEFAULT_MATRIX); + } + + public static float getNearPlane() { + return isAvailable() ? handler.getNearPlane() : 0.05F; + } + + public static float getFarPlane() { + return isAvailable() ? handler.getFarPlane() : 1024.0F; + } + + public static int getChunkRenderDistance() { + return isAvailable() ? handler.getChunkRenderDistance() : 256; + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/compat/DistantHorizonsHandler.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/compat/DistantHorizonsHandler.java new file mode 100644 index 00000000..390e5cba --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/compat/DistantHorizonsHandler.java @@ -0,0 +1,115 @@ +package dev.protomanly.pmweather.compat; + +import com.seibel.distanthorizons.api.DhApi; +import com.seibel.distanthorizons.api.DhApi.Delayed; +import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiAfterDhInitEvent; +import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderPassEvent; +import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderSetupEvent; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; +import com.seibel.distanthorizons.api.objects.DhApiResult; +import dev.protomanly.pmweather.PMWeather; +import org.joml.Matrix4f; + +public class DistantHorizonsHandler { + private boolean dhReady = false; + private int dhDepthTextureId = -1; + private Matrix4f dhProjectionMatrix = new Matrix4f(); + private Matrix4f dhModelViewMatrix = new Matrix4f(); + private float dhNearPlane = 0.05F; + private float dhFarPlane = 1024.0F; + private int dhRenderDistance = 256; + + public DistantHorizonsHandler() { + super(); + } + + public void initialize() { + try { + this.registerEventHandlers(); + } catch (Exception e) { + PMWeather.LOGGER.error("Failed to register DH event handlers", e); + throw e; + } + } + + private void registerEventHandlers() { + DhApi.events.bind(DhApiAfterDhInitEvent.class, new DhApiAfterDhInitEvent() { + public void afterDistantHorizonsInit(DhApiEventParam event) { + PMWeather.LOGGER.info("Distant Horizons initialized"); + DistantHorizonsHandler.this.dhReady = true; + + try { + DistantHorizonsHandler.this.dhRenderDistance = (Integer)Delayed.configs.graphics().chunkRenderDistance().getValue(); + } catch (Exception e) { + PMWeather.LOGGER.warn("Failed to get DH render distance, using default", e); + DistantHorizonsHandler.this.dhRenderDistance = 256; + } + + } + }); + DhApi.events.bind(DhApiBeforeRenderPassEvent.class, new DhApiBeforeRenderPassEvent() { + public void beforeRender(DhApiEventParam event) { + DistantHorizonsHandler.this.captureRenderState((DhApiRenderParam)event.value); + } + }); + DhApi.events.bind(DhApiBeforeRenderSetupEvent.class, new DhApiBeforeRenderSetupEvent() { + public void beforeSetup(DhApiEventParam event) { + DistantHorizonsHandler.this.captureRenderState((DhApiRenderParam)event.value); + } + }); + } + + private void captureRenderState(DhApiRenderParam param) { + try { + DhApiResult depthResult = Delayed.renderProxy.getDhDepthTextureId(); + if (depthResult.success && (Integer)depthResult.payload > 0) { + this.dhDepthTextureId = (Integer)depthResult.payload; + PMWeather.LOGGER.debug("Captured DH depth texture: " + this.dhDepthTextureId); + } + + float[] projValues = param.dhProjectionMatrix.getValuesAsArray(); + float[] mvValues = param.dhModelViewMatrix.getValuesAsArray(); + this.dhProjectionMatrix.set(projValues[0], projValues[4], projValues[8], projValues[12], projValues[1], projValues[5], projValues[9], projValues[13], projValues[2], projValues[6], projValues[10], projValues[14], projValues[3], projValues[7], projValues[11], projValues[15]); + this.dhModelViewMatrix.set(mvValues[0], mvValues[4], mvValues[8], mvValues[12], mvValues[1], mvValues[5], mvValues[9], mvValues[13], mvValues[2], mvValues[6], mvValues[10], mvValues[14], mvValues[3], mvValues[7], mvValues[11], mvValues[15]); + this.dhNearPlane = param.nearClipPlane; + this.dhFarPlane = param.farClipPlane; + + try { + this.dhRenderDistance = (Integer)Delayed.configs.graphics().chunkRenderDistance().getValue(); + } catch (Exception var6) { + } + } catch (Exception e) { + PMWeather.LOGGER.error("Failed to capture DH render state", e); + } + + } + + public boolean isReady() { + return this.dhReady && this.dhDepthTextureId > 0; + } + + public int getDepthTextureId() { + return this.dhDepthTextureId; + } + + public Matrix4f getDhProjectionMatrix() { + return new Matrix4f(this.dhProjectionMatrix); + } + + public Matrix4f getDhModelViewMatrix() { + return new Matrix4f(this.dhModelViewMatrix); + } + + public float getNearPlane() { + return this.dhNearPlane; + } + + public float getFarPlane() { + return this.dhFarPlane; + } + + public int getChunkRenderDistance() { + return this.dhRenderDistance; + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/entity/MovingBlock.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/entity/MovingBlock.java new file mode 100644 index 00000000..f6ae1cb8 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/entity/MovingBlock.java @@ -0,0 +1,153 @@ +package dev.protomanly.pmweather.entity; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.MoverType; +import net.minecraft.world.entity.Entity.MovementEmission; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap.Types; +import net.minecraft.world.phys.Vec3; + +public class MovingBlock extends Entity { + public static EntityDataAccessor DATA_START_POS; + public static EntityDataAccessor DATA_BLOCK_STATE; + + public MovingBlock(EntityType entityType, Level level) { + super(entityType, level); + this.setBlockState(Blocks.STONE.defaultBlockState()); + this.setStartPos(BlockPos.ZERO); + } + + public MovingBlock(EntityType entityType, Level level, BlockState blockstate, BlockPos startPos) { + super(entityType, level); + this.setBlockState(blockstate); + this.setStartPos(startPos); + } + + public void tick() { + super.tick(); + this.applyGravity(); + this.move(MoverType.SELF, this.getDeltaMovement()); + Vec3 motion = this.getDeltaMovement(); + if (!this.level().isClientSide()) { + if (this.tickCount > 3600 || this.level().getNearestPlayer(this.getX(), this.getY(), this.getZ(), (double)96.0F, false) == null) { + this.discard(); + return; + } + + if (this.onGround() && this.tickCount > 40) { + if (this.level().getBlockState(this.blockPosition()).isAir()) { + this.level().setBlockAndUpdate(this.blockPosition(), this.getBlockState()); + } + + this.discard(); + } + } + + if (this.onGround()) { + BlockPos c = this.blockPosition(); + BlockPos n = c.north(2); + BlockPos e = c.east(2); + BlockPos s = c.south(2); + BlockPos w = c.west(2); + n = this.level().getHeightmapPos(Types.MOTION_BLOCKING, n); + e = this.level().getHeightmapPos(Types.MOTION_BLOCKING, e); + s = this.level().getHeightmapPos(Types.MOTION_BLOCKING, s); + w = this.level().getHeightmapPos(Types.MOTION_BLOCKING, w); + if (n.getY() < c.getY()) { + c = n; + } + + if (e.getY() < c.getY()) { + c = e; + } + + if (s.getY() < c.getY()) { + c = s; + } + + if (w.getY() < c.getY()) { + c = w; + } + + Vec3 off = this.getPosition(1.0F).subtract(c.getCenter()).multiply((double)1.0F, (double)0.0F, (double)1.0F); + motion = motion.add(off.multiply((double)0.05F, (double)0.0F, (double)0.05F).multiply((double)1.0F, (double)0.0F, (double)1.0F)); + } + + this.setDeltaMovement(motion.multiply((double)0.99F, (double)0.99F, (double)0.99F)); + } + + public void setStartPos(BlockPos pos) { + this.entityData.set(DATA_START_POS, pos); + } + + public BlockPos getStartPos() { + return (BlockPos)this.entityData.get(DATA_START_POS); + } + + public void setBlockState(BlockState state) { + this.entityData.set(DATA_BLOCK_STATE, state); + } + + public BlockState getBlockState() { + return (BlockState)this.entityData.get(DATA_BLOCK_STATE); + } + + protected Entity.MovementEmission getMovementEmission() { + return MovementEmission.NONE; + } + + public boolean canBeCollidedWith() { + return false; + } + + public boolean shouldRenderAtSqrDistance(double distance) { + return distance < (double)327680.0F; + } + + public boolean isAttackable() { + return false; + } + + public boolean isPickable() { + return false; + } + + protected double getDefaultGravity() { + return 0.04; + } + + public boolean causeFallDamage(float fallDistance, float multiplier, DamageSource source) { + return false; + } + + protected void defineSynchedData(SynchedEntityData.Builder builder) { + builder.define(DATA_START_POS, BlockPos.ZERO); + builder.define(DATA_BLOCK_STATE, Blocks.STONE.defaultBlockState()); + } + + protected void readAdditionalSaveData(CompoundTag compoundTag) { + this.setBlockState(NbtUtils.readBlockState(this.level().holderLookup(Registries.BLOCK), compoundTag.getCompound("blockstate"))); + this.setStartPos((BlockPos)NbtUtils.readBlockPos(compoundTag, "startPos").orElse(BlockPos.ZERO)); + } + + protected void addAdditionalSaveData(CompoundTag compoundTag) { + compoundTag.put("blockstate", NbtUtils.writeBlockState(this.getBlockState())); + compoundTag.put("startPos", NbtUtils.writeBlockPos(this.getStartPos())); + } + + static { + DATA_START_POS = SynchedEntityData.defineId(MovingBlock.class, EntityDataSerializers.BLOCK_POS); + DATA_BLOCK_STATE = SynchedEntityData.defineId(MovingBlock.class, EntityDataSerializers.BLOCK_STATE); + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/entity/client/MovingBlockRenderer.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/entity/client/MovingBlockRenderer.java new file mode 100644 index 00000000..5aec95c7 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/entity/client/MovingBlockRenderer.java @@ -0,0 +1,61 @@ +package dev.protomanly.pmweather.entity.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import dev.protomanly.pmweather.entity.MovingBlock; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.BlockRenderDispatcher; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.client.RenderTypeHelper; +import net.neoforged.neoforge.client.model.data.ModelData; +import org.jetbrains.annotations.NotNull; + +public class MovingBlockRenderer extends EntityRenderer { + private final BlockRenderDispatcher dispatcher; + + public MovingBlockRenderer(EntityRendererProvider.Context context) { + super(context); + this.shadowRadius = 0.5F; + this.dispatcher = context.getBlockRenderDispatcher(); + } + + public void render(MovingBlock entity, float entityYaw, float partialTicks, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) { + BlockState blockstate = entity.getBlockState(); + float age = ((float)entity.tickCount + partialTicks) * 5.0F; + if (blockstate.getRenderShape() == RenderShape.MODEL) { + Level level = entity.level(); + if (blockstate != level.getBlockState(entity.blockPosition()) && blockstate.getRenderShape() != RenderShape.INVISIBLE) { + poseStack.pushPose(); + BlockPos pos = BlockPos.containing(entity.getX(), entity.getBoundingBox().maxY, entity.getZ()); + poseStack.mulPose(Axis.XP.rotationDegrees(age * 2.0F)); + poseStack.mulPose(Axis.YP.rotationDegrees(age * 2.0F)); + poseStack.mulPose(Axis.ZP.rotationDegrees(age * 2.0F)); + poseStack.translate((double)-0.5F, (double)0.0F, (double)-0.5F); + BakedModel model = this.dispatcher.getBlockModel(blockstate); + + for(RenderType renderType : model.getRenderTypes(blockstate, RandomSource.create(blockstate.getSeed(entity.getStartPos())), ModelData.EMPTY)) { + this.dispatcher.getModelRenderer().tesselateBlock(level, model, blockstate, pos, poseStack, bufferSource.getBuffer(RenderTypeHelper.getMovingBlockRenderType(renderType)), false, RandomSource.create(), blockstate.getSeed(entity.getStartPos()), OverlayTexture.NO_OVERLAY, ModelData.EMPTY, renderType); + } + + poseStack.popPose(); + super.render(entity, entityYaw, partialTicks, poseStack, bufferSource, packedLight); + } + } + + } + + public @NotNull ResourceLocation getTextureLocation(@NotNull MovingBlock movingBlock) { + return TextureAtlas.LOCATION_BLOCKS; + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/event/GameBusClientEvents.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/event/GameBusClientEvents.java new file mode 100644 index 00000000..0a8bc470 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/event/GameBusClientEvents.java @@ -0,0 +1,522 @@ +package dev.protomanly.pmweather.event; + +import com.mojang.blaze3d.systems.RenderSystem; +import dev.protomanly.pmweather.PMWeather; +import dev.protomanly.pmweather.config.ClientConfig; +import dev.protomanly.pmweather.config.ServerConfig; +import dev.protomanly.pmweather.interfaces.ParticleData; +import dev.protomanly.pmweather.networking.ModNetworking; +import dev.protomanly.pmweather.particle.EntityRotFX; +import dev.protomanly.pmweather.particle.ParticleCube; +import dev.protomanly.pmweather.particle.ParticleManager; +import dev.protomanly.pmweather.particle.ParticleRegistry; +import dev.protomanly.pmweather.particle.ParticleTexExtraRender; +import dev.protomanly.pmweather.particle.ParticleTexFX; +import dev.protomanly.pmweather.particle.behavior.ParticleBehavior; +import dev.protomanly.pmweather.shaders.ModShaders; +import dev.protomanly.pmweather.sound.ModSounds; +import dev.protomanly.pmweather.util.ChunkCoordinatesBlock; +import dev.protomanly.pmweather.weather.ThermodynamicEngine; +import dev.protomanly.pmweather.weather.WeatherHandler; +import dev.protomanly.pmweather.weather.WeatherHandlerClient; +import dev.protomanly.pmweather.weather.WindEngine; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap.Types; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.common.EventBusSubscriber.Bus; +import net.neoforged.neoforge.client.event.ClientTickEvent; +import net.neoforged.neoforge.client.event.RenderLevelStageEvent; +import net.neoforged.neoforge.client.event.ViewportEvent; +import net.neoforged.neoforge.client.event.RenderLevelStageEvent.Stage; + +@EventBusSubscriber( + modid = "pmweather", + bus = Bus.GAME, + value = {Dist.CLIENT} +) +public class GameBusClientEvents { + public static Level lastLevel; + public static WeatherHandler weatherHandler; + public static ParticleManager particleManager; + public static ParticleManager particleManagerDebris; + public static ParticleBehavior particleBehavior = new ParticleBehavior((Vec3)null); + public static List LEAVES_BLOCKS = new ArrayList() { + { + this.add(Blocks.ACACIA_LEAVES); + this.add(Blocks.AZALEA_LEAVES); + this.add(Blocks.BIRCH_LEAVES); + this.add(Blocks.DARK_OAK_LEAVES); + this.add(Blocks.CHERRY_LEAVES); + this.add(Blocks.FLOWERING_AZALEA_LEAVES); + this.add(Blocks.MANGROVE_LEAVES); + this.add(Blocks.OAK_LEAVES); + this.add(Blocks.JUNGLE_LEAVES); + this.add(Blocks.SPRUCE_LEAVES); + } + }; + public static ArrayList soundLocations = new ArrayList(); + public static HashMap soundTimeLocations = new HashMap(); + public static long lastAmbientTick; + public static long lastAmbientTickThreaded; + public static long lastWindSoundTick; + + public GameBusClientEvents() { + super(); + } + + @SubscribeEvent + public static void fogEvent(ViewportEvent.RenderFog event) { + Minecraft minecraft = Minecraft.getInstance(); + Level level = minecraft.level; + if (level != null && ClientConfig.baseGameFog) { + RenderSystem.setShaderFogStart(10000.0F); + RenderSystem.setShaderFogEnd(40000.0F); + } + + } + + @SubscribeEvent + public static void onStageRenderTick(RenderLevelStageEvent event) { + if (event.getStage() == Stage.AFTER_PARTICLES && weatherHandler != null) { + particleManagerDebris.render(event.getPoseStack(), (MultiBufferSource.BufferSource)null, Minecraft.getInstance().gameRenderer.lightTexture(), event.getCamera(), event.getPartialTick().getGameTimeDeltaPartialTick(false), event.getFrustum()); + } + + } + + public static void doSnowParticles(float precip, Minecraft minecraft, Level level) { + int spawnsNeeded = (int)(precip * 80.0F); + int spawns = 0; + int spawnAreaSize = 50; + + for(int i = 0; i < ClientConfig.rainParticleDensity; ++i) { + BlockPos pos = minecraft.player.blockPosition().offset(PMWeather.RANDOM.nextInt(spawnAreaSize) - spawnAreaSize / 2, -5 + PMWeather.RANDOM.nextInt(25), PMWeather.RANDOM.nextInt(spawnAreaSize) - spawnAreaSize / 2); + if (canPrecipitateAt(level, pos)) { + TextureAtlasSprite var10000; + switch (PMWeather.RANDOM.nextInt(4)) { + case 1 -> var10000 = ParticleRegistry.snow1; + case 2 -> var10000 = ParticleRegistry.snow2; + case 3 -> var10000 = ParticleRegistry.snow3; + default -> var10000 = ParticleRegistry.snow; + } + + TextureAtlasSprite particle = var10000; + ParticleTexExtraRender snow = new ParticleTexExtraRender((ClientLevel)level, (double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)0.0F, (double)0.0F, (double)0.0F, particle); + snow.fullAlphaTarget = 1.0F; + snow.renderOrder = 3; + particleBehavior.initParticleSnow(snow, Math.max((int)(5.0F * precip), 1), (float)(WindEngine.getWind(pos, level, false, false, true).length() / (double)45.0F)); + snow.setScale(Math.max(precip * 0.08F + (PMWeather.RANDOM.nextFloat() - PMWeather.RANDOM.nextFloat()) * 0.02F, 0.01F)); + snow.windWeight = 0.15F; + snow.renderOrder = 3; + snow.spawnAsWeatherEffect(); + ++spawns; + if (spawns > spawnsNeeded) { + break; + } + } + } + + } + + public static void doSleetParticles(float precip, Minecraft minecraft, Level level) { + int spawnsNeeded = (int)(precip * 300.0F); + int spawns = 0; + int spawnAreaSize = 30; + + for(int i = 0; i < ClientConfig.rainParticleDensity; ++i) { + BlockPos pos = minecraft.player.blockPosition().offset(PMWeather.RANDOM.nextInt(spawnAreaSize) - spawnAreaSize / 2, -5 + PMWeather.RANDOM.nextInt(25), PMWeather.RANDOM.nextInt(spawnAreaSize) - spawnAreaSize / 2); + if (canPrecipitateAt(level, pos)) { + ParticleTexExtraRender sleet = new ParticleTexExtraRender((ClientLevel)level, (double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)0.0F, (double)0.0F, (double)0.0F, ParticleRegistry.sleet); + sleet.fullAlphaTarget = 1.0F; + sleet.renderOrder = 3; + particleBehavior.initParticleSleet(sleet, Math.max((int)(20.0F * precip), 1)); + sleet.setScale(Math.max(precip * 0.08F + (PMWeather.RANDOM.nextFloat() - PMWeather.RANDOM.nextFloat()) * 0.02F, 0.02F) * 0.3F); + sleet.renderOrder = 3; + sleet.spawnAsWeatherEffect(); + ++spawns; + if (spawns > spawnsNeeded) { + break; + } + } + } + + } + + public static void doRainParticles(float precip, Minecraft minecraft, Level level) { + int spawnsNeeded = (int)(precip * 300.0F); + int spawns = 0; + int spawnAreaSize = 30; + double windspeed = (double)0.0F; + if (weatherHandler != null) { + windspeed = WindEngine.getWind(minecraft.player.position(), level, false, false, false, true).length(); + } + + for(int i = 0; i < ClientConfig.rainParticleDensity; ++i) { + BlockPos pos = minecraft.player.blockPosition().offset(PMWeather.RANDOM.nextInt(spawnAreaSize) - spawnAreaSize / 2, -5 + PMWeather.RANDOM.nextInt(25), PMWeather.RANDOM.nextInt(spawnAreaSize) - spawnAreaSize / 2); + if (canPrecipitateAt(level, pos)) { + ParticleTexExtraRender rain = new ParticleTexExtraRender((ClientLevel)level, (double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)0.0F, (double)0.0F, (double)0.0F, ParticleRegistry.rain); + rain.fullAlphaTarget = Mth.lerp(precip, 0.3F, 1.0F); + rain.renderOrder = 3; + particleBehavior.initParticleRain(rain, Math.max((int)(20.0F * precip), 1)); + if (windspeed > (double)50.0F && i < ClientConfig.rainParticleDensity / 3) { + float strength = precip * (float)Math.clamp((windspeed - (double)50.0F) / (double)50.0F, (double)0.0F, (double)1.0F); + ParticleTexExtraRender mist = new ParticleTexExtraRender((ClientLevel)level, (double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)0.0F, (double)0.0F, (double)0.0F, ParticleRegistry.mist); + mist.fullAlphaTarget = Mth.lerp(strength, 0.3F, 1.0F); + mist.renderOrder = 4; + particleBehavior.initParticleRain(mist, Math.max((int)(5.0F * strength), 1)); + mist.setScale(0.5F + strength); + mist.setColor(0.9F, 0.9F, 0.9F); + mist.setGravity(0.5F); + } + + ++spawns; + if (spawns > spawnsNeeded) { + break; + } + } + } + + spawnAreaSize = 40; + + for(int i = 0; (float)i < (float)(ClientConfig.rainParticleDensity * 3) * precip; ++i) { + BlockPos pos = minecraft.player.blockPosition().offset(PMWeather.RANDOM.nextInt(spawnAreaSize) - spawnAreaSize / 2, -5 + PMWeather.RANDOM.nextInt(25), PMWeather.RANDOM.nextInt(spawnAreaSize) - spawnAreaSize / 2); + pos = level.getHeightmapPos(Types.MOTION_BLOCKING, pos).below(); + BlockState state = level.getBlockState(pos); + double maxY = (double)0.0F; + double minY = (double)0.0F; + VoxelShape shape = state.getShape(level, pos); + if (!shape.isEmpty()) { + minY = shape.bounds().minY; + maxY = shape.bounds().maxY; + } + + if (!(pos.distSqr(minecraft.player.blockPosition()) > (double)spawnAreaSize / (double)2.0F * ((double)spawnAreaSize / (double)2.0F)) && canPrecipitateAt(level, pos.above())) { + if (level.getBlockState(pos).getBlock().defaultMapColor() == MapColor.WATER) { + pos = pos.offset(0, 1, 0); + } + + ParticleTexFX rain = new ParticleTexFX((ClientLevel)level, (double)((float)pos.getX() + PMWeather.RANDOM.nextFloat()), (double)pos.getY() + 0.01 + maxY, (double)((float)pos.getZ() + PMWeather.RANDOM.nextFloat()), (double)0.0F, (double)0.0F, (double)0.0F, ParticleRegistry.splash); + rain.fullAlphaTarget = Mth.lerp(precip, 0.2F, 0.8F) / 2.0F; + rain.renderOrder = 5; + particleBehavior.initParticleGroundSplash(rain); + rain.spawnAsWeatherEffect(); + } + } + + } + + @SubscribeEvent + public static void onTick(ClientTickEvent.Pre event) { + Minecraft minecraft = Minecraft.getInstance(); + Level level = minecraft.level; + if (level != null && !minecraft.isPaused()) { + getClientWeather(); + tryAmbientSounds(); + trySounds(); + weatherHandler.tick(); + particleManager.tick(); + particleManagerDebris.tick(); + ModShaders.tick(); + WeatherHandlerClient weatherHandlerClient = (WeatherHandlerClient)weatherHandler; + if (minecraft.player != null) { + Entity entity = minecraft.player; + Vec3 w = WindEngine.getWind(entity.getPosition(1.0F), level, false, true, false); + if (w.length() > (double)60.0F && !minecraft.player.isCreative() && !minecraft.player.isSpectator()) { + double factor = Mth.lerp(Math.clamp(w.length() / (double)125.0F, (double)0.0F, (double)1.0F), 0.005, 0.02); + float mult = 0.65F; + if (!entity.onGround()) { + mult = 0.15F; + } + + entity.addDeltaMovement(w.multiply((double)0.05F, (double)0.0F, (double)0.05F).multiply(factor, (double)0.0F, factor).multiply((double)mult, (double)mult, (double)mult)); + } + + minecraft.particleEngine.iterateParticles((particle) -> { + if (particle instanceof ParticleData particleData) { + boolean affect = true; + if (particle instanceof EntityRotFX entityRotFX) { + affect = !entityRotFX.ignoreWind; + } + + if (affect) { + Vec3 wind = WindEngine.getWind(particle.getPos(), level, false, false, false); + particleData.addVelocity(wind.multiply((double)0.05F, (double)0.05F, (double)0.05F).multiply((double)0.04F, (double)0.04F, (double)0.04F)); + double l = wind.length() * 0.01; + if (particleData.getVelocity().length() < l) { + particleData.setVelocity(particleData.getVelocity().normalize().multiply(l, l, l)); + } + } + } + + }); + particleManager.getParticles().forEach((particleRenderType, particles) -> { + for(Particle particle : particles) { + if (particle instanceof ParticleData particleData) { + float affect = 1.0F; + if (particle instanceof EntityRotFX entityRotFX) { + if (entityRotFX.ignoreWind) { + affect = 0.0F; + } else { + affect = entityRotFX.windWeight; + } + } + + if (affect > 0.0F) { + Vec3 wind = WindEngine.getWind(particle.getPos(), level, false, false, false); + particleData.addVelocity(wind.multiply((double)0.05F, (double)0.05F, (double)0.05F).multiply((double)0.04F, (double)0.04F, (double)0.04F).multiply((double)affect, (double)affect, (double)affect)); + double l = wind.length() * 0.01; + if (particleData.getVelocity().length() < l) { + particleData.setVelocity(particleData.getVelocity().normalize().multiply(l, l, l)); + } + } + } + } + + }); + particleManagerDebris.getParticles().forEach((particleRenderType, particles) -> { + for(Particle particle : particles) { + if (particle instanceof ParticleData particleData) { + float affect = 1.0F; + if (particle instanceof EntityRotFX entityRotFX) { + if (entityRotFX.ignoreWind) { + affect = 0.0F; + } else { + affect = entityRotFX.windWeight; + } + } + + if (affect > 0.0F) { + Vec3 wind = WindEngine.getWind(particle.getPos(), level, false, false, false); + particleData.addVelocity(wind.multiply((double)0.05F, (double)0.05F, (double)0.05F).multiply((double)0.04F, (double)0.04F, (double)0.04F).multiply((double)affect, (double)affect, (double)affect)); + } + } + } + + }); + float hail = weatherHandlerClient.getHail(); + float precip = weatherHandlerClient.getPrecipitation(); + if (precip > 0.0F) { + ThermodynamicEngine.Precipitation precipType = ThermodynamicEngine.getPrecipitationType(weatherHandlerClient, minecraft.player.position(), level, 0); + if (precipType == ThermodynamicEngine.Precipitation.RAIN || precipType == ThermodynamicEngine.Precipitation.FREEZING_RAIN || precipType == ThermodynamicEngine.Precipitation.WINTRY_MIX) { + doRainParticles(precip, minecraft, level); + } + + if (precipType == ThermodynamicEngine.Precipitation.SLEET || precipType == ThermodynamicEngine.Precipitation.WINTRY_MIX) { + doSleetParticles(precip, minecraft, level); + } + + if (precipType == ThermodynamicEngine.Precipitation.SNOW || precipType == ThermodynamicEngine.Precipitation.WINTRY_MIX) { + doSnowParticles(precip, minecraft, level); + } + } + + if (hail > 0.0F) { + int spawnsNeeded = (int)(hail * 80.0F); + int spawns = 0; + int spawnAreaSize = 30; + + for(int i = 0; i < 15; ++i) { + BlockPos pos = minecraft.player.blockPosition().offset(PMWeather.RANDOM.nextInt(spawnAreaSize) - spawnAreaSize / 2, -5 + PMWeather.RANDOM.nextInt(25), PMWeather.RANDOM.nextInt(spawnAreaSize) - spawnAreaSize / 2); + if (canPrecipitateAt(level, pos)) { + ParticleCube hailP = new ParticleCube((ClientLevel)level, (double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)0.0F, (double)0.0F, (double)0.0F, Blocks.PACKED_ICE.defaultBlockState()); + particleBehavior.initParticleHail(hailP); + hailP.setScale(0.01F + PMWeather.RANDOM.nextFloat() * hail * 0.08F); + hailP.renderOrder = 3; + hailP.spawnAsDebrisEffect(); + ++spawns; + if (spawns >= spawnsNeeded) { + break; + } + } + } + } + } + } + + } + + public static boolean canPrecipitateAt(Level level, BlockPos pos) { + if ((double)pos.getY() > ServerConfig.layer0Height) { + return false; + } else { + return level.getHeightmapPos(Types.MOTION_BLOCKING, pos).getY() <= pos.getY(); + } + } + + public static void resetClientWeather() { + weatherHandler = null; + } + + public static WeatherHandlerClient getClientWeather() { + try { + Level level = Minecraft.getInstance().level; + if (weatherHandler == null || level != lastLevel) { + init(level); + } + } catch (Exception e) { + PMWeather.LOGGER.error(e.getMessage(), e); + } + + return (WeatherHandlerClient)weatherHandler; + } + + public static void trySounds() { + try { + Minecraft minecraft = Minecraft.getInstance(); + Level level = minecraft.level; + Player player = minecraft.player; + if (player == null || level == null) { + return; + } + + float hail = ((WeatherHandlerClient)weatherHandler).getHail(); + if (hail > 0.0F) { + int chance = (int)Mth.lerp(hail, 20.0F, 2.0F); + if (PMWeather.RANDOM.nextInt(chance) == 0) { + BlockPos pos = player.blockPosition().offset(PMWeather.RANDOM.nextInt(-15, 16), 15, PMWeather.RANDOM.nextInt(-15, 16)); + pos = level.getHeightmapPos(Types.MOTION_BLOCKING, pos); + if (canPrecipitateAt(level, pos) && pos.distSqr(player.blockPosition()) < (double)225.0F) { + level.playLocalSound(pos, (SoundEvent)ModSounds.HAIL.value(), SoundSource.WEATHER, hail * 3.5F, 2.0F + PMWeather.RANDOM.nextFloat() * 0.5F, false); + } + } + } + + if (lastWindSoundTick < System.currentTimeMillis()) { + lastWindSoundTick = System.currentTimeMillis() + 4000L + (long)PMWeather.RANDOM.nextInt(0, 3000); + Vec3 wind = WindEngine.getWind(player.position(), level); + double windspeed = wind.length(); + if (windspeed > (double)55.0F) { + ModSounds.playPlayerLockedSound(player.position(), (SoundEvent)ModSounds.WIND_STRONG.value(), (float)(windspeed / (double)200.0F), 0.9F + PMWeather.RANDOM.nextFloat() * 0.2F); + } + + if (windspeed > (double)35.0F) { + ModSounds.playPlayerLockedSound(player.position(), (SoundEvent)ModSounds.WIND_MED.value(), (float)(windspeed / (double)200.0F), 0.9F + PMWeather.RANDOM.nextFloat() * 0.2F); + } + + if (windspeed > (double)5.0F) { + ModSounds.playPlayerLockedSound(player.position(), (SoundEvent)ModSounds.WIND_CALM.value(), Math.min((float)(windspeed / (double)100.0F), 0.1F), 0.9F + PMWeather.RANDOM.nextFloat() * 0.2F); + } + } + + if (lastAmbientTick < System.currentTimeMillis()) { + lastAmbientTick = System.currentTimeMillis() + 500L; + int size = 32; + int hSize = size / 2; + BlockPos curBlockPos = player.blockPosition(); + + for(int i = 0; i < soundLocations.size(); ++i) { + ChunkCoordinatesBlock chunkCoord = soundLocations.get(i); + if (Math.sqrt(chunkCoord.distSqr(curBlockPos)) > (double)size) { + soundLocations.remove(i--); + soundTimeLocations.remove(chunkCoord); + } else { + Block block = level.getBlockState(chunkCoord).getBlock(); + if (block != null && (block.defaultMapColor() == MapColor.WATER || block.defaultMapColor() == MapColor.PLANT)) { + long lastPlayTime = 0L; + float soundMuffle = 0.6F; + if (soundTimeLocations.containsKey(chunkCoord)) { + lastPlayTime = (Long)soundTimeLocations.get(chunkCoord); + } + + float maxLeavesVolume = 1.0F; + soundMuffle *= (float)ClientConfig.leavesVolume; + if (lastPlayTime < System.currentTimeMillis() && LEAVES_BLOCKS.contains(chunkCoord.block)) { + Vec3 wind = WindEngine.getWind(curBlockPos, level, false, false, false); + double windspeed = wind.length(); + soundTimeLocations.put(chunkCoord, System.currentTimeMillis() + 12000L + (long)PMWeather.RANDOM.nextInt(50)); + minecraft.level.playLocalSound(chunkCoord, (SoundEvent)ModSounds.CALM_AMBIENCE.value(), SoundSource.AMBIENT, (float)Math.min((double)maxLeavesVolume, windspeed * (double)soundMuffle * (double)0.05F), 0.9F + PMWeather.RANDOM.nextFloat() * 0.2F, false); + } + } else { + soundLocations.remove(i); + soundTimeLocations.remove(chunkCoord); + } + } + } + } + } catch (Exception e) { + PMWeather.LOGGER.error(e.getMessage(), e); + } + + } + + public static void tryAmbientSounds() { + Minecraft minecraft = Minecraft.getInstance(); + Level level = minecraft.level; + Player player = minecraft.player; + if (lastAmbientTickThreaded < System.currentTimeMillis() && ClientConfig.leavesVolume > (double)0.0F) { + lastAmbientTickThreaded = System.currentTimeMillis() + 500L; + int size = 32; + int hSize = size / 2; + BlockPos curBlockPos = player.blockPosition(); + + for(int x = curBlockPos.getX() - hSize; x < curBlockPos.getX() + hSize; ++x) { + for(int y = curBlockPos.getY() - hSize; y < curBlockPos.getY() + hSize; ++y) { + for(int z = curBlockPos.getZ() - hSize; z < curBlockPos.getZ() + hSize; ++z) { + Block block = level.getBlockState(new BlockPos(x, y, z)).getBlock(); + if (block.defaultMapColor() == MapColor.PLANT) { + boolean proxFail = false; + + for(ChunkCoordinatesBlock soundLocation : soundLocations) { + if (Math.sqrt(soundLocation.distSqr(new BlockPos(x, y, z))) < (double)15.0F) { + proxFail = true; + break; + } + } + + if (!proxFail) { + soundLocations.add(new ChunkCoordinatesBlock(x, y, z, block)); + } + } + } + } + } + } + + } + + public static void init(Level level) { + lastLevel = level; + if (level != null) { + weatherHandler = new WeatherHandlerClient(level.dimension()); + Minecraft minecraft = Minecraft.getInstance(); + if (particleManager == null) { + particleManager = new ParticleManager(minecraft.level, minecraft.getTextureManager()); + } else { + particleManager.setLevel((ClientLevel)level); + } + + if (particleManagerDebris == null) { + particleManagerDebris = new ParticleManager(minecraft.level, minecraft.getTextureManager()); + } else { + particleManagerDebris.setLevel((ClientLevel)level); + } + + CompoundTag data = new CompoundTag(); + data.putString("command", "syncFull"); + data.putString("packetCommand", "WeatherData"); + ModNetworking.clientSendToSever(data); + } + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/event/ModBusClientEvents.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/event/ModBusClientEvents.java new file mode 100644 index 00000000..ea0e037c --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/event/ModBusClientEvents.java @@ -0,0 +1,78 @@ +package dev.protomanly.pmweather.event; + +import dev.protomanly.pmweather.PMWeather; +import dev.protomanly.pmweather.block.entity.ModBlockEntities; +import dev.protomanly.pmweather.entity.ModEntities; +import dev.protomanly.pmweather.entity.client.MovingBlockRenderer; +import dev.protomanly.pmweather.item.ModItems; +import dev.protomanly.pmweather.item.component.ModComponents; +import dev.protomanly.pmweather.render.AnemometerModel; +import dev.protomanly.pmweather.render.AnemometerRenderer; +import dev.protomanly.pmweather.render.RadarRenderer; +import dev.protomanly.pmweather.render.SoundingViewerRenderer; +import dev.protomanly.pmweather.render.WeatherBalloonModel; +import dev.protomanly.pmweather.render.WeatherPlatformRenderer; +import dev.protomanly.pmweather.shaders.ModShaders; +import net.minecraft.client.renderer.entity.EntityRenderers; +import net.minecraft.client.renderer.item.ItemProperties; +import net.minecraft.client.renderer.item.ItemPropertyFunction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimplePreparableReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.item.Item; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.common.EventBusSubscriber.Bus; +import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; +import net.neoforged.neoforge.client.event.EntityRenderersEvent; +import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; + +@EventBusSubscriber( + modid = "pmweather", + bus = Bus.MOD, + value = {Dist.CLIENT} +) +public class ModBusClientEvents { + public ModBusClientEvents() { + super(); + } + + @SubscribeEvent + public static void reloadListeners(RegisterClientReloadListenersEvent event) { + event.registerReloadListener(new SimplePreparableReloadListener() { + protected Object prepare(ResourceManager resourceManager, ProfilerFiller profilerFiller) { + return null; + } + + protected void apply(Object o, ResourceManager resourceManager, ProfilerFiller profilerFiller) { + ModShaders.reload(); + } + }); + } + + @SubscribeEvent + public static void onClientSetup(FMLClientSetupEvent event) { + EntityRenderers.register(ModEntities.MOVING_BLOCK.get(), MovingBlockRenderer::new); + registerItemProp(event, (Item)ModItems.CONNECTOR.get(), PMWeather.getPath("connected"), (itemStack, clientLevel, livingEntity, i) -> itemStack.has(ModComponents.WEATHER_BALLOON_PLATFORM) ? 1.0F : 0.0F); + } + + public static void registerItemProp(FMLClientSetupEvent event, Item item, ResourceLocation propertyID, ItemPropertyFunction function) { + event.enqueueWork(() -> ItemProperties.register(item, propertyID, function)); + } + + @SubscribeEvent + public static void registerLayers(EntityRenderersEvent.RegisterLayerDefinitions event) { + event.registerLayerDefinition(AnemometerModel.LAYER_LOCATION, AnemometerModel::createBodyLayer); + event.registerLayerDefinition(WeatherBalloonModel.LAYER_LOCATION, WeatherBalloonModel::createBodyLayer); + } + + @SubscribeEvent + public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) { + event.registerBlockEntityRenderer(ModBlockEntities.ANEMOMETER_BE.get(), AnemometerRenderer::new); + event.registerBlockEntityRenderer(ModBlockEntities.RADAR_BE.get(), RadarRenderer::new); + event.registerBlockEntityRenderer(ModBlockEntities.WEATHER_PLATFORM_BE.get(), WeatherPlatformRenderer::new); + event.registerBlockEntityRenderer(ModBlockEntities.SOUNDING_VIEWER_BE.get(), SoundingViewerRenderer::new); + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/interfaces/PostChainData.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/interfaces/PostChainData.java new file mode 100644 index 00000000..8857ae1c --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/interfaces/PostChainData.java @@ -0,0 +1,8 @@ +package dev.protomanly.pmweather.interfaces; + +import java.util.List; +import net.minecraft.client.renderer.PostPass; + +public interface PostChainData { + List getPasses(); +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/mixin/PostChainMixin.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/mixin/PostChainMixin.java new file mode 100644 index 00000000..e43cfd25 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/mixin/PostChainMixin.java @@ -0,0 +1,13 @@ +package dev.protomanly.pmweather.mixin; + +import java.util.List; +import net.minecraft.client.renderer.PostChain; +import net.minecraft.client.renderer.PostPass; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin({PostChain.class}) +public interface PostChainMixin { + @Accessor("passes") + List getPasses(); +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/EntityRotFX.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/EntityRotFX.java new file mode 100644 index 00000000..92e04ccd --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/EntityRotFX.java @@ -0,0 +1,459 @@ +package dev.protomanly.pmweather.particle; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat.Mode; +import com.mojang.math.Axis; +import dev.protomanly.pmweather.PMWeather; +import dev.protomanly.pmweather.config.ClientConfig; +import dev.protomanly.pmweather.event.GameBusClientEvents; +import dev.protomanly.pmweather.particle.behavior.ParticleBehavior; +import java.util.List; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.ParticleRenderType; +import net.minecraft.client.particle.TextureSheetParticle; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.levelgen.Heightmap.Types; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public class EntityRotFX extends TextureSheetParticle { + public static final ParticleRenderType SORTED_TRANSLUCENT = new ParticleRenderType() { + public @NotNull BufferBuilder begin(Tesselator tesselator, @NotNull TextureManager textureManager) { + RenderSystem.disableCull(); + RenderSystem.depthMask(false); + RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_PARTICLES); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + return tesselator.begin(Mode.QUADS, DefaultVertexFormat.PARTICLE); + } + + public String toString() { + return "PARTICLE_SHEET_SORTED_TRANSLUCENT"; + } + }; + public static final ParticleRenderType SORTED_OPAQUE_BLOCK = new ParticleRenderType() { + public @NotNull BufferBuilder begin(Tesselator tesselator, @NotNull TextureManager textureManager) { + RenderSystem.disableBlend(); + RenderSystem.depthMask(true); + RenderSystem.setShader(GameRenderer::getParticleShader); + RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_BLOCKS); + return tesselator.begin(Mode.QUADS, DefaultVertexFormat.PARTICLE); + } + + public String toString() { + return "PARTICLE_BLOCK_SHEET_SORTED_OPAQUE"; + } + }; + public float renderRange = 128.0F; + public int renderOrder = 0; + public float windWeight = 1.0F; + public int entityID = 0; + public float prevRotationYaw; + public float rotationYaw; + public float prevRotationPitch; + public float rotationPitch; + public float rotationRoll; + public boolean isTransparent = true; + public boolean weatherEffect = false; + public boolean killOnCollide = false; + public int killOnCollideActivateAtAge = 0; + public boolean dontRenderUnderTopmostBlock = false; + public boolean killWhenUnderTopmostBlock = false; + public int killWhenUnderTopmostBlock_ScanAheadRange = 0; + public int killWhenUnderCameraAtLeast = 0; + public int killWhenFarFromCameraAtLeast = 0; + public boolean facePlayer = false; + public boolean facePlayerYaw = false; + public boolean spinFast = false; + public float spinFastRate = 10.0F; + public boolean spinTowardsMotionDirection = false; + public float ticksFadeInMax = 0.0F; + public float ticksFadeOutMax = 0.0F; + public float ticksFadeOutMaxOnDeath = -1.0F; + public float ticksFadeOutCurOnDeath = 0.0F; + public boolean fadingOut = false; + public float fullAlphaTarget = 1.0F; + public float rotationAroundCenter = 0.0F; + public float rotationSpeedAroundCenter = 0.0F; + public boolean slantParticleToWind = false; + public boolean fastLight = false; + protected int lastNonZeroBrightness = 15728640; + public ParticleBehavior particleBehavior = null; + public boolean useCustomBBForRenderCulling = false; + public static final AABB INITIAL_AABB; + public AABB bbRender; + public boolean vanillaMotionDampen; + public boolean collisionSpeedDampen; + public boolean collidingHorizontally; + public boolean collidingDownwards; + public boolean collidingUpwards; + public boolean markCollided; + public boolean bounceOnVerticalImpact; + public float bounceOnVerticalImpactEnergy; + public boolean ignoreWind; + public ParticleRenderType renderType; + + public EntityRotFX(ClientLevel level, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) { + super(level, x, y, z, xSpeed, ySpeed, zSpeed); + this.bbRender = INITIAL_AABB; + this.vanillaMotionDampen = true; + this.collisionSpeedDampen = true; + this.collidingHorizontally = false; + this.collidingDownwards = false; + this.collidingUpwards = false; + this.markCollided = false; + this.bounceOnVerticalImpact = false; + this.bounceOnVerticalImpactEnergy = 0.3F; + this.ignoreWind = false; + this.renderType = SORTED_TRANSLUCENT; + this.setSize(0.3F, 0.3F); + this.entityID = PMWeather.RANDOM.nextInt(100000); + } + + public ParticleRenderType getRenderType() { + return this.renderType; + } + + public void remove() { + if (this.particleBehavior != null) { + this.particleBehavior.particles.remove(this); + } + + super.remove(); + } + + public void tick() { + super.tick(); + this.prevRotationPitch = this.rotationPitch; + this.prevRotationYaw = this.rotationYaw; + Entity cam = Minecraft.getInstance().getCameraEntity(); + if (!this.vanillaMotionDampen) { + this.xd /= (double)0.98F; + this.yd /= (double)0.98F; + this.zd /= (double)0.98F; + } + + if (!this.removed && !this.fadingOut) { + if (this.killOnCollide && (this.killOnCollideActivateAtAge == 0 || this.age > this.killOnCollideActivateAtAge) && this.isColliding()) { + this.startDeath(); + } + + BlockPos pos = new BlockPos((int)this.x, (int)this.y, (int)this.z); + if (this.killWhenUnderTopmostBlock) { + int height = this.level.getHeightmapPos(Types.MOTION_BLOCKING, pos).getY(); + if (this.y - (double)this.killWhenUnderTopmostBlock_ScanAheadRange <= (double)height) { + this.startDeath(); + } + } + + if (this.killWhenUnderCameraAtLeast != 0 && cam != null && this.y < cam.getY() - (double)this.killWhenUnderCameraAtLeast) { + this.startDeath(); + } + + if (this.killWhenFarFromCameraAtLeast != 0 && cam != null && this.age > 20 && this.age % 5 == 0 && cam.distanceToSqr(this.x, this.y, this.z) > (double)(this.killWhenFarFromCameraAtLeast * this.killWhenFarFromCameraAtLeast)) { + this.startDeath(); + } + } + + if (!this.collisionSpeedDampen && this.onGround) { + this.xd /= (double)0.7F; + this.zd /= (double)0.7F; + } + + double speedXZ = Math.sqrt(this.getMotionX() * this.getMotionX() + this.getMotionZ() * this.getMotionZ()); + double spinFastRateAdj = (double)this.spinFastRate * speedXZ * (double)10.0F; + if (this.spinFast) { + this.rotationPitch += (float)(this.entityID % 2 == 0 ? spinFastRateAdj : -spinFastRateAdj); + this.rotationYaw += (float)(this.entityID % 2 == 0 ? -spinFastRateAdj : spinFastRateAdj); + } + + float angleToMovement = (float)Math.toDegrees(Math.atan2(this.xd, this.zd)); + if (this.spinTowardsMotionDirection) { + this.rotationYaw = angleToMovement; + this.rotationPitch += this.spinFastRate; + } + + if (!this.fadingOut) { + if (this.ticksFadeInMax > 0.0F && (float)this.age < this.ticksFadeInMax) { + this.setAlpha((float)this.age / this.ticksFadeInMax * this.fullAlphaTarget); + } else if (this.ticksFadeOutMax > 0.0F && (float)this.age > (float)this.lifetime - this.ticksFadeOutMax) { + float count = (float)this.getAge() - ((float)this.getLifetime() - this.ticksFadeOutMax); + float val = (this.ticksFadeOutMax - count) / this.ticksFadeOutMax; + this.setAlpha(val * this.fullAlphaTarget); + } else if (this.ticksFadeInMax > 0.0F || this.ticksFadeOutMax > 0.0F) { + this.setAlpha(this.fullAlphaTarget); + } + } else { + if (this.ticksFadeOutCurOnDeath < this.ticksFadeOutMaxOnDeath) { + ++this.ticksFadeOutCurOnDeath; + } else { + this.remove(); + } + + float val = 1.0F - this.ticksFadeOutCurOnDeath / this.ticksFadeOutMaxOnDeath; + this.setAlpha(val * this.fullAlphaTarget); + } + + this.rotationAroundCenter += this.rotationSpeedAroundCenter; + this.rotationAroundCenter %= 360.0F; + this.tickExtraRotations(); + } + + public void tickExtraRotations() { + if (this.slantParticleToWind) { + double motionXZ = Math.sqrt(this.xd * this.xd + this.zd * this.zd); + this.rotationPitch = (float)Math.atan2(this.yd, motionXZ); + } + + } + + public void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) { + Vec3 vec3d = renderInfo.getPosition(); + float f = (float)(Mth.lerp((double)partialTicks, this.xo, this.x) - vec3d.x()); + float f1 = (float)(Mth.lerp((double)partialTicks, this.yo, this.y) - vec3d.y()); + float f2 = (float)(Mth.lerp((double)partialTicks, this.zo, this.z) - vec3d.z()); + Quaternionf quaternion; + if (this.facePlayer || this.rotationPitch == 0.0F && this.rotationYaw == 0.0F) { + try { + quaternion = (Quaternionf)renderInfo.rotation().clone(); + quaternion.mul(Axis.ZP.rotationDegrees(this.rotationRoll)); + } catch (CloneNotSupportedException var16) { + quaternion = renderInfo.rotation(); + } + } else { + quaternion = new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F); + if (this.facePlayerYaw) { + quaternion.mul(Axis.YP.rotationDegrees(-renderInfo.getYRot())); + } else { + quaternion.mul(Axis.YP.rotationDegrees(Mth.lerp(partialTicks, this.prevRotationYaw, this.rotationYaw))); + } + + quaternion.mul(Axis.XP.rotationDegrees(Mth.lerp(partialTicks, this.prevRotationPitch, this.rotationPitch))); + } + + Vector3f[] v3f = new Vector3f[]{new Vector3f(-1.0F, -1.0F, 0.0F), new Vector3f(-1.0F, 1.0F, 0.0F), new Vector3f(1.0F, 1.0F, 0.0F), new Vector3f(1.0F, -1.0F, 0.0F)}; + float scale = this.getQuadSize(partialTicks); + + for(int i = 0; i < 4; ++i) { + Vector3f vector3f = v3f[i]; + vector3f.rotate(quaternion); + vector3f.mul(scale); + vector3f.add(f, f1, f2); + } + + float u0 = this.getU0(); + float u1 = this.getU1(); + float v0 = this.getV0(); + float v1 = this.getV1(); + int j = this.getLightColor(partialTicks); + if (j > 0) { + this.lastNonZeroBrightness = j; + } else { + j = this.lastNonZeroBrightness; + } + + buffer.addVertex(v3f[0].x, v3f[0].y, v3f[0].z).setUv(u1, v1).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(j); + buffer.addVertex(v3f[1].x, v3f[1].y, v3f[1].z).setUv(u1, v0).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(j); + buffer.addVertex(v3f[2].x, v3f[2].y, v3f[2].z).setUv(u0, v0).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(j); + buffer.addVertex(v3f[3].x, v3f[3].y, v3f[3].z).setUv(u0, v1).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(j); + } + + public void move(double x, double y, double z) { + double xx = x; + double yy = y; + double zz = z; + if (this.hasPhysics && (x != (double)0.0F || y != (double)0.0F || z != (double)0.0F)) { + Vec3 vec3d = Entity.collideBoundingBox((Entity)null, new Vec3(x, y, z), this.getBoundingBox(), this.level, List.of()); + x = vec3d.x; + y = vec3d.y; + z = vec3d.z; + } + + if (x != (double)0.0F || y != (double)0.0F || z != (double)0.0F) { + this.setBoundingBox(this.getBoundingBox().move(x, y, z)); + if (this.useCustomBBForRenderCulling) { + this.bbRender = this.getBoundingBoxForRender().move(x, y, z); + } + + this.setLocationFromBoundingbox(); + } + + this.onGround = yy != y && yy < (double)0.0F; + this.collidingHorizontally = xx != x || zz != z; + this.collidingDownwards = yy < y; + this.collidingUpwards = yy > y; + if (xx != x) { + this.xd = (double)0.0F; + } + + if (zz != z) { + this.zd = (double)0.0F; + } + + if (!this.markCollided) { + if (this.onGround || this.collidingDownwards || this.collidingHorizontally || this.collidingUpwards) { + this.onHit(); + this.markCollided = true; + } + + if (this.bounceOnVerticalImpact && (this.onGround || this.collidingDownwards)) { + this.setMotionY(-this.getMotionY() * (double)this.bounceOnVerticalImpactEnergy); + } + } + + } + + public void onHit() { + } + + public void setSprite(@NotNull TextureAtlasSprite sprite) { + this.sprite = sprite; + } + + public void spawnAsWeatherEffect() { + this.weatherEffect = true; + if (ClientConfig.customParticles) { + GameBusClientEvents.particleManager.add(this); + } else { + Minecraft.getInstance().particleEngine.add(this); + } + + } + + public void spawnAsDebrisEffect() { + this.weatherEffect = true; + if (ClientConfig.customParticles) { + GameBusClientEvents.particleManagerDebris.add(this); + } else { + Minecraft.getInstance().particleEngine.add(this); + } + + } + + public AABB getBoundingBoxForRender() { + return this.useCustomBBForRenderCulling ? this.bbRender : this.getBoundingBox(); + } + + public void setSizeForRenderCulling(float width, float height) { + if (width != this.bbWidth || height != this.bbHeight) { + this.bbWidth = width; + this.bbHeight = height; + AABB aabb = this.getBoundingBox(); + double d0 = (aabb.minX + aabb.maxX - (double)width) / (double)2.0F; + double d1 = (aabb.minZ + aabb.maxZ - (double)width) / (double)2.0F; + this.bbRender = new AABB(d0, aabb.minY, d1, d0 + (double)this.bbWidth, aabb.minY + (double)this.bbHeight, d1 + (double)this.bbWidth); + } + + } + + public void startDeath() { + if (this.ticksFadeOutMaxOnDeath > 0.0F) { + this.ticksFadeOutCurOnDeath = 0.0F; + this.fadingOut = true; + } else { + this.remove(); + } + + } + + public boolean isColliding() { + return this.onGround || this.collidingHorizontally; + } + + public Vec3 getPivotedPosition() { + return Vec3.ZERO; + } + + public double getX() { + return this.x; + } + + public double getY() { + return this.y; + } + + public double getZ() { + return this.z; + } + + public int getAge() { + return this.age; + } + + public void setMotionX(double val) { + this.xd = val; + } + + public void setMotionY(double val) { + this.yd = val; + } + + public void setMotionZ(double val) { + this.zd = val; + } + + public double getMotionX() { + return this.xd; + } + + public double getMotionY() { + return this.yd; + } + + public double getMotionZ() { + return this.zd; + } + + public void setGravity(float val) { + this.gravity = val; + } + + public void setAlpha(float val) { + this.alpha = val; + } + + public void setScale(float val) { + this.setSizeForRenderCulling(val, val); + this.quadSize = val; + } + + public void setPrevPosX(double val) { + this.xo = val; + } + + public void setPrevPosY(double val) { + this.yo = val; + } + + public void setPrevPosZ(double val) { + this.zo = val; + } + + public void setSize(float width, float height) { + super.setSize(width, height); + this.setPos(this.x, this.y, this.z); + } + + public void setCanCollide(boolean val) { + this.hasPhysics = val; + } + + static { + INITIAL_AABB = new AABB(Vec3.ZERO, Vec3.ZERO); + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleCube.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleCube.java new file mode 100644 index 00000000..bbf9cc77 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleCube.java @@ -0,0 +1,127 @@ +package dev.protomanly.pmweather.particle; + +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Axis; +import dev.protomanly.pmweather.PMWeather; +import java.util.ArrayList; +import java.util.List; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.ParticleRenderType; +import net.minecraft.client.renderer.block.BlockRenderDispatcher; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public class ParticleCube extends ParticleTexFX { + public ParticleCube(ClientLevel level, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed, BlockState state) { + super(level, x, y, z, xSpeed, ySpeed, zSpeed, ParticleRegistry.rain); + TextureAtlasSprite sprite1 = this.getSpriteFromState(state); + if (sprite1 != null) { + this.setSprite(sprite1); + } else { + PMWeather.LOGGER.warn("Unable to find sprite from block {}", state); + sprite1 = this.getSpriteFromState(Blocks.OAK_PLANKS.defaultBlockState()); + if (sprite1 != null) { + this.setSprite(sprite1); + } + } + + int multiplier = Minecraft.getInstance().getBlockColors().getColor(state, this.level, new BlockPos((int)x, (int)y, (int)z), 0); + float mr = (float)(multiplier >>> 16 & 255) / 255.0F; + float mg = (float)(multiplier >>> 8 & 255) / 255.0F; + float mb = (float)(multiplier & 255) / 255.0F; + this.setColor(mr, mg, mb); + } + + public TextureAtlasSprite getSpriteFromState(BlockState state) { + BlockRenderDispatcher blockRenderDispatcher = Minecraft.getInstance().getBlockRenderer(); + BakedModel model = blockRenderDispatcher.getBlockModel(state); + Direction[] var4 = Direction.values(); + int var5 = var4.length; + byte var6 = 0; + if (var6 < var5) { + Direction direction = var4[var6]; + List list = model.getQuads(state, direction, RandomSource.create()); + return !list.isEmpty() ? ((BakedQuad)list.getFirst()).getSprite() : model.getParticleIcon(); + } else { + return null; + } + } + + public void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) { + Vec3 vec3d = renderInfo.getPosition(); + float f = (float)(Mth.lerp((double)partialTicks, this.xo, this.x) - vec3d.x()); + float f1 = (float)(Mth.lerp((double)partialTicks, this.yo, this.y) - vec3d.y()); + float f2 = (float)(Mth.lerp((double)partialTicks, this.zo, this.z) - vec3d.z()); + Quaternionf quaternion; + if (!this.facePlayer && (this.rotationPitch != 0.0F || this.rotationYaw != 0.0F)) { + quaternion = new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F); + if (this.facePlayerYaw) { + quaternion.mul(Axis.YP.rotationDegrees(-renderInfo.getYRot())); + } else { + quaternion.mul(Axis.YP.rotationDegrees(Mth.lerp(this.rotationSpeedAroundCenter, this.prevRotationYaw, this.rotationYaw))); + } + + quaternion.mul(Axis.XP.rotationDegrees(Mth.lerp(partialTicks, this.prevRotationPitch, this.rotationPitch))); + } else { + quaternion = renderInfo.rotation(); + } + + List faces = new ArrayList(); + Vector3f[] face = new Vector3f[]{new Vector3f(-1.0F, -1.0F, -1.0F), new Vector3f(-1.0F, 1.0F, -1.0F), new Vector3f(1.0F, 1.0F, -1.0F), new Vector3f(1.0F, -1.0F, -1.0F)}; + faces.add(face); + face = new Vector3f[]{new Vector3f(-1.0F, -1.0F, 1.0F), new Vector3f(-1.0F, 1.0F, 1.0F), new Vector3f(1.0F, 1.0F, 1.0F), new Vector3f(1.0F, -1.0F, 1.0F)}; + faces.add(face); + face = new Vector3f[]{new Vector3f(-1.0F, -1.0F, -1.0F), new Vector3f(-1.0F, 1.0F, -1.0F), new Vector3f(-1.0F, 1.0F, 1.0F), new Vector3f(-1.0F, -1.0F, 1.0F)}; + faces.add(face); + face = new Vector3f[]{new Vector3f(1.0F, -1.0F, -1.0F), new Vector3f(1.0F, 1.0F, -1.0F), new Vector3f(1.0F, 1.0F, 1.0F), new Vector3f(1.0F, -1.0F, 1.0F)}; + faces.add(face); + face = new Vector3f[]{new Vector3f(-1.0F, -1.0F, -1.0F), new Vector3f(-1.0F, -1.0F, 1.0F), new Vector3f(1.0F, -1.0F, 1.0F), new Vector3f(1.0F, -1.0F, -1.0F)}; + faces.add(face); + face = new Vector3f[]{new Vector3f(-1.0F, 1.0F, -1.0F), new Vector3f(-1.0F, 1.0F, 1.0F), new Vector3f(1.0F, 1.0F, 1.0F), new Vector3f(1.0F, 1.0F, -1.0F)}; + faces.add(face); + float f4 = this.getQuadSize(partialTicks); + + for(Vector3f[] entryFace : faces) { + for(int i = 0; i < 4; ++i) { + entryFace[i].rotate(quaternion); + entryFace[i].mul(f4); + entryFace[i].add(f, f1, f2); + } + } + + float u0 = this.getU0(); + float u1 = this.getU1(); + float v0 = this.getV0(); + float v1 = this.getV1(); + int j = this.getLightColor(partialTicks); + if (j > 0) { + this.lastNonZeroBrightness = j; + } else { + j = this.lastNonZeroBrightness; + } + + for(Vector3f[] entryFace : faces) { + buffer.addVertex(entryFace[0].x(), entryFace[0].y(), entryFace[0].z()).setUv(u1, v1).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(j); + buffer.addVertex(entryFace[1].x(), entryFace[1].y(), entryFace[1].z()).setUv(u1, v0).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(j); + buffer.addVertex(entryFace[2].x(), entryFace[2].y(), entryFace[2].z()).setUv(u0, v0).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(j); + buffer.addVertex(entryFace[3].x(), entryFace[3].y(), entryFace[3].z()).setUv(u0, v1).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(j); + } + + } + + public ParticleRenderType getRenderType() { + return SORTED_OPAQUE_BLOCK; + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleManager.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleManager.java new file mode 100644 index 00000000..88dbc2a9 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleManager.java @@ -0,0 +1,398 @@ +package dev.protomanly.pmweather.particle; + +import com.google.common.collect.EvictingQueue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.common.collect.Queues; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.BufferUploader; +import com.mojang.blaze3d.vertex.MeshData; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import dev.protomanly.pmweather.PMWeather; +import dev.protomanly.pmweather.config.ClientConfig; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.Util; +import net.minecraft.client.Camera; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.particle.ParticleDescription; +import net.minecraft.client.particle.ParticleProvider; +import net.minecraft.client.particle.ParticleRenderType; +import net.minecraft.client.particle.SpriteSet; +import net.minecraft.client.particle.TrackingEmitter; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.client.renderer.texture.SpriteLoader; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.core.particles.ParticleGroup; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.FileToIdConverter; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.PreparableReloadListener; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.GsonHelper; +import net.minecraft.util.RandomSource; +import net.minecraft.util.profiling.ProfilerFiller; +import net.neoforged.neoforge.client.ClientHooks; +import org.apache.commons.compress.utils.Lists; +import org.joml.Matrix4fStack; + +public class ParticleManager implements PreparableReloadListener { + private static final FileToIdConverter PARTICLE_LISTER = FileToIdConverter.json("particles"); + private static final ResourceLocation PARTICLES_ATLAS_INFO = ResourceLocation.withDefaultNamespace("particles"); + private static final List RENDER_ORDER; + protected ClientLevel level; + private final TextureAtlas textureAtlas; + private final TextureManager textureManager; + private final Map> providers = new HashMap>(); + private final Map spriteSets = Maps.newHashMap(); + private final Queue particlesToAdd = Queues.newArrayDeque(); + private final Queue trackingEmitters = Queues.newArrayDeque(); + private final Map> particles; + private final Object2IntOpenHashMap trackedParticleCounts; + + public ParticleManager(ClientLevel level, TextureManager textureManager) { + super(); + this.particles = Maps.newTreeMap(ClientHooks.makeParticleRenderTypeComparator(RENDER_ORDER)); + this.trackedParticleCounts = new Object2IntOpenHashMap(); + this.textureAtlas = new TextureAtlas(TextureAtlas.LOCATION_PARTICLES); + this.level = level; + this.textureManager = textureManager; + } + + public CompletableFuture reload(PreparableReloadListener.PreparationBarrier preparationBarrier, ResourceManager resourceManager, ProfilerFiller profilerFiller, ProfilerFiller profilerFiller1, Executor executor, Executor executor1) { + CompletableFuture> completableFuture = CompletableFuture.supplyAsync(() -> PARTICLE_LISTER.listMatchingResources(resourceManager), executor).thenCompose((locationResourceMap) -> { + List> list = new ArrayList>(locationResourceMap.size()); + locationResourceMap.forEach((k, v) -> { + ResourceLocation resourceLocation = PARTICLE_LISTER.fileToId(k); + list.add(CompletableFuture.supplyAsync(() -> { + record ParticleDefinition(ResourceLocation resourceLocation, Optional> sprites) { + ParticleDefinition { + super(); + } + } + + return new ParticleDefinition(resourceLocation, this.loadParticleDescription(resourceLocation, v)); + }, executor)); + }); + return Util.sequence(list); + }); + CompletableFuture completableFuture1 = SpriteLoader.create(this.textureAtlas).loadAndStitch(resourceManager, PARTICLES_ATLAS_INFO, 0, executor).thenCompose(SpriteLoader.Preparations::waitForUpload); + CompletableFuture var10000 = CompletableFuture.allOf(completableFuture1, completableFuture); + Objects.requireNonNull(preparationBarrier); + return var10000.thenCompose(preparationBarrier::wait).thenAcceptAsync((v) -> { + this.clearParticles(); + profilerFiller1.startTick(); + profilerFiller1.push("upload"); + SpriteLoader.Preparations preparations = completableFuture1.join(); + this.textureAtlas.upload(preparations); + profilerFiller1.popPush("bindSpriteSets"); + Set set = new HashSet(); + TextureAtlasSprite textureAtlasSprite = preparations.missing(); + ((List)completableFuture.join()).forEach((particleDefinition) -> { + Optional> optionalResourceLocations = particleDefinition.sprites(); + if (!optionalResourceLocations.isEmpty()) { + List textureAtlasSprites = new ArrayList(); + + for(ResourceLocation resourceLocation : optionalResourceLocations.get()) { + TextureAtlasSprite textureAtlasSprite1 = (TextureAtlasSprite)preparations.regions().get(resourceLocation); + if (textureAtlasSprite1 == null) { + set.add(resourceLocation); + textureAtlasSprites.add(textureAtlasSprite); + } else { + textureAtlasSprites.add(textureAtlasSprite1); + } + } + + if (textureAtlasSprites.isEmpty()) { + textureAtlasSprites.add(textureAtlasSprite); + } + + (this.spriteSets.get(particleDefinition.resourceLocation())).rebind(textureAtlasSprites); + } + + }); + if (!set.isEmpty()) { + PMWeather.LOGGER.warn("Missing particle sprites: {}", set.stream().sorted().map(ResourceLocation::toString).collect(Collectors.joining(","))); + } + + profilerFiller1.pop(); + profilerFiller1.endTick(); + }, executor1); + } + + private Optional> loadParticleDescription(ResourceLocation resourceLocation, Resource resource) { + if (!this.spriteSets.containsKey(resourceLocation)) { + PMWeather.LOGGER.debug("Redundant texture list for particle: {}", resourceLocation); + return Optional.>empty(); + } else { + try (Reader reader = resource.openAsReader()) { + ParticleDescription particleDescription = ParticleDescription.fromJson(GsonHelper.parse(reader)); + return Optional.>of(particleDescription.getTextures()); + } catch (IOException e) { + throw new IllegalStateException("Failed to load description for particle " + String.valueOf((Object)resourceLocation), e); + } + } + } + + @Nullable + private Particle makeParticle(T particleOptions, double x, double y, double z, double xMotion, double yMotion, double zMotion) { + ParticleProvider particleProvider = this.providers.get(BuiltInRegistries.PARTICLE_TYPE.getKey(particleOptions.getType())); + return particleProvider == null ? null : particleProvider.createParticle(particleOptions, this.level, x, y, z, xMotion, yMotion, zMotion); + } + + public void add(Particle particle) { + Optional optional = particle.getParticleGroup(); + if (optional.isPresent()) { + if (this.hasSpaceInParticleLimit(optional.get())) { + this.particlesToAdd.add(particle); + this.updateCount(optional.get(), 1); + } + } else { + this.particlesToAdd.add(particle); + } + + } + + public void tick() { + this.level.getProfiler().push("pmweather_particle_tick"); + this.particles.forEach((particleRenderType, particles1) -> { + this.level.getProfiler().push("pmweather_particle_tick_" + particleRenderType.toString()); + this.tickParticleList(particles1); + this.level.getProfiler().pop(); + }); + if (!this.trackingEmitters.isEmpty()) { + List list = Lists.newArrayList(); + + for(TrackingEmitter trackingEmitter : this.trackingEmitters) { + trackingEmitter.tick(); + if (!trackingEmitter.isAlive()) { + list.add(trackingEmitter); + } + } + + this.trackingEmitters.removeAll(list); + } + + Particle particle; + if (!this.particlesToAdd.isEmpty()) { + while((particle = this.particlesToAdd.poll()) != null) { + (this.particles.computeIfAbsent(particle.getRenderType(), (particleRenderType) -> EvictingQueue.create(32768))).add(particle); + } + } + + this.level.getProfiler().pop(); + } + + private void tickParticleList(Collection particles) { + if (!particles.isEmpty()) { + Iterator iterator = particles.iterator(); + + while(iterator.hasNext()) { + Particle particle = iterator.next(); + this.tickParticle(particle); + if (!particle.isAlive()) { + particle.getParticleGroup().ifPresent((particleGroup) -> this.updateCount(particleGroup, -1)); + iterator.remove(); + } + } + } + + } + + private void updateCount(ParticleGroup particleGroup, int count) { + this.trackedParticleCounts.addTo(particleGroup, count); + } + + private void tickParticle(Particle particle) { + try { + particle.tick(); + } catch (Throwable throwable) { + CrashReport crashReport = CrashReport.forThrowable(throwable, "Ticking Particle"); + CrashReportCategory crashReportCategory = crashReport.addCategory("Particle being ticked"); + Objects.requireNonNull(particle); + crashReportCategory.setDetail("Particle", particle::toString); + ParticleRenderType var10002 = particle.getRenderType(); + Objects.requireNonNull(var10002); + crashReportCategory.setDetail("Particle Type", var10002::toString); + throw new ReportedException(crashReport); + } + } + + public void render(PoseStack poseStack, MultiBufferSource.BufferSource bufferSource, LightTexture lightTexture, Camera camera, float partialTicks, @Nullable Frustum frustum) { + this.level.getProfiler().push("pmweather_particle_render"); + float fogStart = RenderSystem.getShaderFogStart(); + float fogEnd = RenderSystem.getShaderFogEnd(); + RenderSystem.setShaderFogStart(fogStart); + RenderSystem.setShaderFogEnd(fogEnd * 2.0F); + lightTexture.turnOnLightLayer(); + RenderSystem.enableDepthTest(); + RenderSystem.activeTexture(33986); + RenderSystem.activeTexture(33984); + Matrix4fStack matrix4fStack = RenderSystem.getModelViewStack(); + matrix4fStack.pushMatrix(); + matrix4fStack.mul(poseStack.last().pose()); + RenderSystem.applyModelViewMatrix(); + RenderSystem.disableCull(); + int particleCount = 0; + + for(ParticleRenderType particleRenderType : this.particles.keySet()) { + this.level.getProfiler().push(particleRenderType.toString()); + if (particleRenderType != ParticleRenderType.NO_RENDER) { + Iterable iterable = this.particles.get(particleRenderType); + if (iterable != null) { + RenderSystem.setShader(GameRenderer::getParticleShader); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder bufferBuilder = particleRenderType.begin(tesselator, this.textureManager); + Map> sortedList = new HashMap>(); + int maxRenderOrder = 0; + + for(Particle particle : iterable) { + int renderOrder = 10; + if (particle instanceof EntityRotFX) { + EntityRotFX entityRotFX = (EntityRotFX)particle; + renderOrder = entityRotFX.renderOrder; + } + + if (renderOrder > maxRenderOrder) { + maxRenderOrder = renderOrder; + } + + if (sortedList.containsKey(renderOrder)) { + (sortedList.get(renderOrder)).add(particle); + } else { + List list = new ArrayList(); + list.add(particle); + sortedList.put(renderOrder, list); + } + } + + for(int i = 0; i <= maxRenderOrder; ++i) { + if (sortedList.containsKey(i)) { + List particlesSorted = sortedList.get(i); + particlesSorted.sort((p1, p2) -> { + double d1 = p1.getPos().distanceToSqr(camera.getPosition()); + double d2 = p2.getPos().distanceToSqr(camera.getPosition()); + return Double.compare(d2, d1); + }); + + for(Particle particle : particlesSorted) { + if (particle instanceof EntityRotFX) { + EntityRotFX entityRotFX = (EntityRotFX)particle; + if (camera.getPosition().distanceToSqr(particle.getPos()) > (double)(entityRotFX.renderRange * entityRotFX.renderRange) || frustum != null && !frustum.isVisible(entityRotFX.getBoundingBoxForRender())) { + continue; + } + } else if (camera.getPosition().distanceToSqr(particle.getPos()) > (double)65536.0F || frustum != null && !frustum.isVisible(particle.getRenderBoundingBox(partialTicks))) { + continue; + } + + if (!(camera.getPosition().distanceToSqr(particle.getPos()) > (double)(ClientConfig.maxParticleSpawnDistanceFromPlayer * ClientConfig.maxParticleSpawnDistanceFromPlayer))) { + try { + particle.render(bufferBuilder, camera, partialTicks); + } catch (Throwable throwable) { + CrashReport crashReport = CrashReport.forThrowable(throwable, "Rendering Particle"); + CrashReportCategory crashReportCategory = crashReport.addCategory("Particle being rendered"); + Objects.requireNonNull(particle); + crashReportCategory.setDetail("Particle", particle::toString); + Objects.requireNonNull(particleRenderType); + crashReportCategory.setDetail("Particle Type", particleRenderType::toString); + throw new ReportedException(crashReport); + } + } + } + } + } + + MeshData meshData = bufferBuilder.build(); + if (meshData != null) { + BufferUploader.drawWithShader(meshData); + } + } + + this.level.getProfiler().pop(); + } + } + + matrix4fStack.popMatrix(); + RenderSystem.applyModelViewMatrix(); + RenderSystem.depthMask(true); + RenderSystem.disableBlend(); + lightTexture.turnOffLightLayer(); + RenderSystem.setShaderFogStart(fogStart); + RenderSystem.setShaderFogEnd(fogEnd); + this.level.getProfiler().pop(); + } + + public void setLevel(@Nullable ClientLevel level) { + this.level = level; + this.clearParticles(); + this.trackingEmitters.clear(); + } + + private boolean hasSpaceInParticleLimit(ParticleGroup particleGroup) { + return this.trackedParticleCounts.getInt(particleGroup) < particleGroup.getLimit(); + } + + public void clearParticles() { + this.particles.clear(); + this.particlesToAdd.clear(); + this.trackingEmitters.clear(); + this.trackedParticleCounts.clear(); + } + + public Map> getParticles() { + return this.particles; + } + + static { + RENDER_ORDER = ImmutableList.of(ParticleRenderType.TERRAIN_SHEET, ParticleRenderType.PARTICLE_SHEET_OPAQUE, ParticleRenderType.PARTICLE_SHEET_LIT, ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT, ParticleRenderType.CUSTOM, EntityRotFX.SORTED_OPAQUE_BLOCK, EntityRotFX.SORTED_TRANSLUCENT); + } + + static class MutableSpriteSet implements SpriteSet { + private List sprites; + + MutableSpriteSet() { + super(); + } + + public TextureAtlasSprite get(int i, int i1) { + return this.sprites.get(i * (this.sprites.size() - 1) / i1); + } + + public TextureAtlasSprite get(RandomSource randomSource) { + return this.sprites.get(randomSource.nextInt(this.sprites.size())); + } + + public void rebind(List textureAtlasSprites) { + this.sprites = ImmutableList.copyOf(textureAtlasSprites); + } + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleRegistry.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleRegistry.java new file mode 100644 index 00000000..53298b0e --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleRegistry.java @@ -0,0 +1,73 @@ +package dev.protomanly.pmweather.particle; + +import dev.protomanly.pmweather.PMWeather; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.atlas.sources.SingleFile; +import net.minecraft.core.HolderLookup; +import net.minecraft.data.PackOutput; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.common.EventBusSubscriber.Bus; +import net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent; +import net.neoforged.neoforge.common.data.ExistingFileHelper; +import net.neoforged.neoforge.common.data.SpriteSourceProvider; + +public class ParticleRegistry extends SpriteSourceProvider { + public static TextureAtlasSprite rain; + public static TextureAtlasSprite mist; + public static TextureAtlasSprite splash; + public static TextureAtlasSprite snow; + public static TextureAtlasSprite snow1; + public static TextureAtlasSprite snow2; + public static TextureAtlasSprite snow3; + public static TextureAtlasSprite sleet; + + public ParticleRegistry(PackOutput output, CompletableFuture lookupProvider, String modId, ExistingFileHelper existingFileHelper) { + super(output, lookupProvider, modId, existingFileHelper); + } + + protected void gather() { + this.addSprite(PMWeather.getPath("particle/rain")); + this.addSprite(PMWeather.getPath("particle/mist")); + this.addSprite(PMWeather.getPath("particle/splash")); + this.addSprite(PMWeather.getPath("particle/snow")); + this.addSprite(PMWeather.getPath("particle/snow1")); + this.addSprite(PMWeather.getPath("particle/snow2")); + this.addSprite(PMWeather.getPath("particle/snow3")); + this.addSprite(PMWeather.getPath("particle/sleet")); + } + + public void addSprite(ResourceLocation resourceLocation) { + this.atlas(SpriteSourceProvider.PARTICLES_ATLAS).addSource(new SingleFile(resourceLocation, Optional.empty())); + } + + @EventBusSubscriber( + modid = "pmweather", + bus = Bus.MOD, + value = {Dist.CLIENT} + ) + public static class Events { + public Events() { + super(); + } + + @SubscribeEvent + public static void getRegisteredParticles(TextureAtlasStitchedEvent event) { + if (event.getAtlas().location().equals(TextureAtlas.LOCATION_PARTICLES)) { + ParticleRegistry.rain = event.getAtlas().getSprite(PMWeather.getPath("particle/rain")); + ParticleRegistry.mist = event.getAtlas().getSprite(PMWeather.getPath("particle/mist")); + ParticleRegistry.snow = event.getAtlas().getSprite(PMWeather.getPath("particle/snow")); + ParticleRegistry.snow1 = event.getAtlas().getSprite(PMWeather.getPath("particle/snow1")); + ParticleRegistry.snow2 = event.getAtlas().getSprite(PMWeather.getPath("particle/snow2")); + ParticleRegistry.snow3 = event.getAtlas().getSprite(PMWeather.getPath("particle/snow3")); + ParticleRegistry.splash = event.getAtlas().getSprite(PMWeather.getPath("particle/splash")); + ParticleRegistry.sleet = event.getAtlas().getSprite(PMWeather.getPath("particle/sleet")); + } + } + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleTexExtraRender.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleTexExtraRender.java new file mode 100644 index 00000000..6a80fe9e --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleTexExtraRender.java @@ -0,0 +1,104 @@ +package dev.protomanly.pmweather.particle; + +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Axis; +import dev.protomanly.pmweather.util.Util; +import net.minecraft.client.Camera; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.level.levelgen.Heightmap.Types; +import net.minecraft.world.phys.Vec3; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public class ParticleTexExtraRender extends ParticleTexFX { + public int extraParticlesBaseAmount = 5; + public boolean noExtraParticles = false; + public float extraRandomSecondaryYawRotation = 360.0F; + + public ParticleTexExtraRender(ClientLevel level, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed, TextureAtlasSprite sprite) { + super(level, x, y, z, xSpeed, ySpeed, zSpeed, sprite); + } + + public void tickExtraRotations() { + if (this.slantParticleToWind) { + double speed = this.xd * this.xd + this.zd * this.zd; + this.rotationYaw = -((float)Math.toDegrees(Math.atan2(this.zd, this.xd))) - 90.0F; + this.rotationPitch = Math.min(45.0F, (float)(speed * (double)120.0F)); + this.rotationPitch += (float)(this.entityID % 10 - 5); + } + + } + + public void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) { + Vec3 vec3d = renderInfo.getPosition(); + float f = (float)(Mth.lerp((double)partialTicks, this.xo, this.x) - vec3d.x()); + float f1 = (float)(Mth.lerp((double)partialTicks, this.yo, this.y) - vec3d.y()); + float f2 = (float)(Mth.lerp((double)partialTicks, this.zo, this.z) - vec3d.z()); + Quaternionf quaternion; + if (!this.facePlayer && (this.rotationPitch != 0.0F || this.rotationYaw != 0.0F)) { + quaternion = new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F); + quaternion.mul(Axis.YP.rotationDegrees(this.rotationYaw)); + quaternion.mul(Axis.XP.rotationDegrees(this.rotationPitch)); + if (this.extraRandomSecondaryYawRotation > 0.0F) { + quaternion.mul(Axis.YP.rotationDegrees((float)this.entityID % this.extraRandomSecondaryYawRotation)); + } + } else { + quaternion = renderInfo.rotation(); + } + + float u0 = this.getU0(); + float u1 = this.getU1(); + float v0 = this.getV0(); + float v1 = this.getV1(); + int renderAmount; + if (this.noExtraParticles) { + renderAmount = 1; + } else { + renderAmount = Math.min(1 + this.extraParticlesBaseAmount, Util.MAX_RAIN_DROPS); + } + + for(int ii = 0; ii < renderAmount; ++ii) { + double xx = (double)0.0F; + double zz = (double)0.0F; + double yy = (double)0.0F; + if (ii != 0) { + xx = Util.RAIN_POSITIONS[ii].x; + yy = Util.RAIN_POSITIONS[ii].y; + zz = Util.RAIN_POSITIONS[ii].z; + } + + if (this.dontRenderUnderTopmostBlock) { + int height2 = this.level.getHeightmapPos(Types.MOTION_BLOCKING, new BlockPos((int)(this.x + xx), (int)this.y, (int)(this.z + zz))).getY(); + if (this.y + yy < (double)height2) { + continue; + } + } + + int i = this.getLightColor(partialTicks); + if (i > 0) { + this.lastNonZeroBrightness = i; + } else { + i = this.lastNonZeroBrightness; + } + + Vector3f[] v3f = new Vector3f[]{new Vector3f(-1.0F, -1.0F, 0.0F), new Vector3f(-1.0F, 1.0F, 0.0F), new Vector3f(1.0F, 1.0F, 0.0F), new Vector3f(1.0F, -1.0F, 0.0F)}; + float scale = this.getQuadSize(partialTicks); + + for(int v = 0; v < 4; ++v) { + Vector3f vector3f = v3f[v]; + vector3f.rotate(quaternion); + vector3f.mul(scale); + vector3f.add(f, f1, f2); + } + + buffer.addVertex((float)(xx + (double)v3f[0].x), (float)(yy + (double)v3f[0].y), (float)(zz + (double)v3f[0].z)).setUv(u1, v1).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(i); + buffer.addVertex((float)(xx + (double)v3f[1].x), (float)(yy + (double)v3f[1].y), (float)(zz + (double)v3f[1].z)).setUv(u1, v0).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(i); + buffer.addVertex((float)(xx + (double)v3f[2].x), (float)(yy + (double)v3f[2].y), (float)(zz + (double)v3f[2].z)).setUv(u0, v0).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(i); + buffer.addVertex((float)(xx + (double)v3f[3].x), (float)(yy + (double)v3f[3].y), (float)(zz + (double)v3f[3].z)).setUv(u0, v1).setColor(this.rCol, this.gCol, this.bCol, this.alpha).setLight(i); + } + + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleTexFX.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleTexFX.java new file mode 100644 index 00000000..c3f912f8 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/ParticleTexFX.java @@ -0,0 +1,16 @@ +package dev.protomanly.pmweather.particle; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +public class ParticleTexFX extends EntityRotFX { + public ParticleTexFX(ClientLevel level, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed, TextureAtlasSprite sprite) { + super(level, x, y, z, xSpeed, ySpeed - (double)0.5F, zSpeed); + this.setSprite(sprite); + this.setColor(1.0F, 1.0F, 1.0F); + this.gravity = 1.0F; + this.quadSize = 0.15F; + this.setLifetime(100); + this.setCanCollide(false); + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/behavior/ParticleBehavior.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/behavior/ParticleBehavior.java new file mode 100644 index 00000000..fabb9ba6 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/particle/behavior/ParticleBehavior.java @@ -0,0 +1,189 @@ +package dev.protomanly.pmweather.particle.behavior; + +import dev.protomanly.pmweather.PMWeather; +import dev.protomanly.pmweather.particle.EntityRotFX; +import dev.protomanly.pmweather.particle.ParticleTexExtraRender; +import java.util.ArrayList; +import java.util.List; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.Vec3; + +public class ParticleBehavior { + public List particles = new ArrayList(); + public Vec3 coordSource; + public Entity sourceEntity; + public float rateDarken = 0.025F; + public float rateBrighten = 0.01F; + public float rateBrightenSlower = 0.003F; + public float rateAlpha = 0.002F; + public float rateScale = 0.1F; + public int tickSmokifyTrigger = 40; + float vanillaRainRed = 0.7F; + float vanillaRainGreen = 0.7F; + float vanillaRainBlue = 1.0F; + + public ParticleBehavior(Vec3 coordSource) { + super(); + this.coordSource = coordSource; + } + + public EntityRotFX initParticle(EntityRotFX particle) { + particle.setPrevPosX(particle.getX()); + particle.setPrevPosY(particle.getY()); + particle.setPrevPosZ(particle.getZ()); + particle.setSize(0.01F, 0.01F); + return particle; + } + + public void initParticleRain(EntityRotFX particle, int extraRenderCount) { + if (particle instanceof ParticleTexExtraRender particleTexExtraRender) { + particleTexExtraRender.extraParticlesBaseAmount = extraRenderCount; + } + + particle.killWhenUnderTopmostBlock = true; + particle.setCanCollide(false); + particle.killWhenUnderCameraAtLeast = 5; + particle.dontRenderUnderTopmostBlock = true; + particle.fastLight = true; + particle.slantParticleToWind = true; + particle.facePlayer = false; + particle.setScale(0.3F); + particle.isTransparent = true; + particle.setGravity(1.8F); + particle.setLifetime(50); + particle.ticksFadeInMax = 5.0F; + particle.ticksFadeOutMax = 5.0F; + particle.ticksFadeOutMaxOnDeath = 3.0F; + particle.setAlpha(0.0F); + particle.rotationYaw = (float)PMWeather.RANDOM.nextInt(360) - 180.0F; + particle.setMotionY((double)-0.5F); + particle.setColor(this.vanillaRainRed, this.vanillaRainGreen, this.vanillaRainBlue); + particle.spawnAsWeatherEffect(); + } + + public void initParticleGroundSplash(EntityRotFX particle) { + particle.killWhenUnderTopmostBlock = true; + particle.setCanCollide(false); + particle.killWhenUnderCameraAtLeast = 5; + particle.facePlayer = true; + particle.setScale(0.2F + PMWeather.RANDOM.nextFloat() * 0.05F); + particle.setLifetime(15); + particle.setGravity(1.0F); + particle.ticksFadeInMax = 3.0F; + particle.ticksFadeOutMax = 4.0F; + particle.setAlpha(0.0F); + particle.renderOrder = 2; + particle.rotationYaw = (float)PMWeather.RANDOM.nextInt(360) - 180.0F; + particle.rotationPitch = 90.0F; + particle.setMotionY((double)(PMWeather.RANDOM.nextFloat() * 0.2F)); + particle.setMotionX((double)((PMWeather.RANDOM.nextFloat() - 0.5F) * 0.01F)); + particle.setMotionZ((double)((PMWeather.RANDOM.nextFloat() - 0.5F) * 0.01F)); + particle.setColor(this.vanillaRainRed, this.vanillaRainGreen, this.vanillaRainBlue); + } + + public void initParticleSnow(EntityRotFX particle, int extraRenderCount, float windSpeed) { + if (particle instanceof ParticleTexExtraRender particleTexExtraRender) { + particleTexExtraRender.extraParticlesBaseAmount = extraRenderCount; + } + + float windScale = Math.max(0.1F, 1.0F - windSpeed); + particle.setCanCollide(false); + particle.ticksFadeOutMaxOnDeath = 5.0F; + particle.dontRenderUnderTopmostBlock = true; + particle.killWhenUnderTopmostBlock = true; + particle.killWhenFarFromCameraAtLeast = 25; + particle.setMotionX((double)0.0F); + particle.setMotionY((double)0.0F); + particle.setMotionZ((double)0.0F); + particle.setScale(0.19500001F + PMWeather.RANDOM.nextFloat() * 0.05F); + particle.setGravity(0.05F + PMWeather.RANDOM.nextFloat() * 0.1F); + particle.setLifetime((int)(1440.0F * windScale)); + particle.facePlayer = true; + particle.ticksFadeInMax = 40.0F * windScale; + particle.ticksFadeOutMax = 40.0F * windScale; + particle.ticksFadeOutMaxOnDeath = 10.0F; + particle.setAlpha(0.0F); + particle.rotationYaw = (float)PMWeather.RANDOM.nextInt(360) - 180.0F; + } + + public void initParticleSleet(EntityRotFX particle, int extraRenderCount) { + if (particle instanceof ParticleTexExtraRender particleTexExtraRender) { + particleTexExtraRender.extraParticlesBaseAmount = extraRenderCount; + } + + particle.setCanCollide(false); + particle.ticksFadeOutMaxOnDeath = 5.0F; + particle.dontRenderUnderTopmostBlock = true; + particle.killWhenFarFromCameraAtLeast = 25; + particle.setMotionX((double)0.0F); + particle.setMotionY((double)0.0F); + particle.setMotionZ((double)0.0F); + particle.setScale(0.3F); + particle.setGravity(1.2F); + particle.setLifetime(50); + particle.facePlayer = true; + particle.ticksFadeInMax = 5.0F; + particle.ticksFadeOutMax = 5.0F; + particle.ticksFadeOutMaxOnDeath = 10.0F; + particle.setAlpha(0.0F); + particle.rotationYaw = (float)PMWeather.RANDOM.nextInt(360) - 180.0F; + } + + public void initParticleHail(EntityRotFX particle) { + particle.killWhenUnderTopmostBlock = false; + particle.setCanCollide(true); + particle.killOnCollide = true; + particle.killWhenUnderCameraAtLeast = 5; + particle.dontRenderUnderTopmostBlock = true; + particle.rotationYaw = (float)PMWeather.RANDOM.nextInt(360); + particle.rotationPitch = (float)PMWeather.RANDOM.nextInt(360); + particle.fastLight = true; + particle.slantParticleToWind = true; + particle.windWeight = 1.5F; + particle.ignoreWind = false; + particle.spinFast = true; + particle.spinFastRate = 10.0F; + particle.facePlayer = false; + particle.setScale(0.105000004F); + particle.isTransparent = false; + particle.setGravity(3.5F); + particle.ticksFadeInMax = 5.0F; + particle.ticksFadeOutMax = 5.0F; + particle.ticksFadeOutMaxOnDeath = 50.0F; + particle.fullAlphaTarget = 1.0F; + particle.setAlpha(0.0F); + particle.rotationYaw = (float)(PMWeather.RANDOM.nextInt(360) - 180); + particle.setMotionY((double)-0.5F); + particle.setColor(0.9F, 0.9F, 0.9F); + particle.bounceOnVerticalImpact = true; + particle.bounceOnVerticalImpactEnergy = 0.3F; + } + + public void initParticleCube(EntityRotFX particle) { + particle.killWhenUnderTopmostBlock = false; + particle.setCanCollide(true); + particle.killOnCollide = true; + particle.killOnCollideActivateAtAge = 30; + particle.killWhenUnderCameraAtLeast = 0; + particle.dontRenderUnderTopmostBlock = true; + particle.rotationPitch = (float)PMWeather.RANDOM.nextInt(360); + particle.fastLight = true; + particle.ignoreWind = true; + particle.spinFast = true; + particle.spinFastRate = 1.0F; + particle.facePlayer = false; + particle.setScale(0.45F); + particle.isTransparent = false; + particle.setGravity(0.5F); + particle.setLifetime(400); + particle.ticksFadeInMax = 5.0F; + particle.ticksFadeOutMax = 5.0F; + particle.ticksFadeOutMaxOnDeath = 20.0F; + particle.fullAlphaTarget = 1.0F; + particle.setAlpha(0.0F); + particle.rotationYaw = (float)(PMWeather.RANDOM.nextInt(360) - 180); + particle.vanillaMotionDampen = true; + particle.bounceOnVerticalImpact = true; + particle.bounceOnVerticalImpactEnergy = 0.3F; + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/render/RenderEvents.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/render/RenderEvents.java new file mode 100644 index 00000000..c47e98c2 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/render/RenderEvents.java @@ -0,0 +1,72 @@ +package dev.protomanly.pmweather.render; + +import dev.protomanly.pmweather.event.GameBusClientEvents; +import dev.protomanly.pmweather.particle.ParticleManager; +import dev.protomanly.pmweather.shaders.ModShaders; +import dev.protomanly.pmweather.weather.Lightning; +import dev.protomanly.pmweather.weather.WeatherHandlerClient; +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.MultiBufferSource; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.common.EventBusSubscriber.Bus; +import net.neoforged.neoforge.client.event.RenderLevelStageEvent; +import net.neoforged.neoforge.client.event.RenderLevelStageEvent.Stage; + +@EventBusSubscriber( + modid = "pmweather", + bus = Bus.GAME, + value = {Dist.CLIENT} +) +public class RenderEvents { + public static List lightningColors = new ArrayList() { + { + this.add(new Color(16777215)); + this.add(new Color(15587698)); + this.add(new Color(13041578)); + this.add(new Color(10778102)); + this.add(new Color(15106690)); + this.add(new Color(7203548)); + } + }; + + public RenderEvents() { + super(); + } + + @SubscribeEvent + public static void render(RenderLevelStageEvent event) { + if (event.getStage() == Stage.AFTER_WEATHER) { + RadarRenderer.RenderedRadars = 0; + float partialTicks = event.getPartialTick().getGameTimeDeltaTicks(); + WeatherHandlerClient weatherHandlerClient = (WeatherHandlerClient)GameBusClientEvents.weatherHandler; + if (weatherHandlerClient != null) { + List lightnings = weatherHandlerClient.lightnings; + + for(int i = 0; i < lightnings.size(); ++i) { + Lightning lightning = lightnings.get(i); + if (lightning != null) { + Random rand = new Random(lightning.seed); + Color color = lightningColors.get(rand.nextInt(lightningColors.size())); + float p = Math.clamp(((float)lightning.ticks + partialTicks) / (float)lightning.lifetime, 0.0F, 1.0F); + float alpha = (float)Math.abs(Math.cos(Math.sqrt((double)p) * Math.PI * (double)3.0F)) * (1.0F - p); + color = new Color(color.getRed(), color.getGreen(), color.getBlue(), Math.clamp((long)((int)(alpha * 255.0F)), 0, 255)); + CustomLightningRenderer.render(lightning.position, lightning.seed, event.getCamera(), color); + } + } + + ModShaders.renderShaders(event.getPartialTick().getGameTimeDeltaTicks(), event.getCamera(), event.getProjectionMatrix(), event.getModelViewMatrix()); + ParticleManager pm = GameBusClientEvents.particleManager; + if (pm != null) { + pm.render(event.getPoseStack(), (MultiBufferSource.BufferSource)null, Minecraft.getInstance().gameRenderer.lightTexture(), event.getCamera(), event.getPartialTick().getGameTimeDeltaPartialTick(false), event.getFrustum()); + } + } + } + + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/shaders/ModShaders.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/shaders/ModShaders.java new file mode 100644 index 00000000..75756f27 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/shaders/ModShaders.java @@ -0,0 +1,347 @@ +package dev.protomanly.pmweather.shaders; + +import com.google.gson.JsonSyntaxException; +import com.mojang.blaze3d.Blaze3D; +import com.mojang.blaze3d.systems.RenderSystem; +import dev.protomanly.pmweather.PMWeather; +import dev.protomanly.pmweather.compat.DistantHorizons; +import dev.protomanly.pmweather.config.ClientConfig; +import dev.protomanly.pmweather.config.ServerConfig; +import dev.protomanly.pmweather.event.GameBusClientEvents; +import dev.protomanly.pmweather.mixin.PostChainMixin; +import dev.protomanly.pmweather.weather.Lightning; +import dev.protomanly.pmweather.weather.Storm; +import dev.protomanly.pmweather.weather.ThermodynamicEngine; +import dev.protomanly.pmweather.weather.WeatherHandler; +import dev.protomanly.pmweather.weather.WeatherHandlerClient; +import dev.protomanly.pmweather.weather.WindEngine; +import java.io.IOException; +import java.util.List; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.EffectInstance; +import net.minecraft.client.renderer.PostChain; +import net.minecraft.client.renderer.PostPass; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import org.lwjgl.opengl.GL32; + +public class ModShaders { + private static PostChain clouds; + private static Vec2 lastScroll; + public static Vec2 scroll; + private static int lastWidth; + private static int lastHeight; + private static float snow; + private static float lastSnow; + private static float gameTime; + private static boolean dhIntegrationInitialized; + private static Integer noiseTextureID; + + public ModShaders() { + super(); + } + + public static void tick() { + Minecraft minecraft = Minecraft.getInstance(); + LocalPlayer player = minecraft.player; + WeatherHandler weatherHandler = GameBusClientEvents.weatherHandler; + if (player != null && weatherHandler != null && Minecraft.getInstance().level != null) { + Vec3 wind = WindEngine.getWind((Vec3)(new Vec3(player.position().x, (double)minecraft.level.getMaxBuildHeight(), player.position().z)), minecraft.level, true, true, false); + Vec3 wind2 = wind.multiply(0.03, 0.03, 0.03); + lastScroll = scroll; + scroll = scroll.add(new Vec2(-((float)wind2.x), -((float)wind2.z))); + + for(Storm storm : weatherHandler.getStorms()) { + if (storm.lastPosition == null) { + storm.lastPosition = storm.position; + } else { + storm.lastPosition = storm.lastPosition.lerp(storm.position, (double)0.05F); + } + + storm.lastSpin = storm.spin; + storm.spin += storm.smoothWindspeed * 0.01F / Math.max(storm.smoothWidth, 20.0F); + } + + ThermodynamicEngine.Precipitation precip = ThermodynamicEngine.getPrecipitationType(weatherHandler, player.position(), minecraft.level, 0); + lastSnow = snow; + if (precip != ThermodynamicEngine.Precipitation.SNOW && precip != ThermodynamicEngine.Precipitation.WINTRY_MIX) { + snow = Mth.lerp(0.05F, snow, 0.0F); + } else { + float rain = weatherHandler.getPrecipitation(player.position()); + float snowBlindness = (float)Math.clamp(Math.pow(wind.length() / (double)60.0F, (double)2.0F) * (double)rain, (double)0.0F, (double)1.0F); + snow = Mth.lerp(0.05F, snow, snowBlindness); + } + } + + } + + public static void renderShaders(float partialTicks, Camera camera, Matrix4f projMat, Matrix4f modelMat) { + Minecraft minecraft = Minecraft.getInstance(); + LocalPlayer player = minecraft.player; + WeatherHandler weatherHandler = GameBusClientEvents.weatherHandler; + if (clouds != null && player != null && weatherHandler != null && Minecraft.getInstance().level != null && ServerConfig.validDimensions != null && ServerConfig.validDimensions.contains(Minecraft.getInstance().level.dimension())) { + int width = minecraft.getWindow().getWidth(); + int height = minecraft.getWindow().getHeight(); + if (width != lastWidth || height != lastHeight) { + lastWidth = width; + lastHeight = height; + updateShaderGroupSize(clouds); + } + + float gameTime = (float)Blaze3D.getTime(); + RenderSystem.enableDepthTest(); + RenderSystem.resetTextureMatrix(); + RenderSystem.disableBlend(); + RenderSystem.depthMask(false); + PostChain var11 = clouds; + if (var11 instanceof PostChainMixin) { + PostChainMixin data = (PostChainMixin)var11; + List passes = data.getPasses(); + EffectInstance effect = ((PostPass)passes.getFirst()).getEffect(); + effect.safeGetUniform("OutSize").set((float)width, (float)height); + Vec3 camPos = camera.getPosition(); + effect.safeGetUniform("pos").set((float)camPos.x, (float)camPos.y, (float)camPos.z); + effect.safeGetUniform("scroll").set(Mth.lerp(partialTicks, lastScroll.x, scroll.x), Mth.lerp(partialTicks, lastScroll.y, scroll.y)); + effect.safeGetUniform("maxSteps").set(800); + effect.safeGetUniform("stepSize").set(0.01F); + effect.safeGetUniform("fogStart").set(RenderSystem.getShaderFogStart() * 4.0F); + effect.safeGetUniform("fogEnd").set(RenderSystem.getShaderFogEnd() * 4.0F); + effect.safeGetUniform("proj").set(projMat.invert()); + effect.safeGetUniform("viewmat").set(modelMat.invert()); + effect.safeGetUniform("time").set((float)player.tickCount + partialTicks); + long seed = 0L; + if (GameBusClientEvents.weatherHandler != null) { + seed = GameBusClientEvents.weatherHandler.seed; + } + + float gameTimeGoal = (float)player.level().getGameTime() + (float)seed / 1.0E14F + partialTicks; + gameTime = Mth.lerp(0.3F, gameTime, gameTimeGoal); + if (Math.abs(gameTime - gameTimeGoal) > 30.0F) { + gameTime = gameTimeGoal; + } + + effect.safeGetUniform("worldTime").set(gameTime); + effect.safeGetUniform("layer0height").set((float)ServerConfig.layer0Height); + effect.safeGetUniform("layerCheight").set((float)ServerConfig.layerCHeight); + effect.safeGetUniform("stormSize").set((float)ServerConfig.stormSize * 2.0F); + float sunAng = Minecraft.getInstance().level.getSunAngle(partialTicks); + Vec3 sunDir = new Vec3(-Math.sin((double)sunAng), Math.cos((double)sunAng), (double)0.0F); + effect.safeGetUniform("sunDir").set((float)sunDir.x, (float)sunDir.y, (float)sunDir.z); + effect.safeGetUniform("lightIntensity").set((float)Math.pow((Math.cos((double)sunAng) + (double)1.0F) / (double)2.0F, (double)3.0F)); + effect.safeGetUniform("downsample").set((float)ClientConfig.volumetricsDownsample); + ((PostPass)passes.get(1)).getEffect().safeGetUniform("downsample").set((float)ClientConfig.volumetricsDownsample); + ((PostPass)passes.get(1)).getEffect().safeGetUniform("glowFix").set(ClientConfig.glowFix ? 1.0F : 0.0F); + ((PostPass)passes.get(1)).getEffect().safeGetUniform("doBlur").set(ClientConfig.volumetricsBlur ? 1.0F : 0.0F); + effect.safeGetUniform("simpleLighting").set(ClientConfig.simpleLighting ? 0.0F : 1.0F); + + try { + if (DistantHorizons.isAvailable()) { + int depthTextureId = DistantHorizons.getDepthTextureId(); + GL32.glActiveTexture(33991); + GL32.glBindTexture(3553, depthTextureId); + effect.safeGetUniform("dhDepthTex0").set(7); + effect.safeGetUniform("hasDHDepth").set(1.0F); + Matrix4f dhProj = DistantHorizons.getDhProjectionMatrix(); + Matrix4f dhProjInv = (new Matrix4f(dhProj)).invert(); + effect.safeGetUniform("dhProjection").set(dhProj); + effect.safeGetUniform("dhProjectionInverse").set(dhProjInv); + effect.safeGetUniform("dhViewmat").set(DistantHorizons.getDhModelViewMatrix()); + effect.safeGetUniform("dhNearPlane").set(DistantHorizons.getNearPlane()); + effect.safeGetUniform("dhFarPlane").set(DistantHorizons.getFarPlane()); + effect.safeGetUniform("dhRenderDistance").set((float)DistantHorizons.getChunkRenderDistance() * 16.0F); + GL32.glActiveTexture(33984); + } else { + effect.safeGetUniform("hasDHDepth").set(0.0F); + } + } catch (Exception var39) { + PMWeather.LOGGER.debug("DH Uniforms not available"); + effect.safeGetUniform("hasDHDepth").set(0.0F); + } + + if (weatherHandler instanceof WeatherHandlerClient) { + WeatherHandlerClient whc = (WeatherHandlerClient)weatherHandler; + effect.safeGetUniform("rain").set(whc.getPrecipitation()); + effect.safeGetUniform("snow").set(Mth.lerp(partialTicks, lastSnow, snow)); + } + + List storms = weatherHandler.getStorms(); + float[] stormPositions = new float[48]; + float[] stormVelocites = new float[32]; + float[] stormStages = new float[16]; + float[] stormEnergies = new float[16]; + float[] stormTypes = new float[16]; + float[] tornadoWindspeeds = new float[16]; + float[] tornadoWidths = new float[16]; + float[] tornadoTouchdownSpeeds = new float[16]; + float[] visualOnlys = new float[16]; + float[] stormSpins = new float[16]; + float[] stormDyings = new float[16]; + float[] tornadoShapes = new float[16]; + float[] stormOcclusions = new float[16]; + float[] lightningStrikes = new float[192]; + if (GameBusClientEvents.weatherHandler != null) { + float[] lightningBrightness = new float[64]; + List lightnings = ((WeatherHandlerClient)GameBusClientEvents.weatherHandler).lightnings; + + for(int i = 0; i < lightnings.size(); ++i) { + if (i < 64) { + Lightning lightning = lightnings.get(i); + lightningStrikes[i * 3] = (float)lightning.position.x; + lightningStrikes[i * 3 + 1] = (float)lightning.position.y; + lightningStrikes[i * 3 + 2] = (float)lightning.position.z; + float p = Math.clamp(((float)lightning.ticks + partialTicks) / (float)lightning.lifetime, 0.0F, 1.0F); + lightningBrightness[i] = (float)Math.abs(Math.cos(Math.sqrt((double)p) * Math.PI * (double)3.0F)) * (1.0F - p); + } + } + + effect.safeGetUniform("lightningStrikes").set(lightningStrikes); + effect.safeGetUniform("lightningCount").set(lightnings.size()); + effect.safeGetUniform("lightningBrightness").set(lightningBrightness); + } + + int count = 0; + + for(int i = 0; i < storms.size(); ++i) { + if (i < 16) { + Storm storm = storms.get(i); + if (!(storm.position.multiply((double)1.0F, (double)0.0F, (double)1.0F).distanceTo(camera.getPosition().multiply((double)1.0F, (double)0.0F, (double)1.0F)) > (double)32000.0F) && (storm.stage > 0 || storm.energy > 0 || storm.stormType == 2) && storm.lastPosition != null) { + Vec3 pos = storm.lastPosition; + stormPositions[count * 3] = (float)pos.x; + stormPositions[count * 3 + 1] = (float)pos.y; + stormPositions[count * 3 + 2] = (float)pos.z; + Vec3 vel = storm.velocity; + stormVelocites[count * 2] = (float)vel.x; + stormVelocites[count * 2 + 1] = (float)vel.z; + stormStages[count] = (float)storm.stage; + stormEnergies[count] = (float)storm.energy; + tornadoWindspeeds[count] = storm.smoothWindspeed; + tornadoWidths[count] = storm.smoothWidth; + tornadoTouchdownSpeeds[count] = (float)storm.touchdownSpeed; + stormSpins[count] = Mth.lerp(partialTicks, storm.lastSpin, storm.spin); + tornadoShapes[count] = storm.tornadoShape; + stormTypes[count] = (float)storm.stormType; + stormOcclusions[count] = storm.occlusion; + visualOnlys[count] = storm.visualOnly ? 1.0F : -1.0F; + stormDyings[count] = storm.isDying ? 1.0F : -1.0F; + ++count; + } + } + } + + effect.safeGetUniform("stormCount").set(count); + effect.safeGetUniform("stormPositions").set(stormPositions); + effect.safeGetUniform("stormVelocities").set(stormVelocites); + effect.safeGetUniform("stormStages").set(stormStages); + effect.safeGetUniform("stormEnergies").set(stormEnergies); + effect.safeGetUniform("stormTypes").set(stormTypes); + effect.safeGetUniform("tornadoWindspeeds").set(tornadoWindspeeds); + effect.safeGetUniform("tornadoWidths").set(tornadoWidths); + effect.safeGetUniform("tornadoTouchdownSpeeds").set(tornadoTouchdownSpeeds); + effect.safeGetUniform("visualOnlys").set(visualOnlys); + effect.safeGetUniform("stormSpins").set(stormSpins); + effect.safeGetUniform("stormDyings").set(stormDyings); + effect.safeGetUniform("tornadoShapes").set(tornadoShapes); + effect.safeGetUniform("stormOcclusions").set(stormOcclusions); + effect.safeGetUniform("overcastPerc").set((float)ServerConfig.overcastPercent); + effect.safeGetUniform("rainStrength").set((float)ServerConfig.rainStrength); + Vec3 sampPos = camera.getPosition().multiply((double)1.0F, (double)0.0F, (double)1.0F).add((double)0.0F, ServerConfig.layer0Height, (double)0.0F); + Vec3 lightingColor = new Vec3((double)1.0F, (double)1.0F, (double)1.0F); + lightingColor = lightingColor.lerp(new Vec3(0.741, 0.318, 0.227), Math.pow((double)1.0F - sunDir.y, (double)2.5F)); + lightingColor = lightingColor.lerp(new Vec3(0.314, 0.408, 0.525), Math.clamp((sunDir.y + 0.1) / -0.1, (double)0.0F, (double)1.0F)); + Vec3 skyColor = Minecraft.getInstance().level.getSkyColor(sampPos, partialTicks); + effect.safeGetUniform("lightingColor").set((float)lightingColor.x, (float)lightingColor.y, (float)lightingColor.z); + effect.safeGetUniform("skyColor").set((float)skyColor.x, (float)skyColor.y, (float)skyColor.z); + byte var10000; + switch (ClientConfig.volumetricsQuality.ordinal()) { + case 0 -> var10000 = 0; + case 1 -> var10000 = 1; + case 2 -> var10000 = 2; + case 3 -> var10000 = 3; + case 4 -> var10000 = 4; + default -> throw new MatchException((String)null, (Throwable)null); + } + + int quality = var10000; + effect.safeGetUniform("quality").set(quality); + effect.safeGetUniform("nearPlane").set(0.05F); + effect.safeGetUniform("farPlane").set((float)(Integer)Minecraft.getInstance().options.renderDistance().get() * 4.0F * 16.0F); + effect.safeGetUniform("renderDistance").set(6000.0F); + clouds.process(partialTicks); + } + + minecraft.getMainRenderTarget().bindWrite(false); + RenderSystem.depthMask(true); + projMat.invert(); + modelMat.invert(); + } + + } + + public static PostChain createShader(ResourceLocation resourceLocation) { + try { + Minecraft minecraft = Minecraft.getInstance(); + return new PostChain(minecraft.getTextureManager(), minecraft.getResourceManager(), minecraft.getMainRenderTarget(), resourceLocation); + } catch (IOException e) { + PMWeather.LOGGER.error("Failed to load shader: {}", resourceLocation, e); + } catch (JsonSyntaxException e) { + PMWeather.LOGGER.error("Failed to parse shader: {}", resourceLocation, e); + } + + return null; + } + + public static void createShaders() { + if (clouds == null) { + clouds = createShader(PMWeather.getPath("shaders/post/clouds.json")); + } + + } + + public static void reload() { + if (clouds != null) { + clouds.close(); + } + + clouds = null; + createShaders(); + updateShaderGroupSize(clouds); + PMWeather.LOGGER.info("Loaded PMWeather Shaders"); + } + + private static void updateShaderGroupSize(PostChain shaderGroup) { + if (shaderGroup != null) { + Minecraft minecraft = Minecraft.getInstance(); + int width = minecraft.getWindow().getWidth(); + int height = minecraft.getWindow().getHeight(); + shaderGroup.resize(width, height); + } + + } + + static { + lastScroll = Vec2.ZERO; + scroll = Vec2.ZERO; + lastWidth = 0; + lastHeight = 0; + snow = 0.0F; + lastSnow = 0.0F; + gameTime = 0.0F; + dhIntegrationInitialized = false; + } + + public static enum Quality { + POTATO, + LOW, + MEDIUM, + HIGH, + PC_KILLER; + + private Quality() { + } + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/Storm.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/Storm.java new file mode 100644 index 00000000..d47e1d42 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/Storm.java @@ -0,0 +1,1119 @@ +package dev.protomanly.pmweather.weather; + +import dev.protomanly.pmweather.PMWeather; +import dev.protomanly.pmweather.block.ModBlocks; +import dev.protomanly.pmweather.block.entity.RadarBlockEntity; +import dev.protomanly.pmweather.config.Config; +import dev.protomanly.pmweather.config.ServerConfig; +import dev.protomanly.pmweather.entity.ModEntities; +import dev.protomanly.pmweather.entity.MovingBlock; +import dev.protomanly.pmweather.interfaces.ParticleData; +import dev.protomanly.pmweather.particle.EntityRotFX; +import dev.protomanly.pmweather.sound.ModSounds; +import dev.protomanly.pmweather.sound.MovingSoundStreamingSource; +import dev.protomanly.pmweather.util.CachedNBTTagCompound; +import dev.protomanly.pmweather.util.Util; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import net.minecraft.client.Minecraft; +import net.minecraft.client.particle.Particle; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.SectionPos; +import net.minecraft.core.Vec3i; +import net.minecraft.core.Direction.Axis; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.world.level.levelgen.Heightmap.Types; +import net.minecraft.world.level.levelgen.synth.SimplexNoise; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.fml.LogicalSide; +import net.neoforged.fml.util.thread.EffectiveSide; +import net.neoforged.neoforge.common.Tags.Blocks; + +public class Storm { + public static long LastUsedStormID = 0L; + private static final float resistance = 0.985F; + public static final float tickConversion = 0.05F; + @OnlyIn(Dist.CLIENT) + public MovingSoundStreamingSource tornadicWind; + @OnlyIn(Dist.CLIENT) + public MovingSoundStreamingSource tornadicDamage; + @OnlyIn(Dist.CLIENT) + public MovingSoundStreamingSource supercellWind; + @OnlyIn(Dist.CLIENT) + public MovingSoundStreamingSource eyewallWind; + @OnlyIn(Dist.CLIENT) + public MovingSoundStreamingSource undergroundWind; + public long ID; + public WeatherHandler weatherHandler; + public Vec3 position; + public Vec3 lastPosition; + public Vec3 velocity; + public int windspeed; + public float cycloneWindspeed = 0.0F; + public float smoothWindspeed = 0.0F; + public float width = 15.0F; + public float smoothWidth = 15.0F; + public float tornadoShape; + public float spin; + public float lastSpin; + public int energy; + public int stormType; + public int stage; + public int tickCount; + public int tornadoOnGroundTicks; + public boolean dead; + public Level level; + private final CachedNBTTagCompound nbtCache; + public SimplexNoise simplexNoise; + public float rankineFactor; + public List listParticleDebris; + private final List forceLoadedChunks; + public int maxStage; + public int maxProgress; + public boolean isDying; + public int growthSpeed; + public int maxWindspeed; + public int maxWidth; + public int ticksSinceDying; + public int touchdownSpeed; + public boolean onWater; + public float occlusion; + public boolean visualOnly; + public boolean cirus; + public boolean aimedAtPlayer; + public int maxColdEnergy; + public int coldEnergy; + public List vorticies; + + public double FBM(Vec3 pos, int octaves, float lacunarity, float gain, float amplitude) { + double y = (double)0.0F; + + for(int i = 0; i < Math.max(octaves, 1); ++i) { + y += (double)amplitude * this.simplexNoise.getValue(pos.x, pos.y, pos.z); + pos = pos.multiply((double)lacunarity, (double)lacunarity, (double)lacunarity); + amplitude *= gain; + } + + return y; + } + + public Vec3 rotateV3(Vec3 x, double angle) { + double rx = x.x * Math.cos(angle) - x.z * Math.sin(angle); + double rz = x.x * Math.sin(angle) + x.z * Math.cos(angle); + return new Vec3(rx, x.y, rz); + } + + public Storm(WeatherHandler weatherHandler, Level level, @Nullable Float risk, int stormType) { + super(); + this.tornadoShape = PMWeather.RANDOM.nextFloat() * 10.0F + 6.0F; + this.spin = 0.0F; + this.lastSpin = 0.0F; + this.tickCount = 0; + this.tornadoOnGroundTicks = 0; + this.dead = false; + this.rankineFactor = 4.5F; + this.forceLoadedChunks = new ArrayList(); + this.maxStage = 0; + this.maxProgress = 0; + this.isDying = false; + this.growthSpeed = 20; + this.maxWindspeed = 0; + this.maxWidth = 15; + this.ticksSinceDying = 0; + this.touchdownSpeed = PMWeather.RANDOM.nextInt(65, 120); + this.onWater = false; + this.occlusion = 0.0F; + this.visualOnly = false; + this.cirus = false; + this.aimedAtPlayer = false; + this.maxColdEnergy = 300; + this.coldEnergy = 0; + this.vorticies = new ArrayList(); + this.weatherHandler = weatherHandler; + this.level = level; + this.stormType = stormType; + this.simplexNoise = new SimplexNoise(new LegacyRandomSource(weatherHandler.seed)); + this.nbtCache = new CachedNBTTagCompound(); + if (level.isClientSide()) { + this.listParticleDebris = new ArrayList(); + } else { + this.maxStage = 0; + this.maxProgress = PMWeather.RANDOM.nextInt(25, 99); + float stage1Chance = 1.0F / (float)ServerConfig.chanceInOneStage1; + float stage2Chance = 1.0F / (float)ServerConfig.chanceInOneStage2; + float stage3Chance = 1.0F / (float)ServerConfig.chanceInOneStage3; + if (risk != null && ServerConfig.environmentSystem && stormType == 0) { + stage1Chance *= risk * 1.75F + 0.05F; + stage2Chance *= risk; + stage3Chance *= risk * 0.75F; + PMWeather.LOGGER.debug("Readjusted stage chances: 1: {} 2: {} 3: {}", new Object[]{stage1Chance, stage2Chance, stage3Chance}); + } + + if (PMWeather.RANDOM.nextFloat() <= stage1Chance) { + this.maxStage = 1; + } + + if (PMWeather.RANDOM.nextFloat() <= stage2Chance) { + this.maxStage = 2; + } + + if (PMWeather.RANDOM.nextFloat() <= stage3Chance) { + this.maxStage = 3; + } + + if (this.maxStage == 3 && stormType == 0) { + this.maxProgress = 100; + float mW; + if (risk != null && ServerConfig.environmentSystem) { + mW = risk * 80.0F; + } else { + mW = 125.0F; + } + + mW += 55.0F; + this.maxWindspeed = Math.min((int)Mth.lerp(PMWeather.RANDOM.nextFloat(), 55.0F, mW), 220); + this.touchdownSpeed = PMWeather.RANDOM.nextInt(75, Math.max(25 + (int)((float)this.maxWindspeed * 1.1F), 100)); + } + + this.growthSpeed = PMWeather.RANDOM.nextInt(30, 80); + if (stormType == 1) { + this.growthSpeed = PMWeather.RANDOM.nextInt(40, 70); + } + + this.maxWidth = PMWeather.RANDOM.nextInt(15, 25 + (int)(Math.pow((double)((float)this.maxWindspeed / 220.0F), (double)1.75F) * (ServerConfig.maxTornadoWidth - (double)25.0F))); + PMWeather.LOGGER.debug("Max Stage: {}, Max Energy: {}, Max Windspeed: {}, Max Width: {}, Touchdown Speed: {}", new Object[]{this.maxStage, this.maxProgress, this.maxWindspeed, this.maxWidth, this.touchdownSpeed}); + } + + } + + public void recalc(@Nullable Float risk) { + if (this.maxStage == 3 && this.stormType == 0) { + this.maxProgress = 100; + float mW; + if (risk != null && ServerConfig.environmentSystem) { + mW = risk * 80.0F; + } else { + mW = 125.0F; + } + + mW += 55.0F; + this.maxWindspeed = Math.min((int)Mth.lerp(PMWeather.RANDOM.nextFloat(), 55.0F, mW), 220); + this.touchdownSpeed = PMWeather.RANDOM.nextInt(75, Math.max(25 + (int)((float)this.maxWindspeed * 1.1F), 100)); + } + + this.growthSpeed = PMWeather.RANDOM.nextInt(30, 80); + if (this.stormType == 1) { + this.growthSpeed = PMWeather.RANDOM.nextInt(40, 70); + } + + this.maxWidth = PMWeather.RANDOM.nextInt(15, 25 + (int)(Math.pow((double)((float)this.maxWindspeed / 220.0F), (double)1.75F) * (ServerConfig.maxTornadoWidth - (double)25.0F))); + PMWeather.LOGGER.debug("Max Stage: {}, Max Energy: {}, Max Windspeed: {}, Max Width: {}, Touchdown Speed: {}", new Object[]{this.maxStage, this.maxProgress, this.maxWindspeed, this.maxWidth, this.touchdownSpeed}); + } + + public void aimAtPlayer() { + if (this.stormType != 1) { + Player nearest = this.level.getNearestPlayer(this.position.x, this.position.y, this.position.z, (double)4096.0F, false); + if (nearest != null) { + Vec3 aimPos = nearest.position().add(new Vec3((double)(PMWeather.RANDOM.nextFloat() - 0.5F) * ServerConfig.aimAtPlayerOffset, (double)0.0F, (double)(PMWeather.RANDOM.nextFloat() - 0.5F) * ServerConfig.aimAtPlayerOffset)); + if (this.position.distanceTo(aimPos) >= ServerConfig.aimAtPlayerOffset) { + Vec3 toward = this.position.subtract(new Vec3(aimPos.x, this.position.y, aimPos.z)).multiply((double)1.0F, (double)0.0F, (double)1.0F).normalize(); + double speed = PMWeather.RANDOM.nextDouble() * (double)5.0F + (double)1.0F; + this.velocity = toward.multiply(-speed, (double)0.0F, -speed); + } + + this.aimedAtPlayer = true; + } + + } + } + + public void tick() { + ++this.tickCount; + Iterator vorts = this.vorticies.iterator(); + + while(vorts.hasNext()) { + Vorticy vorticy = vorts.next(); + vorticy.tick(); + if (vorticy.dead) { + vorts.remove(); + } + } + + float vorticySpawnChance = 0.05F; + if (this.isDying) { + vorticySpawnChance = 0.25F; + } + + vorticySpawnChance += Mth.clamp((float)Math.pow((double)(((float)this.windspeed - 100.0F) / 200.0F), (double)2.0F), 0.0F, 0.5F); + if ((float)this.windspeed >= 39.0F && this.stormType == 2) { + vorticySpawnChance *= 2.0F; + if (!this.level.isClientSide && PMWeather.RANDOM.nextFloat() < vorticySpawnChance * 0.05F && this.vorticies.size() < 10) { + Vorticy vorticy = new Vorticy(this, (float)Math.pow((double)PMWeather.RANDOM.nextFloat(), (double)0.75F) * 0.1F, PMWeather.RANDOM.nextFloat() * 0.15F + 0.05F, 0.075F, PMWeather.RANDOM.nextInt(900, 3000)); + this.vorticies.add(vorticy); + } + } + + if (this.stage == 3 && (float)this.windspeed >= 40.0F && this.stormType == 0) { + ++this.tornadoOnGroundTicks; + if (!this.level.isClientSide && PMWeather.RANDOM.nextFloat() < vorticySpawnChance * 0.05F && this.vorticies.size() < 10) { + Vorticy vorticy = new Vorticy(this, (float)Math.pow((double)PMWeather.RANDOM.nextFloat(), (double)0.75F) * 0.4F, PMWeather.RANDOM.nextFloat() * 0.3F + 0.05F, 1.0F / this.rankineFactor * 0.5F, PMWeather.RANDOM.nextInt(35, 120)); + this.vorticies.add(vorticy); + } + } + + if (this.isDying) { + ++this.ticksSinceDying; + } + + BlockPos blockPos = new BlockPos((int)this.position.x, (int)this.position.y, (int)this.position.z); + if (!this.level.isClientSide() && this.stage >= 2 && this.stormType == 0) { + float y = 0.0F; + int count = 0; + + for(int x = -1; x <= 1; ++x) { + for(int z = -1; z <= 1; ++z) { + float r = Math.max(this.width, 45.0F); + Vec3 samplePos = this.position.add((double)((float)x * r * 0.5F), (double)0.0F, (double)((float)z * r * 0.5F)); + BlockPos sample = this.level.getHeightmapPos(Types.WORLD_SURFACE_WG, new BlockPos((int)samplePos.x, this.level.getMaxBuildHeight(), (int)samplePos.z)); + y += (float)sample.getY(); + ++count; + } + } + + y /= (float)count; + blockPos = new BlockPos((int)this.position.x, (int)y, (int)this.position.z); + this.position = new Vec3(this.position.x, Mth.lerp((double)0.01F, this.position.y, (double)y), this.position.z); + } + + if (this.tickCount % 20 == 0 && !this.level.isClientSide()) { + Level iterator = this.level; + if (iterator instanceof ServerLevel) { + ServerLevel serverLevel = (ServerLevel)iterator; + if (this.windspeed > 40 && this.stormType == 0) { + ChunkPos cChunkPos = new ChunkPos(blockPos); + + for(int x = -((int)this.width); (float)x <= this.width; x += 16) { + for(int z = -((int)this.width); (float)z <= this.width; z += 16) { + ChunkPos chunkPos = new ChunkPos(blockPos.offset(x, 0, z)); + if (!serverLevel.hasChunk(chunkPos.x, chunkPos.z) && !this.forceLoadedChunks.contains(chunkPos) && serverLevel.isInWorldBounds(blockPos)) { + this.forceLoadedChunks.add(chunkPos); + serverLevel.setChunkForced(chunkPos.x, chunkPos.z, true); + } + } + } + + Iterator iterator = this.forceLoadedChunks.iterator(); + + while(iterator.hasNext()) { + ChunkPos cpos = iterator.next(); + double dist = Math.sqrt((double)cpos.distanceSquared(cChunkPos)); + if (dist > (double)(this.width * 2.0F / 16.0F)) { + iterator.remove(); + serverLevel.setChunkForced(cpos.x, cpos.z, false); + } + } + } else { + Iterator iterator = this.forceLoadedChunks.iterator(); + + while(iterator.hasNext()) { + ChunkPos cpos = iterator.next(); + iterator.remove(); + serverLevel.setChunkForced(cpos.x, cpos.z, false); + } + } + } + } + + if (this.tickCount % 10 == 0 && !this.level.isClientSide()) { + Level targetProgress = this.level; + if (targetProgress instanceof ServerLevel) { + ServerLevel serverLevel = (ServerLevel)targetProgress; + float lightningChance = 0.0F; + if (this.stage == 1) { + lightningChance = (float)this.energy / 100.0F; + } else if (this.stage == 2) { + lightningChance = 1.0F + (float)this.energy / 100.0F; + } else if (this.stage > 2) { + lightningChance = 2.0F; + } + + if (this.visualOnly) { + lightningChance = 0.0F; + } + + lightningChance = Math.min(lightningChance * 0.035F, 0.1F); + if (this.stormType == 1) { + lightningChance *= 3.0F; + } + + if (PMWeather.RANDOM.nextFloat() <= lightningChance * 0.5F) { + Vec3 lPos = this.position.add((double)(PMWeather.RANDOM.nextFloat((float)(-ServerConfig.stormSize), (float)ServerConfig.stormSize) / 2.0F), (double)0.0F, (double)(PMWeather.RANDOM.nextFloat((float)(-ServerConfig.stormSize), (float)ServerConfig.stormSize) / 2.0F)); + if (this.stormType == 1) { + Vec2 stormVel = new Vec2((float)this.velocity.x, (float)this.velocity.z); + Vec2 right = (new Vec2(stormVel.y, -stormVel.x)).normalized(); + Vec2 fwd = stormVel.normalized(); + right = Util.mulVec2(right, PMWeather.RANDOM.nextFloat((float)(-ServerConfig.stormSize), (float)ServerConfig.stormSize) * 5.0F); + fwd = Util.mulVec2(fwd, PMWeather.RANDOM.nextFloat((float)(-ServerConfig.stormSize), (float)ServerConfig.stormSize) / 2.0F); + lPos = this.position.add(new Vec3((double)right.x, (double)0.0F, (double)right.y)).add(new Vec3((double)fwd.x, (double)0.0F, (double)fwd.y)); + } + + int height = this.level.getHeightmapPos(Types.MOTION_BLOCKING, new BlockPos((int)lPos.x, (int)lPos.y, (int)lPos.z)).getY(); + ((WeatherHandlerServer)this.weatherHandler).syncLightningNew(new Vec3(lPos.x, (double)height, lPos.z)); + } + } + } + + int gs = this.growthSpeed / 2; + if (this.stormType == 0 && this.stage < 3) { + gs = (int)((float)gs / 1.5F); + } + + if (this.tickCount % gs == 0) { + if (this.stormType == 2 && !this.level.isClientSide()) { + this.stage = 0; + if (this.windspeed >= 15) { + this.stage = 1; + } + + if (this.windspeed >= 25) { + this.stage = 2; + } + + if (this.windspeed >= 40) { + this.stage = 3; + } + + Float sst = ThermodynamicEngine.GetSST(this.weatherHandler, this.position, this.level, (RadarBlockEntity)null, 0); + if (sst != null && this.tickCount <= 48000) { + if (sst > 32.0F) { + sst = 32.0F; + } + + float v = 24.0F; + if (this.cycloneWindspeed > 60.0F) { + v += (this.cycloneWindspeed - 60.0F) / 18.5F; + } + + float growth = (sst - v) / 3.5F; + if ((float)this.windspeed > 165.0F) { + growth -= ((float)this.windspeed - 165.0F) / 15.0F; + } + + if (growth < 0.0F) { + growth = Math.max(growth, -1.5F); + } else { + growth *= 1.25F; + growth = Math.min(growth, 3.0F); + } + + this.cycloneWindspeed += growth; + } else { + float death = 1.0F; + death += Math.max((this.cycloneWindspeed - 75.0F) / 100.0F, 0.0F); + this.cycloneWindspeed -= death * 0.25F; + } + + this.windspeed = Math.round(this.cycloneWindspeed); + if (this.windspeed < -5) { + this.dead = true; + } + } else if (!this.isDying) { + int targetProgress = this.maxProgress; + if (this.maxStage > this.stage) { + targetProgress = 100; + } + + if (this.energy < targetProgress) { + ++this.energy; + if (this.stormType == 1) { + this.coldEnergy = Math.clamp((long)(this.coldEnergy + 1), 0, this.maxColdEnergy); + } + } + + if (this.stage >= 3 && this.stormType == 0) { + if (this.windspeed < this.maxWindspeed) { + ++this.windspeed; + this.occlusion = Math.clamp(this.occlusion - 0.025F, 0.0F, 1.0F); + } + + if (this.windspeed >= this.maxWindspeed) { + this.isDying = true; + this.growthSpeed = PMWeather.RANDOM.nextInt(20, 70); + } + } else if (this.stage >= this.maxStage && this.energy >= targetProgress) { + this.isDying = true; + this.growthSpeed = PMWeather.RANDOM.nextInt(40, 80); + if (PMWeather.RANDOM.nextInt(2) == 0 || this.maxWidth > 200) { + this.maxWidth = Math.min(this.maxWidth, PMWeather.RANDOM.nextInt(5, 35)); + } + } + + if (this.energy >= 100) { + this.energy = 0; + if (this.stormType == 0) { + if (this.stage < 3 && this.stage < this.maxStage) { + ++this.stage; + if (this.stage == 3) { + this.windspeed = 0; + } + } + } else if (this.stage < this.maxStage) { + ++this.stage; + } + } + } else if (this.ticksSinceDying > (this.stormType == 1 ? 2400 : 1200)) { + if (this.stage >= 3 && this.stormType == 0) { + if (this.windspeed < 85 && this.windspeed > 15) { + if (PMWeather.RANDOM.nextInt(2) == 0 && !this.level.isClientSide()) { + --this.windspeed; + } + } else { + --this.windspeed; + } + + this.occlusion = Math.clamp(this.occlusion + 0.015F, 0.0F, 1.0F); + if (this.windspeed <= 0) { + this.windspeed = 0; + --this.stage; + this.energy = 100; + } + } else { + --this.energy; + if (this.energy <= 0) { + this.energy = 100; + --this.stage; + if (this.stage < 0) { + this.energy = 0; + this.stage = 0; + if (this.coldEnergy > 0) { + --this.coldEnergy; + } else { + this.dead = true; + } + } + } + } + } + + if (Config.DEBUG) { + PMWeather.LOGGER.debug("Stage: {}, Energy: {}, Windspeed: {}, Width: {}", new Object[]{this.stage, this.energy, this.windspeed, this.width}); + } + } + + if (this.stormType == 0) { + this.width = Mth.lerp(0.025F, this.width, Math.max(5.0F, Math.clamp((float)this.windspeed / (float)this.maxWindspeed, 0.1F, 1.0F) * (float)this.maxWidth)); + } else if (this.stormType == 2) { + this.width = (float)this.maxWidth; + } + + Vec3 vel = this.velocity.multiply((double)0.05F, (double)0.05F, (double)0.05F).multiply((double)2.0F, (double)0.0F, (double)2.0F); + if (!this.aimedAtPlayer) { + vel = vel.add((new Vec3((double)0.0F, (double)0.0F, (double)-3.0F)).multiply((double)(0.05F * this.occlusion), (double)(0.05F * this.occlusion), (double)(0.05F * this.occlusion))); + } + + this.position = this.position.add(vel); + if (!this.aimedAtPlayer) { + if (this.stormType != 1) { + this.velocity = this.velocity.multiply((double)0.985F, (double)0.985F, (double)0.985F); + Vec3 baseWind = WindEngine.getWind(new Vec3(this.position.x, (double)(this.level.getMaxBuildHeight() + 1), this.position.z), this.level, true, true, false, false); + float factor = 0.018181818F; + if (this.stormType == 2) { + factor = 0.05F; + } + + Vec3 velAdd = (new Vec3(baseWind.x, (double)0.0F, baseWind.z)).multiply((double)factor, (double)0.0F, (double)factor); + this.velocity = this.velocity.add(velAdd.multiply((double)0.05F, (double)0.05F, (double)0.05F)); + } + + if (!this.level.isClientSide() && this.stage >= 3 && ServerConfig.aimAtPlayer && this.stormType == 0) { + this.aimAtPlayer(); + } + } + + if (!this.level.isClientSide() && this.tickCount % this.getUpdateRate() == 0) { + WeatherHandlerServer weatherHandlerServer = (WeatherHandlerServer)this.weatherHandler; + weatherHandlerServer.syncStormUpdate(this); + } + + if (this.level.isClientSide()) { + this.tickClient(); + } else if (this.stage >= 3 && this.stormType == 0) { + if (this.windspeed >= 40) { + AABB aabb = new AABB(this.position.x, this.position.y, this.position.z, this.position.x, this.position.y, this.position.z); + aabb = aabb.inflate((double)this.width / (double)2.0F, (double)85.0F, (double)this.width / (double)2.0F); + + for(Entity entity : this.level.getEntities((Entity)null, aabb)) { + if (entity instanceof Player) { + Player player = (Player)entity; + if (!player.isCreative() && !player.isSpectator()) { + this.pull(entity, 2.5F); + continue; + } + } + + if (!(entity instanceof Player)) { + this.pull(entity, 2.5F); + } + } + + boolean dd = this.tickCount % 5 == 0 || !ServerConfig.damageEvery5thTick; + if (dd) { + int windfieldWidth = Math.max((int)this.width, 40); + int numBlocks = Math.min(windfieldWidth * Math.max(windfieldWidth / 2, 20) + this.windspeed * 3 + 300, ServerConfig.maxBlocksDamagedPerTick); + Map checkedMap = new HashMap(); + Map chunkMap = new HashMap(); + int damaged = 0; + int damageMax = (500 + (int)this.width) / 3; + + for(int i = 0; i < numBlocks && damaged < damageMax; ++i) { + int x = (int)(PMWeather.RANDOM.nextFloat() * (float)windfieldWidth * 2.0F - (float)windfieldWidth); + int z = (int)(PMWeather.RANDOM.nextFloat() * (float)windfieldWidth * 2.0F - (float)windfieldWidth); + Vec3i off = new Vec3i(x, 0, z); + if (!checkedMap.containsKey(off)) { + checkedMap.put(off, true); + double dist = off.distSqr(Vec3i.ZERO); + if (!(dist > (double)(windfieldWidth * windfieldWidth))) { + float percAdj = 16.0F; + if (ServerConfig.damageEvery5thTick) { + percAdj *= 5.0F; + } + + BlockPos bPos = blockPos.offset(off.getX(), 60, off.getZ()); + if (this.level.isInWorldBounds(bPos)) { + BlockPos blockPosTop = this.level.getHeightmapPos(Types.MOTION_BLOCKING, bPos).below(); + double windEffect = (double)this.getWind(blockPosTop.getCenter()); + if (!(windEffect < (double)40.0F)) { + ChunkPos chunkPos = new ChunkPos(SectionPos.blockToSectionCoord(blockPosTop.getX()), SectionPos.blockToSectionCoord(blockPosTop.getZ())); + LevelChunk chunk; + if (chunkMap.containsKey(chunkPos)) { + chunk = chunkMap.get(chunkPos); + } else { + PMWeather.LOGGER.debug("{}", chunkPos); + chunk = this.level.getChunk(chunkPos.x, chunkPos.z); + chunkMap.put(chunkPos, chunk); + } + + this.doDamage(chunk, blockPosTop, windEffect, percAdj, windfieldWidth); + } + } + } + } + } + } + } + + } + } + + public void doDamage(LevelChunk chunk, BlockPos blockPosTop, double windEffect, float percAdj, int windfieldWidth) { + BlockState state = chunk.getBlockState(blockPosTop); + BlockPos randomDown = blockPosTop.below(PMWeather.RANDOM.nextInt(10)); + BlockState stateDown = chunk.getBlockState(randomDown); + boolean downBlacklisted = false; + + for(TagKey tag : ServerConfig.blacklistedBlockTags) { + if (stateDown.is(tag)) { + downBlacklisted = true; + break; + } + } + + if (!downBlacklisted && !ServerConfig.blacklistedBlocks.contains(stateDown.getBlock())) { + if (stateDown.is(Blocks.GLASS_BLOCKS) || stateDown.is(Blocks.GLASS_PANES)) { + double percChance = Math.clamp((windEffect - (double)75.0F) / (double)15.0F, (double)0.0F, (double)1.0F); + if ((double)PMWeather.RANDOM.nextFloat() <= percChance * (double)(0.3F * percAdj) && Util.canWindAffect(randomDown.getCenter(), this.level)) { + this.level.removeBlock(randomDown, false); + this.level.playSound((Player)null, randomDown, SoundEvents.GLASS_BREAK, SoundSource.BLOCKS, 1.0F, PMWeather.RANDOM.nextFloat(0.8F, 1.2F)); + } + } + + if (stateDown.is(BlockTags.LOGS) && !stateDown.is(Blocks.STRIPPED_LOGS) && ServerConfig.doDebarking) { + double percChance = Math.clamp((windEffect - (double)140.0F) / (double)20.0F, (double)0.0F, (double)1.0F); + if ((double)PMWeather.RANDOM.nextFloat() <= percChance * (double)(0.5F * percAdj) && Util.canWindAffect(randomDown.getCenter(), this.level)) { + Block replacement = Util.STRIPPED_VARIANTS.getOrDefault(stateDown.getBlock(), net.minecraft.world.level.block.Blocks.STRIPPED_OAK_LOG); + this.level.setBlockAndUpdate(randomDown, (BlockState)replacement.defaultBlockState().trySetValue(BlockStateProperties.AXIS, (Direction.Axis)stateDown.getOptionalValue(BlockStateProperties.AXIS).orElse(Axis.Y))); + } + } + } + + BlockState aboveState = chunk.getBlockState(blockPosTop.above()); + if (!aboveState.isAir()) { + Block aboveBlock = aboveState.getBlock(); + float blockStrength = getBlockStrength(aboveBlock, this.level, blockPosTop.above()); + double percChance = Math.clamp(Math.pow(Math.clamp(Math.max(windEffect - (double)blockStrength, (double)0.0F) / (double)20.0F, (double)0.0F, (double)1.0F), (double)4.0F) + 0.02, (double)0.0F, (double)1.0F) * 0.05 * (double)percAdj; + if (windEffect < (double)blockStrength) { + percChance = (double)0.0F; + } + + if (aboveBlock.defaultDestroyTime() < 0.05F && aboveBlock.defaultDestroyTime() >= 0.0F && !ServerConfig.blacklistedBlocks.contains(aboveBlock) && (double)PMWeather.RANDOM.nextFloat() <= percChance) { + this.level.removeBlock(blockPosTop.above(), false); + return; + } + + boolean blacklisted = false; + + for(TagKey tag : ServerConfig.blacklistedBlockTags) { + if (aboveBlock.defaultBlockState().is(tag)) { + blacklisted = true; + break; + } + } + + if (windEffect >= (double)blockStrength && aboveBlock.defaultDestroyTime() > 0.0F && !ServerConfig.blacklistedBlocks.contains(aboveBlock) && !blacklisted && state.getFluidState().isEmpty() && (double)PMWeather.RANDOM.nextFloat() <= percChance) { + this.level.removeBlock(blockPosTop.above(), false); + } + } + + if (!state.is(net.minecraft.world.level.block.Blocks.GRASS_BLOCK) && !state.is((Block)ModBlocks.SCOURED_GRASS.get())) { + if (state.is(net.minecraft.world.level.block.Blocks.DIRT)) { + double percChance = Math.clamp((windEffect - (double)170.0F) / (double)40.0F, (double)0.0F, (double)1.0F); + if ((double)PMWeather.RANDOM.nextFloat() <= percChance * (double)(0.02F * percAdj)) { + this.level.setBlockAndUpdate(blockPosTop, ((Block)ModBlocks.MEDIUM_SCOURING.get()).defaultBlockState()); + } + + } else if (state.is((Block)ModBlocks.MEDIUM_SCOURING.get())) { + double percChance = Math.clamp((windEffect - (double)200.0F) / (double)30.0F, (double)0.0F, (double)1.0F); + if ((double)PMWeather.RANDOM.nextFloat() <= percChance * (double)(0.02F * percAdj)) { + this.level.setBlockAndUpdate(blockPosTop, ((Block)ModBlocks.HEAVY_SCOURING.get()).defaultBlockState()); + } + + } else { + Block block = state.getBlock(); + float blockStrength = getBlockStrength(block, this.level, blockPosTop); + if (state.is(Blocks.STRIPPED_LOGS)) { + blockStrength *= 2.0F; + } + + if (ServerConfig.blockStrengths.containsKey(block)) { + blockStrength = (Float)ServerConfig.blockStrengths.get(block); + } + + double stretch = (double)35.0F; + if (state.is(BlockTags.LEAVES)) { + stretch = (double)70.0F; + } else if (state.is(BlockTags.LOGS) || state.is(BlockTags.PLANKS)) { + stretch = (double)50.0F; + } + + double percChance = Math.clamp(Math.pow(Math.clamp(Math.max(windEffect - (double)blockStrength, (double)0.0F) / stretch, (double)0.0F, (double)1.0F), (double)4.0F) + 0.02, (double)0.0F, (double)1.0F) * 0.05 * (double)percAdj; + if (windEffect < (double)blockStrength) { + percChance = (double)0.0F; + } + + if (block.defaultDestroyTime() < 0.05F && block.defaultDestroyTime() >= 0.0F && !ServerConfig.blacklistedBlocks.contains(block) && (double)PMWeather.RANDOM.nextFloat() <= percChance) { + this.level.removeBlock(blockPosTop, false); + } else { + boolean blacklisted = false; + + for(TagKey tag : ServerConfig.blacklistedBlockTags) { + if (block.defaultBlockState().is(tag)) { + blacklisted = true; + break; + } + } + + if (windEffect >= (double)blockStrength && block.defaultDestroyTime() > 0.0F && !ServerConfig.blacklistedBlocks.contains(block) && !blacklisted && state.getFluidState().isEmpty() && (double)PMWeather.RANDOM.nextFloat() <= percChance) { + MovingBlock movingBlock = (MovingBlock)((EntityType)ModEntities.MOVING_BLOCK.get()).create(this.level); + if (movingBlock != null) { + movingBlock.setStartPos(blockPosTop); + movingBlock.setBlockState(state); + movingBlock.setPos((double)blockPosTop.getX(), (double)blockPosTop.getY(), (double)blockPosTop.getZ()); + this.level.removeBlock(blockPosTop, false); + Player nearest = this.level.getNearestPlayer((double)blockPosTop.getX(), (double)blockPosTop.getY(), (double)blockPosTop.getZ(), (double)128.0F, false); + if (PMWeather.RANDOM.nextInt(Math.max(1, windfieldWidth / 10)) == 0 && nearest != null && nearest.position().distanceTo(blockPosTop.getCenter()) < (double)128.0F) { + if (this.level.isLoaded(blockPosTop)) { + this.level.addFreshEntity(movingBlock); + } else { + movingBlock.discard(); + } + } else { + movingBlock.discard(); + ((WeatherHandlerServer)this.weatherHandler).syncBlockParticleNew(blockPosTop, state, this); + } + } + } + + } + } + } else { + double percChance = Math.clamp((windEffect - (double)140.0F) / (double)80.0F, (double)0.0F, (double)1.0F); + if ((double)PMWeather.RANDOM.nextFloat() <= percChance * (double)(0.02F * percAdj)) { + this.level.setBlockAndUpdate(blockPosTop, net.minecraft.world.level.block.Blocks.DIRT.defaultBlockState()); + } + + } + } + + public float getRankine(double dist, int windfieldWidth) { + float rankineWidth = (float)windfieldWidth / this.rankineFactor; + float perc = 0.0F; + if (dist <= (double)(rankineWidth / 2.0F)) { + perc = (float)dist / (rankineWidth / 2.0F); + } else if (dist <= (double)((float)windfieldWidth * 2.0F)) { + perc = Math.clamp((float)Math.pow((double)1.0F - (dist - (double)(rankineWidth / 2.0F)) / (double)(((float)windfieldWidth * 2.0F - rankineWidth) / 2.0F), (double)1.5F), 0.0F, 1.0F); + } + + if (Float.isNaN(perc)) { + perc = 0.0F; + } + + return perc; + } + + public float getWind(Vec3 pos) { + int windfieldWidth = Math.max((int)this.width, 40); + double dist = this.position.multiply((double)1.0F, (double)0.0F, (double)1.0F).distanceTo(pos.multiply((double)1.0F, (double)0.0F, (double)1.0F)); + float perc = this.getRankine(dist, windfieldWidth); + float affectPerc = (float)Math.sqrt((double)1.0F - dist / (double)((float)windfieldWidth * 2.0F)); + Vec3 relativePos = pos.subtract(this.position); + Vec3 rotational = (new Vec3(relativePos.z, (double)0.0F, -relativePos.x)).normalize(); + Vec3 rPosNoise = this.rotateV3(relativePos, (double)this.tickCount / (double)60.0F); + double wNoise = this.FBM(new Vec3(rPosNoise.x / (double)100.0F, rPosNoise.z / (double)100.0F, (double)this.tickCount / (double)200.0F), 5, 2.0F, 0.5F, 1.0F); + double realWind = (double)this.windspeed * ((double)1.0F + wNoise * 0.1); + Vec3 motion = rotational.multiply(realWind * (double)perc, (double)0.0F, realWind * (double)perc); + motion = motion.add(this.velocity.multiply((double)(15.0F * affectPerc), (double)0.0F, (double)(15.0F * affectPerc))); + + for(Vorticy vorticy : this.vorticies) { + double d = vorticy.getPosition().multiply((double)1.0F, (double)0.0F, (double)1.0F).distanceTo(pos.multiply((double)1.0F, (double)0.0F, (double)1.0F)); + Vec3 rPos = pos.subtract(vorticy.getPosition()); + Vec3 rot = (new Vec3(rPos.z, (double)0.0F, -rPos.x)).normalize(); + int windWid = (int)((float)windfieldWidth * vorticy.widthPerc); + float p = this.getRankine(d, windWid); + float wind = vorticy.windspeedMult * (float)this.windspeed; + motion = motion.add(rot.multiply((double)(wind * p), (double)0.0F, (double)(wind * p))); + } + + return (float)motion.length(); + } + + public void initFirstTime() { + this.ID = (long)(LastUsedStormID++); + } + + public void pull(Particle particle, float multiplier) { + int windfieldWidth = Math.max((int)this.width, 40); + BlockPos blockPos = new BlockPos((int)particle.getPos().x, (int)particle.getPos().y, (int)particle.getPos().z); + int worldHeight = this.level.getHeightmapPos(Types.MOTION_BLOCKING, blockPos).getY(); + if (worldHeight <= blockPos.getY()) { + double dist = particle.getPos().distanceTo(new Vec3(this.position.x, particle.getPos().y, this.position.z)); + if (!(dist > (double)windfieldWidth)) { + Vec3 relativePos = particle.getPos().subtract(this.position); + double heightDifference = particle.getPos().y - this.position.y; + if (!(Math.abs(heightDifference) > (double)150.0F)) { + Vec3 inward = (new Vec3(-relativePos.x, (double)0.0F, -relativePos.z)).normalize(); + Vec3 rotational = (new Vec3(relativePos.z, (double)0.0F, -relativePos.x)).normalize(); + double windEffect = (double)this.getWind(particle.getPos()); + double effectStrength = Math.clamp(windEffect / (double)Math.max((float)this.windspeed, 130.0F), (double)0.0F, (double)1.0F) * (double)multiplier; + double pullFactor = (double)4.0F; + pullFactor -= Math.max(heightDifference, (double)0.0F) / (double)100.0F * (double)3.0F; + pullFactor /= (double)Math.max(this.width / 100.0F, 1.0F); + if (dist <= (double)(this.width / (this.rankineFactor * 2.0F))) { + pullFactor = (double)-1.5F; + } + + Vec3 add = inward.multiply(effectStrength * pullFactor, effectStrength * pullFactor, effectStrength * pullFactor).add(rotational.multiply(effectStrength, effectStrength, effectStrength)); + add = add.add(new Vec3((double)0.0F, effectStrength, (double)0.0F)); + if (particle instanceof ParticleData) { + ParticleData particleData = (ParticleData)particle; + particleData.addVelocity(add.multiply((double)0.05F, (double)0.05F, (double)0.05F)); + } + + } + } + } + } + + public void pull(Entity entity, float multiplier) { + int windfieldWidth = Math.max((int)this.width, 40); + int worldHeight = this.level.getHeightmapPos(Types.MOTION_BLOCKING, entity.blockPosition()).getY(); + if (worldHeight <= entity.blockPosition().getY()) { + double dist = entity.position().distanceTo(new Vec3(this.position.x, entity.position().y, this.position.z)); + if (!(dist > (double)windfieldWidth)) { + Vec3 relativePos = entity.position().subtract(this.position); + double heightDifference = entity.position().y - this.position.y; + if (!(Math.abs(heightDifference) > (double)150.0F)) { + Vec3 inward = (new Vec3(-relativePos.x, (double)0.0F, -relativePos.z)).normalize(); + Vec3 rotational = (new Vec3(relativePos.z, (double)0.0F, -relativePos.x)).normalize(); + double windEffect = (double)this.getWind(entity.position()); + if (!(windEffect < (double)60.0F)) { + double effectStrength = Math.clamp((windEffect - (double)60.0F) / (double)Math.max((float)this.windspeed * 1.2F, 130.0F), (double)0.0F, (double)1.0F) * (double)multiplier * (double)1.5F; + double pullFactor = (double)4.0F; + pullFactor -= Math.max(heightDifference, (double)0.0F) / (double)65.0F * (double)3.0F; + if (dist <= (double)(this.width / this.rankineFactor)) { + pullFactor = (double)-1.5F; + } + + Vec3 add = inward.multiply(effectStrength * pullFactor, effectStrength * pullFactor, effectStrength * pullFactor).add(rotational.multiply(effectStrength, effectStrength, effectStrength)); + add = add.add(new Vec3((double)0.0F, effectStrength, (double)0.0F)); + entity.addDeltaMovement(add.multiply((double)0.05F, (double)0.05F, (double)0.05F)); + Vec3 motion = entity.getDeltaMovement(); + if (motion.y > (double)-0.25F) { + entity.fallDistance = 0.0F; + } + + } + } + } + } + } + + @OnlyIn(Dist.CLIENT) + public void tickClient() { + Player player = Minecraft.getInstance().player; + if (player != null && (this.undergroundWind == null || this.undergroundWind.isStopped()) && !this.dead) { + this.undergroundWind = new MovingSoundStreamingSource(this, (SoundEvent)ModSounds.UNDERGROUND_WIND.value(), SoundSource.WEATHER, 0.1F, 1.0F, (float)this.maxWidth, true, 3); + Minecraft.getInstance().getSoundManager().play(this.undergroundWind); + } + + if (player != null && this.stormType == 2) { + this.smoothWidth = this.width; + this.smoothWindspeed = Mth.lerp(0.1F, this.smoothWindspeed, (float)this.windspeed); + if ((this.eyewallWind == null || this.eyewallWind.isStopped()) && !this.dead) { + this.eyewallWind = new MovingSoundStreamingSource(this, (SoundEvent)ModSounds.EYEWALL_WIND.value(), SoundSource.WEATHER, 0.1F, 1.0F, (float)this.maxWidth, true, 2); + Minecraft.getInstance().getSoundManager().play(this.eyewallWind); + } + } + + if (player != null && this.stormType == 0) { + this.smoothWindspeed = Mth.lerp(0.1F, this.smoothWindspeed, (float)this.windspeed); + this.smoothWidth = Mth.lerp(0.05F, this.smoothWidth, this.width); + if (this.stage >= 3) { + if ((this.tornadicWind == null || this.tornadicWind.isStopped()) && !this.dead) { + this.tornadicWind = new MovingSoundStreamingSource(this, (SoundEvent)ModSounds.TORNADIC_WIND.value(), SoundSource.WEATHER, 0.1F, 1.0F, this.width, true, 1); + Minecraft.getInstance().getSoundManager().play(this.tornadicWind); + } + + if ((this.tornadicDamage == null || this.tornadicDamage.isStopped()) && !this.dead) { + this.tornadicDamage = new MovingSoundStreamingSource(this, (SoundEvent)ModSounds.TORNADIC_DAMAGE.value(), SoundSource.WEATHER, 0.1F, 1.0F, this.width, true, 4); + Minecraft.getInstance().getSoundManager().play(this.tornadicDamage); + } + + if (this.windspeed >= 40 && !player.isCreative() && !player.isSpectator()) { + this.pull((Entity)player, 2.5F); + } + } + + if (this.stage >= 2 && (this.supercellWind == null || this.supercellWind.isStopped()) && !this.dead) { + this.supercellWind = new MovingSoundStreamingSource(this, (SoundEvent)ModSounds.SUPERCELL_WIND.value(), SoundSource.WEATHER, 0.1F, 1.0F, this.width, true, 0); + Minecraft.getInstance().getSoundManager().play(this.supercellWind); + } + + if (this.stage < 3 && this.tornadicWind != null) { + this.tornadicWind.stopPlaying(); + this.tornadicWind = null; + } + + if (this.stage < 2 && this.supercellWind != null) { + this.supercellWind.stopPlaying(); + this.supercellWind = null; + } + + for(int i = 0; i < this.listParticleDebris.size(); ++i) { + EntityRotFX debris = this.listParticleDebris.get(i); + if (!debris.isAlive()) { + this.listParticleDebris.remove(debris); + } else { + this.pull((Particle)debris, 1.0F); + } + } + } + + } + + public void remove() { + this.dead = true; + if (EffectiveSide.get().equals(LogicalSide.CLIENT)) { + this.cleanupClient(); + } + + this.cleanup(); + } + + public void cleanup() { + this.weatherHandler = null; + if (!this.level.isClientSide()) { + for(ChunkPos chunkPos : this.forceLoadedChunks) { + ((ServerLevel)this.level).setChunkForced(chunkPos.x, chunkPos.z, false); + } + } + + } + + @OnlyIn(Dist.CLIENT) + public void cleanupClient() { + if (this.tornadicWind != null) { + this.tornadicWind.stopPlaying(); + this.tornadicWind = null; + } + + if (this.tornadicDamage != null) { + this.tornadicDamage.stopPlaying(); + this.tornadicDamage = null; + } + + if (this.supercellWind != null) { + this.supercellWind.stopPlaying(); + this.supercellWind = null; + } + + if (this.eyewallWind != null) { + this.eyewallWind.stopPlaying(); + this.eyewallWind = null; + } + + if (this.undergroundWind != null) { + this.undergroundWind.stopPlaying(); + this.undergroundWind = null; + } + + } + + public void read() { + this.nbtSyncFromServer(); + } + + public void write() { + this.nbtSyncForClient(); + } + + public int getUpdateRate() { + return this.stormType == 0 && this.stage >= 3 ? 2 : 40; + } + + public void nbtSyncFromServer() { + CachedNBTTagCompound nbt = this.getNBTCache(); + this.ID = nbt.getLong("ID"); + this.onWater = nbt.getBoolean("onWater"); + this.position = new Vec3(nbt.getDouble("positionX"), nbt.getDouble("positionY"), nbt.getDouble("positionZ")); + this.velocity = new Vec3(nbt.getDouble("velocityX"), nbt.getDouble("velocityY"), nbt.getDouble("velocityZ")); + this.windspeed = nbt.getInt("windspeed"); + this.cycloneWindspeed = (float)this.windspeed; + this.width = nbt.getFloat("width"); + this.energy = nbt.getInt("energy"); + this.coldEnergy = nbt.getInt("coldEnergy"); + this.stormType = nbt.getInt("stormType"); + this.stage = nbt.getInt("stage"); + this.dead = nbt.getBoolean("dead"); + this.isDying = nbt.getBoolean("isDying"); + this.maxWidth = nbt.getInt("maxWidth"); + this.maxWindspeed = nbt.getInt("maxWindspeed"); + this.maxStage = nbt.getInt("maxStage"); + this.maxProgress = nbt.getInt("maxProgress"); + this.ticksSinceDying = nbt.getInt("ticksSinceDying"); + this.growthSpeed = nbt.getInt("growthSpeed"); + this.visualOnly = nbt.getBoolean("visualOnly"); + this.aimedAtPlayer = nbt.getBoolean("aimedAtPlayer"); + this.cirus = nbt.getBoolean("cirus"); + this.touchdownSpeed = nbt.getInt("touchdownSpeed"); + this.occlusion = nbt.getFloat("occlusion"); + CompoundTag vorticiesData = nbt.get("vorticies"); + int vorticyCount = vorticiesData.getInt("vorticyCount"); + this.vorticies.clear(); + + for(int i = 0; i < vorticyCount; ++i) { + CompoundTag vorticyData = vorticiesData.getCompound("vorticy" + i); + Vorticy vorticy = new Vorticy(this, vorticyData.getFloat("maxWindspeedMult"), vorticyData.getFloat("widthPerc"), vorticyData.getFloat("distancePerc"), vorticyData.getInt("lifetime")); + vorticy.dead = vorticyData.getBoolean("dead"); + vorticy.angle = vorticyData.getFloat("angle"); + vorticy.tickCount = vorticyData.getInt("tickCount"); + vorticy.windspeedMult = vorticyData.getFloat("windspeedMult"); + this.vorticies.add(vorticy); + } + + } + + public void nbtSyncForClient() { + CachedNBTTagCompound nbt = this.getNBTCache(); + CompoundTag vorticiesData = new CompoundTag(); + vorticiesData.putInt("vorticyCount", this.vorticies.size()); + + for(int i = 0; i < this.vorticies.size(); ++i) { + Vorticy vorticy = this.vorticies.get(i); + CompoundTag vorticyData = new CompoundTag(); + vorticyData.putBoolean("dead", vorticy.dead); + vorticyData.putFloat("windspeedMult", vorticy.windspeedMult); + vorticyData.putFloat("maxWindspeedMult", vorticy.maxWindspeedMult); + vorticyData.putFloat("widthPerc", vorticy.widthPerc); + vorticyData.putFloat("distancePerc", vorticy.distancePerc); + vorticyData.putFloat("angle", vorticy.angle); + vorticyData.putInt("lifetime", vorticy.lifetime); + vorticyData.putInt("tickCount", vorticy.tickCount); + vorticiesData.put("vorticy" + i, vorticyData); + } + + nbt.put("vorticies", vorticiesData); + nbt.putBoolean("onWater", this.onWater); + nbt.putInt("touchdownSpeed", this.touchdownSpeed); + nbt.putBoolean("cirus", this.cirus); + nbt.putBoolean("aimedAtPlayer", this.aimedAtPlayer); + nbt.putBoolean("visualOnly", this.visualOnly); + nbt.putBoolean("isDying", this.isDying); + nbt.putInt("maxWidth", this.maxWidth); + nbt.putInt("maxWindspeed", this.maxWindspeed); + nbt.putInt("maxStage", this.maxStage); + nbt.putInt("maxProgress", this.maxProgress); + nbt.putInt("ticksSinceDying", this.ticksSinceDying); + nbt.putInt("growthSpeed", this.growthSpeed); + nbt.putFloat("occlusion", this.occlusion); + nbt.putDouble("positionX", this.position.x); + nbt.putDouble("positionY", this.position.y); + nbt.putDouble("positionZ", this.position.z); + nbt.putDouble("velocityX", this.velocity.x); + nbt.putDouble("velocityY", this.velocity.y); + nbt.putDouble("velocityZ", this.velocity.z); + nbt.putLong("ID", this.ID); + nbt.getNewNBT().putLong("ID", this.ID); + nbt.putInt("windspeed", this.windspeed); + nbt.putFloat("width", this.width); + nbt.putInt("energy", this.energy); + nbt.putInt("coldEnergy", this.coldEnergy); + nbt.putInt("stormType", this.stormType); + nbt.putInt("stage", this.stage); + nbt.putBoolean("dead", this.dead); + } + + public CachedNBTTagCompound getNBTCache() { + return this.nbtCache; + } + + public static float getBlockStrength(Block block, Level level, @Nullable BlockPos blockPos) { + ItemStack item = new ItemStack(Items.IRON_AXE); + float destroySpeed = block.defaultBlockState().getDestroySpeed(level, blockPos != null ? blockPos : BlockPos.ZERO); + + try { + destroySpeed /= item.getDestroySpeed(block.defaultBlockState()); + } catch (Exception e) { + PMWeather.LOGGER.warn(e.getMessage()); + } + + return 60.0F + Mth.sqrt(destroySpeed) * 60.0F; + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/Vorticy.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/Vorticy.java new file mode 100644 index 00000000..19c4744f --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/Vorticy.java @@ -0,0 +1,68 @@ +package dev.protomanly.pmweather.weather; + +import dev.protomanly.pmweather.PMWeather; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; + +public class Vorticy { + public float windspeedMult = 0.0F; + public float maxWindspeedMult; + public float widthPerc; + public float distancePerc; + public float angle; + public int lifetime; + public int tickCount; + public boolean dead = false; + private Storm storm; + + public Vorticy(Storm storm, float maxWindspeedMult, float widthPerc, float distancePerc, int lifetime) { + super(); + this.storm = storm; + this.maxWindspeedMult = maxWindspeedMult; + this.distancePerc = distancePerc; + this.widthPerc = widthPerc; + this.lifetime = lifetime; + this.angle = PMWeather.RANDOM.nextFloat() * ((float)Math.PI * 2F); + } + + public void tick() { + if (!this.dead) { + ++this.tickCount; + float lifeDelta = (float)this.tickCount / (float)this.lifetime; + float wind = (float)this.storm.windspeed * (1.0F - this.distancePerc); + float angleAdd = (float)Math.toRadians((double)(wind / 300.0F)); + if ((double)lifeDelta > (double)0.5F) { + this.windspeedMult = Mth.lerp((lifeDelta - 0.5F) * 2.0F, this.maxWindspeedMult, 0.0F); + } else { + this.windspeedMult = Mth.lerp(lifeDelta * 2.0F, 0.0F, this.maxWindspeedMult); + } + + if (this.tickCount > this.lifetime) { + this.dead = true; + } + + if (this.storm.stormType == 2) { + angleAdd *= 0.1F; + } + + this.angle += angleAdd; + if (this.angle > ((float)Math.PI * 2F)) { + this.angle = 0.0F; + } + + } + } + + public float getWidth() { + return this.storm.stormType == 2 ? this.widthPerc * (float)this.storm.maxWidth : this.widthPerc * this.storm.width; + } + + public float getDistance() { + return this.storm.stormType == 2 ? this.distancePerc * (float)this.storm.maxWidth : this.distancePerc * this.storm.width; + } + + public Vec3 getPosition() { + float realDist = this.getDistance(); + return this.storm.position.add(new Vec3(Math.sin((double)this.angle) * (double)realDist, (double)0.0F, Math.cos((double)this.angle) * (double)realDist)); + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/WeatherHandler.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/WeatherHandler.java new file mode 100644 index 00000000..d3eeb592 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/WeatherHandler.java @@ -0,0 +1,268 @@ +package dev.protomanly.pmweather.weather; + +import dev.protomanly.pmweather.PMWeather; +import dev.protomanly.pmweather.config.ServerConfig; +import dev.protomanly.pmweather.data.LevelSavedData; +import dev.protomanly.pmweather.interfaces.IWorldData; +import dev.protomanly.pmweather.util.Util; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; +import org.joml.Vector2f; + +public abstract class WeatherHandler implements IWorldData { + private List storms = new ArrayList(); + private ResourceKey dimension; + public HashMap lookupStormByID = new HashMap(); + public long seed; + + public WeatherHandler(ResourceKey dimension) { + super(); + this.dimension = dimension; + } + + public void tick() { + Level level = this.getWorld(); + if (level != null) { + List stormList = this.getStorms(); + + for(int i = 0; i < stormList.size(); ++i) { + Storm storm = stormList.get(i); + if (this instanceof WeatherHandlerServer) { + WeatherHandlerServer weatherHandlerServer = (WeatherHandlerServer)this; + if (storm.dead) { + this.removeStorm(storm.ID); + weatherHandlerServer.syncStormRemove(storm); + continue; + } + } + + if (!storm.dead) { + storm.tick(); + } else { + this.removeStorm(storm.ID); + } + } + } + + } + + public List getStorms() { + return this.storms; + } + + public void addStorm(Storm storm) { + if (!this.lookupStormByID.containsKey(storm.ID)) { + this.storms.add(storm); + this.lookupStormByID.put(storm.ID, storm); + } else { + PMWeather.LOGGER.warn("Tried to add a storm with existing ID: {}", storm.ID); + } + + } + + public void removeStorm(long id) { + Storm storm = this.lookupStormByID.get(id); + if (storm != null) { + storm.remove(); + this.storms.remove(storm); + this.lookupStormByID.remove(id); + } else { + PMWeather.LOGGER.warn("Tried to remove a non-existent storm with ID: {}", id); + } + + } + + public float getPrecipitation(Vec3 pos) { + float precip = 0.0F; + float cloudDensity = Clouds.getCloudDensity(this, new Vector2f((float)pos.x, (float)pos.z), 0.0F); + if (cloudDensity > 0.15F) { + precip += (cloudDensity - 0.15F) * 2.0F; + } + + for(Storm storm : this.getStorms()) { + if (!storm.visualOnly) { + double dist = pos.distanceTo(new Vec3(storm.position.x, pos.y, storm.position.z)); + double perc = (double)0.0F; + float smoothStage = (float)storm.stage + (float)storm.energy / 100.0F; + if (storm.stage == 3) { + smoothStage = 3.0F; + } + + if (storm.stormType == 2) { + Vec3 cPos = storm.position.multiply((double)1.0F, (double)0.0F, (double)1.0F); + float intensity = (float)Math.pow((double)Math.clamp((float)storm.windspeed / 65.0F, 0.0F, 1.0F), (double)0.85F); + Vec3 relPos = cPos.subtract(pos); + double d = (double)((float)storm.maxWidth / (3.0F + (float)storm.windspeed / 12.0F)); + double d2 = (double)((float)storm.maxWidth / (1.15F + (float)storm.windspeed / 12.0F)); + double dE = (double)((float)storm.maxWidth * 0.65F / (1.75F + (float)storm.windspeed / 12.0F)); + double fac = (double)1.0F + Math.max((dist - (double)((float)storm.maxWidth * 0.2F)) / (double)storm.maxWidth, (double)0.0F) * (double)2.0F; + d *= fac; + d2 *= fac; + double angle = Math.atan2(relPos.z, relPos.x) - dist / d; + double angle2 = Math.atan2(relPos.z, relPos.x) - dist / d2; + double angleE = Math.atan2(relPos.z, relPos.x) - dist / 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(dist / (double)((float)storm.maxWidth * 0.25F), (double)0.0F, (double)1.0F), (double)0.1F); + staticBands *= 1.25F * (float)Math.pow((double)intensity, (double)0.75F); + if (staticBands < 0.0F) { + weak += Math.abs(staticBands); + } else { + weak += Math.abs(staticBands) * (float)Math.pow((double)1.0F - Math.clamp(dist / (double)((float)storm.maxWidth * 0.65F), (double)0.0F, (double)1.0F), (double)0.5F); + weak *= Math.clamp(((float)storm.windspeed - 70.0F) / 40.0F, 0.0F, 1.0F); + } + + float rotatingBands = (float)Math.sin((angle2 + Math.toRadians((double)((float)storm.tickCount / 8.0F))) * (double)6.0F); + rotatingBands *= (float)Math.pow(Math.clamp(dist / (double)((float)storm.maxWidth * 0.25F), (double)0.0F, (double)1.0F), (double)0.1F); + rotatingBands *= 1.25F * (float)Math.pow((double)intensity, (double)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; + float localRain = 0.0F; + localRain += 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((double)((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); + localRain = Math.max((float)Math.pow((double)1.0F - Math.clamp(dist / (double)((float)storm.maxWidth * efc), (double)0.0F, (double)1.0F), (double)0.5F) * (Math.abs(eye * 0.1F) + 0.9F) * 1.35F * intensity, localRain); + localRain *= (float)Math.pow((double)1.0F - Math.clamp(dist / (double)storm.maxWidth, (double)0.0F, (double)1.0F), (double)0.5F); + localRain *= 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(dist / (double)((float)storm.maxWidth * 0.1F), (double)0.0F, (double)1.0F), (double)2.0F)); + if (localRain > 0.6F) { + float dif = (localRain - 0.6F) / 2.5F; + localRain -= dif; + } + + precip += Math.max(localRain - 0.15F, 0.0F) * 2.0F; + } + + if (storm.stormType == 1) { + Vec2 v2fWorldPos = new Vec2((float)pos.x, (float)pos.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, -((float)ServerConfig.stormSize) * 5.0F); + Vec2 ri = Util.mulVec2(right, (float)ServerConfig.stormSize * 5.0F); + Vec2 off = Util.mulVec2(fwd, -((float)Math.pow(Mth.clamp(dist / (double)((float)ServerConfig.stormSize * 5.0F), (double)0.0F, (double)1.0F), (double)2.0F)) * (float)ServerConfig.stormSize * 1.5F); + le = le.add(off); + ri = ri.add(off); + le = le.add(v2fStormPos); + ri = ri.add(v2fStormPos); + float d = Util.minimumDistance(le, ri, v2fWorldPos); + if ((double)d > ServerConfig.stormSize * (double)16.0F) { + continue; + } + + Vec2 nearPoint = Util.nearestPoint(le, ri, v2fWorldPos); + Vec2 facing = v2fWorldPos.add(nearPoint.negated()); + float behind = -facing.dot(fwd); + float sze = (float)ServerConfig.stormSize * 1.5F; + sze *= Mth.lerp(Mth.clamp(smoothStage - 1.0F, 0.0F, 1.0F), 4.0F, 12.0F); + behind += (float)ServerConfig.stormSize / 2.0F; + if (behind > 0.0F) { + float p = Mth.clamp(Math.abs(behind) / sze, 0.0F, 1.0F); + float start = 0.06F; + if (p <= start) { + p /= start; + } else { + p = 1.0F - (p - start) / (1.0F - start); + } + + perc = (double)((float)Math.pow((double)Mth.clamp(p, 0.0F, 1.0F), (double)3.0F)); + } + + if (storm.stage <= 0) { + perc = (double)0.0F; + } else if (storm.stage == 1) { + perc *= (double)((float)storm.energy / 100.0F); + } + + perc *= (double)Mth.sqrt(1.0F - Mth.clamp(d / sze, 0.0F, 1.0F)); + } + + if (storm.stormType == 0) { + double coreDist = pos.distanceTo(new Vec3(storm.position.x + (double)2000.0F, pos.y, storm.position.z - (double)900.0F)); + if (Math.min(dist, coreDist) > ServerConfig.stormSize * (double)6.0F) { + continue; + } + + perc = (double)1.0F - Math.clamp(dist / ServerConfig.stormSize, (double)0.0F, (double)1.0F); + if (storm.stage == 0) { + perc *= (double)((float)storm.energy / 100.0F); + } + + if (storm.stage >= 2) { + perc *= (double)Mth.lerp(Math.clamp(smoothStage - 2.0F, 0.0F, 1.0F), 1.0F, storm.occlusion * 0.5F + 0.5F); + } + + double p = (double)1.0F - Math.clamp(coreDist / (ServerConfig.stormSize * (double)6.0F), (double)0.0F, (double)1.0F); + if (storm.stage <= 1) { + p *= (double)0.0F; + } + + if (storm.stage >= 2) { + p *= (double)Math.clamp((smoothStage - 2.0F) / 0.5F, 0.0F, 1.0F); + } + + perc = Math.max(p, perc); + } + + precip += (float)perc; + } + } + + return Math.clamp(precip * (float)ServerConfig.rainStrength, 0.0F, 1.0F); + } + + public abstract Level getWorld(); + + public CompoundTag save(CompoundTag data) { + PMWeather.LOGGER.debug("WeatherHandler save"); + CompoundTag listStormsNBT = new CompoundTag(); + + for(int i = 0; i < this.storms.size(); ++i) { + Storm storm = this.storms.get(i); + storm.getNBTCache().setUpdateForced(true); + storm.write(); + storm.getNBTCache().setUpdateForced(false); + listStormsNBT.put("storm_" + storm.ID, storm.getNBTCache().getNewNBT()); + } + + data.put("stormData", listStormsNBT); + data.putLong("lastUsedIDStorm", Storm.LastUsedStormID); + return null; + } + + public void read() { + LevelSavedData savedData = (LevelSavedData)((ServerLevel)this.getWorld()).getDataStorage().computeIfAbsent(LevelSavedData.factory(), "pmweather_weather_data"); + savedData.setDataHandler(this); + PMWeather.LOGGER.debug("Weather Data: {}", savedData.getData()); + CompoundTag data = savedData.getData(); + Storm.LastUsedStormID = data.getLong("lastUsedIDStorm"); + CompoundTag storms = data.getCompound("stormData"); + + for(String tagName : storms.getAllKeys()) { + CompoundTag stormData = storms.getCompound(tagName); + Storm storm = new Storm(this, this.getWorld(), (Float)null, stormData.getInt("stormType")); + + try { + storm.getNBTCache().setNewNBT(stormData); + storm.read(); + storm.getNBTCache().updateCacheFromNew(); + } catch (Exception e) { + PMWeather.LOGGER.error(e.getMessage(), e); + } + + this.addStorm(storm); + } + + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/WeatherHandlerClient.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/WeatherHandlerClient.java new file mode 100644 index 00000000..79bd53b8 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/WeatherHandlerClient.java @@ -0,0 +1,158 @@ +package dev.protomanly.pmweather.weather; + +import dev.protomanly.pmweather.PMWeather; +import dev.protomanly.pmweather.config.ClientConfig; +import dev.protomanly.pmweather.config.ServerConfig; +import dev.protomanly.pmweather.event.GameBusClientEvents; +import dev.protomanly.pmweather.particle.ParticleCube; +import dev.protomanly.pmweather.sound.ModSounds; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.resources.ResourceKey; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; + +public class WeatherHandlerClient extends WeatherHandler { + public List lightnings = new ArrayList(); + + public WeatherHandlerClient(ResourceKey dimension) { + super(dimension); + } + + public Level getWorld() { + return Minecraft.getInstance().level; + } + + public float getHail() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return 0.0F; + } else { + float precip = 0.0F; + + for(Storm storm : this.getStorms()) { + if (!storm.visualOnly) { + double dist = player.position().distanceTo(new Vec3(storm.position.x + (double)2000.0F, player.position().y, storm.position.z - (double)900.0F)); + if (!(dist > ServerConfig.stormSize * (double)4.0F)) { + double perc = (double)0.0F; + if (storm.stormType == 0) { + perc = (double)1.0F - Math.clamp(dist / (ServerConfig.stormSize * (double)6.0F), (double)0.0F, (double)1.0F); + if (storm.stage == 2) { + perc *= (double)((float)storm.energy / 100.0F); + } + + if (storm.stage > 2) { + perc *= (double)1.0F; + } + + if (storm.stage < 2) { + perc *= (double)0.0F; + } + } + + precip += (float)perc; + } + } + } + + return Math.clamp(precip, 0.0F, 1.0F); + } + } + + public float getPrecipitation() { + Player player = Minecraft.getInstance().player; + return player == null ? 0.0F : this.getPrecipitation(player.position()); + } + + public void strike(Vec3 pos) { + Lightning lightning = new Lightning(pos, this.getWorld()); + this.lightnings.add(lightning); + Player player = Minecraft.getInstance().player; + if (player != null) { + double dist = player.position().multiply((double)1.0F, (double)0.0F, (double)1.0F).distanceTo(pos.multiply((double)1.0F, (double)0.0F, (double)1.0F)); + if (dist > (double)256.0F) { + this.getWorld().playLocalSound(pos.x, pos.y, pos.z, (SoundEvent)ModSounds.THUNDER_FAR.value(), SoundSource.WEATHER, 5000.0F, PMWeather.RANDOM.nextFloat(0.8F, 1.0F), true); + } else { + this.getWorld().playLocalSound(pos.x, pos.y, pos.z, (SoundEvent)ModSounds.THUNDER_NEAR.value(), SoundSource.WEATHER, 5000.0F, PMWeather.RANDOM.nextFloat(0.8F, 1.0F), true); + } + } + + } + + public void tick() { + super.tick(); + Iterator iterator = this.lightnings.iterator(); + + while(iterator.hasNext()) { + Lightning lightning = iterator.next(); + if (!lightning.dead && lightning.level == this.getWorld()) { + lightning.tick(); + } else { + iterator.remove(); + } + } + + } + + public void nbtSyncFromServer(CompoundTag compoundTag) { + String command = compoundTag.getString("command"); + if (command.equals("syncStormNew")) { + CompoundTag stormCompoundTag = compoundTag.getCompound("data"); + long ID = stormCompoundTag.getLong("ID"); + PMWeather.LOGGER.debug("syncStormNew, ID: {}", ID); + Storm storm = new Storm(this, this.getWorld(), (Float)null, stormCompoundTag.getInt("stormType")); + storm.getNBTCache().setNewNBT(stormCompoundTag); + storm.nbtSyncFromServer(); + storm.getNBTCache().updateCacheFromNew(); + this.addStorm(storm); + } else if (command.equals("syncStormRemove")) { + CompoundTag stormCompoundTag = compoundTag.getCompound("data"); + long ID = stormCompoundTag.getLong("ID"); + Storm storm = this.lookupStormByID.get(ID); + if (storm != null) { + this.removeStorm(ID); + } + } else if (command.equals("syncStormUpdate")) { + CompoundTag stormCompoundTag = compoundTag.getCompound("data"); + long ID = stormCompoundTag.getLong("ID"); + Storm storm = this.lookupStormByID.get(ID); + if (storm != null) { + storm.getNBTCache().setNewNBT(stormCompoundTag); + storm.nbtSyncFromServer(); + storm.getNBTCache().updateCacheFromNew(); + } + } else if (command.equals("syncBlockParticleNew")) { + if ((double)PMWeather.RANDOM.nextFloat() > ClientConfig.debrisParticleDensity) { + return; + } + + CompoundTag nbt = compoundTag.getCompound("data"); + Vec3 pos = new Vec3((double)nbt.getInt("positionX"), (double)(nbt.getInt("positionY") + 1), (double)nbt.getInt("positionZ")); + BlockState state = NbtUtils.readBlockState(this.getWorld().holderLookup(Registries.BLOCK), nbt.getCompound("blockstate")); + long stormID = nbt.getLong("stormID"); + Storm storm = this.lookupStormByID.get(stormID); + if (storm != null) { + ParticleCube debris = new ParticleCube((ClientLevel)this.getWorld(), pos.x + (double)((PMWeather.RANDOM.nextFloat() - PMWeather.RANDOM.nextFloat()) * 3.0F), pos.y + (double)((PMWeather.RANDOM.nextFloat() - PMWeather.RANDOM.nextFloat()) * 3.0F), pos.z + (double)((PMWeather.RANDOM.nextFloat() - PMWeather.RANDOM.nextFloat()) * 3.0F), (double)0.0F, (double)0.0F, (double)0.0F, state); + GameBusClientEvents.particleBehavior.initParticleCube(debris); + storm.listParticleDebris.add(debris); + debris.ignoreWind = true; + debris.renderRange = 256.0F; + debris.spawnAsDebrisEffect(); + } + } else if (command.equals("syncLightningNew")) { + CompoundTag nbt = compoundTag.getCompound("data"); + this.strike(new Vec3(nbt.getDouble("positionX"), nbt.getDouble("positionY"), nbt.getDouble("positionZ"))); + } + + } +} diff --git a/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/WindEngine.java b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/WindEngine.java new file mode 100644 index 00000000..249eba06 --- /dev/null +++ b/pmweather_tornado_extraction_export/dev/protomanly/pmweather/weather/WindEngine.java @@ -0,0 +1,297 @@ +package dev.protomanly.pmweather.weather; + +import dev.protomanly.pmweather.PMWeather; +import dev.protomanly.pmweather.config.ServerConfig; +import dev.protomanly.pmweather.event.GameBusClientEvents; +import dev.protomanly.pmweather.event.GameBusEvents; +import dev.protomanly.pmweather.util.Util; +import java.util.ArrayList; +import java.util.List; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.world.level.levelgen.Heightmap.Types; +import net.minecraft.world.level.levelgen.synth.SimplexNoise; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +public class WindEngine { + public static SimplexNoise simplexNoise; + + public WindEngine() { + super(); + } + + public static void init(WeatherHandler weatherHandler) { + simplexNoise = new SimplexNoise(new LegacyRandomSource(weatherHandler.seed)); + } + + public static double FBM(Vec3 pos, int octaves, float lacunarity, float gain, float amplitude) { + double y = (double)0.0F; + if (simplexNoise != null) { + for(int i = 0; i < Math.max(octaves, 1); ++i) { + y += (double)amplitude * simplexNoise.getValue(pos.x, pos.y, pos.z); + pos = pos.multiply((double)lacunarity, (double)lacunarity, (double)lacunarity); + amplitude *= gain; + } + } + + return y; + } + + public static float getSwirl(Vec3 position, Level level, float sampleSize) { + Vec3 sample1Z = getWind(position.add((double)0.0F, (double)0.0F, (double)sampleSize), level).normalize(); + Vec3 sample2Z = getWind(position.add((double)0.0F, (double)0.0F, (double)(-sampleSize)), level).normalize(); + Vec3 sample1X = getWind(position.add((double)(-sampleSize), (double)0.0F, (double)0.0F), level).normalize(); + Vec3 sample2X = getWind(position.add((double)sampleSize, (double)0.0F, (double)0.0F), level).normalize(); + double compZ = (-sample1Z.dot(sample2Z) + (double)1.0F) / (double)2.0F; + double compX = (-sample1X.dot(sample2X) + (double)1.0F) / (double)2.0F; + return (float)(compZ * compX); + } + + public static Vec3 getWind(Vec3 position, Level level) { + return getWind(position, level, false, false, true, false); + } + + public static Vec3 getWind(Vec3 position, Level level, boolean ignoreStorms, boolean ignoreTornadoes, boolean windCheck) { + return getWind(position, level, ignoreStorms, ignoreTornadoes, windCheck, false); + } + + public static Vec3 getWind(Vec3 position, Level level, boolean ignoreStorms, boolean ignoreTornadoes, boolean windCheck, boolean windAnyway) { + Vec3 wind = Vec3.ZERO; + Vec3 rawWind = Vec3.ZERO; + BlockPos blockPos = new BlockPos((int)position.x, (int)position.y, (int)position.z); + List tornadicStorms = new ArrayList(); + if (level == null) { + PMWeather.LOGGER.warn("Level is null"); + return wind; + } else { + int worldHeight = level.getHeightmapPos(Types.MOTION_BLOCKING, blockPos).getY(); + if (windCheck && !windAnyway) { + if (!Util.canWindAffect(position, level)) { + return wind; + } + } else if (!windAnyway && position.y < (double)worldHeight) { + return wind; + } + + if (simplexNoise != null) { + float timeScale = 20000.0F; + float scale = 12000.0F; + double ang = FBM(new Vec3(position.x / (double)(scale * 3.0F), position.z / (double)(scale * 3.0F), (double)((float)level.getGameTime() / (timeScale * 6.0F))), 5, 2.0F, 0.1F, 1.0F); + ang *= Math.PI; + Vec3 dir = (new Vec3(Math.cos(ang), (double)0.0F, Math.sin(ang))).normalize(); + double speed = Math.max(simplexNoise.getValue(-position.z / (double)scale, -position.x / (double)scale, (double)(-((float)level.getGameTime()) / timeScale)) + (double)1.0F, (double)0.0F) * (double)10.0F; + wind = wind.add(dir.multiply(speed, speed, speed)); + WeatherHandler weatherHandler; + if (level.isClientSide()) { + weatherHandler = GameBusClientEvents.weatherHandler; + } else { + weatherHandler = GameBusEvents.MANAGERS.get(level.dimension()); + } + + if (weatherHandler != null && !ignoreStorms) { + for(Storm storm : weatherHandler.getStorms()) { + if (!storm.visualOnly) { + if (storm.stage >= 3 && storm.stormType == 0) { + tornadicStorms.add(storm); + } + + double distance = position.multiply((double)1.0F, (double)0.0F, (double)1.0F).distanceTo(storm.position.multiply((double)1.0F, (double)0.0F, (double)1.0F)); + if (storm.stormType == 2) { + Vec3 relativePos = position.subtract(storm.position); + Vec3 inward = (new Vec3(-relativePos.x, (double)0.0F, -relativePos.z)).normalize(); + Vec3 rotational = (new Vec3(relativePos.z, (double)0.0F, -relativePos.x)).normalize(); + double pullStrngth = (double)((float)storm.windspeed * 0.3F); + double rotStrngth = (double)((float)storm.windspeed * 0.7F); + float mult = (float)Math.pow((double)1.0F - Math.clamp(distance / (double)storm.maxWidth, (double)0.0F, (double)1.0F), (double)3.0F); + if (distance < (double)((float)storm.maxWidth * 0.1F)) { + mult += (float)Math.pow((double)1.0F - Math.clamp(distance / (double)((float)storm.maxWidth * 0.1F), (double)0.0F, (double)1.0F), (double)0.75F) * 0.15F; + mult = Math.clamp(mult, 0.0F, 1.0F); + } + + double d = (double)((float)storm.maxWidth / (1.5F + (float)storm.windspeed / 30.0F)); + float effect = Math.clamp((float)distance / (float)storm.maxWidth, 0.0F, 1.0F); + float noiseX = (float)FBM(new Vec3(position.x / (double)((float)storm.maxWidth * 0.5F), position.z / (double)((float)storm.maxWidth * 0.5F), (double)((float)level.getGameTime() / timeScale)), 5, 2.0F, 0.2F, 1.0F); + float noiseZ = (float)FBM(new Vec3(position.z / (double)((float)storm.maxWidth * 0.5F), position.x / (double)((float)storm.maxWidth * 0.5F), (double)((float)level.getGameTime() / timeScale)), 5, 2.0F, 0.2F, 1.0F); + relativePos = relativePos.add(new Vec3((double)(noiseX * (float)storm.maxWidth * 0.3F * effect), (double)0.0F, (double)(noiseZ * (float)storm.maxWidth * 0.3F * effect))); + double angle = Math.atan2(relativePos.z, relativePos.x) - distance / d; + float bands = (float)Math.sin((angle + Math.toRadians((double)((float)storm.tickCount / 8.0F))) * (double)4.0F); + mult += Mth.lerp(1.0F - Math.clamp((float)distance / ((float)storm.maxWidth * 0.35F), 0.0F, 1.0F), (float)Math.pow((double)Math.abs(bands), (double)2.0F) * 0.5F * mult, 0.5F * mult); + float noise = (float)FBM(new Vec3(position.x / (double)((float)storm.maxWidth * 0.5F), position.z / (double)((float)storm.maxWidth * 0.5F), (double)((float)level.getGameTime() / timeScale)), 5, 2.0F, 0.2F, 1.0F); + mult *= Math.clamp(noise, 0.0F, 1.0F) * 0.2F + 0.8F; + float noise2 = (float)FBM(new Vec3(position.x / (double)((float)storm.maxWidth * 0.1F), position.z / (double)((float)storm.maxWidth * 0.1F), (double)((float)level.getGameTime() / timeScale)), 5, 2.0F, 0.2F, 1.0F); + mult *= Math.clamp(noise2, 0.0F, 1.0F) * 0.1F + 0.9F; + mult *= 1.15F + (float)Math.pow((double)1.0F - Math.clamp((distance - (double)((float)storm.maxWidth * 0.1F)) / (double)((float)storm.maxWidth * 0.1F), (double)0.0F, (double)1.0F), (double)2.5F) * 0.35F; + float eye = (float)Math.pow(Math.clamp(distance / (double)((float)storm.maxWidth * 0.1F), (double)0.0F, (double)1.0F), (double)Mth.lerp(Math.clamp((float)storm.windspeed / 120.0F, 0.0F, 1.0F), 0.5F, 4.0F)); + mult *= Mth.lerp((float)Math.pow((double)Math.clamp((float)storm.windspeed / 65.0F, 0.0F, 1.0F), (double)2.0F), 1.0F, eye); + Vec3 vec = inward.multiply(pullStrngth, (double)0.0F, pullStrngth).add(rotational.multiply(rotStrngth, (double)0.0F, rotStrngth)).multiply((double)mult, (double)0.0F, (double)mult); + if (vec.length() > (double)storm.windspeed) { + double dif = vec.length() - (double)storm.windspeed; + vec = vec.subtract(new Vec3(dif, (double)0.0F, dif)); + } + + rawWind = rawWind.add(vec); + + for(Vorticy vorticy : storm.vorticies) { + Vec3 pos = vorticy.getPosition(); + Vec3 rPos = position.subtract(pos); + Vec3 in = (new Vec3(-rPos.x, (double)0.0F, -rPos.z)).normalize(); + Vec3 rot = (new Vec3(rPos.z, (double)0.0F, -rPos.x)).normalize(); + float width = vorticy.getWidth(); + double dist = position.multiply((double)1.0F, (double)0.0F, (double)1.0F).distanceTo(pos.multiply((double)1.0F, (double)0.0F, (double)1.0F)); + double pullStrn = (double)(vorticy.windspeedMult * (float)storm.windspeed * 0.3F); + double rotStrn = (double)(vorticy.windspeedMult * (float)storm.windspeed * 0.7F); + float m = (float)Math.pow((double)(1.0F - Math.clamp((float)dist / width, 0.0F, 1.0F)), (double)3.75F); + m *= Math.clamp((float)dist / (width * 0.1F), 0.0F, 1.0F); + m *= 7.0F; + Vec3 v = in.multiply(pullStrn, (double)0.0F, pullStrn).add(rot.multiply(rotStrn, (double)0.0F, rotStrn)).multiply((double)m, (double)0.0F, (double)m); + rawWind = rawWind.add(v); + } + } + + if (storm.stormType == 1) { + Vec2 v2fWorldPos = new Vec2((float)position.x, (float)position.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, -((float)ServerConfig.stormSize) * 5.0F); + Vec2 ri = Util.mulVec2(right, (float)ServerConfig.stormSize * 5.0F); + Vec2 off = Util.mulVec2(fwd, -((float)Math.pow(Mth.clamp(distance / (double)((float)ServerConfig.stormSize * 5.0F), (double)0.0F, (double)1.0F), (double)2.0F)) * (float)ServerConfig.stormSize * 1.5F); + le = le.add(off); + ri = ri.add(off); + le = le.add(v2fStormPos); + ri = ri.add(v2fStormPos); + float d = Util.minimumDistance(le, ri, v2fWorldPos); + Vec2 nearPoint = Util.nearestPoint(le, ri, v2fWorldPos); + Vec2 facing = v2fWorldPos.add(nearPoint.negated()); + float behind = -facing.dot(fwd); + behind += (float)FBM(new Vec3(position.x / (ServerConfig.stormSize * (double)2.0F), position.z / (ServerConfig.stormSize * (double)2.0F), (double)((float)level.getGameTime() / timeScale)), 5, 2.0F, 0.2F, 1.0F) * (float)ServerConfig.stormSize * 0.25F; + float perc = 0.0F; + float sze = (float)ServerConfig.stormSize * 4.0F; + behind += (float)ServerConfig.stormSize; + if (behind > 0.0F) { + float p = Mth.clamp(Math.abs(behind) / sze, 0.0F, 1.0F); + float start = 0.06F; + if (storm.stage >= 3) { + start = Mth.lerp((float)storm.energy / 100.0F, start, start * 2.5F); + } + + if (p <= start) { + p /= start; + } else { + p = 1.0F - (p - start) / (1.0F - start); + if (storm.stage >= 3) { + p = (float)Math.pow((double)p, (double)Mth.lerp((float)storm.energy / 100.0F, 1.0F, 0.75F)); + } + } + + perc = Mth.clamp(p, 0.0F, 1.0F); + } + + if (storm.stage < 1) { + perc *= (float)storm.energy / 100.0F; + } else if (storm.stage == 1) { + perc *= (float)storm.energy / 125.0F + 1.0F; + } else if (storm.stage == 2) { + perc *= (float)storm.energy / 200.0F + 1.8F; + } else { + perc *= (float)storm.energy / 100.0F + 2.3F; + } + + float gustNoise = (float)FBM(new Vec3(position.z / (ServerConfig.stormSize * (double)2.0F), position.x / (ServerConfig.stormSize * (double)2.0F), (double)((float)level.getGameTime() / timeScale)), 7, 2.0F, 0.4F, 1.0F); + if (storm.stage >= 3) { + float p = (float)storm.energy / 100.0F; + gustNoise *= 1.0F - p; + perc *= 1.0F + p * 0.3F; + } + + perc *= Mth.lerp(Mth.clamp(behind / ((float)ServerConfig.stormSize * 3.0F), 0.0F, 1.0F), (float)Math.pow((double)(0.8F + gustNoise * 0.5F), (double)1.5F), 0.5F); + perc *= Mth.sqrt(1.0F - Mth.clamp(d / sze, 0.0F, 1.0F)); + wind = wind.add(storm.velocity.multiply((double)(perc * 13.0F) * ServerConfig.squallStrengthMultiplier, (double)0.0F, (double)(perc * 13.0F) * ServerConfig.squallStrengthMultiplier)); + } + + if (storm.stormType == 0) { + Vec3 relativePos = position.subtract(storm.position); + Vec3 inward = (new Vec3(-relativePos.x, (double)0.0F, -relativePos.z)).normalize(); + Vec3 rotational = (new Vec3(relativePos.z, (double)0.0F, -relativePos.x)).normalize(); + double pullStrngth = (double)1.0F - Math.clamp(distance / (ServerConfig.stormSize * (double)4.0F), (double)0.0F, (double)1.0F); + double rotStrngth = (double)1.0F - Math.clamp(distance / ServerConfig.stormSize, (double)0.0F, (double)1.0F); + if (storm.stage < 1) { + pullStrngth *= (double)0.5F; + pullStrngth *= (double)((float)storm.energy / 100.0F); + rotStrngth *= (double)0.0F; + } else if (storm.stage == 1) { + pullStrngth *= (double)((float)storm.energy / 200.0F + 0.5F); + rotStrngth *= (double)((float)storm.energy / 100.0F * 0.1F); + } else if (storm.stage == 2) { + pullStrngth *= (double)(1.0F + (float)storm.energy / 100.0F); + rotStrngth *= (double)(0.1F + (float)storm.energy / 100.0F * 0.4F); + } else { + pullStrngth *= (double)(2.0F + (float)storm.windspeed / 400.0F); + rotStrngth *= (double)(0.5F + (float)storm.windspeed / 400.0F); + } + + pullStrngth *= (double)0.5F; + rotStrngth *= (double)6.0F; + Vec3 vec = inward.multiply(pullStrngth, (double)0.0F, pullStrngth).add(rotational.multiply(rotStrngth, (double)0.0F, rotStrngth)).multiply((double)20.0F, (double)20.0F, (double)20.0F); + wind = wind.add(vec); + } + } + } + } + } + + if (wind.length() > (double)30.0F) { + double over = wind.length() - (double)40.0F; + double val = (double)30.0F + over / (double)3.0F; + wind = wind.normalize().multiply(val, val, val); + } + + if (blockPos.getY() > 85) { + float val = Math.clamp((float)(blockPos.getY() - 85) / 40.0F, 0.0F, 1.0F) / 3.0F + 1.0F; + wind = wind.multiply((double)val, (double)val, (double)val); + } + + wind = wind.add(rawWind); + int heightAbove = blockPos.getY() - worldHeight; + if (heightAbove > 0) { + float val = Math.clamp((float)heightAbove / 15.0F, 0.0F, 1.0F) / 3.0F + 1.0F; + wind = wind.multiply((double)val, (double)val, (double)val); + } + + float tornadicEffect = 0.0F; + Vec3 tornadicWind = Vec3.ZERO; + if (!ignoreStorms && !ignoreTornadoes) { + for(Storm tornadicStorm : tornadicStorms) { + Vec3 relativePos = position.subtract(tornadicStorm.position); + Vec3 inward = (new Vec3(-relativePos.x, (double)0.0F, -relativePos.z)).normalize(); + Vec3 rotational = (new Vec3(relativePos.z, (double)0.0F, -relativePos.x)).normalize(); + double distance = position.distanceTo(tornadicStorm.position); + if (!(distance > (double)(tornadicStorm.width * 2.0F))) { + double windEffect = (double)tornadicStorm.getWind(position); + tornadicEffect = Math.clamp((float)windEffect / (float)Math.max(tornadicStorm.windspeed, 30), tornadicEffect, 1.0F); + if (Float.isNaN(tornadicEffect)) { + tornadicEffect = 0.0F; + } + + double inPerc = 0.35; + tornadicWind = tornadicWind.add(inward.multiply(windEffect * inPerc, windEffect * inPerc, windEffect * inPerc)).add(rotational.add(windEffect * ((double)1.0F - inPerc), windEffect * ((double)1.0F - inPerc), windEffect * ((double)1.0F - inPerc))); + } + } + } + + return wind.lerp(tornadicWind, (double)tornadicEffect); + } + } + + public static Vec3 getWind(BlockPos position, Level level, boolean ignoreStorms, boolean ignoreTornadoes, boolean windCheck) { + return getWind(new Vec3((double)position.getX(), (double)(position.getY() + 1), (double)position.getZ()), level, ignoreStorms, ignoreTornadoes, windCheck, false); + } + + public static Vec3 getWind(BlockPos position, Level level) { + return getWind(new Vec3((double)position.getX(), (double)(position.getY() + 1), (double)position.getZ()), level, false, false, true, false); + } +} diff --git a/pmweather_tornado_extraction_export/pmweather.mixins.json b/pmweather_tornado_extraction_export/pmweather.mixins.json new file mode 100644 index 00000000..88096ef8 --- /dev/null +++ b/pmweather_tornado_extraction_export/pmweather.mixins.json @@ -0,0 +1,21 @@ +{ + "required": true, + "package": "dev.protomanly.pmweather.mixin", + "compatibilityLevel": "JAVA_21", + "refmap": "pmweather.refmap.json", + "mixins": [ + "LevelMixin", + "ServerLevelMixin", + "SnowLayerBlockMixin", + "SpreadingSnowyDirtBlockMixin" + ], + "client": [ + "ParticleMixin", + "LevelRendererMixin", + "PostChainMixin", + "ClientLevelMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/run-data/logs/2026-03-17-2.log.gz b/run-data/logs/2026-03-17-2.log.gz new file mode 100644 index 00000000..7879fc01 Binary files /dev/null and b/run-data/logs/2026-03-17-2.log.gz differ diff --git a/run-data/logs/2026-05-12-1.log.gz b/run-data/logs/2026-05-12-1.log.gz new file mode 100644 index 00000000..ff430f54 Binary files /dev/null and b/run-data/logs/2026-05-12-1.log.gz differ diff --git a/run-data/logs/2026-05-12-2.log.gz b/run-data/logs/2026-05-12-2.log.gz new file mode 100644 index 00000000..919d4f07 Binary files /dev/null and b/run-data/logs/2026-05-12-2.log.gz differ diff --git a/run-data/logs/debug-1.log.gz b/run-data/logs/debug-1.log.gz index 82a90596..ab0238ac 100644 Binary files a/run-data/logs/debug-1.log.gz and b/run-data/logs/debug-1.log.gz differ diff --git a/run-data/logs/debug-2.log.gz b/run-data/logs/debug-2.log.gz index 37b9c7fa..546a8a1b 100644 Binary files a/run-data/logs/debug-2.log.gz and b/run-data/logs/debug-2.log.gz differ diff --git a/run-data/logs/debug-3.log.gz b/run-data/logs/debug-3.log.gz index 65f89ec0..fd4a7f23 100644 Binary files a/run-data/logs/debug-3.log.gz and b/run-data/logs/debug-3.log.gz differ diff --git a/run-data/logs/debug-4.log.gz b/run-data/logs/debug-4.log.gz new file mode 100644 index 00000000..82a90596 Binary files /dev/null and b/run-data/logs/debug-4.log.gz differ diff --git a/run-data/logs/debug-5.log.gz b/run-data/logs/debug-5.log.gz new file mode 100644 index 00000000..37b9c7fa Binary files /dev/null and b/run-data/logs/debug-5.log.gz differ diff --git a/run-data/logs/debug.log b/run-data/logs/debug.log index 3ff35303..915ce5a9 100644 --- a/run-data/logs/debug.log +++ b/run-data/logs/debug.log @@ -1,819 +1,809 @@ -[24nov.2025 23:48:47.382] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher running: args [--launchTarget, forgedatauserdev, --assetIndex, 5, --assetsDir, C:\Users\matga\.gradle\caches\forge_gradle\assets, --gameDir, ., --fml.forgeVersion, 47.4.9, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412, --mod, projectatmosphere, --all, --output, G:\Project-Atmosphere\src\generated\resources, --existing, G:\Project-Atmosphere\src\main\resources, --mixin.config, projectatmosphere.mixins.json] -[24nov.2025 23:48:47.390] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.15 by Eclipse Adoptium; OS Windows 11 arch amd64 version 10.0 -[24nov.2025 23:48:47.440] [main/DEBUG] [cpw.mods.modlauncher.LaunchServiceHandler/MODLAUNCHER]: Found launch services [fmlclientdev,forgeclient,minecraft,forgegametestserverdev,fmlserveruserdev,fmlclient,fmldatauserdev,forgeserverdev,forgeserveruserdev,forgeclientdev,forgeclientuserdev,forgeserver,forgedatadev,fmlserver,fmlclientuserdev,fmlserverdev,forgedatauserdev,testharness,forgegametestserveruserdev] -[24nov.2025 23:48:47.456] [main/DEBUG] [cpw.mods.modlauncher.NameMappingServiceHandler/MODLAUNCHER]: Found naming services : [srgtomcp] -[24nov.2025 23:48:47.466] [main/DEBUG] [cpw.mods.modlauncher.LaunchPluginHandler/MODLAUNCHER]: Found launch plugins: [mixin,eventbus,slf4jfixer,object_holder_definalize,runtime_enum_extender,capability_token_subclass,accesstransformer,runtimedistcleaner] -[24nov.2025 23:48:47.473] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Discovering transformation services -[24nov.2025 23:48:47.476] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path GAMEDIR is G:\Project-Atmosphere\run-data -[24nov.2025 23:48:47.476] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path MODSDIR is G:\Project-Atmosphere\run-data\mods -[24nov.2025 23:48:47.477] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path CONFIGDIR is G:\Project-Atmosphere\run-data\config -[24nov.2025 23:48:47.477] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path FMLCONFIG is G:\Project-Atmosphere\run-data\config\fml.toml -[24nov.2025 23:48:47.500] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Found additional transformation services from discovery services: -[24nov.2025 23:48:47.503] [main/INFO] [net.minecraftforge.fml.loading.ImmediateWindowHandler/]: ImmediateWindowProvider not loading because launch target is forgedatauserdev -[24nov.2025 23:48:47.508] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Found transformer services : [mixin,fml] -[24nov.2025 23:48:47.508] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Transformation services loading -[24nov.2025 23:48:47.508] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Loading service mixin -[24nov.2025 23:48:47.508] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Loaded service mixin -[24nov.2025 23:48:47.508] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Loading service fml -[24nov.2025 23:48:47.509] [main/DEBUG] [net.minecraftforge.fml.loading.LauncherVersion/CORE]: Found FMLLauncher version 1.0 -[24nov.2025 23:48:47.509] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: FML 1.0 loading -[24nov.2025 23:48:47.509] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: FML found ModLauncher version : 10.0.9+10.0.9+main.dcd20f30 -[24nov.2025 23:48:47.510] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Requesting CoreMods to not apply the fix for ASMAPI.findFirstInstructionBefore by default -[24nov.2025 23:48:47.510] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: FML found AccessTransformer version : 8.0.4+66+master.c09db6d7 -[24nov.2025 23:48:47.510] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: FML found EventBus version : 6.0.5+6.0.5+master.eb8e549b -[24nov.2025 23:48:47.510] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Found Runtime Dist Cleaner -[24nov.2025 23:48:47.511] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/]: CoreMods will preserve legacy behavior of ASMAPI.findFirstInstructionBefore for backwards-compatibility -[24nov.2025 23:48:47.511] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: FML found CoreMod version : 5.2.4 -[24nov.2025 23:48:47.512] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Found ForgeSPI package implementation version 7.0.1+7.0.1+master.d2b38bf6 -[24nov.2025 23:48:47.512] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Found ForgeSPI package specification 5 -[24nov.2025 23:48:47.512] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Loaded service fml -[24nov.2025 23:48:47.512] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Configuring option handling for services -[24nov.2025 23:48:47.517] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Transformation services initializing -[24nov.2025 23:48:47.517] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initializing transformation service mixin -[24nov.2025 23:48:47.525] [main/DEBUG] [mixin/]: MixinService [ModLauncher] was successfully booted in cpw.mods.cl.ModuleClassLoader@6ca8564a -[24nov.2025 23:48:47.534] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/C:/Users/matga/.gradle/caches/modules-2/files-2.1/org.spongepowered/mixin/0.8.5/9d1c0c3a304ae6697ecd477218fa61b850bf57fc/mixin-0.8.5.jar%23132!/ Service=ModLauncher Env=CLIENT -[24nov.2025 23:48:47.536] [main/DEBUG] [mixin/]: Initialising Mixin Platform Manager -[24nov.2025 23:48:47.536] [main/DEBUG] [mixin/]: Adding mixin platform agents for container ModLauncher Root Container(ModLauncher:4f56a0a2) -[24nov.2025 23:48:47.537] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for ModLauncher Root Container(ModLauncher:4f56a0a2) -[24nov.2025 23:48:47.537] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container ModLauncher Root Container(ModLauncher:4f56a0a2) -[24nov.2025 23:48:47.537] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for ModLauncher Root Container(ModLauncher:4f56a0a2) -[24nov.2025 23:48:47.537] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container ModLauncher Root Container(ModLauncher:4f56a0a2) -[24nov.2025 23:48:47.538] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initialized transformation service mixin -[24nov.2025 23:48:47.540] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initializing transformation service fml -[24nov.2025 23:48:47.540] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Setting up basic FML game directories -[24nov.2025 23:48:47.540] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path GAMEDIR is G:\Project-Atmosphere\run-data -[24nov.2025 23:48:47.540] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path MODSDIR is G:\Project-Atmosphere\run-data\mods -[24nov.2025 23:48:47.540] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path CONFIGDIR is G:\Project-Atmosphere\run-data\config -[24nov.2025 23:48:47.540] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path FMLCONFIG is G:\Project-Atmosphere\run-data\config\fml.toml -[24nov.2025 23:48:47.541] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Loading configuration -[24nov.2025 23:48:47.549] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Preparing ModFile -[24nov.2025 23:48:47.551] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Preparing launch handler -[24nov.2025 23:48:47.551] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Using forgedatauserdev as launch service -[24nov.2025 23:48:47.558] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Received command line version data : VersionInfo[forgeVersion=47.4.9, mcVersion=1.20.1, mcpVersion=20230612.114412, forgeGroup=net.minecraftforge] -[24nov.2025 23:48:47.558] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initialized transformation service fml -[24nov.2025 23:48:47.559] [main/DEBUG] [cpw.mods.modlauncher.NameMappingServiceHandler/MODLAUNCHER]: Current naming domain is 'mcp' -[24nov.2025 23:48:47.559] [main/DEBUG] [cpw.mods.modlauncher.NameMappingServiceHandler/MODLAUNCHER]: Identified name mapping providers {srg=srgtomcp:1234} -[24nov.2025 23:48:47.559] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Transformation services begin scanning -[24nov.2025 23:48:47.560] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Beginning scan trigger - transformation service mixin -[24nov.2025 23:48:47.560] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: End scan trigger - transformation service mixin -[24nov.2025 23:48:47.560] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Beginning scan trigger - transformation service fml -[24nov.2025 23:48:47.560] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Initiating mod scan -[24nov.2025 23:48:47.566] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModListHandler/CORE]: Found mod coordinates from lists: [] -[24nov.2025 23:48:47.569] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer/CORE]: Found Mod Locators : (mods folder:null),(maven libs:null),(exploded directory:null),(minecraft:null),(userdev classpath:null) -[24nov.2025 23:48:47.569] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer/CORE]: Found Dependency Locators : (JarInJar:null) -[24nov.2025 23:48:47.573] [main/DEBUG] [net.minecraftforge.fml.loading.targets.CommonLaunchHandler/CORE]: Got mod coordinates projectatmosphere%%G:\Project-Atmosphere\build\resources\main;projectatmosphere%%G:\Project-Atmosphere\build\classes\java\main from env -[24nov.2025 23:48:47.573] [main/DEBUG] [net.minecraftforge.fml.loading.targets.CommonLaunchHandler/CORE]: Found supplied mod coordinates [{projectatmosphere=[G:\Project-Atmosphere\build\resources\main, G:\Project-Atmosphere\build\classes\java\main]}] -[24nov.2025 23:48:47.785] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar with {minecraft} mods - versions {1.20.1} -[24nov.2025 23:48:47.788] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\javafmllanguage\1.20.1-47.4.9\6802c410879d06cd2c16ac24988dd5893293e21c\javafmllanguage-1.20.1-47.4.9.jar -[24nov.2025 23:48:47.788] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\javafmllanguage\1.20.1-47.4.9\6802c410879d06cd2c16ac24988dd5893293e21c\javafmllanguage-1.20.1-47.4.9.jar is missing mods.toml file -[24nov.2025 23:48:47.790] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\lowcodelanguage\1.20.1-47.4.9\1a098871d069f3cb2ed12f392f96f462902c0350\lowcodelanguage-1.20.1-47.4.9.jar -[24nov.2025 23:48:47.790] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\lowcodelanguage\1.20.1-47.4.9\1a098871d069f3cb2ed12f392f96f462902c0350\lowcodelanguage-1.20.1-47.4.9.jar is missing mods.toml file -[24nov.2025 23:48:47.792] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\mclanguage\1.20.1-47.4.9\5279b751da7297817db4fa9729e3f3c7bf03d594\mclanguage-1.20.1-47.4.9.jar -[24nov.2025 23:48:47.792] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\mclanguage\1.20.1-47.4.9\5279b751da7297817db4fa9729e3f3c7bf03d594\mclanguage-1.20.1-47.4.9.jar is missing mods.toml file -[24nov.2025 23:48:47.793] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\fmlcore\1.20.1-47.4.9\f4f03c369d155fb551ebf908db260a0c5600c29b\fmlcore-1.20.1-47.4.9.jar -[24nov.2025 23:48:47.795] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\fmlcore\1.20.1-47.4.9\f4f03c369d155fb551ebf908db260a0c5600c29b\fmlcore-1.20.1-47.4.9.jar is missing mods.toml file -[24nov.2025 23:48:47.800] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate G:\Project-Atmosphere\build\resources\main -[24nov.2025 23:48:47.810] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file main with {projectatmosphere} mods - versions {0.6.0.0-pre2} -[24nov.2025 23:48:47.812] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate / -[24nov.2025 23:48:47.814] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file with {forge} mods - versions {47.4.9} -[24nov.2025 23:48:47.828] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate G:\Project-Atmosphere\libs\tectonic-3.0.17-forge-1.20.1-dev.jar -[24nov.2025 23:48:47.828] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file tectonic-3.0.17-forge-1.20.1-dev.jar with {tectonic} mods - versions {3.0.17} -[24nov.2025 23:48:47.831] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\simpleclouds\0.7.3+1.20.1-forge_mapped_official_1.20.1\simpleclouds-0.7.3+1.20.1-forge_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.832] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file simpleclouds-0.7.3+1.20.1-forge_mapped_official_1.20.1.jar with {simpleclouds} mods - versions {0.7.3+1.20.1-forge} -[24nov.2025 23:48:47.832] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\crackerslib-forge\1.20.1-0.4.6_mapped_official_1.20.1\crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.833] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar with {crackerslib} mods - versions {1.20.1-0.4.6} -[24nov.2025 23:48:47.834] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\betterdays-895618\7013807_mapped_official_1.20.1\betterdays-895618-7013807_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.835] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file betterdays-895618-7013807_mapped_official_1.20.1.jar with {betterdays} mods - versions {3.3.4.4} -[24nov.2025 23:48:47.836] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-plus-1288843\7174860_mapped_official_1.20.1\serene-seasons-plus-1288843-7174860_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.837] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-plus-1288843-7174860_mapped_official_1.20.1.jar with {sereneseasonsplus} mods - versions {4.1.0} -[24nov.2025 23:48:47.837] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\rainboows-1121832\6421980_mapped_official_1.20.1\rainboows-1121832-6421980_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.842] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file rainboows-1121832-6421980_mapped_official_1.20.1.jar with {rainbows} mods - versions {1.5} -[24nov.2025 23:48:47.843] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\auroras-1105290\6040671_mapped_official_1.20.1\auroras-1105290-6040671_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.844] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file auroras-1105290-6040671_mapped_official_1.20.1.jar with {auroras} mods - versions {1.6.2} -[24nov.2025 23:48:47.845] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\io.github.xgabou\gaboulibs_forge\1.2\7247c49d39f07ab149de67aa345e8cf65978be1c\gaboulibs_forge-1.2.jar -[24nov.2025 23:48:47.846] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file gaboulibs_forge-1.2.jar with {gaboulibs} mods - versions {1.2} -[24nov.2025 23:48:47.847] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\architectury-api-419699\5137938_mapped_official_1.20.1\architectury-api-419699-5137938_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.848] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file architectury-api-419699-5137938_mapped_official_1.20.1.jar with {architectury} mods - versions {9.2.14} -[24nov.2025 23:48:47.849] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\tectonic-686836\7197834_mapped_official_1.20.1\tectonic-686836-7197834_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.849] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file tectonic-686836-7197834_mapped_official_1.20.1.jar with {tectonic} mods - versions {3.0.17} -[24nov.2025 23:48:47.851] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\lithostitched-936015\6742615_mapped_official_1.20.1\lithostitched-936015-6742615_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.851] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file lithostitched-936015-6742615_mapped_official_1.20.1.jar with {lithostitched} mods - versions {1.4.11} -[24nov.2025 23:48:47.852] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-291874\6398227_mapped_official_1.20.1\serene-seasons-291874-6398227_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.853] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-291874-6398227_mapped_official_1.20.1.jar with {sereneseasons} mods - versions {9.1.0.2} -[24nov.2025 23:48:47.855] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\glitchcore-955399\5787839_mapped_official_1.20.1\glitchcore-955399-5787839_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.855] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file glitchcore-955399-5787839_mapped_official_1.20.1.jar with {glitchcore} mods - versions {0.0.1.1} -[24nov.2025 23:48:47.856] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\spark-361579\4738952_mapped_official_1.20.1\spark-361579-4738952_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.857] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file spark-361579-4738952_mapped_official_1.20.1.jar with {spark} mods - versions {1.10.53} -[24nov.2025 23:48:47.858] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\cloth-config-348521\5729105_mapped_official_1.20.1\cloth-config-348521-5729105_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.858] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file cloth-config-348521-5729105_mapped_official_1.20.1.jar with {cloth_config} mods - versions {11.1.136} -[24nov.2025 23:48:47.859] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\chloride-931925\6615986_mapped_official_1.20.1\chloride-931925-6615986_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.860] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file chloride-931925-6615986_mapped_official_1.20.1.jar with {chloride} mods - versions {1.7.2} -[24nov.2025 23:48:47.862] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\embeddium-908741\5681725_mapped_official_1.20.1\embeddium-908741-5681725_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.862] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file embeddium-908741-5681725_mapped_official_1.20.1.jar with {embeddium,rubidium} mods - versions {0.3.31+mc1.20.1,0.7.1} -[24nov.2025 23:48:47.864] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\mezz\jei\jei-1.20.1-forge\15.8.2.26_mapped_official_1.20.1\jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.865] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar with {jei} mods - versions {15.8.2.26} -[24nov.2025 23:48:47.866] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\jade-324717\6271651_mapped_official_1.20.1\jade-324717-6271651_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.867] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jade-324717-6271651_mapped_official_1.20.1.jar with {jade} mods - versions {11.13.1+forge} -[24nov.2025 23:48:47.868] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\dev.architectury\architectury-forge\9.2.14\f48e657815d2e540d63bd162238631ed3c5633d3\architectury-forge-9.2.14.jar -[24nov.2025 23:48:47.868] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file architectury-forge-9.2.14.jar with {architectury} mods - versions {9.2.14} -[24nov.2025 23:48:47.870] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate G:\Project-Atmosphere\libs\tectonic-3.0.17-forge-1.20.1-dev.jar -[24nov.2025 23:48:47.871] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file tectonic-3.0.17-forge-1.20.1-dev.jar with {tectonic} mods - versions {3.0.17} -[24nov.2025 23:48:47.872] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\simpleclouds\0.7.3+1.20.1-forge_mapped_official_1.20.1\simpleclouds-0.7.3+1.20.1-forge_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.872] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file simpleclouds-0.7.3+1.20.1-forge_mapped_official_1.20.1.jar with {simpleclouds} mods - versions {0.7.3+1.20.1-forge} -[24nov.2025 23:48:47.873] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\crackerslib-forge\1.20.1-0.4.6_mapped_official_1.20.1\crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.874] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar with {crackerslib} mods - versions {1.20.1-0.4.6} -[24nov.2025 23:48:47.882] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\betterdays-895618\7013807_mapped_official_1.20.1\betterdays-895618-7013807_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.882] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file betterdays-895618-7013807_mapped_official_1.20.1.jar with {betterdays} mods - versions {3.3.4.4} -[24nov.2025 23:48:47.883] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-plus-1288843\7174860_mapped_official_1.20.1\serene-seasons-plus-1288843-7174860_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.883] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-plus-1288843-7174860_mapped_official_1.20.1.jar with {sereneseasonsplus} mods - versions {4.1.0} -[24nov.2025 23:48:47.884] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\rainboows-1121832\6421980_mapped_official_1.20.1\rainboows-1121832-6421980_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.885] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file rainboows-1121832-6421980_mapped_official_1.20.1.jar with {rainbows} mods - versions {1.5} -[24nov.2025 23:48:47.886] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\auroras-1105290\6040671_mapped_official_1.20.1\auroras-1105290-6040671_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.887] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file auroras-1105290-6040671_mapped_official_1.20.1.jar with {auroras} mods - versions {1.6.2} -[24nov.2025 23:48:47.888] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\io.github.xgabou\gaboulibs_forge\1.2\7247c49d39f07ab149de67aa345e8cf65978be1c\gaboulibs_forge-1.2.jar -[24nov.2025 23:48:47.888] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file gaboulibs_forge-1.2.jar with {gaboulibs} mods - versions {1.2} -[24nov.2025 23:48:47.891] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\architectury-api-419699\5137938_mapped_official_1.20.1\architectury-api-419699-5137938_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.891] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file architectury-api-419699-5137938_mapped_official_1.20.1.jar with {architectury} mods - versions {9.2.14} -[24nov.2025 23:48:47.892] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\tectonic-686836\7197834_mapped_official_1.20.1\tectonic-686836-7197834_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.893] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file tectonic-686836-7197834_mapped_official_1.20.1.jar with {tectonic} mods - versions {3.0.17} -[24nov.2025 23:48:47.894] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\lithostitched-936015\6742615_mapped_official_1.20.1\lithostitched-936015-6742615_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.895] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file lithostitched-936015-6742615_mapped_official_1.20.1.jar with {lithostitched} mods - versions {1.4.11} -[24nov.2025 23:48:47.896] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-291874\6398227_mapped_official_1.20.1\serene-seasons-291874-6398227_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.896] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-291874-6398227_mapped_official_1.20.1.jar with {sereneseasons} mods - versions {9.1.0.2} -[24nov.2025 23:48:47.898] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\glitchcore-955399\5787839_mapped_official_1.20.1\glitchcore-955399-5787839_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.898] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file glitchcore-955399-5787839_mapped_official_1.20.1.jar with {glitchcore} mods - versions {0.0.1.1} -[24nov.2025 23:48:47.899] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\spark-361579\4738952_mapped_official_1.20.1\spark-361579-4738952_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.899] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file spark-361579-4738952_mapped_official_1.20.1.jar with {spark} mods - versions {1.10.53} -[24nov.2025 23:48:47.901] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\cloth-config-348521\5729105_mapped_official_1.20.1\cloth-config-348521-5729105_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.901] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file cloth-config-348521-5729105_mapped_official_1.20.1.jar with {cloth_config} mods - versions {11.1.136} -[24nov.2025 23:48:47.903] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\chloride-931925\6615986_mapped_official_1.20.1\chloride-931925-6615986_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.904] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file chloride-931925-6615986_mapped_official_1.20.1.jar with {chloride} mods - versions {1.7.2} -[24nov.2025 23:48:47.905] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\embeddium-908741\5681725_mapped_official_1.20.1\embeddium-908741-5681725_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.906] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file embeddium-908741-5681725_mapped_official_1.20.1.jar with {embeddium,rubidium} mods - versions {0.3.31+mc1.20.1,0.7.1} -[24nov.2025 23:48:47.907] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\mezz\jei\jei-1.20.1-forge\15.8.2.26_mapped_official_1.20.1\jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.907] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar with {jei} mods - versions {15.8.2.26} -[24nov.2025 23:48:47.910] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\jade-324717\6271651_mapped_official_1.20.1\jade-324717-6271651_mapped_official_1.20.1.jar -[24nov.2025 23:48:47.910] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jade-324717-6271651_mapped_official_1.20.1.jar with {jade} mods - versions {11.13.1+forge} -[24nov.2025 23:48:47.911] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\dev.architectury\architectury-forge\9.2.14\f48e657815d2e540d63bd162238631ed3c5633d3\architectury-forge-9.2.14.jar -[24nov.2025 23:48:47.912] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file architectury-forge-9.2.14.jar with {architectury} mods - versions {9.2.14} -[24nov.2025 23:48:47.922] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid sereneseasons, selecting most recent based on version data -[24nov.2025 23:48:47.922] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file serene-seasons-291874-6398227_mapped_official_1.20.1.jar for modid sereneseasons with version 9.1.0.2 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid betterdays, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file betterdays-895618-7013807_mapped_official_1.20.1.jar for modid betterdays with version 3.3.4.4 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid crackerslib, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar for modid crackerslib with version 1.20.1-0.4.6 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid glitchcore, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file glitchcore-955399-5787839_mapped_official_1.20.1.jar for modid glitchcore with version 0.0.1.1 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid jade, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file jade-324717-6271651_mapped_official_1.20.1.jar for modid jade with version 11.13.1+forge -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid simpleclouds, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file simpleclouds-0.7.3+1.20.1-forge_mapped_official_1.20.1.jar for modid simpleclouds with version 0.7.3+1.20.1-forge -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 4 mods for first modid architectury, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file architectury-api-419699-5137938_mapped_official_1.20.1.jar for modid architectury with version 9.2.14 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid auroras, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file auroras-1105290-6040671_mapped_official_1.20.1.jar for modid auroras with version 1.6.2 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid jei, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar for modid jei with version 15.8.2.26 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid lithostitched, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file lithostitched-936015-6742615_mapped_official_1.20.1.jar for modid lithostitched with version 1.4.11 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid cloth_config, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file cloth-config-348521-5729105_mapped_official_1.20.1.jar for modid cloth_config with version 11.1.136 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid spark, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file spark-361579-4738952_mapped_official_1.20.1.jar for modid spark with version 1.10.53 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid gaboulibs, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file gaboulibs_forge-1.2.jar for modid gaboulibs with version 1.2 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid rainbows, selecting most recent based on version data -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file rainboows-1121832-6421980_mapped_official_1.20.1.jar for modid rainbows with version 1.5 -[24nov.2025 23:48:47.923] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid chloride, selecting most recent based on version data -[24nov.2025 23:48:47.924] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file chloride-931925-6615986_mapped_official_1.20.1.jar for modid chloride with version 1.7.2 -[24nov.2025 23:48:47.924] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid embeddium, selecting most recent based on version data -[24nov.2025 23:48:47.924] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file embeddium-908741-5681725_mapped_official_1.20.1.jar for modid embeddium with version 0.3.31+mc1.20.1 -[24nov.2025 23:48:47.924] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid sereneseasonsplus, selecting most recent based on version data -[24nov.2025 23:48:47.924] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file serene-seasons-plus-1288843-7174860_mapped_official_1.20.1.jar for modid sereneseasonsplus with version 4.1.0 -[24nov.2025 23:48:47.924] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 4 mods for first modid tectonic, selecting most recent based on version data -[24nov.2025 23:48:47.924] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file tectonic-3.0.17-forge-1.20.1-dev.jar for modid tectonic with version 3.0.17 -[24nov.2025 23:48:47.937] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from serene-seasons-291874-6398227_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from glitchcore-955399-5787839_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from jade-324717-6271651_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from simpleclouds-0.7.3+1.20.1-forge_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from architectury-api-419699-5137938_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from auroras-1105290-6040671_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from lithostitched-936015-6742615_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from cloth-config-348521-5729105_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from spark-361579-4738952_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from gaboulibs_forge-1.2.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from rainboows-1121832-6421980_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.938] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from , it does not contain dependency information. -[24nov.2025 23:48:47.939] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from simplecloudsapi-forge-1.4-1.20.1_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.939] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from chloride-931925-6615986_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.939] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from main, it does not contain dependency information. -[24nov.2025 23:48:47.939] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from serene-seasons-plus-1288843-7174860_mapped_official_1.20.1.jar, it does not contain dependency information. -[24nov.2025 23:48:47.939] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from tectonic-3.0.17-forge-1.20.1-dev.jar, it does not contain dependency information. -[24nov.2025 23:48:47.939] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from mclanguage-1.20.1-47.4.9.jar, it does not contain dependency information. -[24nov.2025 23:48:47.939] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from javafmllanguage-1.20.1-47.4.9.jar, it does not contain dependency information. -[24nov.2025 23:48:47.939] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from fmlcore-1.20.1-47.4.9.jar, it does not contain dependency information. -[24nov.2025 23:48:47.939] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from lowcodelanguage-1.20.1-47.4.9.jar, it does not contain dependency information. -[24nov.2025 23:48:47.994] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate -[24nov.2025 23:48:47.995] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file mixinextras-forge-0.3.5.jar with {mixinextras} mods - versions {0.3.5} -[24nov.2025 23:48:47.998] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate -[24nov.2025 23:48:47.998] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file whitenoise-1.20.1-forge-1.0.0.jar with {whitenoise} mods - versions {1.0.0} -[24nov.2025 23:48:48.003] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from whitenoise-1.20.1-forge-1.0.0.jar, it does not contain dependency information. -[24nov.2025 23:48:48.003] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from MixinExtras-0.3.5.jar, it does not contain dependency information. -[24nov.2025 23:48:48.026] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate -[24nov.2025 23:48:48.026] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file whitenoise-1.20.1-forge-1.0.0.jar with {whitenoise} mods - versions {1.0.0} -[24nov.2025 23:48:48.030] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate -[24nov.2025 23:48:48.030] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file mixinextras-forge-0.3.5.jar with {mixinextras} mods - versions {0.3.5} -[24nov.2025 23:48:48.031] [main/INFO] [net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator/]: Found 3 dependencies adding them to mods collection -[24nov.2025 23:48:48.032] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar with {minecraft} mods - versions {1.20.1} -[24nov.2025 23:48:48.033] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\minecraft_user_repo\net\minecraftforge\forge\1.20.1-47.4.9_mapped_official_1.20.1\forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar with languages [LanguageSpec[languageName=minecraft, acceptedVersions=1]] -[24nov.2025 23:48:48.034] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\betterdays-895618\7013807_mapped_official_1.20.1\betterdays-895618-7013807_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.034] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file betterdays-895618-7013807_mapped_official_1.20.1.jar with {betterdays} mods - versions {3.3.4.4} -[24nov.2025 23:48:48.034] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\betterdays-895618\7013807_mapped_official_1.20.1\betterdays-895618-7013807_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[46,)]] -[24nov.2025 23:48:48.034] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-291874\6398227_mapped_official_1.20.1\serene-seasons-291874-6398227_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.035] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-291874-6398227_mapped_official_1.20.1.jar with {sereneseasons} mods - versions {9.1.0.2} -[24nov.2025 23:48:48.035] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-291874\6398227_mapped_official_1.20.1\serene-seasons-291874-6398227_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] -[24nov.2025 23:48:48.035] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\crackerslib-forge\1.20.1-0.4.6_mapped_official_1.20.1\crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.035] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar with {crackerslib} mods - versions {1.20.1-0.4.6} -[24nov.2025 23:48:48.035] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\crackerslib-forge\1.20.1-0.4.6_mapped_official_1.20.1\crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] -[24nov.2025 23:48:48.035] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\glitchcore-955399\5787839_mapped_official_1.20.1\glitchcore-955399-5787839_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.036] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file glitchcore-955399-5787839_mapped_official_1.20.1.jar with {glitchcore} mods - versions {0.0.1.1} -[24nov.2025 23:48:48.036] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\glitchcore-955399\5787839_mapped_official_1.20.1\glitchcore-955399-5787839_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] -[24nov.2025 23:48:48.036] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\jade-324717\6271651_mapped_official_1.20.1\jade-324717-6271651_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.036] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jade-324717-6271651_mapped_official_1.20.1.jar with {jade} mods - versions {11.13.1+forge} -[24nov.2025 23:48:48.036] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\jade-324717\6271651_mapped_official_1.20.1\jade-324717-6271651_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[46,)]] -[24nov.2025 23:48:48.036] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\simpleclouds\0.7.3+1.20.1-forge_mapped_official_1.20.1\simpleclouds-0.7.3+1.20.1-forge_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.036] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file simpleclouds-0.7.3+1.20.1-forge_mapped_official_1.20.1.jar with {simpleclouds} mods - versions {0.7.3+1.20.1-forge} -[24nov.2025 23:48:48.037] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\simpleclouds\0.7.3+1.20.1-forge_mapped_official_1.20.1\simpleclouds-0.7.3+1.20.1-forge_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] -[24nov.2025 23:48:48.037] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate -[24nov.2025 23:48:48.037] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file whitenoise-1.20.1-forge-1.0.0.jar with {whitenoise} mods - versions {1.0.0} -[24nov.2025 23:48:48.037] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file with languages [LanguageSpec[languageName=javafml, acceptedVersions=[46,)]] -[24nov.2025 23:48:48.037] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\architectury-api-419699\5137938_mapped_official_1.20.1\architectury-api-419699-5137938_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.037] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file architectury-api-419699-5137938_mapped_official_1.20.1.jar with {architectury} mods - versions {9.2.14} -[24nov.2025 23:48:48.037] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\architectury-api-419699\5137938_mapped_official_1.20.1\architectury-api-419699-5137938_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[46,)]] -[24nov.2025 23:48:48.037] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\auroras-1105290\6040671_mapped_official_1.20.1\auroras-1105290-6040671_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file auroras-1105290-6040671_mapped_official_1.20.1.jar with {auroras} mods - versions {1.6.2} -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\auroras-1105290\6040671_mapped_official_1.20.1\auroras-1105290-6040671_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[40,)]] -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\mezz\jei\jei-1.20.1-forge\15.8.2.26_mapped_official_1.20.1\jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar with {jei} mods - versions {15.8.2.26} -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\mezz\jei\jei-1.20.1-forge\15.8.2.26_mapped_official_1.20.1\jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\lithostitched-936015\6742615_mapped_official_1.20.1\lithostitched-936015-6742615_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file lithostitched-936015-6742615_mapped_official_1.20.1.jar with {lithostitched} mods - versions {1.4.11} -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\lithostitched-936015\6742615_mapped_official_1.20.1\lithostitched-936015-6742615_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[46,)]] -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\spark-361579\4738952_mapped_official_1.20.1\spark-361579-4738952_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file spark-361579-4738952_mapped_official_1.20.1.jar with {spark} mods - versions {1.10.53} -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\spark-361579\4738952_mapped_official_1.20.1\spark-361579-4738952_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[34,)]] -[24nov.2025 23:48:48.038] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\cloth-config-348521\5729105_mapped_official_1.20.1\cloth-config-348521-5729105_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.040] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file cloth-config-348521-5729105_mapped_official_1.20.1.jar with {cloth_config} mods - versions {11.1.136} -[24nov.2025 23:48:48.040] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\cloth-config-348521\5729105_mapped_official_1.20.1\cloth-config-348521-5729105_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[34,)]] -[24nov.2025 23:48:48.040] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate / -[24nov.2025 23:48:48.041] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file with {forge} mods - versions {47.4.9} -[24nov.2025 23:48:48.041] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file / with languages [LanguageSpec[languageName=javafml, acceptedVersions=[24,]]] -[24nov.2025 23:48:48.044] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Found coremod field_to_method with Javascript path coremods/field_to_method.js -[24nov.2025 23:48:48.044] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Found coremod field_to_instanceof with Javascript path coremods/field_to_instanceof.js -[24nov.2025 23:48:48.044] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Found coremod add_bouncer_method with Javascript path coremods/add_bouncer_method.js -[24nov.2025 23:48:48.044] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Found coremod method_redirector with Javascript path coremods/method_redirector.js -[24nov.2025 23:48:48.044] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Found coremod coremods/field_to_method.js -[24nov.2025 23:48:48.044] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Found coremod coremods/field_to_instanceof.js -[24nov.2025 23:48:48.044] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Found coremod coremods/add_bouncer_method.js -[24nov.2025 23:48:48.044] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Found coremod coremods/method_redirector.js -[24nov.2025 23:48:48.044] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\rainboows-1121832\6421980_mapped_official_1.20.1\rainboows-1121832-6421980_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.044] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file rainboows-1121832-6421980_mapped_official_1.20.1.jar with {rainbows} mods - versions {1.5} -[24nov.2025 23:48:48.045] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\rainboows-1121832\6421980_mapped_official_1.20.1\rainboows-1121832-6421980_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[40,)]] -[24nov.2025 23:48:48.045] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\io.github.xgabou\gaboulibs_forge\1.2\7247c49d39f07ab149de67aa345e8cf65978be1c\gaboulibs_forge-1.2.jar -[24nov.2025 23:48:48.045] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file gaboulibs_forge-1.2.jar with {gaboulibs} mods - versions {1.2} -[24nov.2025 23:48:48.045] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\io.github.xgabou\gaboulibs_forge\1.2\7247c49d39f07ab149de67aa345e8cf65978be1c\gaboulibs_forge-1.2.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] -[24nov.2025 23:48:48.045] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\chloride-931925\6615986_mapped_official_1.20.1\chloride-931925-6615986_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.046] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file chloride-931925-6615986_mapped_official_1.20.1.jar with {chloride} mods - versions {1.7.2} -[24nov.2025 23:48:48.046] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\chloride-931925\6615986_mapped_official_1.20.1\chloride-931925-6615986_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,48)]] -[24nov.2025 23:48:48.046] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\embeddium-908741\5681725_mapped_official_1.20.1\embeddium-908741-5681725_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.046] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file embeddium-908741-5681725_mapped_official_1.20.1.jar with {embeddium,rubidium} mods - versions {0.3.31+mc1.20.1,0.7.1} -[24nov.2025 23:48:48.046] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\embeddium-908741\5681725_mapped_official_1.20.1\embeddium-908741-5681725_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] -[24nov.2025 23:48:48.046] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate G:\Project-Atmosphere\build\resources\main -[24nov.2025 23:48:48.047] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file main with {projectatmosphere} mods - versions {0.6.0.0-pre2} -[24nov.2025 23:48:48.047] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file G:\Project-Atmosphere\build\resources\main with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] -[24nov.2025 23:48:48.047] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate G:\Project-Atmosphere\libs\tectonic-3.0.17-forge-1.20.1-dev.jar -[24nov.2025 23:48:48.047] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file tectonic-3.0.17-forge-1.20.1-dev.jar with {tectonic} mods - versions {3.0.17} -[24nov.2025 23:48:48.047] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file G:\Project-Atmosphere\libs\tectonic-3.0.17-forge-1.20.1-dev.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] -[24nov.2025 23:48:48.047] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-plus-1288843\7174860_mapped_official_1.20.1\serene-seasons-plus-1288843-7174860_mapped_official_1.20.1.jar -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-plus-1288843-7174860_mapped_official_1.20.1.jar with {sereneseasonsplus} mods - versions {4.1.0} -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-plus-1288843\7174860_mapped_official_1.20.1\serene-seasons-plus-1288843-7174860_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file mixinextras-forge-0.3.5.jar with {mixinextras} mods - versions {0.3.5} -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file with languages [LanguageSpec[languageName=javafml, acceptedVersions=*]] -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file with languages [] -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\simplecloudsapi-forge\1.4-1.20.1_mapped_official_1.20.1\simplecloudsapi-forge-1.4-1.20.1_mapped_official_1.20.1.jar with languages [] -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file mixinextras-forge-0.3.5.jar with {mixinextras} mods - versions {0.3.5} -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file with languages [LanguageSpec[languageName=javafml, acceptedVersions=*]] -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file with languages [] -[24nov.2025 23:48:48.048] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\simplecloudsapi-forge\1.4-1.20.1_mapped_official_1.20.1\simplecloudsapi-forge-1.4-1.20.1_mapped_official_1.20.1.jar with languages [] -[24nov.2025 23:48:48.050] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: End scan trigger - transformation service fml -[24nov.2025 23:48:48.057] [main/DEBUG] [net.minecraftforge.fml.loading.LanguageLoadingProvider/CORE]: Found 3 language providers -[24nov.2025 23:48:48.057] [main/DEBUG] [net.minecraftforge.fml.loading.LanguageLoadingProvider/CORE]: Found language provider minecraft, version 1.0 -[24nov.2025 23:48:48.058] [main/DEBUG] [net.minecraftforge.fml.loading.LanguageLoadingProvider/CORE]: Found language provider lowcodefml, version 47 -[24nov.2025 23:48:48.058] [main/DEBUG] [net.minecraftforge.fml.loading.LanguageLoadingProvider/CORE]: Found language provider javafml, version 47 -[24nov.2025 23:48:48.063] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid simplecloudsapi.forge, selecting most recent based on version data -[24nov.2025 23:48:48.064] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file simplecloudsapi-forge-1.4-1.20.1_mapped_official_1.20.1.jar for modid simplecloudsapi.forge with version 1.4-1.20.1 -[24nov.2025 23:48:48.064] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid mixinextras, selecting most recent based on version data -[24nov.2025 23:48:48.064] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file mixinextras-forge-0.3.5.jar for modid mixinextras with version 0.3.5 -[24nov.2025 23:48:48.064] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid MixinExtras, selecting most recent based on version data -[24nov.2025 23:48:48.064] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file MixinExtras-0.3.5.jar for modid MixinExtras with version 0.0NONE -[24nov.2025 23:48:48.066] [main/DEBUG] [net.minecraftforge.fml.loading.ModSorter/]: Configured system mods: [minecraft, forge] -[24nov.2025 23:48:48.066] [main/DEBUG] [net.minecraftforge.fml.loading.ModSorter/]: Found system mod: minecraft -[24nov.2025 23:48:48.066] [main/DEBUG] [net.minecraftforge.fml.loading.ModSorter/]: Found system mod: forge -[24nov.2025 23:48:48.070] [main/DEBUG] [net.minecraftforge.fml.loading.ModSorter/LOADING]: Found 47 mod requirements (40 mandatory, 7 optional) -[24nov.2025 23:48:48.071] [main/DEBUG] [net.minecraftforge.fml.loading.ModSorter/LOADING]: Found 0 mod requirements missing (0 mandatory, 0 optional) -[24nov.2025 23:48:48.446] [main/DEBUG] [net.minecraftforge.fml.loading.MCPNamingService/CORE]: Loaded 33222 method mappings from methods.csv -[24nov.2025 23:48:48.468] [main/DEBUG] [net.minecraftforge.fml.loading.MCPNamingService/CORE]: Loaded 31003 field mappings from fields.csv -[24nov.2025 23:48:48.518] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Transformation services loading transformers -[24nov.2025 23:48:48.518] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initializing transformers for transformation service mixin -[24nov.2025 23:48:48.519] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initialized transformers for transformation service mixin -[24nov.2025 23:48:48.519] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initializing transformers for transformation service fml -[24nov.2025 23:48:48.519] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Loading coremod transformers -[24nov.2025 23:48:48.519] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: Loading CoreMod from coremods/field_to_method.js -[24nov.2025 23:48:48.764] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: CoreMod loaded successfully -[24nov.2025 23:48:48.764] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: Loading CoreMod from coremods/field_to_instanceof.js -[24nov.2025 23:48:48.854] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: CoreMod loaded successfully -[24nov.2025 23:48:48.854] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: Loading CoreMod from coremods/add_bouncer_method.js -[24nov.2025 23:48:48.885] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: CoreMod loaded successfully -[24nov.2025 23:48:48.885] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: Loading CoreMod from coremods/method_redirector.js -[24nov.2025 23:48:48.936] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: CoreMod loaded successfully -[24nov.2025 23:48:48.952] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@6198e9b5 to Target : CLASS {Lnet/minecraft/world/level/biome/Biome;} {} {V} -[24nov.2025 23:48:48.954] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@d535a3d to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/Structure;} {} {V} -[24nov.2025 23:48:48.954] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@2d760326 to Target : CLASS {Lnet/minecraft/world/effect/MobEffectInstance;} {} {V} -[24nov.2025 23:48:48.954] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@9e54c59 to Target : CLASS {Lnet/minecraft/world/level/block/LiquidBlock;} {} {V} -[24nov.2025 23:48:48.954] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@5dbb50f3 to Target : CLASS {Lnet/minecraft/world/item/BucketItem;} {} {V} -[24nov.2025 23:48:48.954] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a2e7bcb to Target : CLASS {Lnet/minecraft/world/level/block/StairBlock;} {} {V} -[24nov.2025 23:48:48.954] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@575c3e9b to Target : CLASS {Lnet/minecraft/world/level/block/FlowerPotBlock;} {} {V} -[24nov.2025 23:48:48.954] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@74f827ad to Target : CLASS {Lnet/minecraft/world/item/ItemStack;} {} {V} -[24nov.2025 23:48:48.954] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@73c3cd09 to Target : CLASS {Lnet/minecraft/network/play/client/CClientSettingsPacket;} {} {V} -[24nov.2025 23:48:48.954] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/EntityType;} {} {V} -[24nov.2025 23:48:48.954] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces$OceanRuinPiece;} {} {V} -[24nov.2025 23:48:48.954] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/npc/CatSpawner;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/server/commands/RaidCommand;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/structures/SwampHutPiece;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/level/levelgen/PatrolSpawner;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/monster/Spider;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/server/commands/SummonCommand;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/level/NaturalSpawner;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces$OceanMonumentPiece;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/monster/Zombie;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/npc/Villager;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/animal/frog/Tadpole;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/raid/Raid;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces$WoodlandMansionPiece;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/animal/horse/SkeletonTrapGoal;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/monster/Strider;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/monster/ZombieVillager;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/level/levelgen/PhantomSpawner;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/monster/Evoker$EvokerSummonSpellGoal;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@4a8a0099 to Target : CLASS {Lnet/minecraft/world/entity/ai/village/VillageSiege;} {} {V} -[24nov.2025 23:48:48.955] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initialized transformers for transformation service fml -[24nov.2025 23:48:49.287] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:ModLauncher Root Container(ModLauncher:4f56a0a2)] -[24nov.2025 23:48:49.287] [main/DEBUG] [mixin/]: Registering mixin config: projectatmosphere.mixins.json -[24nov.2025 23:48:49.310] [main/DEBUG] [mixin/]: Compatibility level JAVA_17 specified by projectatmosphere.mixins.json is higher than the maximum level supported by this version of mixin (JAVA_13). -[24nov.2025 23:48:49.314] [main/INFO] [mixin/]: Compatibility level set to JAVA_17 -[24nov.2025 23:48:49.314] [main/ERROR] [mixin/]: Mixin config projectatmosphere.mixins.json does not specify "minVersion" property -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Processing launch tasks for PlatformAgent[MixinPlatformAgentDefault:ModLauncher Root Container(ModLauncher:4f56a0a2)] -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(betterdays) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(betterdays) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(betterdays) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(betterdays) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(betterdays) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(betterdays)] -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(minecraft) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(minecraft) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(minecraft) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(minecraft) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(minecraft) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(minecraft)] -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(jade) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(jade) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(jade) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(jade) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(jade) -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(jade)] -[24nov.2025 23:48:49.315] [main/DEBUG] [mixin/]: Registering mixin config: jade.mixins.json -[24nov.2025 23:48:49.316] [main/DEBUG] [mixin/]: Compatibility level JAVA_16 specified by jade.mixins.json is higher than the maximum level supported by this version of mixin (JAVA_13). -[24nov.2025 23:48:49.316] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(jei) -[24nov.2025 23:48:49.316] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(jei) -[24nov.2025 23:48:49.317] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(jei) -[24nov.2025 23:48:49.317] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(jei) -[24nov.2025 23:48:49.317] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(jei) -[24nov.2025 23:48:49.317] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(jei)] -[24nov.2025 23:48:49.317] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(lithostitched) -[24nov.2025 23:48:49.317] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(lithostitched) -[24nov.2025 23:48:49.317] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(lithostitched) -[24nov.2025 23:48:49.317] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(lithostitched) -[24nov.2025 23:48:49.317] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(lithostitched) -[24nov.2025 23:48:49.317] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(lithostitched)] -[24nov.2025 23:48:49.317] [main/DEBUG] [mixin/]: Registering mixin config: lithostitched.mixins.json -[24nov.2025 23:48:49.318] [main/DEBUG] [mixin/]: Registering mixin config: lithostitched.forge.mixins.json -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(spark) -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(spark) -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(spark) -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(spark) -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(spark) -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(spark)] -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(sereneseasonsplus) -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(sereneseasonsplus) -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(sereneseasonsplus) -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(sereneseasonsplus) -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(sereneseasonsplus) -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(sereneseasonsplus)] -[24nov.2025 23:48:49.320] [main/DEBUG] [mixin/]: Registering mixin config: sereneseasonsplus.mixins.json -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(crackerslib) -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(crackerslib) -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(crackerslib) -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(crackerslib) -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(crackerslib) -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(crackerslib)] -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: Registering mixin config: crackerslib.mixins.json -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(mixinextras) -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(mixinextras) -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(mixinextras) -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(mixinextras) -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(mixinextras) -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(mixinextras)] -[24nov.2025 23:48:49.321] [main/DEBUG] [mixin/]: Registering mixin config: mixinextras.init.mixins.json -[24nov.2025 23:48:49.322] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(glitchcore) -[24nov.2025 23:48:49.322] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(glitchcore) -[24nov.2025 23:48:49.322] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(glitchcore) -[24nov.2025 23:48:49.322] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(glitchcore) -[24nov.2025 23:48:49.322] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(glitchcore) -[24nov.2025 23:48:49.322] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(glitchcore)] -[24nov.2025 23:48:49.322] [main/DEBUG] [mixin/]: Registering mixin config: glitchcore.mixins.json -[24nov.2025 23:48:49.322] [main/DEBUG] [mixin/]: Registering mixin config: glitchcore.forge.mixins.json -[24nov.2025 23:48:49.322] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(sereneseasons) -[24nov.2025 23:48:49.323] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(sereneseasons) -[24nov.2025 23:48:49.323] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(sereneseasons) -[24nov.2025 23:48:49.323] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(sereneseasons) -[24nov.2025 23:48:49.323] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(sereneseasons) -[24nov.2025 23:48:49.323] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(sereneseasons)] -[24nov.2025 23:48:49.323] [main/DEBUG] [mixin/]: Registering mixin config: sereneseasons.mixins.json -[24nov.2025 23:48:49.323] [main/DEBUG] [mixin/]: Registering mixin config: sereneseasons.forge.mixins.json -[24nov.2025 23:48:49.324] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(simpleclouds) -[24nov.2025 23:48:49.324] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(simpleclouds) -[24nov.2025 23:48:49.324] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(simpleclouds) -[24nov.2025 23:48:49.324] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(simpleclouds) -[24nov.2025 23:48:49.324] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(simpleclouds) -[24nov.2025 23:48:49.324] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(simpleclouds)] -[24nov.2025 23:48:49.324] [main/DEBUG] [mixin/]: Registering mixin config: simpleclouds.mixins.json -[24nov.2025 23:48:49.324] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(projectatmosphere) -[24nov.2025 23:48:49.324] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(projectatmosphere) -[24nov.2025 23:48:49.324] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(projectatmosphere) -[24nov.2025 23:48:49.325] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(projectatmosphere) -[24nov.2025 23:48:49.325] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(projectatmosphere) -[24nov.2025 23:48:49.325] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(projectatmosphere)] -[24nov.2025 23:48:49.325] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(architectury) -[24nov.2025 23:48:49.325] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(architectury) -[24nov.2025 23:48:49.325] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(architectury) -[24nov.2025 23:48:49.325] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(architectury) -[24nov.2025 23:48:49.325] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(architectury) -[24nov.2025 23:48:49.325] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(architectury)] -[24nov.2025 23:48:49.325] [main/DEBUG] [mixin/]: Registering mixin config: architectury.mixins.json -[24nov.2025 23:48:49.326] [main/DEBUG] [mixin/]: Compatibility level JAVA_16 specified by architectury.mixins.json is higher than the maximum level supported by this version of mixin (JAVA_13). -[24nov.2025 23:48:49.327] [main/DEBUG] [mixin/]: Registering mixin config: architectury-common.mixins.json -[24nov.2025 23:48:49.327] [main/DEBUG] [mixin/]: Compatibility level JAVA_16 specified by architectury-common.mixins.json is higher than the maximum level supported by this version of mixin (JAVA_13). -[24nov.2025 23:48:49.328] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(whitenoise) -[24nov.2025 23:48:49.328] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(whitenoise) -[24nov.2025 23:48:49.328] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(whitenoise) -[24nov.2025 23:48:49.328] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(whitenoise) -[24nov.2025 23:48:49.328] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(whitenoise) -[24nov.2025 23:48:49.328] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(whitenoise)] -[24nov.2025 23:48:49.328] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(cloth_config) -[24nov.2025 23:48:49.328] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(cloth_config) -[24nov.2025 23:48:49.328] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(cloth_config) -[24nov.2025 23:48:49.329] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(cloth_config) -[24nov.2025 23:48:49.329] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(cloth_config) -[24nov.2025 23:48:49.329] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(cloth_config)] -[24nov.2025 23:48:49.329] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(gaboulibs) -[24nov.2025 23:48:49.329] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(gaboulibs) -[24nov.2025 23:48:49.329] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(gaboulibs) -[24nov.2025 23:48:49.329] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(gaboulibs) -[24nov.2025 23:48:49.329] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(gaboulibs) -[24nov.2025 23:48:49.329] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(gaboulibs)] -[24nov.2025 23:48:49.329] [main/DEBUG] [mixin/]: Registering mixin config: gaboulibs.mixins.json -[24nov.2025 23:48:49.329] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(forge) -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(forge) -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(forge) -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(forge) -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(forge) -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(forge)] -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(auroras) -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(auroras) -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(auroras) -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(auroras) -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(auroras) -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(auroras)] -[24nov.2025 23:48:49.330] [main/DEBUG] [mixin/]: Registering mixin config: auroras.mixins.json -[24nov.2025 23:48:49.331] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(rainbows) -[24nov.2025 23:48:49.331] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(rainbows) -[24nov.2025 23:48:49.331] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(rainbows) -[24nov.2025 23:48:49.331] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(rainbows) -[24nov.2025 23:48:49.331] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(rainbows) -[24nov.2025 23:48:49.331] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(rainbows)] -[24nov.2025 23:48:49.331] [main/DEBUG] [mixin/]: Registering mixin config: rainbows.mixins.json -[24nov.2025 23:48:49.331] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(embeddium) -[24nov.2025 23:48:49.331] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(embeddium) -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(embeddium) -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(embeddium) -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(embeddium) -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(embeddium)] -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: Registering mixin config: embeddium.mixins.json -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(chloride) -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(chloride) -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(chloride) -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(chloride) -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(chloride) -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(chloride)] -[24nov.2025 23:48:49.332] [main/DEBUG] [mixin/]: Registering mixin config: chloride.mixin.json -[24nov.2025 23:48:49.333] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(tectonic) -[24nov.2025 23:48:49.333] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(tectonic) -[24nov.2025 23:48:49.333] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(tectonic) -[24nov.2025 23:48:49.333] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(tectonic) -[24nov.2025 23:48:49.333] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(tectonic) -[24nov.2025 23:48:49.333] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(tectonic)] -[24nov.2025 23:48:49.333] [main/DEBUG] [mixin/]: Registering mixin config: tectonic.mixins.json -[24nov.2025 23:48:49.333] [main/DEBUG] [mixin/]: Registering mixin config: tectonic_1.20.1.mixins.json -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(MixinExtras) -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(MixinExtras) -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(MixinExtras) -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(MixinExtras) -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(MixinExtras) -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(MixinExtras)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(simplecloudsapi.forge) -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(simplecloudsapi.forge) -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(simplecloudsapi.forge) -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(simplecloudsapi.forge) -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(simplecloudsapi.forge) -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(simplecloudsapi.forge)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: inject() running with 26 agents -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:ModLauncher Root Container(ModLauncher:4f56a0a2)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(betterdays)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(minecraft)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(jade)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(jei)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(lithostitched)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(spark)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(sereneseasonsplus)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(crackerslib)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(mixinextras)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(glitchcore)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(sereneseasons)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(simpleclouds)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(projectatmosphere)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(architectury)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(whitenoise)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(cloth_config)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(gaboulibs)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(forge)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(auroras)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(rainbows)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(embeddium)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(chloride)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(tectonic)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(MixinExtras)] -[24nov.2025 23:48:49.335] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(simplecloudsapi.forge)] -[24nov.2025 23:48:49.336] [main/INFO] [cpw.mods.modlauncher.LaunchServiceHandler/MODLAUNCHER]: Launching target 'forgedatauserdev' with arguments [--gameDir, ., --assetsDir, C:\Users\matga\.gradle\caches\forge_gradle\assets, --assetIndex, 5, --mod, projectatmosphere, --all, --output, G:\Project-Atmosphere\src\generated\resources, --existing, G:\Project-Atmosphere\src\main\resources] -[24nov.2025 23:48:49.419] [main/DEBUG] [mixin/]: Error cleaning class output directory: .mixin.out -[24nov.2025 23:48:49.421] [main/DEBUG] [mixin/]: Preparing mixins for MixinEnvironment[DEFAULT] -[24nov.2025 23:48:49.421] [main/DEBUG] [mixin/]: Selecting config projectatmosphere.mixins.json -[24nov.2025 23:48:49.453] [main/WARN] [mixin/]: Reference map 'projectatmosphere.refmap.json' for projectatmosphere.mixins.json could not be read. If this is a development environment you can ignore this message -[24nov.2025 23:48:49.454] [main/DEBUG] [mixin/]: Selecting config jade.mixins.json -[24nov.2025 23:48:49.600] [main/INFO] [mixin/]: Remapping refMap jade.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.600] [main/DEBUG] [mixin/]: Selecting config lithostitched.mixins.json -[24nov.2025 23:48:49.601] [main/INFO] [mixin/]: Remapping refMap lithostitched.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.601] [main/DEBUG] [mixin/]: Selecting config lithostitched.forge.mixins.json -[24nov.2025 23:48:49.602] [main/INFO] [mixin/]: Remapping refMap lithostitched.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.602] [main/DEBUG] [mixin/]: Selecting config sereneseasonsplus.mixins.json -[24nov.2025 23:48:49.602] [main/INFO] [mixin/]: Remapping refMap sereneseasonsplus-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.602] [main/DEBUG] [mixin/]: Selecting config crackerslib.mixins.json -[24nov.2025 23:48:49.602] [main/INFO] [mixin/]: Remapping refMap crackerslib.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.602] [main/DEBUG] [mixin/]: Selecting config mixinextras.init.mixins.json -[24nov.2025 23:48:49.640] [main/DEBUG] [MixinExtras|Service/]: com.llamalad7.mixinextras.service.MixinExtrasServiceImpl(version=0.3.5) is taking over from null -[24nov.2025 23:48:49.690] [main/DEBUG] [mixin/]: Registering new injector for @Inject with org.spongepowered.asm.mixin.injection.struct.CallbackInjectionInfo -[24nov.2025 23:48:49.692] [main/DEBUG] [mixin/]: Registering new injector for @ModifyArg with org.spongepowered.asm.mixin.injection.struct.ModifyArgInjectionInfo -[24nov.2025 23:48:49.692] [main/DEBUG] [mixin/]: Registering new injector for @ModifyArgs with org.spongepowered.asm.mixin.injection.struct.ModifyArgsInjectionInfo -[24nov.2025 23:48:49.694] [main/DEBUG] [mixin/]: Registering new injector for @Redirect with org.spongepowered.asm.mixin.injection.struct.RedirectInjectionInfo -[24nov.2025 23:48:49.695] [main/DEBUG] [mixin/]: Registering new injector for @ModifyVariable with org.spongepowered.asm.mixin.injection.struct.ModifyVariableInjectionInfo -[24nov.2025 23:48:49.696] [main/DEBUG] [mixin/]: Registering new injector for @ModifyConstant with org.spongepowered.asm.mixin.injection.struct.ModifyConstantInjectionInfo -[24nov.2025 23:48:49.704] [main/DEBUG] [mixin/]: Selecting config glitchcore.mixins.json -[24nov.2025 23:48:49.705] [main/INFO] [mixin/]: Remapping refMap glitchcore.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.705] [main/DEBUG] [mixin/]: Selecting config glitchcore.forge.mixins.json -[24nov.2025 23:48:49.705] [main/INFO] [mixin/]: Remapping refMap glitchcore.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.705] [main/DEBUG] [mixin/]: Selecting config sereneseasons.mixins.json -[24nov.2025 23:48:49.706] [main/INFO] [mixin/]: Remapping refMap sereneseasons.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.706] [main/DEBUG] [mixin/]: Selecting config sereneseasons.forge.mixins.json -[24nov.2025 23:48:49.706] [main/INFO] [mixin/]: Remapping refMap sereneseasons.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.706] [main/DEBUG] [mixin/]: Selecting config simpleclouds.mixins.json -[24nov.2025 23:48:49.707] [main/INFO] [mixin/]: Remapping refMap simpleclouds.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.707] [main/DEBUG] [mixin/]: Selecting config architectury.mixins.json -[24nov.2025 23:48:49.710] [main/INFO] [mixin/]: Remapping refMap architectury-forge-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.710] [main/DEBUG] [mixin/]: Selecting config architectury-common.mixins.json -[24nov.2025 23:48:49.710] [main/INFO] [mixin/]: Remapping refMap architectury-common-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.710] [main/DEBUG] [mixin/]: Selecting config gaboulibs.mixins.json -[24nov.2025 23:48:49.711] [main/DEBUG] [mixin/]: Selecting config auroras.mixins.json -[24nov.2025 23:48:49.711] [main/INFO] [mixin/]: Remapping refMap auroras.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.711] [main/DEBUG] [mixin/]: Selecting config rainbows.mixins.json -[24nov.2025 23:48:49.712] [main/WARN] [mixin/]: Reference map 'rainbows.refmap.json' for rainbows.mixins.json could not be read. If this is a development environment you can ignore this message -[24nov.2025 23:48:49.712] [main/DEBUG] [mixin/]: Selecting config embeddium.mixins.json -[24nov.2025 23:48:49.720] [main/INFO] [Embeddium/]: Loaded configuration file for Embeddium: 68 options available, 0 override(s) found -[24nov.2025 23:48:49.722] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Searching for graphics cards... -[24nov.2025 23:48:49.839] [main/DEBUG] [oshi.util.FileUtil/]: No oshi.properties file found from ClassLoader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 -[24nov.2025 23:48:49.963] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Found graphics card: GraphicsAdapterInfo[vendor=UNKNOWN, name=Meta Virtual Monitor, version=DriverVersion=17.12.55.198] -[24nov.2025 23:48:49.963] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Found graphics card: GraphicsAdapterInfo[vendor=NVIDIA, name=NVIDIA GeForce RTX 4070 Ti SUPER, version=DriverVersion=32.0.15.8180] -[24nov.2025 23:48:49.967] [main/WARN] [Embeddium-Workarounds/]: Embeddium has applied one or more workarounds to prevent crashes or other issues on your system: [NVIDIA_THREADED_OPTIMIZATIONS] -[24nov.2025 23:48:49.967] [main/WARN] [Embeddium-Workarounds/]: This is not necessarily an issue, but it may result in certain features or optimizations being disabled. You can sometimes fix these issues by upgrading your graphics driver. -[24nov.2025 23:48:49.969] [main/INFO] [mixin/]: Remapping refMap embeddium-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.969] [main/DEBUG] [mixin/]: Selecting config chloride.mixin.json -[24nov.2025 23:48:49.971] [main/INFO] [mixin/]: Remapping refMap chloride.mixin-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.971] [main/DEBUG] [mixin/]: Selecting config tectonic.mixins.json -[24nov.2025 23:48:49.971] [main/DEBUG] [mixin/]: Selecting config tectonic_1.20.1.mixins.json -[24nov.2025 23:48:49.972] [main/DEBUG] [mixin/]: Preparing projectatmosphere.mixins.json (12) -[24nov.2025 23:48:50.018] [main/DEBUG] [mixin/]: Preparing jade.mixins.json (2) -[24nov.2025 23:48:50.022] [main/DEBUG] [mixin/]: Preparing lithostitched.mixins.json (18) -[24nov.2025 23:48:50.044] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate -[24nov.2025 23:48:50.192] [main/DEBUG] [mixin/]: Preparing lithostitched.forge.mixins.json (2) -[24nov.2025 23:48:50.193] [main/DEBUG] [mixin/]: Preparing sereneseasonsplus.mixins.json (7) -[24nov.2025 23:48:50.200] [main/DEBUG] [mixin/]: Preparing crackerslib.mixins.json (2) -[24nov.2025 23:48:50.203] [main/DEBUG] [mixin/]: Preparing mixinextras.init.mixins.json (0) -[24nov.2025 23:48:50.203] [main/DEBUG] [mixin/]: Preparing glitchcore.mixins.json (4) -[24nov.2025 23:48:50.204] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/item/ItemStack -[24nov.2025 23:48:50.222] [main/DEBUG] [mixin/]: Preparing glitchcore.forge.mixins.json (7) -[24nov.2025 23:48:50.229] [main/DEBUG] [mixin/]: Preparing sereneseasons.mixins.json (6) -[24nov.2025 23:48:50.230] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/biome/Biome -[24nov.2025 23:48:50.245] [main/DEBUG] [mixin/]: Preparing sereneseasons.forge.mixins.json (0) -[24nov.2025 23:48:50.245] [main/DEBUG] [mixin/]: Preparing simpleclouds.mixins.json (20) -[24nov.2025 23:48:50.267] [main/WARN] [mixin/]: Error loading class: org/vivecraft/client_vr/provider/VRRenderer (java.lang.ClassNotFoundException: org.vivecraft.client_vr.provider.VRRenderer) -[24nov.2025 23:48:50.267] [main/DEBUG] [mixin/]: Skipping virtual target org.vivecraft.client_vr.provider.VRRenderer for simpleclouds.mixins.json:vivecraft.MixinVRRenderer -[24nov.2025 23:48:50.267] [main/DEBUG] [mixin/]: Preparing architectury.mixins.json (9) -[24nov.2025 23:48:50.272] [main/DEBUG] [mixin/]: Preparing architectury-common.mixins.json (10) -[24nov.2025 23:48:50.276] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/item/BucketItem -[24nov.2025 23:48:50.279] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/EntityType -[24nov.2025 23:48:50.296] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/block/LiquidBlock -[24nov.2025 23:48:50.299] [main/DEBUG] [mixin/]: Preparing gaboulibs.mixins.json (0) -[24nov.2025 23:48:50.300] [main/DEBUG] [mixin/]: Preparing auroras.mixins.json (3) -[24nov.2025 23:48:50.301] [main/WARN] [mixin/]: Error loading class: com/aetherteam/aether/client/renderer/level/AetherSkyRenderEffects (java.lang.ClassNotFoundException: com.aetherteam.aether.client.renderer.level.AetherSkyRenderEffects) -[24nov.2025 23:48:50.301] [main/WARN] [mixin/]: @Mixin target com.aetherteam.aether.client.renderer.level.AetherSkyRenderEffects was not found auroras.mixins.json:client.AetherSkyRenderEffectsMixin -[24nov.2025 23:48:50.301] [main/DEBUG] [mixin/]: Preparing rainbows.mixins.json (0) -[24nov.2025 23:48:50.301] [main/DEBUG] [mixin/]: Preparing embeddium.mixins.json (0) -[24nov.2025 23:48:50.301] [main/DEBUG] [mixin/]: Preparing chloride.mixin.json (32) -[24nov.2025 23:48:50.315] [main/WARN] [mixin/]: Error loading class: dev/emi/emi/screen/EmiScreenManager (java.lang.ClassNotFoundException: dev.emi.emi.screen.EmiScreenManager) -[24nov.2025 23:48:50.315] [main/DEBUG] [mixin/]: Skipping virtual target dev.emi.emi.screen.EmiScreenManager for chloride.mixin.json:jei_rei_emi.EmiOverlayMixin -[24nov.2025 23:48:50.317] [main/WARN] [mixin/]: Error loading class: me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl (java.lang.ClassNotFoundException: me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl) -[24nov.2025 23:48:50.317] [main/DEBUG] [mixin/]: Skipping virtual target me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl for chloride.mixin.json:jei_rei_emi.ReiOverlayMixin -[24nov.2025 23:48:50.317] [main/DEBUG] [mixin/]: Preparing tectonic.mixins.json (13) -[24nov.2025 23:48:50.325] [main/DEBUG] [mixin/]: Preparing tectonic_1.20.1.mixins.json (3) -[24nov.2025 23:48:50.347] [main/DEBUG] [mixin/]: Inner class glitchcore/forge/mixin/impl/MixinPacketHandler$1 in glitchcore/forge/mixin/impl/MixinPacketHandler on glitchcore/network/PacketHandler gets unique name glitchcore/network/PacketHandler$Anonymous$fe06fa1677ef4bf39dbb3409f7251026 -[24nov.2025 23:48:50.436] [main/DEBUG] [mixin/]: Prepared 143 mixins in 1,014 sec (7,1ms avg) (0ms load, 0ms transform, 0ms plugin) -[24nov.2025 23:48:50.512] [main/DEBUG] [io.netty.util.internal.logging.InternalLoggerFactory/]: Using SLF4J as the default logging framework -[24nov.2025 23:48:50.521] [main/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.level: simple -[24nov.2025 23:48:50.521] [main/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.targetRecords: 4 -[24nov.2025 23:48:50.741] [main/INFO] [net.minecraftforge.data.loading.DatagenModLoader/]: Initializing Data Gatherer for mods [projectatmosphere] -[24nov.2025 23:48:50.752] [main/INFO] [MixinExtras|Service/]: Initializing MixinExtras via com.llamalad7.mixinextras.service.MixinExtrasServiceImpl(version=0.3.5). -[24nov.2025 23:48:50.753] [main/DEBUG] [mixin/]: Registering new injector for @SugarWrapper with com.llamalad7.mixinextras.sugar.impl.SugarWrapperInjectionInfo -[24nov.2025 23:48:50.754] [main/DEBUG] [mixin/]: Registering new injector for @FactoryRedirectWrapper with com.llamalad7.mixinextras.wrapper.factory.FactoryRedirectWrapperInjectionInfo -[24nov.2025 23:48:50.757] [main/DEBUG] [mixin/]: Mixing common.MappedRegistryAccessor from lithostitched.mixins.json into net.minecraft.core.MappedRegistry -[24nov.2025 23:48:50.834] [main/DEBUG] [mixin/]: Mixing inject.MixinGameEvent from architectury-common.mixins.json into net.minecraft.world.level.gameevent.GameEvent -[24nov.2025 23:48:50.842] [main/DEBUG] [mixin/]: Mixing common.HolderReferenceAccessor from lithostitched.mixins.json into net.minecraft.core.Holder$Reference -[24nov.2025 23:48:50.868] [main/DEBUG] [mixin/]: Mixing inject.MixinBlock from architectury-common.mixins.json into net.minecraft.world.level.block.Block -[24nov.2025 23:48:50.887] [main/DEBUG] [mixin/]: Mixing MixinBlockStateBase from sereneseasons.mixins.json into net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase -[24nov.2025 23:48:50.926] [main/DEBUG] [mixin/]: Mixing MixinLevel from sereneseasons.mixins.json into net.minecraft.world.level.Level -[24nov.2025 23:48:50.928] [main/DEBUG] [mixin/]: Mixing MixinLevel from simpleclouds.mixins.json into net.minecraft.world.level.Level -[24nov.2025 23:48:50.945] [main/DEBUG] [mixin/]: Mixing ServerLevelSnowStormMixin from projectatmosphere.mixins.json into net.minecraft.server.level.ServerLevel -[24nov.2025 23:48:50.947] [main/DEBUG] [mixin/]: Mixing ServerLevelMixin from sereneseasonsplus.mixins.json into net.minecraft.server.level.ServerLevel -[24nov.2025 23:48:50.970] [main/DEBUG] [mixin/]: Mixing MixinServerLevel from glitchcore.mixins.json into net.minecraft.server.level.ServerLevel -[24nov.2025 23:48:50.971] [main/DEBUG] [mixin/]: Mixing MixinServerLevel from sereneseasons.mixins.json into net.minecraft.server.level.ServerLevel -[24nov.2025 23:48:50.971] [main/DEBUG] [mixin/]: Mixing MixinServerLevel from simpleclouds.mixins.json into net.minecraft.server.level.ServerLevel -[24nov.2025 23:48:50.971] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$createCloudManager_init$1()Ldev/nonamecrackers2/simpleclouds/common/world/CloudData; to mdd47aa8$lambda$simpleclouds$createCloudManager_init$1$0 in simpleclouds.mixins.json:MixinServerLevel -[24nov.2025 23:48:50.972] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$createCloudManager_init$0(Lnet/minecraft/nbt/CompoundTag;)Ldev/nonamecrackers2/simpleclouds/common/world/CloudData; to mdd47aa8$lambda$simpleclouds$createCloudManager_init$0$1 in simpleclouds.mixins.json:MixinServerLevel -[24nov.2025 23:48:50.975] [main/DEBUG] [mixin/]: Mixing MixinServerLevelAccessor from simpleclouds.mixins.json into net.minecraft.server.level.ServerLevel -[24nov.2025 23:48:51.085] [main/DEBUG] [mixin/]: Mixing inject.MixinFluid from architectury-common.mixins.json into net.minecraft.world.level.material.Fluid -[24nov.2025 23:48:51.093] [main/DEBUG] [mixin/]: Mixing inject.MixinItem from architectury-common.mixins.json into net.minecraft.world.item.Item -[24nov.2025 23:48:51.112] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/EntityType -[24nov.2025 23:48:51.121] [main/DEBUG] [mixin/]: Mixing inject.MixinEntityType from architectury-common.mixins.json into net.minecraft.world.entity.EntityType -[24nov.2025 23:48:51.122] [main/DEBUG] [mixin/]: Mixing EntityDistanceCullingMixin$EntityTypeMixin from chloride.mixin.json into net.minecraft.world.entity.EntityType -[24nov.2025 23:48:51.152] [main/DEBUG] [mixin/]: Mixing ParticlesMixins$ParticleTypeMixin from chloride.mixin.json into net.minecraft.core.particles.ParticleType -[24nov.2025 23:48:51.155] [main/DEBUG] [mixin/]: Mixing MixinBlockEntityType from crackerslib.mixins.json into net.minecraft.world.level.block.entity.BlockEntityType -[24nov.2025 23:48:51.157] [main/DEBUG] [mixin/]: Mixing EntityDistanceCullingMixin$TileEntityTypeMixin from chloride.mixin.json into net.minecraft.world.level.block.entity.BlockEntityType -[24nov.2025 23:48:51.205] [main/DEBUG] [mixin/]: Mixing WorldCarverMixin from tectonic.mixins.json into net.minecraft.world.level.levelgen.carver.WorldCarver -[24nov.2025 23:48:51.282] [main/DEBUG] [mixin/]: Mixing features.render.immediate.DirectionMixin from embeddium.mixins.json into net.minecraft.core.Direction -[24nov.2025 23:48:51.400] [main/DEBUG] [mixin/]: Mixing SpreadingSnowyDirtBlockMixin from sereneseasonsplus.mixins.json into net.minecraft.world.level.block.SpreadingSnowyDirtBlock -[24nov.2025 23:48:51.413] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/block/LiquidBlock -[24nov.2025 23:48:51.414] [main/DEBUG] [mixin/]: Mixing inject.MixinLiquidBlock from architectury-common.mixins.json into net.minecraft.world.level.block.LiquidBlock -[24nov.2025 23:48:51.418] [main/DEBUG] [mixin/]: Mixing leaves_culling.LeavesBlockMixin from chloride.mixin.json into net.minecraft.world.level.block.LeavesBlock -[24nov.2025 23:48:51.418] [main/DEBUG] [mixin/]: Mixing features.options.render_layers.LeavesBlockMixin from embeddium.mixins.json into net.minecraft.world.level.block.LeavesBlock -[24nov.2025 23:48:51.426] [main/WARN] [mixin/]: Method overwrite conflict for skipRendering in embeddium.mixins.json:features.options.render_layers.LeavesBlockMixin, previously written by me.srrapero720.chloride.mixins.impl.leaves_culling.LeavesBlockMixin. Skipping method. -[24nov.2025 23:48:51.426] [main/DEBUG] [mixin/]: Unexpected: Registered method skipRendering(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/Direction;)Z in net.minecraft.world.level.block.LeavesBlock was not merged -[24nov.2025 23:48:51.432] [main/DEBUG] [mixin/]: Mixing FastBlocksMixins$BedMixin from chloride.mixin.json into net.minecraft.world.level.block.BedBlock -[24nov.2025 23:48:51.448] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/block/StairBlock -[24nov.2025 23:48:51.452] [main/DEBUG] [mixin/]: Mixing FastBlocksMixins$ChestsMixin from chloride.mixin.json into net.minecraft.world.level.block.ChestBlock -[24nov.2025 23:48:51.509] [main/DEBUG] [mixin/]: Mixing FastBlocksMixins$ChestsMixin from chloride.mixin.json into net.minecraft.world.level.block.EnderChestBlock -[24nov.2025 23:48:51.514] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/block/FlowerPotBlock -[24nov.2025 23:48:51.623] [main/DEBUG] [mixin/]: Mixing MixinFallingBlockEntity from architectury.mixins.json into net.minecraft.world.entity.item.FallingBlockEntity -[24nov.2025 23:48:51.991] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/item/ItemStack -[24nov.2025 23:48:51.991] [main/DEBUG] [mixin/]: Mixing MixinItemStack from glitchcore.mixins.json into net.minecraft.world.item.ItemStack -[24nov.2025 23:48:52.283] [main/DEBUG] [mixin/]: Mixing MixinLightningBolt from architectury-common.mixins.json into net.minecraft.world.entity.LightningBolt -[24nov.2025 23:48:52.323] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/animal/frog/Tadpole -[24nov.2025 23:48:52.370] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/item/BucketItem -[24nov.2025 23:48:52.370] [main/DEBUG] [mixin/]: Mixing inject.MixinBucketItem from architectury-common.mixins.json into net.minecraft.world.item.BucketItem -[24nov.2025 23:48:52.401] [main/DEBUG] [mixin/]: Mixing inject.MixinItemProperties from architectury-common.mixins.json into net.minecraft.world.item.Item$Properties -[24nov.2025 23:48:52.887] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/monster/Spider -[24nov.2025 23:48:52.937] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/monster/Zombie -[24nov.2025 23:48:52.956] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/monster/ZombieVillager -[24nov.2025 23:48:52.965] [main/DEBUG] [mixin/]: Mixing vanillacompat.MixinThrownTrident from simpleclouds.mixins.json into net.minecraft.world.entity.projectile.ThrownTrident -[24nov.2025 23:48:52.988] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/monster/Evoker$EvokerSummonSpellGoal -[24nov.2025 23:48:53.048] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/animal/horse/SkeletonTrapGoal -[24nov.2025 23:48:53.056] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/monster/Strider -[24nov.2025 23:48:53.109] [main/DEBUG] [mixin/]: Mixing MixinServerPlayer from glitchcore.forge.mixins.json into net.minecraft.server.level.ServerPlayer -[24nov.2025 23:48:53.127] [main/DEBUG] [mixin/]: Mixing ChunkAccessMixin from tectonic.mixins.json into net.minecraft.world.level.chunk.ChunkAccess -[24nov.2025 23:48:53.132] [main/DEBUG] [mixin/]: Mixing LevelChunkSnowMixin from sereneseasonsplus.mixins.json into net.minecraft.world.level.chunk.LevelChunk -[24nov.2025 23:48:53.150] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/npc/Villager -[24nov.2025 23:48:53.218] [main/DEBUG] [mixin/]: Mixing inject.MixinFoodPropertiesBuilder from architectury-common.mixins.json into net.minecraft.world.food.FoodProperties$Builder -[24nov.2025 23:48:53.220] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/effect/MobEffectInstance -[24nov.2025 23:48:53.377] [main/DEBUG] [mixin/]: Mixing HeightmapMixin from tectonic.mixins.json into net.minecraft.world.level.levelgen.Heightmap -[24nov.2025 23:48:53.482] [main/DEBUG] [mixin/]: Mixing darkness.DimensionTypeMixin from chloride.mixin.json into net.minecraft.world.level.dimension.DimensionType -[24nov.2025 23:48:53.483] [main/DEBUG] [mixin/]: Mixing DimensionTypeAccessor from tectonic.mixins.json into net.minecraft.world.level.dimension.DimensionType -[24nov.2025 23:48:53.522] [main/DEBUG] [mixin/]: Mixing common.PlacedFeatureAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.placement.PlacedFeature -[24nov.2025 23:48:53.530] [main/DEBUG] [mixin/]: Mixing common.StructureProcessorListAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList -[24nov.2025 23:48:53.566] [main/DEBUG] [mixin/]: Mixing common.StructureSetAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.StructureSet -[24nov.2025 23:48:53.569] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/Structure -[24nov.2025 23:48:53.574] [main/DEBUG] [mixin/]: Mixing StructurePieceMixin from tectonic.mixins.json into net.minecraft.world.level.levelgen.structure.StructurePiece -[24nov.2025 23:48:53.589] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces$OceanRuinPiece -[24nov.2025 23:48:53.594] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece -[24nov.2025 23:48:53.597] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces$OceanMonumentPiece -[24nov.2025 23:48:53.603] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces$WoodlandMansionPiece -[24nov.2025 23:48:53.606] [main/DEBUG] [mixin/]: Mixing common.ShipwreckPieceMixin from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.structures.ShipwreckPieces$ShipwreckPiece -[24nov.2025 23:48:53.618] [main/DEBUG] [mixin/]: Mixing common.JigsawStructureMixin from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.structures.JigsawStructure -[24nov.2025 23:48:53.619] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/Structure -[24nov.2025 23:48:53.627] [main/DEBUG] [mixin/]: Mixing common.StructureTemplatePoolAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool -[24nov.2025 23:48:53.630] [main/DEBUG] [mixin/]: Mixing common.StructureTemplatePoolMixin from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool -[24nov.2025 23:48:53.631] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$compileRawTemplates$0(Lcom/mojang/datafixers/util/Pair;)V to mdd47aa8$lambda$compileRawTemplates$0$0 in lithostitched.mixins.json:common.StructureTemplatePoolMixin -[24nov.2025 23:48:53.664] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/biome/Biome -[24nov.2025 23:48:53.664] [main/DEBUG] [mixin/]: Mixing features.world.biome.BiomeMixin from embeddium.mixins.json into net.minecraft.world.level.biome.Biome -[24nov.2025 23:48:53.665] [main/DEBUG] [mixin/]: Mixing MixinBiome from sereneseasons.mixins.json into net.minecraft.world.level.biome.Biome -[24nov.2025 23:48:53.666] [main/DEBUG] [mixin/]: Mixing client.MixinBiomeClient from sereneseasons.mixins.json into net.minecraft.world.level.biome.Biome -[24nov.2025 23:48:53.666] [main/DEBUG] [mixin/]: Mixing BiomeMixin from tectonic.mixins.json into net.minecraft.world.level.biome.Biome -[24nov.2025 23:48:53.680] [main/DEBUG] [mixin/]: Mixing features.render.world.ClientLevelMixin from embeddium.mixins.json into net.minecraft.client.multiplayer.ClientLevel -[24nov.2025 23:48:53.681] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$new$0(Lnet/minecraft/world/level/biome/AmbientParticleSettings;)V to mdd47aa8$lambda$new$0$0 in embeddium.mixins.json:features.render.world.ClientLevelMixin -[24nov.2025 23:48:53.682] [main/DEBUG] [mixin/]: Mixing MixinClientLevel from simpleclouds.mixins.json into net.minecraft.client.multiplayer.ClientLevel -[24nov.2025 23:48:53.685] [main/DEBUG] [mixin/]: Mixing MixinClientLevel from architectury.mixins.json into net.minecraft.client.multiplayer.ClientLevel -[24nov.2025 23:48:53.685] [main/DEBUG] [mixin/]: Mixing core.world.biome.ClientWorldMixin from embeddium.mixins.json into net.minecraft.client.multiplayer.ClientLevel -[24nov.2025 23:48:53.685] [main/DEBUG] [mixin/]: Mixing core.world.map.ClientWorldMixin from embeddium.mixins.json into net.minecraft.client.multiplayer.ClientLevel -[24nov.2025 23:48:53.686] [main/DEBUG] [mixin/]: Mixing features.render.world.sky.ClientWorldMixin from embeddium.mixins.json into net.minecraft.client.multiplayer.ClientLevel -[24nov.2025 23:48:53.686] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$redirectSampleColor$0(Lnet/minecraft/world/level/Level;III)I to mdd47aa8$lambda$redirectSampleColor$0$1 in embeddium.mixins.json:features.render.world.sky.ClientWorldMixin -[24nov.2025 23:48:53.715] [main/DEBUG] [mixin/]: Mixing TemperatureModifierMixin from tectonic.mixins.json into net.minecraft.world.level.biome.Biome$TemperatureModifier$2 -[24nov.2025 23:48:53.752] [main/DEBUG] [mixin/]: Mixing NoiseBasedChunkGeneratorMixin from tectonic.mixins.json into net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator -[24nov.2025 23:48:53.753] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$tectonic$fixLavaLevel$2(Lnet/minecraft/core/Holder;)Lnet/minecraft/world/level/levelgen/Aquifer$FluidPicker; to mdd47aa8$lambda$tectonic$fixLavaLevel$2$0 in tectonic.mixins.json:NoiseBasedChunkGeneratorMixin -[24nov.2025 23:48:53.753] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$tectonic$fixLavaLevel$1(Lnet/minecraft/world/level/levelgen/Aquifer$FluidStatus;IILnet/minecraft/world/level/levelgen/Aquifer$FluidStatus;Lnet/minecraft/world/level/levelgen/Aquifer$FluidStatus;III)Lnet/minecraft/world/level/levelgen/Aquifer$FluidStatus; to mdd47aa8$lambda$tectonic$fixLavaLevel$1$1 in tectonic.mixins.json:NoiseBasedChunkGeneratorMixin -[24nov.2025 23:48:53.753] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$tectonic$fixLavaLevel$0(Lnet/minecraft/resources/ResourceKey;)Ljava/lang/Boolean; to mdd47aa8$lambda$tectonic$fixLavaLevel$0$2 in tectonic.mixins.json:NoiseBasedChunkGeneratorMixin -[24nov.2025 23:48:53.765] [main/DEBUG] [mixin/]: Mixing common.NoiseGeneratorSettingsAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.NoiseGeneratorSettings -[24nov.2025 23:48:53.770] [main/DEBUG] [mixin/]: Mixing NoiseSettingsAccessor from tectonic.mixins.json into net.minecraft.world.level.levelgen.NoiseSettings -[24nov.2025 23:48:53.833] [main/DEBUG] [mixin/]: Mixing common.SinglePoolElementAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement -[24nov.2025 23:48:53.882] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Creating vanilla freeze snapshot -[24nov.2025 23:48:53.886] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:block Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.893] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:fluid Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.893] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:item Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.897] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:mob_effect Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.897] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:sound_event Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.900] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:potion Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.900] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:enchantment Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.900] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:entity_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.900] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:block_entity_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.900] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:particle_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.901] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:menu Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.901] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:painting_variant Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.901] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:recipe_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.901] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:recipe_serializer Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.901] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:attribute Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.901] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:stat_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.901] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:command_argument_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.901] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:villager_profession Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.901] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:point_of_interest_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.902] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:memory_module_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.902] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:sensor_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.902] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:schedule Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.902] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:activity Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.902] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/carver Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.903] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/feature Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.903] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:chunk_status Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.903] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/block_state_provider_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.903] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/foliage_placer_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.903] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/tree_decorator_type Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.903] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/biome Sync: VANILLA -> ACTIVE -[24nov.2025 23:48:53.907] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Vanilla freeze snapshot created -[24nov.2025 23:48:53.934] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: -Dio.netty.noUnsafe: false -[24nov.2025 23:48:53.935] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: Java version: 17 -[24nov.2025 23:48:53.935] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: sun.misc.Unsafe.theUnsafe: available -[24nov.2025 23:48:53.936] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: sun.misc.Unsafe.copyMemory: available -[24nov.2025 23:48:53.936] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: sun.misc.Unsafe.storeFence: available -[24nov.2025 23:48:53.936] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: java.nio.Buffer.address: available -[24nov.2025 23:48:53.936] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: direct buffer constructor: unavailable +[12mai2026 16:10:21.587] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher running: args [--launchTarget, forgedatauserdev, --assetIndex, 5, --assetsDir, C:\Users\matga\.gradle\caches\forge_gradle\assets, --gameDir, ., --fml.forgeVersion, 47.4.13, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412, --mod, projectatmosphere, --all, --output, G:\Project-Atmosphere\src\generated\resources, --existing, G:\Project-Atmosphere\src\main\resources, --mixin.config, projectatmosphere.mixins.json] +[12mai2026 16:10:21.591] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.17 by Eclipse Adoptium; OS Windows 11 arch amd64 version 10.0 +[12mai2026 16:10:21.783] [main/DEBUG] [cpw.mods.modlauncher.LaunchServiceHandler/MODLAUNCHER]: Found launch services [fmlclientdev,forgeclient,minecraft,forgegametestserverdev,fmlserveruserdev,fmlclient,fmldatauserdev,forgeserverdev,forgeserveruserdev,forgeclientdev,forgeclientuserdev,forgeserver,forgedatadev,fmlserver,fmlclientuserdev,fmlserverdev,forgedatauserdev,testharness,forgegametestserveruserdev] +[12mai2026 16:10:21.823] [main/DEBUG] [cpw.mods.modlauncher.NameMappingServiceHandler/MODLAUNCHER]: Found naming services : [srgtomcp] +[12mai2026 16:10:21.857] [main/DEBUG] [cpw.mods.modlauncher.LaunchPluginHandler/MODLAUNCHER]: Found launch plugins: [mixin,eventbus,slf4jfixer,object_holder_definalize,runtime_enum_extender,capability_token_subclass,accesstransformer,runtimedistcleaner] +[12mai2026 16:10:21.866] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Discovering transformation services +[12mai2026 16:10:21.871] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path GAMEDIR is G:\Project-Atmosphere\run-data +[12mai2026 16:10:21.871] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path MODSDIR is G:\Project-Atmosphere\run-data\mods +[12mai2026 16:10:21.871] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path CONFIGDIR is G:\Project-Atmosphere\run-data\config +[12mai2026 16:10:21.872] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path FMLCONFIG is G:\Project-Atmosphere\run-data\config\fml.toml +[12mai2026 16:10:22.000] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Found additional transformation services from discovery services: +[12mai2026 16:10:22.009] [main/INFO] [net.minecraftforge.fml.loading.ImmediateWindowHandler/]: ImmediateWindowProvider not loading because launch target is forgedatauserdev +[12mai2026 16:10:22.023] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Found transformer services : [mixin,fml] +[12mai2026 16:10:22.054] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Transformation services loading +[12mai2026 16:10:22.055] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Loading service mixin +[12mai2026 16:10:22.055] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Loaded service mixin +[12mai2026 16:10:22.055] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Loading service fml +[12mai2026 16:10:22.057] [main/DEBUG] [net.minecraftforge.fml.loading.LauncherVersion/CORE]: Found FMLLauncher version 1.0 +[12mai2026 16:10:22.057] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: FML 1.0 loading +[12mai2026 16:10:22.058] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: FML found ModLauncher version : 10.0.9+10.0.9+main.dcd20f30 +[12mai2026 16:10:22.058] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Requesting CoreMods to not apply the fix for ASMAPI.findFirstInstructionBefore by default +[12mai2026 16:10:22.059] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: FML found AccessTransformer version : 8.0.4+66+master.c09db6d7 +[12mai2026 16:10:22.061] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: FML found EventBus version : 6.0.5+6.0.5+master.eb8e549b +[12mai2026 16:10:22.061] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Found Runtime Dist Cleaner +[12mai2026 16:10:22.064] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/]: CoreMods will preserve legacy behavior of ASMAPI.findFirstInstructionBefore for backwards-compatibility +[12mai2026 16:10:22.065] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: FML found CoreMod version : 5.2.4 +[12mai2026 16:10:22.066] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Found ForgeSPI package implementation version 7.0.1+7.0.1+master.d2b38bf6 +[12mai2026 16:10:22.066] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Found ForgeSPI package specification 5 +[12mai2026 16:10:22.066] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Loaded service fml +[12mai2026 16:10:22.067] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Configuring option handling for services +[12mai2026 16:10:22.090] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Transformation services initializing +[12mai2026 16:10:22.093] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initializing transformation service mixin +[12mai2026 16:10:22.119] [main/DEBUG] [mixin/]: MixinService [ModLauncher] was successfully booted in cpw.mods.cl.ModuleClassLoader@5e82df6a +[12mai2026 16:10:22.144] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/C:/Users/matga/.gradle/caches/modules-2/files-2.1/org.spongepowered/mixin/0.8.5/9d1c0c3a304ae6697ecd477218fa61b850bf57fc/mixin-0.8.5.jar%23129!/ Service=ModLauncher Env=CLIENT +[12mai2026 16:10:22.149] [main/DEBUG] [mixin/]: Initialising Mixin Platform Manager +[12mai2026 16:10:22.150] [main/DEBUG] [mixin/]: Adding mixin platform agents for container ModLauncher Root Container(ModLauncher:4f56a0a2) +[12mai2026 16:10:22.150] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for ModLauncher Root Container(ModLauncher:4f56a0a2) +[12mai2026 16:10:22.151] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container ModLauncher Root Container(ModLauncher:4f56a0a2) +[12mai2026 16:10:22.152] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for ModLauncher Root Container(ModLauncher:4f56a0a2) +[12mai2026 16:10:22.152] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container ModLauncher Root Container(ModLauncher:4f56a0a2) +[12mai2026 16:10:22.163] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initialized transformation service mixin +[12mai2026 16:10:22.164] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initializing transformation service fml +[12mai2026 16:10:22.164] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Setting up basic FML game directories +[12mai2026 16:10:22.164] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path GAMEDIR is G:\Project-Atmosphere\run-data +[12mai2026 16:10:22.165] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path MODSDIR is G:\Project-Atmosphere\run-data\mods +[12mai2026 16:10:22.165] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path CONFIGDIR is G:\Project-Atmosphere\run-data\config +[12mai2026 16:10:22.165] [main/DEBUG] [net.minecraftforge.fml.loading.FMLPaths/CORE]: Path FMLCONFIG is G:\Project-Atmosphere\run-data\config\fml.toml +[12mai2026 16:10:22.165] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Loading configuration +[12mai2026 16:10:22.169] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Preparing ModFile +[12mai2026 16:10:22.173] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Preparing launch handler +[12mai2026 16:10:22.173] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Using forgedatauserdev as launch service +[12mai2026 16:10:22.187] [main/DEBUG] [net.minecraftforge.fml.loading.FMLLoader/CORE]: Received command line version data : VersionInfo[forgeVersion=47.4.13, mcVersion=1.20.1, mcpVersion=20230612.114412, forgeGroup=net.minecraftforge] +[12mai2026 16:10:22.189] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initialized transformation service fml +[12mai2026 16:10:22.190] [main/DEBUG] [cpw.mods.modlauncher.NameMappingServiceHandler/MODLAUNCHER]: Current naming domain is 'mcp' +[12mai2026 16:10:22.191] [main/DEBUG] [cpw.mods.modlauncher.NameMappingServiceHandler/MODLAUNCHER]: Identified name mapping providers {srg=srgtomcp:1234} +[12mai2026 16:10:22.191] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Transformation services begin scanning +[12mai2026 16:10:22.193] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Beginning scan trigger - transformation service mixin +[12mai2026 16:10:22.193] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: End scan trigger - transformation service mixin +[12mai2026 16:10:22.194] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Beginning scan trigger - transformation service fml +[12mai2026 16:10:22.194] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Initiating mod scan +[12mai2026 16:10:22.210] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModListHandler/CORE]: Found mod coordinates from lists: [] +[12mai2026 16:10:22.215] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer/CORE]: Found Mod Locators : (mods folder:null),(maven libs:null),(exploded directory:null),(minecraft:null),(userdev classpath:null) +[12mai2026 16:10:22.216] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer/CORE]: Found Dependency Locators : (JarInJar:null) +[12mai2026 16:10:22.225] [main/DEBUG] [net.minecraftforge.fml.loading.targets.CommonLaunchHandler/CORE]: Got mod coordinates projectatmosphere%%G:\Project-Atmosphere\build\resources\main;projectatmosphere%%G:\Project-Atmosphere\build\classes\java\main from env +[12mai2026 16:10:22.227] [main/DEBUG] [net.minecraftforge.fml.loading.targets.CommonLaunchHandler/CORE]: Found supplied mod coordinates [{projectatmosphere=[G:\Project-Atmosphere\build\resources\main, G:\Project-Atmosphere\build\classes\java\main]}] +[12mai2026 16:10:22.494] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar with {minecraft} mods - versions {1.20.1} +[12mai2026 16:10:22.498] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\javafmllanguage\1.20.1-47.4.13\cf01e2179f72bd2829c65f5226d7dd0cfb6c686\javafmllanguage-1.20.1-47.4.13.jar +[12mai2026 16:10:22.499] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\javafmllanguage\1.20.1-47.4.13\cf01e2179f72bd2829c65f5226d7dd0cfb6c686\javafmllanguage-1.20.1-47.4.13.jar is missing mods.toml file +[12mai2026 16:10:22.501] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\lowcodelanguage\1.20.1-47.4.13\e3f96129ea14eba84316f2d69816253e228e6e4c\lowcodelanguage-1.20.1-47.4.13.jar +[12mai2026 16:10:22.501] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\lowcodelanguage\1.20.1-47.4.13\e3f96129ea14eba84316f2d69816253e228e6e4c\lowcodelanguage-1.20.1-47.4.13.jar is missing mods.toml file +[12mai2026 16:10:22.504] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\mclanguage\1.20.1-47.4.13\85796906112d9b621a7b2996f8bfda49da991ef0\mclanguage-1.20.1-47.4.13.jar +[12mai2026 16:10:22.505] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\mclanguage\1.20.1-47.4.13\85796906112d9b621a7b2996f8bfda49da991ef0\mclanguage-1.20.1-47.4.13.jar is missing mods.toml file +[12mai2026 16:10:22.507] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\fmlcore\1.20.1-47.4.13\f77e3021d1eb31d61f8d1b92d18ed4fb3c2806e5\fmlcore-1.20.1-47.4.13.jar +[12mai2026 16:10:22.508] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\fmlcore\1.20.1-47.4.13\f77e3021d1eb31d61f8d1b92d18ed4fb3c2806e5\fmlcore-1.20.1-47.4.13.jar is missing mods.toml file +[12mai2026 16:10:22.510] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate G:\Project-Atmosphere\build\resources\main +[12mai2026 16:10:22.517] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file main with {projectatmosphere} mods - versions {0.9.0.0} +[12mai2026 16:10:22.518] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate / +[12mai2026 16:10:22.522] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file with {forge} mods - versions {47.4.13} +[12mai2026 16:10:22.550] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate G:\Project-Atmosphere\libs\tectonic-3.0.17-forge-1.20.1-dev.jar +[12mai2026 16:10:22.551] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file tectonic-3.0.17-forge-1.20.1-dev.jar with {tectonic} mods - versions {3.0.17} +[12mai2026 16:10:22.553] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\crackerslib-forge\1.20.1-0.4.6_mapped_official_1.20.1\crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar +[12mai2026 16:10:22.554] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar with {crackerslib} mods - versions {1.20.1-0.4.6} +[12mai2026 16:10:22.557] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\betterdays-895618\7013807_mapped_official_1.20.1\betterdays-895618-7013807_mapped_official_1.20.1.jar +[12mai2026 16:10:22.558] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file betterdays-895618-7013807_mapped_official_1.20.1.jar with {betterdays} mods - versions {3.3.4.4} +[12mai2026 16:10:22.562] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-plus-1288843\7325179_mapped_official_1.20.1\serene-seasons-plus-1288843-7325179_mapped_official_1.20.1.jar +[12mai2026 16:10:22.562] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-plus-1288843-7325179_mapped_official_1.20.1.jar with {sereneseasonsplus} mods - versions {4.2.2} +[12mai2026 16:10:22.564] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\gabous-libs-1367332\7519775_mapped_official_1.20.1\gabous-libs-1367332-7519775_mapped_official_1.20.1.jar +[12mai2026 16:10:22.566] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file gabous-libs-1367332-7519775_mapped_official_1.20.1.jar with {gaboulibs} mods - versions {1.7.1} +[12mai2026 16:10:22.568] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\architectury-api-419699\5137938_mapped_official_1.20.1\architectury-api-419699-5137938_mapped_official_1.20.1.jar +[12mai2026 16:10:22.568] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file architectury-api-419699-5137938_mapped_official_1.20.1.jar with {architectury} mods - versions {9.2.14} +[12mai2026 16:10:22.570] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\tectonic-686836\7197834_mapped_official_1.20.1\tectonic-686836-7197834_mapped_official_1.20.1.jar +[12mai2026 16:10:22.571] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file tectonic-686836-7197834_mapped_official_1.20.1.jar with {tectonic} mods - versions {3.0.17} +[12mai2026 16:10:22.574] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\lithostitched-936015\6742615_mapped_official_1.20.1\lithostitched-936015-6742615_mapped_official_1.20.1.jar +[12mai2026 16:10:22.575] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file lithostitched-936015-6742615_mapped_official_1.20.1.jar with {lithostitched} mods - versions {1.4.11} +[12mai2026 16:10:22.577] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\simple-clouds-1121215\6928978_mapped_official_1.20.1\simple-clouds-1121215-6928978_mapped_official_1.20.1.jar +[12mai2026 16:10:22.578] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file simple-clouds-1121215-6928978_mapped_official_1.20.1.jar with {simpleclouds} mods - versions {0.7.3+1.20.1-forge} +[12mai2026 16:10:22.581] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\distant-horizons-508933\7948170_mapped_official_1.20.1\distant-horizons-508933-7948170_mapped_official_1.20.1.jar +[12mai2026 16:10:22.583] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file distant-horizons-508933-7948170_mapped_official_1.20.1.jar with {distanthorizons} mods - versions {3.0.1-b} +[12mai2026 16:10:22.585] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-291874\6398227_mapped_official_1.20.1\serene-seasons-291874-6398227_mapped_official_1.20.1.jar +[12mai2026 16:10:22.586] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-291874-6398227_mapped_official_1.20.1.jar with {sereneseasons} mods - versions {9.1.0.2} +[12mai2026 16:10:22.588] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\glitchcore-955399\5787839_mapped_official_1.20.1\glitchcore-955399-5787839_mapped_official_1.20.1.jar +[12mai2026 16:10:22.589] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file glitchcore-955399-5787839_mapped_official_1.20.1.jar with {glitchcore} mods - versions {0.0.1.1} +[12mai2026 16:10:22.593] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\spark-361579\4738952_mapped_official_1.20.1\spark-361579-4738952_mapped_official_1.20.1.jar +[12mai2026 16:10:22.593] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file spark-361579-4738952_mapped_official_1.20.1.jar with {spark} mods - versions {1.10.53} +[12mai2026 16:10:22.596] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\cloth-config-348521\5729105_mapped_official_1.20.1\cloth-config-348521-5729105_mapped_official_1.20.1.jar +[12mai2026 16:10:22.596] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file cloth-config-348521-5729105_mapped_official_1.20.1.jar with {cloth_config} mods - versions {11.1.136} +[12mai2026 16:10:22.599] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\chloride-931925\6615986_mapped_official_1.20.1\chloride-931925-6615986_mapped_official_1.20.1.jar +[12mai2026 16:10:22.599] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file chloride-931925-6615986_mapped_official_1.20.1.jar with {chloride} mods - versions {1.7.2} +[12mai2026 16:10:22.602] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\embeddium-908741\5681725_mapped_official_1.20.1\embeddium-908741-5681725_mapped_official_1.20.1.jar +[12mai2026 16:10:22.602] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file embeddium-908741-5681725_mapped_official_1.20.1.jar with {embeddium,rubidium} mods - versions {0.3.31+mc1.20.1,0.7.1} +[12mai2026 16:10:22.605] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\mezz\jei\jei-1.20.1-forge\15.8.2.26_mapped_official_1.20.1\jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar +[12mai2026 16:10:22.606] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar with {jei} mods - versions {15.8.2.26} +[12mai2026 16:10:22.609] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\jade-324717\6271651_mapped_official_1.20.1\jade-324717-6271651_mapped_official_1.20.1.jar +[12mai2026 16:10:22.612] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jade-324717-6271651_mapped_official_1.20.1.jar with {jade} mods - versions {11.13.1+forge} +[12mai2026 16:10:22.614] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate G:\Project-Atmosphere\libs\tectonic-3.0.17-forge-1.20.1-dev.jar +[12mai2026 16:10:22.614] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file tectonic-3.0.17-forge-1.20.1-dev.jar with {tectonic} mods - versions {3.0.17} +[12mai2026 16:10:22.616] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\crackerslib-forge\1.20.1-0.4.6_mapped_official_1.20.1\crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar +[12mai2026 16:10:22.617] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar with {crackerslib} mods - versions {1.20.1-0.4.6} +[12mai2026 16:10:22.625] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\betterdays-895618\7013807_mapped_official_1.20.1\betterdays-895618-7013807_mapped_official_1.20.1.jar +[12mai2026 16:10:22.626] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file betterdays-895618-7013807_mapped_official_1.20.1.jar with {betterdays} mods - versions {3.3.4.4} +[12mai2026 16:10:22.628] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-plus-1288843\7325179_mapped_official_1.20.1\serene-seasons-plus-1288843-7325179_mapped_official_1.20.1.jar +[12mai2026 16:10:22.629] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-plus-1288843-7325179_mapped_official_1.20.1.jar with {sereneseasonsplus} mods - versions {4.2.2} +[12mai2026 16:10:22.631] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\gabous-libs-1367332\7519775_mapped_official_1.20.1\gabous-libs-1367332-7519775_mapped_official_1.20.1.jar +[12mai2026 16:10:22.631] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file gabous-libs-1367332-7519775_mapped_official_1.20.1.jar with {gaboulibs} mods - versions {1.7.1} +[12mai2026 16:10:22.633] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\architectury-api-419699\5137938_mapped_official_1.20.1\architectury-api-419699-5137938_mapped_official_1.20.1.jar +[12mai2026 16:10:22.634] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file architectury-api-419699-5137938_mapped_official_1.20.1.jar with {architectury} mods - versions {9.2.14} +[12mai2026 16:10:22.636] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\tectonic-686836\7197834_mapped_official_1.20.1\tectonic-686836-7197834_mapped_official_1.20.1.jar +[12mai2026 16:10:22.636] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file tectonic-686836-7197834_mapped_official_1.20.1.jar with {tectonic} mods - versions {3.0.17} +[12mai2026 16:10:22.638] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\lithostitched-936015\6742615_mapped_official_1.20.1\lithostitched-936015-6742615_mapped_official_1.20.1.jar +[12mai2026 16:10:22.638] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file lithostitched-936015-6742615_mapped_official_1.20.1.jar with {lithostitched} mods - versions {1.4.11} +[12mai2026 16:10:22.640] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\simple-clouds-1121215\6928978_mapped_official_1.20.1\simple-clouds-1121215-6928978_mapped_official_1.20.1.jar +[12mai2026 16:10:22.641] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file simple-clouds-1121215-6928978_mapped_official_1.20.1.jar with {simpleclouds} mods - versions {0.7.3+1.20.1-forge} +[12mai2026 16:10:22.643] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\distant-horizons-508933\7948170_mapped_official_1.20.1\distant-horizons-508933-7948170_mapped_official_1.20.1.jar +[12mai2026 16:10:22.644] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file distant-horizons-508933-7948170_mapped_official_1.20.1.jar with {distanthorizons} mods - versions {3.0.1-b} +[12mai2026 16:10:22.645] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-291874\6398227_mapped_official_1.20.1\serene-seasons-291874-6398227_mapped_official_1.20.1.jar +[12mai2026 16:10:22.646] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-291874-6398227_mapped_official_1.20.1.jar with {sereneseasons} mods - versions {9.1.0.2} +[12mai2026 16:10:22.647] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\glitchcore-955399\5787839_mapped_official_1.20.1\glitchcore-955399-5787839_mapped_official_1.20.1.jar +[12mai2026 16:10:22.648] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file glitchcore-955399-5787839_mapped_official_1.20.1.jar with {glitchcore} mods - versions {0.0.1.1} +[12mai2026 16:10:22.652] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\spark-361579\4738952_mapped_official_1.20.1\spark-361579-4738952_mapped_official_1.20.1.jar +[12mai2026 16:10:22.652] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file spark-361579-4738952_mapped_official_1.20.1.jar with {spark} mods - versions {1.10.53} +[12mai2026 16:10:22.653] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\cloth-config-348521\5729105_mapped_official_1.20.1\cloth-config-348521-5729105_mapped_official_1.20.1.jar +[12mai2026 16:10:22.655] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file cloth-config-348521-5729105_mapped_official_1.20.1.jar with {cloth_config} mods - versions {11.1.136} +[12mai2026 16:10:22.657] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\chloride-931925\6615986_mapped_official_1.20.1\chloride-931925-6615986_mapped_official_1.20.1.jar +[12mai2026 16:10:22.657] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file chloride-931925-6615986_mapped_official_1.20.1.jar with {chloride} mods - versions {1.7.2} +[12mai2026 16:10:22.659] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\embeddium-908741\5681725_mapped_official_1.20.1\embeddium-908741-5681725_mapped_official_1.20.1.jar +[12mai2026 16:10:22.660] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file embeddium-908741-5681725_mapped_official_1.20.1.jar with {embeddium,rubidium} mods - versions {0.3.31+mc1.20.1,0.7.1} +[12mai2026 16:10:22.662] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\mezz\jei\jei-1.20.1-forge\15.8.2.26_mapped_official_1.20.1\jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar +[12mai2026 16:10:22.663] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar with {jei} mods - versions {15.8.2.26} +[12mai2026 16:10:22.665] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\jade-324717\6271651_mapped_official_1.20.1\jade-324717-6271651_mapped_official_1.20.1.jar +[12mai2026 16:10:22.665] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jade-324717-6271651_mapped_official_1.20.1.jar with {jade} mods - versions {11.13.1+forge} +[12mai2026 16:10:22.669] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid sereneseasons, selecting most recent based on version data +[12mai2026 16:10:22.669] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file serene-seasons-291874-6398227_mapped_official_1.20.1.jar for modid sereneseasons with version 9.1.0.2 +[12mai2026 16:10:22.669] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid betterdays, selecting most recent based on version data +[12mai2026 16:10:22.669] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file betterdays-895618-7013807_mapped_official_1.20.1.jar for modid betterdays with version 3.3.4.4 +[12mai2026 16:10:22.669] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid crackerslib, selecting most recent based on version data +[12mai2026 16:10:22.669] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar for modid crackerslib with version 1.20.1-0.4.6 +[12mai2026 16:10:22.669] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid glitchcore, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file glitchcore-955399-5787839_mapped_official_1.20.1.jar for modid glitchcore with version 0.0.1.1 +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid jade, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file jade-324717-6271651_mapped_official_1.20.1.jar for modid jade with version 11.13.1+forge +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid simpleclouds, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file simple-clouds-1121215-6928978_mapped_official_1.20.1.jar for modid simpleclouds with version 0.7.3+1.20.1-forge +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid architectury, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file architectury-api-419699-5137938_mapped_official_1.20.1.jar for modid architectury with version 9.2.14 +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid distanthorizons, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file distant-horizons-508933-7948170_mapped_official_1.20.1.jar for modid distanthorizons with version 3.0.1-b +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid jei, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar for modid jei with version 15.8.2.26 +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid lithostitched, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file lithostitched-936015-6742615_mapped_official_1.20.1.jar for modid lithostitched with version 1.4.11 +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid cloth_config, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file cloth-config-348521-5729105_mapped_official_1.20.1.jar for modid cloth_config with version 11.1.136 +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid spark, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file spark-361579-4738952_mapped_official_1.20.1.jar for modid spark with version 1.10.53 +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid gaboulibs, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file gabous-libs-1367332-7519775_mapped_official_1.20.1.jar for modid gaboulibs with version 1.7.1 +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid chloride, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file chloride-931925-6615986_mapped_official_1.20.1.jar for modid chloride with version 1.7.2 +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid embeddium, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file embeddium-908741-5681725_mapped_official_1.20.1.jar for modid embeddium with version 0.3.31+mc1.20.1 +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid sereneseasonsplus, selecting most recent based on version data +[12mai2026 16:10:22.670] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file serene-seasons-plus-1288843-7325179_mapped_official_1.20.1.jar for modid sereneseasonsplus with version 4.2.2 +[12mai2026 16:10:22.671] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 4 mods for first modid tectonic, selecting most recent based on version data +[12mai2026 16:10:22.671] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file tectonic-3.0.17-forge-1.20.1-dev.jar for modid tectonic with version 3.0.17 +[12mai2026 16:10:22.674] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from serene-seasons-291874-6398227_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.674] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar, it does not contain dependency information. +[12mai2026 16:10:22.674] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.674] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from glitchcore-955399-5787839_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.675] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from jade-324717-6271651_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.675] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from architectury-api-419699-5137938_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.675] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from distant-horizons-508933-7948170_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.675] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.675] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from lithostitched-936015-6742615_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.675] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from cloth-config-348521-5729105_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.675] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from spark-361579-4738952_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.675] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from gabous-libs-1367332-7519775_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.676] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from , it does not contain dependency information. +[12mai2026 16:10:22.676] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from simplecloudsapi-forge-1.4-1.20.1_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.676] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from chloride-931925-6615986_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.676] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from main, it does not contain dependency information. +[12mai2026 16:10:22.676] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from serene-seasons-plus-1288843-7325179_mapped_official_1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.676] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from tectonic-3.0.17-forge-1.20.1-dev.jar, it does not contain dependency information. +[12mai2026 16:10:22.676] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from mclanguage-1.20.1-47.4.13.jar, it does not contain dependency information. +[12mai2026 16:10:22.676] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from javafmllanguage-1.20.1-47.4.13.jar, it does not contain dependency information. +[12mai2026 16:10:22.676] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from fmlcore-1.20.1-47.4.13.jar, it does not contain dependency information. +[12mai2026 16:10:22.676] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from lowcodelanguage-1.20.1-47.4.13.jar, it does not contain dependency information. +[12mai2026 16:10:22.718] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate +[12mai2026 16:10:22.720] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file crackerslib-forge-1.20.1-0.4.6.jar with {crackerslib} mods - versions {1.20.1-0.4.6} +[12mai2026 16:10:22.725] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate +[12mai2026 16:10:22.725] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file whitenoise-1.20.1-forge-1.0.0.jar with {whitenoise} mods - versions {1.0.0} +[12mai2026 16:10:22.727] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate +[12mai2026 16:10:22.728] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file mixinextras-forge-0.3.5.jar with {mixinextras} mods - versions {0.3.5} +[12mai2026 16:10:22.728] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from crackerslib-forge-1.20.1-0.4.6.jar, it does not contain dependency information. +[12mai2026 16:10:22.728] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from simplecloudsapi-forge-1.4-1.20.1.jar, it does not contain dependency information. +[12mai2026 16:10:22.728] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from whitenoise-1.20.1-forge-1.0.0.jar, it does not contain dependency information. +[12mai2026 16:10:22.733] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileDependencyLocator/]: Failed to load resource META-INF\jarjar\metadata.json from MixinExtras-0.3.5.jar, it does not contain dependency information. +[12mai2026 16:10:22.749] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate +[12mai2026 16:10:22.750] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file whitenoise-1.20.1-forge-1.0.0.jar with {whitenoise} mods - versions {1.0.0} +[12mai2026 16:10:22.754] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate +[12mai2026 16:10:22.754] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file mixinextras-forge-0.3.5.jar with {mixinextras} mods - versions {0.3.5} +[12mai2026 16:10:22.756] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate +[12mai2026 16:10:22.757] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file crackerslib-forge-1.20.1-0.4.6.jar with {crackerslib} mods - versions {1.20.1-0.4.6} +[12mai2026 16:10:22.759] [main/WARN] [net.minecraftforge.jarjar.selection.JarSelector/]: Attempted to select a dependency jar for JarJar which was passed in as source: crackerslib. Using Mod File: C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\crackerslib-forge\1.20.1-0.4.6_mapped_official_1.20.1\crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar +[12mai2026 16:10:22.759] [main/INFO] [net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator/]: Found 4 dependencies adding them to mods collection +[12mai2026 16:10:22.760] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid simplecloudsapi.forge, selecting most recent based on version data +[12mai2026 16:10:22.760] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file simplecloudsapi-forge-1.4-1.20.1_mapped_official_1.20.1.jar for modid simplecloudsapi.forge with version 1.4-1.20.1 +[12mai2026 16:10:22.764] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar with {minecraft} mods - versions {1.20.1} +[12mai2026 16:10:22.766] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\minecraft_user_repo\net\minecraftforge\forge\1.20.1-47.4.13_mapped_official_1.20.1\forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar with languages [LanguageSpec[languageName=minecraft, acceptedVersions=1]] +[12mai2026 16:10:22.766] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\betterdays-895618\7013807_mapped_official_1.20.1\betterdays-895618-7013807_mapped_official_1.20.1.jar +[12mai2026 16:10:22.767] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file betterdays-895618-7013807_mapped_official_1.20.1.jar with {betterdays} mods - versions {3.3.4.4} +[12mai2026 16:10:22.767] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\betterdays-895618\7013807_mapped_official_1.20.1\betterdays-895618-7013807_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[46,)]] +[12mai2026 16:10:22.767] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-291874\6398227_mapped_official_1.20.1\serene-seasons-291874-6398227_mapped_official_1.20.1.jar +[12mai2026 16:10:22.768] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-291874-6398227_mapped_official_1.20.1.jar with {sereneseasons} mods - versions {9.1.0.2} +[12mai2026 16:10:22.768] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-291874\6398227_mapped_official_1.20.1\serene-seasons-291874-6398227_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] +[12mai2026 16:10:22.768] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\crackerslib-forge\1.20.1-0.4.6_mapped_official_1.20.1\crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar +[12mai2026 16:10:22.768] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar with {crackerslib} mods - versions {1.20.1-0.4.6} +[12mai2026 16:10:22.769] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\crackerslib-forge\1.20.1-0.4.6_mapped_official_1.20.1\crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] +[12mai2026 16:10:22.769] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\glitchcore-955399\5787839_mapped_official_1.20.1\glitchcore-955399-5787839_mapped_official_1.20.1.jar +[12mai2026 16:10:22.769] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file glitchcore-955399-5787839_mapped_official_1.20.1.jar with {glitchcore} mods - versions {0.0.1.1} +[12mai2026 16:10:22.769] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\glitchcore-955399\5787839_mapped_official_1.20.1\glitchcore-955399-5787839_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] +[12mai2026 16:10:22.769] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\jade-324717\6271651_mapped_official_1.20.1\jade-324717-6271651_mapped_official_1.20.1.jar +[12mai2026 16:10:22.769] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jade-324717-6271651_mapped_official_1.20.1.jar with {jade} mods - versions {11.13.1+forge} +[12mai2026 16:10:22.769] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\jade-324717\6271651_mapped_official_1.20.1\jade-324717-6271651_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[46,)]] +[12mai2026 16:10:22.769] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\simple-clouds-1121215\6928978_mapped_official_1.20.1\simple-clouds-1121215-6928978_mapped_official_1.20.1.jar +[12mai2026 16:10:22.771] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file simple-clouds-1121215-6928978_mapped_official_1.20.1.jar with {simpleclouds} mods - versions {0.7.3+1.20.1-forge} +[12mai2026 16:10:22.771] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\simple-clouds-1121215\6928978_mapped_official_1.20.1\simple-clouds-1121215-6928978_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] +[12mai2026 16:10:22.771] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate +[12mai2026 16:10:22.771] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file whitenoise-1.20.1-forge-1.0.0.jar with {whitenoise} mods - versions {1.0.0} +[12mai2026 16:10:22.771] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file with languages [LanguageSpec[languageName=javafml, acceptedVersions=[46,)]] +[12mai2026 16:10:22.771] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\architectury-api-419699\5137938_mapped_official_1.20.1\architectury-api-419699-5137938_mapped_official_1.20.1.jar +[12mai2026 16:10:22.772] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file architectury-api-419699-5137938_mapped_official_1.20.1.jar with {architectury} mods - versions {9.2.14} +[12mai2026 16:10:22.772] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\architectury-api-419699\5137938_mapped_official_1.20.1\architectury-api-419699-5137938_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[46,)]] +[12mai2026 16:10:22.773] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\distant-horizons-508933\7948170_mapped_official_1.20.1\distant-horizons-508933-7948170_mapped_official_1.20.1.jar +[12mai2026 16:10:22.773] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file distant-horizons-508933-7948170_mapped_official_1.20.1.jar with {distanthorizons} mods - versions {3.0.1-b} +[12mai2026 16:10:22.773] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\distant-horizons-508933\7948170_mapped_official_1.20.1\distant-horizons-508933-7948170_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=*]] +[12mai2026 16:10:22.774] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\mezz\jei\jei-1.20.1-forge\15.8.2.26_mapped_official_1.20.1\jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar +[12mai2026 16:10:22.775] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar with {jei} mods - versions {15.8.2.26} +[12mai2026 16:10:22.775] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\mezz\jei\jei-1.20.1-forge\15.8.2.26_mapped_official_1.20.1\jei-1.20.1-forge-15.8.2.26_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] +[12mai2026 16:10:22.775] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\lithostitched-936015\6742615_mapped_official_1.20.1\lithostitched-936015-6742615_mapped_official_1.20.1.jar +[12mai2026 16:10:22.775] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file lithostitched-936015-6742615_mapped_official_1.20.1.jar with {lithostitched} mods - versions {1.4.11} +[12mai2026 16:10:22.776] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\lithostitched-936015\6742615_mapped_official_1.20.1\lithostitched-936015-6742615_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[46,)]] +[12mai2026 16:10:22.776] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\spark-361579\4738952_mapped_official_1.20.1\spark-361579-4738952_mapped_official_1.20.1.jar +[12mai2026 16:10:22.776] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file spark-361579-4738952_mapped_official_1.20.1.jar with {spark} mods - versions {1.10.53} +[12mai2026 16:10:22.776] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\spark-361579\4738952_mapped_official_1.20.1\spark-361579-4738952_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[34,)]] +[12mai2026 16:10:22.776] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\cloth-config-348521\5729105_mapped_official_1.20.1\cloth-config-348521-5729105_mapped_official_1.20.1.jar +[12mai2026 16:10:22.777] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file cloth-config-348521-5729105_mapped_official_1.20.1.jar with {cloth_config} mods - versions {11.1.136} +[12mai2026 16:10:22.777] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\cloth-config-348521\5729105_mapped_official_1.20.1\cloth-config-348521-5729105_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[34,)]] +[12mai2026 16:10:22.777] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate / +[12mai2026 16:10:22.777] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file with {forge} mods - versions {47.4.13} +[12mai2026 16:10:22.777] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file / with languages [LanguageSpec[languageName=javafml, acceptedVersions=[24,]]] +[12mai2026 16:10:22.779] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Found coremod field_to_method with Javascript path coremods/field_to_method.js +[12mai2026 16:10:22.779] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Found coremod field_to_instanceof with Javascript path coremods/field_to_instanceof.js +[12mai2026 16:10:22.779] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Found coremod add_bouncer_method with Javascript path coremods/add_bouncer_method.js +[12mai2026 16:10:22.779] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Found coremod method_redirector with Javascript path coremods/method_redirector.js +[12mai2026 16:10:22.779] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Found coremod coremods/field_to_method.js +[12mai2026 16:10:22.780] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Found coremod coremods/field_to_instanceof.js +[12mai2026 16:10:22.780] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Found coremod coremods/add_bouncer_method.js +[12mai2026 16:10:22.780] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Found coremod coremods/method_redirector.js +[12mai2026 16:10:22.780] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\gabous-libs-1367332\7519775_mapped_official_1.20.1\gabous-libs-1367332-7519775_mapped_official_1.20.1.jar +[12mai2026 16:10:22.780] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file gabous-libs-1367332-7519775_mapped_official_1.20.1.jar with {gaboulibs} mods - versions {1.7.1} +[12mai2026 16:10:22.780] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\gabous-libs-1367332\7519775_mapped_official_1.20.1\gabous-libs-1367332-7519775_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] +[12mai2026 16:10:22.780] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\chloride-931925\6615986_mapped_official_1.20.1\chloride-931925-6615986_mapped_official_1.20.1.jar +[12mai2026 16:10:22.781] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file chloride-931925-6615986_mapped_official_1.20.1.jar with {chloride} mods - versions {1.7.2} +[12mai2026 16:10:22.781] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\chloride-931925\6615986_mapped_official_1.20.1\chloride-931925-6615986_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,48)]] +[12mai2026 16:10:22.781] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\embeddium-908741\5681725_mapped_official_1.20.1\embeddium-908741-5681725_mapped_official_1.20.1.jar +[12mai2026 16:10:22.781] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file embeddium-908741-5681725_mapped_official_1.20.1.jar with {embeddium,rubidium} mods - versions {0.3.31+mc1.20.1,0.7.1} +[12mai2026 16:10:22.781] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\embeddium-908741\5681725_mapped_official_1.20.1\embeddium-908741-5681725_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] +[12mai2026 16:10:22.782] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate G:\Project-Atmosphere\build\resources\main +[12mai2026 16:10:22.783] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file main with {projectatmosphere} mods - versions {0.9.0.0} +[12mai2026 16:10:22.783] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file G:\Project-Atmosphere\build\resources\main with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] +[12mai2026 16:10:22.783] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate G:\Project-Atmosphere\libs\tectonic-3.0.17-forge-1.20.1-dev.jar +[12mai2026 16:10:22.783] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file tectonic-3.0.17-forge-1.20.1-dev.jar with {tectonic} mods - versions {3.0.17} +[12mai2026 16:10:22.783] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file G:\Project-Atmosphere\libs\tectonic-3.0.17-forge-1.20.1-dev.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] +[12mai2026 16:10:22.783] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-plus-1288843\7325179_mapped_official_1.20.1\serene-seasons-plus-1288843-7325179_mapped_official_1.20.1.jar +[12mai2026 16:10:22.783] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file serene-seasons-plus-1288843-7325179_mapped_official_1.20.1.jar with {sereneseasonsplus} mods - versions {4.2.2} +[12mai2026 16:10:22.784] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\curse\maven\serene-seasons-plus-1288843\7325179_mapped_official_1.20.1\serene-seasons-plus-1288843-7325179_mapped_official_1.20.1.jar with languages [LanguageSpec[languageName=javafml, acceptedVersions=[47,)]] +[12mai2026 16:10:22.784] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate +[12mai2026 16:10:22.784] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file mixinextras-forge-0.3.5.jar with {mixinextras} mods - versions {0.3.5} +[12mai2026 16:10:22.784] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file with languages [LanguageSpec[languageName=javafml, acceptedVersions=*]] +[12mai2026 16:10:22.784] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file with languages [] +[12mai2026 16:10:22.784] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\simplecloudsapi-forge\1.4-1.20.1_mapped_official_1.20.1\simplecloudsapi-forge-1.4-1.20.1_mapped_official_1.20.1.jar with languages [] +[12mai2026 16:10:22.784] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Considering mod file candidate +[12mai2026 16:10:22.784] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFileInfo/LOADING]: Found valid mod file mixinextras-forge-0.3.5.jar with {mixinextras} mods - versions {0.3.5} +[12mai2026 16:10:22.784] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file with languages [LanguageSpec[languageName=javafml, acceptedVersions=*]] +[12mai2026 16:10:22.784] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file with languages [] +[12mai2026 16:10:22.785] [main/DEBUG] [net.minecraftforge.fml.loading.moddiscovery.ModFile/LOADING]: Loading mod file C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\simplecloudsapi-forge\1.4-1.20.1_mapped_official_1.20.1\simplecloudsapi-forge-1.4-1.20.1_mapped_official_1.20.1.jar with languages [] +[12mai2026 16:10:22.786] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: End scan trigger - transformation service fml +[12mai2026 16:10:22.800] [main/DEBUG] [net.minecraftforge.fml.loading.LanguageLoadingProvider/CORE]: Found 3 language providers +[12mai2026 16:10:22.800] [main/DEBUG] [net.minecraftforge.fml.loading.LanguageLoadingProvider/CORE]: Found language provider minecraft, version 1.0 +[12mai2026 16:10:22.802] [main/DEBUG] [net.minecraftforge.fml.loading.LanguageLoadingProvider/CORE]: Found language provider lowcodefml, version 47 +[12mai2026 16:10:22.802] [main/DEBUG] [net.minecraftforge.fml.loading.LanguageLoadingProvider/CORE]: Found language provider javafml, version 47 +[12mai2026 16:10:22.805] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid mixinextras, selecting most recent based on version data +[12mai2026 16:10:22.805] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file mixinextras-forge-0.3.5.jar for modid mixinextras with version 0.3.5 +[12mai2026 16:10:22.805] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid MixinExtras, selecting most recent based on version data +[12mai2026 16:10:22.806] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file MixinExtras-0.3.5.jar for modid MixinExtras with version 0.0NONE +[12mai2026 16:10:22.806] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Found 2 mods for first modid simplecloudsapi.forge, selecting most recent based on version data +[12mai2026 16:10:22.806] [main/DEBUG] [net.minecraftforge.fml.loading.UniqueModListBuilder/]: Selected file simplecloudsapi-forge-1.4-1.20.1_mapped_official_1.20.1.jar for modid simplecloudsapi.forge with version 1.4-1.20.1 +[12mai2026 16:10:22.808] [main/DEBUG] [net.minecraftforge.fml.loading.ModSorter/]: Configured system mods: [minecraft, forge] +[12mai2026 16:10:22.808] [main/DEBUG] [net.minecraftforge.fml.loading.ModSorter/]: Found system mod: minecraft +[12mai2026 16:10:22.808] [main/DEBUG] [net.minecraftforge.fml.loading.ModSorter/]: Found system mod: forge +[12mai2026 16:10:22.811] [main/DEBUG] [net.minecraftforge.fml.loading.ModSorter/LOADING]: Found 45 mod requirements (37 mandatory, 8 optional) +[12mai2026 16:10:22.811] [main/DEBUG] [net.minecraftforge.fml.loading.ModSorter/LOADING]: Found 0 mod requirements missing (0 mandatory, 0 optional) +[12mai2026 16:10:23.153] [main/DEBUG] [net.minecraftforge.fml.loading.MCPNamingService/CORE]: Loaded 33222 method mappings from methods.csv +[12mai2026 16:10:23.177] [main/DEBUG] [net.minecraftforge.fml.loading.MCPNamingService/CORE]: Loaded 31003 field mappings from fields.csv +[12mai2026 16:10:23.238] [main/DEBUG] [cpw.mods.modlauncher.TransformationServicesHandler/MODLAUNCHER]: Transformation services loading transformers +[12mai2026 16:10:23.239] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initializing transformers for transformation service mixin +[12mai2026 16:10:23.240] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initialized transformers for transformation service mixin +[12mai2026 16:10:23.241] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initializing transformers for transformation service fml +[12mai2026 16:10:23.241] [main/DEBUG] [net.minecraftforge.fml.loading.FMLServiceProvider/CORE]: Loading coremod transformers +[12mai2026 16:10:23.241] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: Loading CoreMod from coremods/field_to_method.js +[12mai2026 16:10:23.584] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: CoreMod loaded successfully +[12mai2026 16:10:23.584] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: Loading CoreMod from coremods/field_to_instanceof.js +[12mai2026 16:10:23.704] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: CoreMod loaded successfully +[12mai2026 16:10:23.704] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: Loading CoreMod from coremods/add_bouncer_method.js +[12mai2026 16:10:23.746] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: CoreMod loaded successfully +[12mai2026 16:10:23.746] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: Loading CoreMod from coremods/method_redirector.js +[12mai2026 16:10:23.819] [main/DEBUG] [net.minecraftforge.coremod.CoreModEngine/COREMOD]: CoreMod loaded successfully +[12mai2026 16:10:23.842] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@6824b913 to Target : CLASS {Lnet/minecraft/world/level/biome/Biome;} {} {V} +[12mai2026 16:10:23.844] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@3c66b7d8 to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/Structure;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@37e69c43 to Target : CLASS {Lnet/minecraft/world/effect/MobEffectInstance;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@5c7dfc05 to Target : CLASS {Lnet/minecraft/world/level/block/LiquidBlock;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@345d053b to Target : CLASS {Lnet/minecraft/world/item/BucketItem;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@3d0cac1f to Target : CLASS {Lnet/minecraft/world/level/block/StairBlock;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@3e8b3b79 to Target : CLASS {Lnet/minecraft/world/level/block/FlowerPotBlock;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@d257579 to Target : CLASS {Lnet/minecraft/world/item/ItemStack;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@518ddd3b to Target : CLASS {Lnet/minecraft/network/play/client/CClientSettingsPacket;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/monster/Zombie;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/monster/ZombieVillager;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/npc/Villager;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/monster/Spider;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/ai/village/VillageSiege;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/monster/Strider;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces$OceanMonumentPiece;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/npc/CatSpawner;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/level/levelgen/PhantomSpawner;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/animal/frog/Tadpole;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces$WoodlandMansionPiece;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/server/commands/RaidCommand;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/level/NaturalSpawner;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/level/levelgen/PatrolSpawner;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/EntityType;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces$OceanRuinPiece;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/raid/Raid;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/structures/SwampHutPiece;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/animal/horse/SkeletonTrapGoal;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/server/commands/SummonCommand;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformStore/MODLAUNCHER]: Adding transformer net.minecraftforge.coremod.transformer.CoreModClassTransformer@939ff41 to Target : CLASS {Lnet/minecraft/world/entity/monster/Evoker$EvokerSummonSpellGoal;} {} {V} +[12mai2026 16:10:23.845] [main/DEBUG] [cpw.mods.modlauncher.TransformationServiceDecorator/MODLAUNCHER]: Initialized transformers for transformation service fml +[12mai2026 16:10:24.474] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:ModLauncher Root Container(ModLauncher:4f56a0a2)] +[12mai2026 16:10:24.474] [main/DEBUG] [mixin/]: Registering mixin config: projectatmosphere.mixins.json +[12mai2026 16:10:24.507] [main/DEBUG] [mixin/]: Compatibility level JAVA_17 specified by projectatmosphere.mixins.json is higher than the maximum level supported by this version of mixin (JAVA_13). +[12mai2026 16:10:24.512] [main/INFO] [mixin/]: Compatibility level set to JAVA_17 +[12mai2026 16:10:24.513] [main/ERROR] [mixin/]: Mixin config projectatmosphere.mixins.json does not specify "minVersion" property +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Processing launch tasks for PlatformAgent[MixinPlatformAgentDefault:ModLauncher Root Container(ModLauncher:4f56a0a2)] +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(betterdays) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(betterdays) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(betterdays) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(betterdays) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(betterdays) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(betterdays)] +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(minecraft) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(minecraft) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(minecraft) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(minecraft) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(minecraft) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(minecraft)] +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(crackerslib) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(crackerslib) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(crackerslib) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(crackerslib) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(crackerslib) +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(crackerslib)] +[12mai2026 16:10:24.513] [main/DEBUG] [mixin/]: Registering mixin config: crackerslib.mixins.json +[12mai2026 16:10:24.514] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(mixinextras) +[12mai2026 16:10:24.514] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(mixinextras) +[12mai2026 16:10:24.514] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(mixinextras) +[12mai2026 16:10:24.514] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(mixinextras) +[12mai2026 16:10:24.514] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(mixinextras) +[12mai2026 16:10:24.514] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(mixinextras)] +[12mai2026 16:10:24.514] [main/DEBUG] [mixin/]: Registering mixin config: mixinextras.init.mixins.json +[12mai2026 16:10:24.516] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(glitchcore) +[12mai2026 16:10:24.516] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(glitchcore) +[12mai2026 16:10:24.516] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(glitchcore) +[12mai2026 16:10:24.516] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(glitchcore) +[12mai2026 16:10:24.516] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(glitchcore) +[12mai2026 16:10:24.516] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(glitchcore)] +[12mai2026 16:10:24.516] [main/DEBUG] [mixin/]: Registering mixin config: glitchcore.mixins.json +[12mai2026 16:10:24.518] [main/DEBUG] [mixin/]: Registering mixin config: glitchcore.forge.mixins.json +[12mai2026 16:10:24.519] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(sereneseasons) +[12mai2026 16:10:24.519] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(sereneseasons) +[12mai2026 16:10:24.519] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(sereneseasons) +[12mai2026 16:10:24.520] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(sereneseasons) +[12mai2026 16:10:24.520] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(sereneseasons) +[12mai2026 16:10:24.520] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(sereneseasons)] +[12mai2026 16:10:24.520] [main/DEBUG] [mixin/]: Registering mixin config: sereneseasons.mixins.json +[12mai2026 16:10:24.520] [main/DEBUG] [mixin/]: Registering mixin config: sereneseasons.forge.mixins.json +[12mai2026 16:10:24.521] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(jade) +[12mai2026 16:10:24.521] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(jade) +[12mai2026 16:10:24.521] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(jade) +[12mai2026 16:10:24.521] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(jade) +[12mai2026 16:10:24.521] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(jade) +[12mai2026 16:10:24.521] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(jade)] +[12mai2026 16:10:24.521] [main/DEBUG] [mixin/]: Registering mixin config: jade.mixins.json +[12mai2026 16:10:24.522] [main/DEBUG] [mixin/]: Compatibility level JAVA_16 specified by jade.mixins.json is higher than the maximum level supported by this version of mixin (JAVA_13). +[12mai2026 16:10:24.523] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(simpleclouds) +[12mai2026 16:10:24.523] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(simpleclouds) +[12mai2026 16:10:24.523] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(simpleclouds) +[12mai2026 16:10:24.523] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(simpleclouds) +[12mai2026 16:10:24.523] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(simpleclouds) +[12mai2026 16:10:24.523] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(simpleclouds)] +[12mai2026 16:10:24.523] [main/DEBUG] [mixin/]: Registering mixin config: simpleclouds.mixins.json +[12mai2026 16:10:24.524] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(architectury) +[12mai2026 16:10:24.524] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(architectury) +[12mai2026 16:10:24.524] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(architectury) +[12mai2026 16:10:24.524] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(architectury) +[12mai2026 16:10:24.524] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(architectury) +[12mai2026 16:10:24.524] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(architectury)] +[12mai2026 16:10:24.524] [main/DEBUG] [mixin/]: Registering mixin config: architectury.mixins.json +[12mai2026 16:10:24.524] [main/DEBUG] [mixin/]: Compatibility level JAVA_16 specified by architectury.mixins.json is higher than the maximum level supported by this version of mixin (JAVA_13). +[12mai2026 16:10:24.524] [main/DEBUG] [mixin/]: Registering mixin config: architectury-common.mixins.json +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: Compatibility level JAVA_16 specified by architectury-common.mixins.json is higher than the maximum level supported by this version of mixin (JAVA_13). +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(whitenoise) +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(whitenoise) +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(whitenoise) +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(whitenoise) +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(whitenoise) +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(whitenoise)] +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(distanthorizons) +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(distanthorizons) +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(distanthorizons) +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(distanthorizons) +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(distanthorizons) +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(distanthorizons)] +[12mai2026 16:10:24.527] [main/DEBUG] [mixin/]: Registering mixin config: DistantHorizons.forge.mixins.json +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(jei) +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(jei) +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(jei) +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(jei) +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(jei) +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(jei)] +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(lithostitched) +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(lithostitched) +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(lithostitched) +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(lithostitched) +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(lithostitched) +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(lithostitched)] +[12mai2026 16:10:24.529] [main/DEBUG] [mixin/]: Registering mixin config: lithostitched.mixins.json +[12mai2026 16:10:24.531] [main/DEBUG] [mixin/]: Registering mixin config: lithostitched.forge.mixins.json +[12mai2026 16:10:24.532] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(cloth_config) +[12mai2026 16:10:24.532] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(cloth_config) +[12mai2026 16:10:24.533] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(cloth_config) +[12mai2026 16:10:24.533] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(cloth_config) +[12mai2026 16:10:24.533] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(cloth_config) +[12mai2026 16:10:24.533] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(cloth_config)] +[12mai2026 16:10:24.533] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(spark) +[12mai2026 16:10:24.533] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(spark) +[12mai2026 16:10:24.533] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(spark) +[12mai2026 16:10:24.533] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(spark) +[12mai2026 16:10:24.533] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(spark) +[12mai2026 16:10:24.534] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(spark)] +[12mai2026 16:10:24.534] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(gaboulibs) +[12mai2026 16:10:24.534] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(gaboulibs) +[12mai2026 16:10:24.534] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(gaboulibs) +[12mai2026 16:10:24.534] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(gaboulibs) +[12mai2026 16:10:24.534] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(gaboulibs) +[12mai2026 16:10:24.534] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(gaboulibs)] +[12mai2026 16:10:24.534] [main/DEBUG] [mixin/]: Registering mixin config: gaboulibs.mixins.json +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(forge) +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(forge) +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(forge) +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(forge) +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(forge) +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(forge)] +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(embeddium) +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(embeddium) +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(embeddium) +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(embeddium) +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(embeddium) +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(embeddium)] +[12mai2026 16:10:24.535] [main/DEBUG] [mixin/]: Registering mixin config: embeddium.mixins.json +[12mai2026 16:10:24.536] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(chloride) +[12mai2026 16:10:24.536] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(chloride) +[12mai2026 16:10:24.537] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(chloride) +[12mai2026 16:10:24.537] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(chloride) +[12mai2026 16:10:24.537] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(chloride) +[12mai2026 16:10:24.537] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(chloride)] +[12mai2026 16:10:24.537] [main/DEBUG] [mixin/]: Registering mixin config: chloride.mixin.json +[12mai2026 16:10:24.538] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(projectatmosphere) +[12mai2026 16:10:24.538] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(projectatmosphere) +[12mai2026 16:10:24.538] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(projectatmosphere) +[12mai2026 16:10:24.538] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(projectatmosphere) +[12mai2026 16:10:24.538] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(projectatmosphere) +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(projectatmosphere)] +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(sereneseasonsplus) +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(sereneseasonsplus) +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(sereneseasonsplus) +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(sereneseasonsplus) +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(sereneseasonsplus) +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(sereneseasonsplus)] +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: Registering mixin config: sereneseasonsplus.mixins.json +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(tectonic) +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(tectonic) +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(tectonic) +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(tectonic) +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(tectonic) +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(tectonic)] +[12mai2026 16:10:24.539] [main/DEBUG] [mixin/]: Registering mixin config: tectonic.mixins.json +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: Registering mixin config: tectonic_1.20.1.mixins.json +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(MixinExtras) +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(MixinExtras) +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(MixinExtras) +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(MixinExtras) +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(MixinExtras) +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(MixinExtras)] +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: Adding mixin platform agents for container SecureJarResource(simplecloudsapi.forge) +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentMinecraftForge for SecureJarResource(simplecloudsapi.forge) +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: MixinPlatformAgentMinecraftForge rejected container SecureJarResource(simplecloudsapi.forge) +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: Instancing new MixinPlatformAgentDefault for SecureJarResource(simplecloudsapi.forge) +[12mai2026 16:10:24.541] [main/DEBUG] [mixin/]: MixinPlatformAgentDefault accepted container SecureJarResource(simplecloudsapi.forge) +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing prepare() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(simplecloudsapi.forge)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: inject() running with 25 agents +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:ModLauncher Root Container(ModLauncher:4f56a0a2)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(betterdays)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(minecraft)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(crackerslib)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(mixinextras)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(glitchcore)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(sereneseasons)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(jade)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(simpleclouds)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(architectury)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(whitenoise)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(distanthorizons)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(jei)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(lithostitched)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(cloth_config)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(spark)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(gaboulibs)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(forge)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(embeddium)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(chloride)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(projectatmosphere)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(sereneseasonsplus)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(tectonic)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(MixinExtras)] +[12mai2026 16:10:24.542] [main/DEBUG] [mixin/]: Processing inject() for PlatformAgent[MixinPlatformAgentDefault:SecureJarResource(simplecloudsapi.forge)] +[12mai2026 16:10:24.543] [main/INFO] [cpw.mods.modlauncher.LaunchServiceHandler/MODLAUNCHER]: Launching target 'forgedatauserdev' with arguments [--gameDir, ., --assetsDir, C:\Users\matga\.gradle\caches\forge_gradle\assets, --assetIndex, 5, --mod, projectatmosphere, --all, --output, G:\Project-Atmosphere\src\generated\resources, --existing, G:\Project-Atmosphere\src\main\resources] +[12mai2026 16:10:24.668] [main/DEBUG] [mixin/]: Error cleaning class output directory: .mixin.out +[12mai2026 16:10:24.672] [main/DEBUG] [mixin/]: Preparing mixins for MixinEnvironment[DEFAULT] +[12mai2026 16:10:24.672] [main/DEBUG] [mixin/]: Selecting config projectatmosphere.mixins.json +[12mai2026 16:10:24.698] [main/WARN] [mixin/]: Reference map 'projectatmosphere.refmap.json' for projectatmosphere.mixins.json could not be read. If this is a development environment you can ignore this message +[12mai2026 16:10:24.700] [main/DEBUG] [mixin/]: Selecting config crackerslib.mixins.json +[12mai2026 16:10:24.932] [main/INFO] [mixin/]: Remapping refMap crackerslib.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:24.932] [main/DEBUG] [mixin/]: Selecting config mixinextras.init.mixins.json +[12mai2026 16:10:24.997] [main/DEBUG] [MixinExtras|Service/]: com.llamalad7.mixinextras.service.MixinExtrasServiceImpl(version=0.3.5) is taking over from null +[12mai2026 16:10:25.048] [main/DEBUG] [mixin/]: Registering new injector for @Inject with org.spongepowered.asm.mixin.injection.struct.CallbackInjectionInfo +[12mai2026 16:10:25.049] [main/DEBUG] [mixin/]: Registering new injector for @ModifyArg with org.spongepowered.asm.mixin.injection.struct.ModifyArgInjectionInfo +[12mai2026 16:10:25.049] [main/DEBUG] [mixin/]: Registering new injector for @ModifyArgs with org.spongepowered.asm.mixin.injection.struct.ModifyArgsInjectionInfo +[12mai2026 16:10:25.051] [main/DEBUG] [mixin/]: Registering new injector for @Redirect with org.spongepowered.asm.mixin.injection.struct.RedirectInjectionInfo +[12mai2026 16:10:25.052] [main/DEBUG] [mixin/]: Registering new injector for @ModifyVariable with org.spongepowered.asm.mixin.injection.struct.ModifyVariableInjectionInfo +[12mai2026 16:10:25.052] [main/DEBUG] [mixin/]: Registering new injector for @ModifyConstant with org.spongepowered.asm.mixin.injection.struct.ModifyConstantInjectionInfo +[12mai2026 16:10:25.062] [main/DEBUG] [mixin/]: Selecting config glitchcore.mixins.json +[12mai2026 16:10:25.063] [main/INFO] [mixin/]: Remapping refMap glitchcore.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.063] [main/DEBUG] [mixin/]: Selecting config glitchcore.forge.mixins.json +[12mai2026 16:10:25.064] [main/INFO] [mixin/]: Remapping refMap glitchcore.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.064] [main/DEBUG] [mixin/]: Selecting config sereneseasons.mixins.json +[12mai2026 16:10:25.065] [main/INFO] [mixin/]: Remapping refMap sereneseasons.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.065] [main/DEBUG] [mixin/]: Selecting config sereneseasons.forge.mixins.json +[12mai2026 16:10:25.066] [main/INFO] [mixin/]: Remapping refMap sereneseasons.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.066] [main/DEBUG] [mixin/]: Selecting config jade.mixins.json +[12mai2026 16:10:25.066] [main/INFO] [mixin/]: Remapping refMap jade.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.066] [main/DEBUG] [mixin/]: Selecting config simpleclouds.mixins.json +[12mai2026 16:10:25.067] [main/INFO] [mixin/]: Remapping refMap simpleclouds.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.068] [main/DEBUG] [mixin/]: Selecting config architectury.mixins.json +[12mai2026 16:10:25.071] [main/INFO] [mixin/]: Remapping refMap architectury-forge-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.071] [main/DEBUG] [mixin/]: Selecting config architectury-common.mixins.json +[12mai2026 16:10:25.072] [main/INFO] [mixin/]: Remapping refMap architectury-common-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.072] [main/DEBUG] [mixin/]: Selecting config DistantHorizons.forge.mixins.json +[12mai2026 16:10:25.073] [main/INFO] [mixin/]: Remapping refMap DistantHorizons.forge.mixins-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.073] [main/DEBUG] [mixin/]: Selecting config lithostitched.mixins.json +[12mai2026 16:10:25.074] [main/INFO] [mixin/]: Remapping refMap lithostitched.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.074] [main/DEBUG] [mixin/]: Selecting config lithostitched.forge.mixins.json +[12mai2026 16:10:25.074] [main/INFO] [mixin/]: Remapping refMap lithostitched.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.074] [main/DEBUG] [mixin/]: Selecting config gaboulibs.mixins.json +[12mai2026 16:10:25.076] [main/DEBUG] [mixin/]: Selecting config embeddium.mixins.json +[12mai2026 16:10:25.086] [main/INFO] [Embeddium/]: Loaded configuration file for Embeddium: 67 options available, 0 override(s) found +[12mai2026 16:10:25.089] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Searching for graphics cards... +[12mai2026 16:10:25.251] [main/DEBUG] [oshi.util.FileUtil/]: No oshi.properties file found from ClassLoader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf +[12mai2026 16:10:25.367] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Found graphics card: GraphicsAdapterInfo[vendor=UNKNOWN, name=Meta Virtual Monitor, version=DriverVersion=5.3.57.114] +[12mai2026 16:10:25.367] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Found graphics card: GraphicsAdapterInfo[vendor=NVIDIA, name=NVIDIA GeForce RTX 4070 Ti SUPER, version=DriverVersion=32.0.15.9597] +[12mai2026 16:10:25.371] [main/WARN] [Embeddium-Workarounds/]: Embeddium has applied one or more workarounds to prevent crashes or other issues on your system: [NVIDIA_THREADED_OPTIMIZATIONS] +[12mai2026 16:10:25.371] [main/WARN] [Embeddium-Workarounds/]: This is not necessarily an issue, but it may result in certain features or optimizations being disabled. You can sometimes fix these issues by upgrading your graphics driver. +[12mai2026 16:10:25.378] [main/INFO] [mixin/]: Remapping refMap embeddium-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.378] [main/DEBUG] [mixin/]: Selecting config chloride.mixin.json +[12mai2026 16:10:25.381] [main/INFO] [mixin/]: Remapping refMap chloride.mixin-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.382] [main/DEBUG] [mixin/]: Selecting config sereneseasonsplus.mixins.json +[12mai2026 16:10:25.388] [main/INFO] [mixin/]: Remapping refMap sereneseasonsplus-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.389] [main/DEBUG] [mixin/]: Selecting config tectonic.mixins.json +[12mai2026 16:10:25.389] [main/DEBUG] [mixin/]: Selecting config tectonic_1.20.1.mixins.json +[12mai2026 16:10:25.390] [main/DEBUG] [mixin/]: Preparing projectatmosphere.mixins.json (29) +[12mai2026 16:10:25.432] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/biome/Biome +[12mai2026 16:10:25.505] [main/DEBUG] [mixin/]: Preparing crackerslib.mixins.json (2) +[12mai2026 16:10:25.510] [main/DEBUG] [mixin/]: Preparing mixinextras.init.mixins.json (0) +[12mai2026 16:10:25.510] [main/DEBUG] [mixin/]: Preparing glitchcore.mixins.json (4) +[12mai2026 16:10:25.513] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/item/ItemStack +[12mai2026 16:10:25.535] [main/DEBUG] [mixin/]: Preparing glitchcore.forge.mixins.json (7) +[12mai2026 16:10:25.545] [main/DEBUG] [mixin/]: Preparing sereneseasons.mixins.json (6) +[12mai2026 16:10:25.561] [main/DEBUG] [mixin/]: Preparing sereneseasons.forge.mixins.json (0) +[12mai2026 16:10:25.561] [main/DEBUG] [mixin/]: Preparing jade.mixins.json (2) +[12mai2026 16:10:25.564] [main/DEBUG] [mixin/]: Preparing simpleclouds.mixins.json (20) +[12mai2026 16:10:25.601] [main/WARN] [mixin/]: Error loading class: org/vivecraft/client_vr/provider/VRRenderer (java.lang.ClassNotFoundException: org.vivecraft.client_vr.provider.VRRenderer) +[12mai2026 16:10:25.601] [main/DEBUG] [mixin/]: Skipping virtual target org.vivecraft.client_vr.provider.VRRenderer for simpleclouds.mixins.json:vivecraft.MixinVRRenderer +[12mai2026 16:10:25.601] [main/DEBUG] [mixin/]: Preparing architectury.mixins.json (9) +[12mai2026 16:10:25.608] [main/DEBUG] [mixin/]: Preparing architectury-common.mixins.json (10) +[12mai2026 16:10:25.612] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/item/BucketItem +[12mai2026 16:10:25.618] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/EntityType +[12mai2026 16:10:25.786] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/block/LiquidBlock +[12mai2026 16:10:25.791] [main/DEBUG] [mixin/]: Preparing DistantHorizons.forge.mixins.json (12) +[12mai2026 16:10:25.831] [main/DEBUG] [mixin/]: Preparing lithostitched.mixins.json (18) +[12mai2026 16:10:25.843] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate +[12mai2026 16:10:25.901] [main/DEBUG] [mixin/]: Preparing lithostitched.forge.mixins.json (2) +[12mai2026 16:10:25.904] [main/DEBUG] [mixin/]: Preparing gaboulibs.mixins.json (0) +[12mai2026 16:10:25.904] [main/DEBUG] [mixin/]: Preparing embeddium.mixins.json (0) +[12mai2026 16:10:25.904] [main/DEBUG] [mixin/]: Preparing chloride.mixin.json (32) +[12mai2026 16:10:25.926] [main/WARN] [mixin/]: Error loading class: dev/emi/emi/screen/EmiScreenManager (java.lang.ClassNotFoundException: dev.emi.emi.screen.EmiScreenManager) +[12mai2026 16:10:25.927] [main/DEBUG] [mixin/]: Skipping virtual target dev.emi.emi.screen.EmiScreenManager for chloride.mixin.json:jei_rei_emi.EmiOverlayMixin +[12mai2026 16:10:25.929] [main/WARN] [mixin/]: Error loading class: me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl (java.lang.ClassNotFoundException: me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl) +[12mai2026 16:10:25.930] [main/DEBUG] [mixin/]: Skipping virtual target me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl for chloride.mixin.json:jei_rei_emi.ReiOverlayMixin +[12mai2026 16:10:25.931] [main/DEBUG] [mixin/]: Preparing sereneseasonsplus.mixins.json (8) +[12mai2026 16:10:25.942] [main/DEBUG] [mixin/]: Preparing tectonic.mixins.json (13) +[12mai2026 16:10:25.954] [main/DEBUG] [mixin/]: Preparing tectonic_1.20.1.mixins.json (3) +[12mai2026 16:10:25.981] [main/DEBUG] [mixin/]: Inner class glitchcore/forge/mixin/impl/MixinPacketHandler$1 in glitchcore/forge/mixin/impl/MixinPacketHandler on glitchcore/network/PacketHandler gets unique name glitchcore/network/PacketHandler$Anonymous$267cb16fd0234a6bb2a36c78471b8ab9 +[12mai2026 16:10:26.099] [main/DEBUG] [mixin/]: Prepared 168 mixins in 1,426 sec (8,5ms avg) (0ms load, 0ms transform, 0ms plugin) +[12mai2026 16:10:26.203] [main/DEBUG] [io.netty.util.internal.logging.InternalLoggerFactory/]: Using SLF4J as the default logging framework +[12mai2026 16:10:26.211] [main/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.level: simple +[12mai2026 16:10:26.211] [main/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.targetRecords: 4 +[12mai2026 16:10:26.358] [main/INFO] [net.minecraftforge.data.loading.DatagenModLoader/]: Initializing Data Gatherer for mods [projectatmosphere] +[12mai2026 16:10:26.370] [main/INFO] [MixinExtras|Service/]: Initializing MixinExtras via com.llamalad7.mixinextras.service.MixinExtrasServiceImpl(version=0.3.5). +[12mai2026 16:10:26.371] [main/DEBUG] [mixin/]: Registering new injector for @SugarWrapper with com.llamalad7.mixinextras.sugar.impl.SugarWrapperInjectionInfo +[12mai2026 16:10:26.371] [main/DEBUG] [mixin/]: Registering new injector for @FactoryRedirectWrapper with com.llamalad7.mixinextras.wrapper.factory.FactoryRedirectWrapperInjectionInfo +[12mai2026 16:10:26.375] [main/DEBUG] [mixin/]: Mixing common.MappedRegistryAccessor from lithostitched.mixins.json into net.minecraft.core.MappedRegistry +[12mai2026 16:10:26.424] [main/DEBUG] [mixin/]: Mixing server.MixinUtilBackgroundThread from DistantHorizons.forge.mixins.json into net.minecraft.Util +[12mai2026 16:10:26.534] [main/DEBUG] [mixin/]: Mixing inject.MixinGameEvent from architectury-common.mixins.json into net.minecraft.world.level.gameevent.GameEvent +[12mai2026 16:10:26.544] [main/DEBUG] [mixin/]: Mixing common.HolderReferenceAccessor from lithostitched.mixins.json into net.minecraft.core.Holder$Reference +[12mai2026 16:10:26.567] [main/DEBUG] [mixin/]: Mixing inject.MixinBlock from architectury-common.mixins.json into net.minecraft.world.level.block.Block +[12mai2026 16:10:26.587] [main/DEBUG] [mixin/]: Mixing MixinBlockStateBase from sereneseasons.mixins.json into net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase +[12mai2026 16:10:26.600] [main/DEBUG] [mixin/]: Mixing server.MixinEntity from DistantHorizons.forge.mixins.json into net.minecraft.world.entity.Entity +[12mai2026 16:10:26.652] [main/DEBUG] [mixin/]: Mixing MixinLevel from sereneseasons.mixins.json into net.minecraft.world.level.Level +[12mai2026 16:10:26.654] [main/DEBUG] [mixin/]: Mixing MixinLevel from simpleclouds.mixins.json into net.minecraft.world.level.Level +[12mai2026 16:10:26.675] [main/DEBUG] [mixin/]: Mixing ServerLevelSnowStormMixin from projectatmosphere.mixins.json into net.minecraft.server.level.ServerLevel +[12mai2026 16:10:26.677] [main/DEBUG] [mixin/]: Mixing MixinServerLevel from glitchcore.mixins.json into net.minecraft.server.level.ServerLevel +[12mai2026 16:10:26.678] [main/DEBUG] [mixin/]: Mixing MixinServerLevel from sereneseasons.mixins.json into net.minecraft.server.level.ServerLevel +[12mai2026 16:10:26.678] [main/DEBUG] [mixin/]: Mixing MixinServerLevel from simpleclouds.mixins.json into net.minecraft.server.level.ServerLevel +[12mai2026 16:10:26.679] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$createCloudManager_init$1()Ldev/nonamecrackers2/simpleclouds/common/world/CloudData; to mdb27d7e$lambda$simpleclouds$createCloudManager_init$1$0 in simpleclouds.mixins.json:MixinServerLevel +[12mai2026 16:10:26.680] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$createCloudManager_init$0(Lnet/minecraft/nbt/CompoundTag;)Ldev/nonamecrackers2/simpleclouds/common/world/CloudData; to mdb27d7e$lambda$simpleclouds$createCloudManager_init$0$1 in simpleclouds.mixins.json:MixinServerLevel +[12mai2026 16:10:26.685] [main/DEBUG] [mixin/]: Mixing MixinServerLevelAccessor from simpleclouds.mixins.json into net.minecraft.server.level.ServerLevel +[12mai2026 16:10:26.687] [main/DEBUG] [mixin/]: Mixing ServerLevelMixin from sereneseasonsplus.mixins.json into net.minecraft.server.level.ServerLevel +[12mai2026 16:10:26.801] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/biome/Biome +[12mai2026 16:10:26.868] [main/DEBUG] [mixin/]: Mixing inject.MixinFluid from architectury-common.mixins.json into net.minecraft.world.level.material.Fluid +[12mai2026 16:10:26.877] [main/DEBUG] [mixin/]: Mixing inject.MixinItem from architectury-common.mixins.json into net.minecraft.world.item.Item +[12mai2026 16:10:26.901] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/EntityType +[12mai2026 16:10:26.917] [main/DEBUG] [mixin/]: Mixing inject.MixinEntityType from architectury-common.mixins.json into net.minecraft.world.entity.EntityType +[12mai2026 16:10:26.918] [main/DEBUG] [mixin/]: Mixing EntityDistanceCullingMixin$EntityTypeMixin from chloride.mixin.json into net.minecraft.world.entity.EntityType +[12mai2026 16:10:26.955] [main/DEBUG] [mixin/]: Mixing ParticlesMixins$ParticleTypeMixin from chloride.mixin.json into net.minecraft.core.particles.ParticleType +[12mai2026 16:10:26.959] [main/DEBUG] [mixin/]: Mixing MixinBlockEntityType from crackerslib.mixins.json into net.minecraft.world.level.block.entity.BlockEntityType +[12mai2026 16:10:26.961] [main/DEBUG] [mixin/]: Mixing EntityDistanceCullingMixin$TileEntityTypeMixin from chloride.mixin.json into net.minecraft.world.level.block.entity.BlockEntityType +[12mai2026 16:10:27.009] [main/DEBUG] [mixin/]: Mixing WorldCarverMixin from tectonic.mixins.json into net.minecraft.world.level.levelgen.carver.WorldCarver +[12mai2026 16:10:27.087] [main/DEBUG] [mixin/]: Mixing features.render.immediate.DirectionMixin from embeddium.mixins.json into net.minecraft.core.Direction +[12mai2026 16:10:27.211] [main/DEBUG] [mixin/]: Mixing SpreadingSnowyDirtBlockMixin from sereneseasonsplus.mixins.json into net.minecraft.world.level.block.SpreadingSnowyDirtBlock +[12mai2026 16:10:27.243] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/block/LiquidBlock +[12mai2026 16:10:27.243] [main/DEBUG] [mixin/]: Mixing inject.MixinLiquidBlock from architectury-common.mixins.json into net.minecraft.world.level.block.LiquidBlock +[12mai2026 16:10:27.252] [main/DEBUG] [mixin/]: Mixing leaves_culling.LeavesBlockMixin from chloride.mixin.json into net.minecraft.world.level.block.LeavesBlock +[12mai2026 16:10:27.254] [main/DEBUG] [mixin/]: Mixing features.options.render_layers.LeavesBlockMixin from embeddium.mixins.json into net.minecraft.world.level.block.LeavesBlock +[12mai2026 16:10:27.264] [main/WARN] [mixin/]: Method overwrite conflict for skipRendering in embeddium.mixins.json:features.options.render_layers.LeavesBlockMixin, previously written by me.srrapero720.chloride.mixins.impl.leaves_culling.LeavesBlockMixin. Skipping method. +[12mai2026 16:10:27.266] [main/DEBUG] [mixin/]: Unexpected: Registered method skipRendering(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/Direction;)Z in net.minecraft.world.level.block.LeavesBlock was not merged +[12mai2026 16:10:27.274] [main/DEBUG] [mixin/]: Mixing FastBlocksMixins$BedMixin from chloride.mixin.json into net.minecraft.world.level.block.BedBlock +[12mai2026 16:10:27.298] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/block/StairBlock +[12mai2026 16:10:27.302] [main/DEBUG] [mixin/]: Mixing FastBlocksMixins$ChestsMixin from chloride.mixin.json into net.minecraft.world.level.block.ChestBlock +[12mai2026 16:10:27.352] [main/DEBUG] [mixin/]: Mixing FastBlocksMixins$ChestsMixin from chloride.mixin.json into net.minecraft.world.level.block.EnderChestBlock +[12mai2026 16:10:27.357] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/block/FlowerPotBlock +[12mai2026 16:10:27.511] [main/DEBUG] [mixin/]: Mixing MixinFallingBlockEntity from architectury.mixins.json into net.minecraft.world.entity.item.FallingBlockEntity +[12mai2026 16:10:28.092] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/item/ItemStack +[12mai2026 16:10:28.093] [main/DEBUG] [mixin/]: Mixing MixinItemStack from glitchcore.mixins.json into net.minecraft.world.item.ItemStack +[12mai2026 16:10:28.584] [main/DEBUG] [mixin/]: Mixing MixinLightningBolt from architectury-common.mixins.json into net.minecraft.world.entity.LightningBolt +[12mai2026 16:10:28.634] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/animal/frog/Tadpole +[12mai2026 16:10:28.683] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/item/BucketItem +[12mai2026 16:10:28.683] [main/DEBUG] [mixin/]: Mixing inject.MixinBucketItem from architectury-common.mixins.json into net.minecraft.world.item.BucketItem +[12mai2026 16:10:28.765] [main/DEBUG] [mixin/]: Mixing inject.MixinItemProperties from architectury-common.mixins.json into net.minecraft.world.item.Item$Properties +[12mai2026 16:10:29.423] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/monster/Spider +[12mai2026 16:10:29.452] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/monster/Zombie +[12mai2026 16:10:29.468] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/monster/ZombieVillager +[12mai2026 16:10:29.481] [main/DEBUG] [mixin/]: Mixing vanillacompat.MixinThrownTrident from simpleclouds.mixins.json into net.minecraft.world.entity.projectile.ThrownTrident +[12mai2026 16:10:29.514] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/monster/Evoker$EvokerSummonSpellGoal +[12mai2026 16:10:29.601] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/animal/horse/SkeletonTrapGoal +[12mai2026 16:10:29.610] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/monster/Strider +[12mai2026 16:10:29.648] [main/DEBUG] [mixin/]: Mixing MixinServerPlayer from glitchcore.forge.mixins.json into net.minecraft.server.level.ServerPlayer +[12mai2026 16:10:29.649] [main/DEBUG] [mixin/]: Mixing server.MixinServerPlayer from DistantHorizons.forge.mixins.json into net.minecraft.server.level.ServerPlayer +[12mai2026 16:10:29.679] [main/DEBUG] [mixin/]: Mixing ChunkAccessMixin from tectonic.mixins.json into net.minecraft.world.level.chunk.ChunkAccess +[12mai2026 16:10:29.686] [main/DEBUG] [mixin/]: Mixing LevelChunkSnowMixin from sereneseasonsplus.mixins.json into net.minecraft.world.level.chunk.LevelChunk +[12mai2026 16:10:29.717] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/entity/npc/Villager +[12mai2026 16:10:29.783] [main/DEBUG] [mixin/]: Mixing inject.MixinFoodPropertiesBuilder from architectury-common.mixins.json into net.minecraft.world.food.FoodProperties$Builder +[12mai2026 16:10:29.785] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/effect/MobEffectInstance +[12mai2026 16:10:29.957] [main/DEBUG] [mixin/]: Mixing HeightmapMixin from tectonic.mixins.json into net.minecraft.world.level.levelgen.Heightmap +[12mai2026 16:10:29.963] [main/DEBUG] [mixin/]: Mixing server.MixinChunkGenerator from DistantHorizons.forge.mixins.json into net.minecraft.world.level.chunk.ChunkGenerator +[12mai2026 16:10:29.963] [main/DEBUG] [mixin/]: Mixing server.MixinTFChunkGenerator from DistantHorizons.forge.mixins.json into net.minecraft.world.level.chunk.ChunkGenerator +[12mai2026 16:10:30.106] [main/DEBUG] [mixin/]: Mixing darkness.DimensionTypeMixin from chloride.mixin.json into net.minecraft.world.level.dimension.DimensionType +[12mai2026 16:10:30.106] [main/DEBUG] [mixin/]: Mixing DimensionTypeAccessor from tectonic.mixins.json into net.minecraft.world.level.dimension.DimensionType +[12mai2026 16:10:30.168] [main/DEBUG] [mixin/]: Mixing common.PlacedFeatureAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.placement.PlacedFeature +[12mai2026 16:10:30.179] [main/DEBUG] [mixin/]: Mixing common.StructureProcessorListAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList +[12mai2026 16:10:30.233] [main/DEBUG] [mixin/]: Mixing common.StructureSetAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.StructureSet +[12mai2026 16:10:30.238] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/Structure +[12mai2026 16:10:30.246] [main/DEBUG] [mixin/]: Mixing StructurePieceMixin from tectonic.mixins.json into net.minecraft.world.level.levelgen.structure.StructurePiece +[12mai2026 16:10:30.269] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces$OceanRuinPiece +[12mai2026 16:10:30.274] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece +[12mai2026 16:10:30.279] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces$OceanMonumentPiece +[12mai2026 16:10:30.287] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces$WoodlandMansionPiece +[12mai2026 16:10:30.291] [main/DEBUG] [mixin/]: Mixing common.ShipwreckPieceMixin from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.structures.ShipwreckPieces$ShipwreckPiece +[12mai2026 16:10:30.305] [main/DEBUG] [mixin/]: Mixing common.JigsawStructureMixin from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.structures.JigsawStructure +[12mai2026 16:10:30.306] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/levelgen/structure/Structure +[12mai2026 16:10:30.313] [main/DEBUG] [mixin/]: Mixing common.StructureTemplatePoolAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool +[12mai2026 16:10:30.317] [main/DEBUG] [mixin/]: Mixing common.StructureTemplatePoolMixin from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool +[12mai2026 16:10:30.318] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$compileRawTemplates$0(Lcom/mojang/datafixers/util/Pair;)V to mdb27d7e$lambda$compileRawTemplates$0$0 in lithostitched.mixins.json:common.StructureTemplatePoolMixin +[12mai2026 16:10:30.363] [main/DEBUG] [net.minecraftforge.coremod.transformer.CoreModBaseTransformer/COREMOD]: Transforming net/minecraft/world/level/biome/Biome +[12mai2026 16:10:30.363] [main/DEBUG] [mixin/]: Mixing features.world.biome.BiomeMixin from embeddium.mixins.json into net.minecraft.world.level.biome.Biome +[12mai2026 16:10:30.364] [main/DEBUG] [mixin/]: Mixing BiomeFreezingMixin from projectatmosphere.mixins.json into net.minecraft.world.level.biome.Biome +[12mai2026 16:10:30.364] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$resolveTemperature$1(Lnet/minecraft/resources/ResourceKey;)Lnet/minecraft/resources/ResourceLocation; to mdb27d7e$lambda$resolveTemperature$1$0 in projectatmosphere.mixins.json:BiomeFreezingMixin +[12mai2026 16:10:30.365] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$resolveTemperature$0(Lnet/minecraft/resources/ResourceKey;)Lnet/minecraft/resources/ResourceLocation; to mdb27d7e$lambda$resolveTemperature$0$1 in projectatmosphere.mixins.json:BiomeFreezingMixin +[12mai2026 16:10:30.369] [main/DEBUG] [mixin/]: Mixing MixinBiome from sereneseasons.mixins.json into net.minecraft.world.level.biome.Biome +[12mai2026 16:10:30.369] [main/DEBUG] [mixin/]: Mixing client.MixinBiomeClient from sereneseasons.mixins.json into net.minecraft.world.level.biome.Biome +[12mai2026 16:10:30.369] [main/DEBUG] [mixin/]: Mixing BiomeMixin from tectonic.mixins.json into net.minecraft.world.level.biome.Biome +[12mai2026 16:10:30.389] [main/DEBUG] [mixin/]: Mixing features.render.world.ClientLevelMixin from embeddium.mixins.json into net.minecraft.client.multiplayer.ClientLevel +[12mai2026 16:10:30.389] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$new$0(Lnet/minecraft/world/level/biome/AmbientParticleSettings;)V to mdb27d7e$lambda$new$0$0 in embeddium.mixins.json:features.render.world.ClientLevelMixin +[12mai2026 16:10:30.391] [main/DEBUG] [mixin/]: Mixing MixinClientLevel from simpleclouds.mixins.json into net.minecraft.client.multiplayer.ClientLevel +[12mai2026 16:10:30.393] [main/DEBUG] [mixin/]: Mixing MixinClientLevel from architectury.mixins.json into net.minecraft.client.multiplayer.ClientLevel +[12mai2026 16:10:30.393] [main/DEBUG] [mixin/]: Mixing core.world.biome.ClientWorldMixin from embeddium.mixins.json into net.minecraft.client.multiplayer.ClientLevel +[12mai2026 16:10:30.393] [main/DEBUG] [mixin/]: Mixing core.world.map.ClientWorldMixin from embeddium.mixins.json into net.minecraft.client.multiplayer.ClientLevel +[12mai2026 16:10:30.395] [main/DEBUG] [mixin/]: Mixing features.render.world.sky.ClientWorldMixin from embeddium.mixins.json into net.minecraft.client.multiplayer.ClientLevel +[12mai2026 16:10:30.395] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$redirectSampleColor$0(Lnet/minecraft/world/level/Level;III)I to mdb27d7e$lambda$redirectSampleColor$0$1 in embeddium.mixins.json:features.render.world.sky.ClientWorldMixin +[12mai2026 16:10:30.434] [main/DEBUG] [mixin/]: Mixing TemperatureModifierMixin from tectonic.mixins.json into net.minecraft.world.level.biome.Biome$TemperatureModifier$2 +[12mai2026 16:10:30.480] [main/DEBUG] [mixin/]: Mixing NoiseBasedChunkGeneratorMixin from tectonic.mixins.json into net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator +[12mai2026 16:10:30.480] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$tectonic$fixLavaLevel$2(Lnet/minecraft/core/Holder;)Lnet/minecraft/world/level/levelgen/Aquifer$FluidPicker; to mdb27d7e$lambda$tectonic$fixLavaLevel$2$0 in tectonic.mixins.json:NoiseBasedChunkGeneratorMixin +[12mai2026 16:10:30.480] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$tectonic$fixLavaLevel$1(Lnet/minecraft/world/level/levelgen/Aquifer$FluidStatus;IILnet/minecraft/world/level/levelgen/Aquifer$FluidStatus;Lnet/minecraft/world/level/levelgen/Aquifer$FluidStatus;III)Lnet/minecraft/world/level/levelgen/Aquifer$FluidStatus; to mdb27d7e$lambda$tectonic$fixLavaLevel$1$1 in tectonic.mixins.json:NoiseBasedChunkGeneratorMixin +[12mai2026 16:10:30.480] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$tectonic$fixLavaLevel$0(Lnet/minecraft/resources/ResourceKey;)Ljava/lang/Boolean; to mdb27d7e$lambda$tectonic$fixLavaLevel$0$2 in tectonic.mixins.json:NoiseBasedChunkGeneratorMixin +[12mai2026 16:10:30.486] [main/DEBUG] [mixin/]: Mixing common.NoiseGeneratorSettingsAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.NoiseGeneratorSettings +[12mai2026 16:10:30.490] [main/DEBUG] [mixin/]: Mixing NoiseSettingsAccessor from tectonic.mixins.json into net.minecraft.world.level.levelgen.NoiseSettings +[12mai2026 16:10:30.544] [main/DEBUG] [mixin/]: Mixing common.SinglePoolElementAccessor from lithostitched.mixins.json into net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement +[12mai2026 16:10:30.589] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Creating vanilla freeze snapshot +[12mai2026 16:10:30.593] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:block Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.599] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:fluid Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.599] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:item Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.604] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:mob_effect Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.604] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:sound_event Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.608] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:potion Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.608] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:enchantment Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.608] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:entity_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.609] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:block_entity_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.609] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:particle_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.610] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:menu Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.610] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:painting_variant Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.610] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:recipe_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.610] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:recipe_serializer Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.610] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:attribute Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.610] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:stat_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.610] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:command_argument_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.610] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:villager_profession Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.610] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:point_of_interest_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.611] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:memory_module_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.612] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:sensor_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.612] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:schedule Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.612] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:activity Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.612] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/carver Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.612] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/feature Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.613] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:chunk_status Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.613] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/block_state_provider_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.613] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/foliage_placer_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.613] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/tree_decorator_type Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.613] [main/DEBUG] [net.minecraftforge.registries.ForgeRegistry/REGISTRIES]: Registry minecraft:worldgen/biome Sync: VANILLA -> ACTIVE +[12mai2026 16:10:30.616] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Vanilla freeze snapshot created +[12mai2026 16:10:30.652] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: -Dio.netty.noUnsafe: false +[12mai2026 16:10:30.652] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: Java version: 17 +[12mai2026 16:10:30.653] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: sun.misc.Unsafe.theUnsafe: available +[12mai2026 16:10:30.653] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: sun.misc.Unsafe.copyMemory: available +[12mai2026 16:10:30.653] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: sun.misc.Unsafe.storeFence: available +[12mai2026 16:10:30.653] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: java.nio.Buffer.address: available +[12mai2026 16:10:30.654] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: direct buffer constructor: unavailable java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled at io.netty.util.internal.ReflectionUtil.trySetAccessible(ReflectionUtil.java:31) ~[netty-common-4.1.82.Final.jar:4.1.82.Final] at io.netty.util.internal.PlatformDependent0$5.run(PlatformDependent0.java:288) ~[netty-common-4.1.82.Final.jar:4.1.82.Final] @@ -824,19 +814,19 @@ java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled at io.netty.util.ConstantPool.(ConstantPool.java:34) ~[netty-common-4.1.82.Final.jar:4.1.82.Final] at io.netty.util.AttributeKey$1.(AttributeKey.java:27) ~[netty-common-4.1.82.Final.jar:4.1.82.Final] at io.netty.util.AttributeKey.(AttributeKey.java:27) ~[netty-common-4.1.82.Final.jar:4.1.82.Final] - at net.minecraftforge.network.NetworkConstants.(NetworkConstants.java:34) ~[forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar:?] - at net.minecraftforge.network.NetworkHooks.init(NetworkHooks.java:52) ~[forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar:?] - at net.minecraft.server.Bootstrap.bootStrap(Bootstrap.java:62) ~[forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar:?] - at net.minecraftforge.data.loading.DatagenModLoader.begin(DatagenModLoader.java:42) ~[forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar:?] - at net.minecraft.data.Main.main(Main.java:90) ~[forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar:?] + at net.minecraftforge.network.NetworkConstants.(NetworkConstants.java:34) ~[forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar:?] + at net.minecraftforge.network.NetworkHooks.init(NetworkHooks.java:52) ~[forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar:?] + at net.minecraft.server.Bootstrap.bootStrap(Bootstrap.java:62) ~[forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar:?] + at net.minecraftforge.data.loading.DatagenModLoader.begin(DatagenModLoader.java:42) ~[forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar:?] + at net.minecraft.data.Main.main(Main.java:90) ~[forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?] at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.lang.reflect.Method.invoke(Method.java:569) ~[?:?] - at net.minecraftforge.fml.loading.targets.CommonLaunchHandler.runTarget(CommonLaunchHandler.java:111) ~[fmlloader-1.20.1-47.4.9.jar:?] - at net.minecraftforge.fml.loading.targets.CommonLaunchHandler.dataService(CommonLaunchHandler.java:107) ~[fmlloader-1.20.1-47.4.9.jar:?] - at net.minecraftforge.fml.loading.targets.ForgeDataUserdevLaunchHandler.devService(ForgeDataUserdevLaunchHandler.java:22) ~[fmlloader-1.20.1-47.4.9.jar:?] - at net.minecraftforge.fml.loading.targets.CommonDevLaunchHandler.lambda$makeService$7(CommonDevLaunchHandler.java:135) ~[fmlloader-1.20.1-47.4.9.jar:?] + at net.minecraftforge.fml.loading.targets.CommonLaunchHandler.runTarget(CommonLaunchHandler.java:111) ~[fmlloader-1.20.1-47.4.13.jar:?] + at net.minecraftforge.fml.loading.targets.CommonLaunchHandler.dataService(CommonLaunchHandler.java:107) ~[fmlloader-1.20.1-47.4.13.jar:?] + at net.minecraftforge.fml.loading.targets.ForgeDataUserdevLaunchHandler.devService(ForgeDataUserdevLaunchHandler.java:22) ~[fmlloader-1.20.1-47.4.13.jar:?] + at net.minecraftforge.fml.loading.targets.CommonDevLaunchHandler.lambda$makeService$7(CommonDevLaunchHandler.java:135) ~[fmlloader-1.20.1-47.4.13.jar:?] at cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:30) ~[modlauncher-10.0.9.jar:?] at cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53) ~[modlauncher-10.0.9.jar:?] at cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71) ~[modlauncher-10.0.9.jar:?] @@ -845,8 +835,8 @@ java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled at cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) ~[modlauncher-10.0.9.jar:?] at cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) ~[modlauncher-10.0.9.jar:?] at cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) ~[bootstraplauncher-1.1.2.jar:?] -[24nov.2025 23:48:53.953] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: java.nio.Bits.unaligned: available, true -[24nov.2025 23:48:53.953] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable +[12mai2026 16:10:30.666] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: java.nio.Bits.unaligned: available, true +[12mai2026 16:10:30.667] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable java.lang.IllegalAccessException: class io.netty.util.internal.PlatformDependent0$7 (in module io.netty.common) cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to module io.netty.common at jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392) ~[?:?] at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674) ~[?:?] @@ -859,19 +849,19 @@ java.lang.IllegalAccessException: class io.netty.util.internal.PlatformDependent at io.netty.util.ConstantPool.(ConstantPool.java:34) ~[netty-common-4.1.82.Final.jar:4.1.82.Final] at io.netty.util.AttributeKey$1.(AttributeKey.java:27) ~[netty-common-4.1.82.Final.jar:4.1.82.Final] at io.netty.util.AttributeKey.(AttributeKey.java:27) ~[netty-common-4.1.82.Final.jar:4.1.82.Final] - at net.minecraftforge.network.NetworkConstants.(NetworkConstants.java:34) ~[forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar:?] - at net.minecraftforge.network.NetworkHooks.init(NetworkHooks.java:52) ~[forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar:?] - at net.minecraft.server.Bootstrap.bootStrap(Bootstrap.java:62) ~[forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar:?] - at net.minecraftforge.data.loading.DatagenModLoader.begin(DatagenModLoader.java:42) ~[forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar:?] - at net.minecraft.data.Main.main(Main.java:90) ~[forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar:?] + at net.minecraftforge.network.NetworkConstants.(NetworkConstants.java:34) ~[forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar:?] + at net.minecraftforge.network.NetworkHooks.init(NetworkHooks.java:52) ~[forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar:?] + at net.minecraft.server.Bootstrap.bootStrap(Bootstrap.java:62) ~[forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar:?] + at net.minecraftforge.data.loading.DatagenModLoader.begin(DatagenModLoader.java:42) ~[forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar:?] + at net.minecraft.data.Main.main(Main.java:90) ~[forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?] at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.lang.reflect.Method.invoke(Method.java:569) ~[?:?] - at net.minecraftforge.fml.loading.targets.CommonLaunchHandler.runTarget(CommonLaunchHandler.java:111) ~[fmlloader-1.20.1-47.4.9.jar:?] - at net.minecraftforge.fml.loading.targets.CommonLaunchHandler.dataService(CommonLaunchHandler.java:107) ~[fmlloader-1.20.1-47.4.9.jar:?] - at net.minecraftforge.fml.loading.targets.ForgeDataUserdevLaunchHandler.devService(ForgeDataUserdevLaunchHandler.java:22) ~[fmlloader-1.20.1-47.4.9.jar:?] - at net.minecraftforge.fml.loading.targets.CommonDevLaunchHandler.lambda$makeService$7(CommonDevLaunchHandler.java:135) ~[fmlloader-1.20.1-47.4.9.jar:?] + at net.minecraftforge.fml.loading.targets.CommonLaunchHandler.runTarget(CommonLaunchHandler.java:111) ~[fmlloader-1.20.1-47.4.13.jar:?] + at net.minecraftforge.fml.loading.targets.CommonLaunchHandler.dataService(CommonLaunchHandler.java:107) ~[fmlloader-1.20.1-47.4.13.jar:?] + at net.minecraftforge.fml.loading.targets.ForgeDataUserdevLaunchHandler.devService(ForgeDataUserdevLaunchHandler.java:22) ~[fmlloader-1.20.1-47.4.13.jar:?] + at net.minecraftforge.fml.loading.targets.CommonDevLaunchHandler.lambda$makeService$7(CommonDevLaunchHandler.java:135) ~[fmlloader-1.20.1-47.4.13.jar:?] at cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:30) ~[modlauncher-10.0.9.jar:?] at cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53) ~[modlauncher-10.0.9.jar:?] at cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71) ~[modlauncher-10.0.9.jar:?] @@ -880,222 +870,196 @@ java.lang.IllegalAccessException: class io.netty.util.internal.PlatformDependent at cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) ~[modlauncher-10.0.9.jar:?] at cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) ~[modlauncher-10.0.9.jar:?] at cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) ~[bootstraplauncher-1.1.2.jar:?] -[24nov.2025 23:48:53.954] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: java.nio.DirectByteBuffer.(long, int): unavailable -[24nov.2025 23:48:53.954] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: sun.misc.Unsafe: available -[24nov.2025 23:48:53.954] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: maxDirectMemory: 17112760320 bytes (maybe) -[24nov.2025 23:48:53.955] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: -Dio.netty.tmpdir: C:\Users\matga\AppData\Local\Temp (java.io.tmpdir) -[24nov.2025 23:48:53.955] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: -Dio.netty.bitMode: 64 (sun.arch.data.model) -[24nov.2025 23:48:53.955] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: Platform: Windows -[24nov.2025 23:48:53.955] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: -Dio.netty.maxDirectMemory: -1 bytes -[24nov.2025 23:48:53.955] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: -Dio.netty.uninitializedArrayAllocationThreshold: -1 -[24nov.2025 23:48:53.956] [main/DEBUG] [io.netty.util.internal.CleanerJava9/]: java.nio.ByteBuffer.cleaner(): available -[24nov.2025 23:48:53.956] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: -Dio.netty.noPreferDirect: false -[24nov.2025 23:48:53.986] [main/DEBUG] [net.minecraftforge.network.NetworkHooks/]: Loading Network data for FML net version: FML3 -[24nov.2025 23:48:53.995] [main/DEBUG] [net.minecraftforge.fml.ModWorkManager/LOADING]: Using 24 threads for parallel mod-loading -[24nov.2025 23:48:53.997] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:53.998] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for betterdays.BetterDaysForge -[24nov.2025 23:48:53.999] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:53.999] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for snownee.jade.util.CommonProxy -[24nov.2025 23:48:54.000] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.000] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for mezz.jei.forge.JustEnoughItems -[24nov.2025 23:48:54.000] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.000] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for dev.worldgen.lithostitched.LithostitchedForge -[24nov.2025 23:48:54.001] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.001] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for me.lucko.spark.forge.ForgeSparkMod -[24nov.2025 23:48:54.001] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.001] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for com.Gabou.sereneseasonsplus.SereneSeasonsPlusForge -[24nov.2025 23:48:54.002] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.002] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for nonamecrackers2.crackerslib.CrackersLib -[24nov.2025 23:48:54.002] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.002] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for com.llamalad7.mixinextras.platform.forge.MixinExtrasMod -[24nov.2025 23:48:54.002] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.003] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for glitchcore.forge.GlitchCoreForge -[24nov.2025 23:48:54.003] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.003] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for sereneseasons.forge.core.SereneSeasonsForge -[24nov.2025 23:48:54.004] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.004] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for dev.nonamecrackers2.simpleclouds.SimpleCloudsMod -[24nov.2025 23:48:54.004] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.004] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for net.Gabou.projectatmosphere.ProjectAtmosphere -[24nov.2025 23:48:54.005] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.005] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for dev.architectury.forge.ArchitecturyForge -[24nov.2025 23:48:54.005] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.005] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for technology.roughness.whitenoise.WhiteNoiseForge -[24nov.2025 23:48:54.006] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.006] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for me.shedaniel.clothconfig.ClothConfigForge -[24nov.2025 23:48:54.006] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.006] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for net.Gabou.gaboulibs.forge.GaboulibsForge -[24nov.2025 23:48:54.006] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.006] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for net.minecraftforge.common.ForgeMod -[24nov.2025 23:48:54.007] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.007] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for auroras.Auroras -[24nov.2025 23:48:54.007] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.007] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for rainbows.Rainbows -[24nov.2025 23:48:54.007] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.007] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for me.jellysquid.mods.sodium.client.SodiumClientMod -[24nov.2025 23:48:54.008] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.008] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for me.jellysquid.mods.sodium.client.RubidiumStub -[24nov.2025 23:48:54.008] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.008] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for me.srrapero720.chloride.Chloride -[24nov.2025 23:48:54.009] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 - got cpw.mods.cl.ModuleClassLoader@398474a2 -[24nov.2025 23:48:54.009] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for dev.worldgen.tectonic.TectonicLexforge -[24nov.2025 23:48:54.017] [modloading-worker-0/INFO] [projectatmosphere/]: Project Atmosphere is loading! -[24nov.2025 23:48:54.019] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for mixinextras -[24nov.2025 23:48:54.019] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for glitchcore -[24nov.2025 23:48:54.024] [modloading-worker-0/DEBUG] [Better Days/]: Loaded betterdays.platform.ForgeClientPlatform@7eeadf10 for service interface betterdays.platform.services.IClientPlatform -[24nov.2025 23:48:54.024] [modloading-worker-0/INFO] [chloride/Main]: Chloride is here, lets make your experience taste-able -[24nov.2025 23:48:54.024] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for chloride -[24nov.2025 23:48:54.024] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for rubidium -[24nov.2025 23:48:54.027] [modloading-worker-0/DEBUG] [Better Days/]: Loaded betterdays.platform.ForgePlatform@3ecc3959 for service interface betterdays.platform.services.IPlatform -[24nov.2025 23:48:54.028] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for cloth_config -[24nov.2025 23:48:54.028] [modloading-worker-0/DEBUG] [mixin/]: Mixing MinecraftServerMixin from sereneseasonsplus.mixins.json into net.minecraft.server.MinecraftServer -[24nov.2025 23:48:54.029] [modloading-worker-0/DEBUG] [Better Days/]: Loaded betterdays.platform.ForgeRegistryProvider@3b3132ef for service interface betterdays.platform.services.IRegistryFactory -[24nov.2025 23:48:54.034] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinLevelEvent from architectury.mixins.json into net.minecraftforge.event.level.LevelEvent -[24nov.2025 23:48:54.036] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.VillagerTradesEventHandler to FORGE -[24nov.2025 23:48:54.036] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.sodium.SodiumFeatures to FORGE -[24nov.2025 23:48:54.043] [modloading-worker-0/DEBUG] [mixin/]: Mixing impl.MixinEnvironment from glitchcore.forge.mixins.json into glitchcore.util.Environment -[24nov.2025 23:48:54.060] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.TooltipEventHandler to FORGE -[24nov.2025 23:48:54.067] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.ToolModificationEventHandler to FORGE -[24nov.2025 23:48:54.068] [modloading-worker-0/DEBUG] [mixin/]: Mixing vivecraft.MixinMinecraftVRMixin from simpleclouds.mixins.json into net.minecraft.client.Minecraft -[24nov.2025 23:48:54.069] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$onVRStateSwitched_vivecrafft$switchVRSate$0(Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;)V to mdd47aa8$lambda$simpleclouds$onVRStateSwitched_vivecrafft$switchVRSate$0$0 in simpleclouds.mixins.json:vivecraft.MixinMinecraftVRMixin -[24nov.2025 23:48:54.069] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.MixinMinecraft from glitchcore.mixins.json into net.minecraft.client.Minecraft -[24nov.2025 23:48:54.069] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for spark -[24nov.2025 23:48:54.072] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinMinecraft from simpleclouds.mixins.json into net.minecraft.client.Minecraft -[24nov.2025 23:48:54.072] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$onClientLevelChange_setLevel$1(Lnet/minecraft/client/multiplayer/ClientLevel;Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;)V to mdd47aa8$lambda$simpleclouds$onClientLevelChange_setLevel$1$1 in simpleclouds.mixins.json:MixinMinecraft -[24nov.2025 23:48:54.073] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$appendCrashReportDetails_fillReport$0(Lnet/minecraft/CrashReport;Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;)V to mdd47aa8$lambda$simpleclouds$appendCrashReportDetails_fillReport$0$2 in simpleclouds.mixins.json:MixinMinecraft -[24nov.2025 23:48:54.077] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinMinecraft from architectury.mixins.json into net.minecraft.client.Minecraft -[24nov.2025 23:48:54.077] [modloading-worker-0/DEBUG] [mixin/]: Mixing MinecraftMixin from chloride.mixin.json into net.minecraft.client.Minecraft -[24nov.2025 23:48:54.078] [modloading-worker-0/DEBUG] [mixin/]: Mixing OverlayMixin from chloride.mixin.json into net.minecraft.client.Minecraft -[24nov.2025 23:48:54.078] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file crackerslib-client.toml for crackerslib tracking -[24nov.2025 23:48:54.079] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.render.MinecraftAccessor from embeddium.mixins.json into net.minecraft.client.Minecraft -[24nov.2025 23:48:54.080] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.MinecraftClientMixin from embeddium.mixins.json into net.minecraft.client.Minecraft -[24nov.2025 23:48:54.080] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$postInit$0(Lnet/minecraft/client/gui/screens/Screen;)Lnet/minecraft/client/gui/screens/Screen; to mdd47aa8$lambda$postInit$0$3 in embeddium.mixins.json:core.MinecraftClientMixin -[24nov.2025 23:48:54.149] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for gaboulibs -[24nov.2025 23:48:54.151] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file jei-server.toml for jei tracking -[24nov.2025 23:48:54.154] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for crackerslib -[24nov.2025 23:48:54.155] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.TickEventHandler to FORGE -[24nov.2025 23:48:54.155] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for whitenoise -[24nov.2025 23:48:54.155] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.Zoom to FORGE -[24nov.2025 23:48:54.157] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.TagsUpdatedEventHandler to FORGE -[24nov.2025 23:48:54.158] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file auroras-common.toml for auroras tracking -[24nov.2025 23:48:54.158] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file projectatmosphere-common.toml for projectatmosphere tracking -[24nov.2025 23:48:54.161] [modloading-worker-0/DEBUG] [mixin/]: Mixing compat.auroras.AuroraRendererMixin from projectatmosphere.mixins.json into auroras.util.AuroraRenderer -[24nov.2025 23:48:54.171] [modloading-worker-0/INFO] [projectatmosphere/]: No temperature mod loaded, skipping compatibility setup. -[24nov.2025 23:48:54.171] [modloading-worker-0/INFO] [projectatmosphere/]: Sand Storms mod not found. -[24nov.2025 23:48:54.171] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.forge.ForgeVersion/CORE]: Forge Version package package net.minecraftforge.versions.forge, Forge, version 47.4 from cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 -[24nov.2025 23:48:54.172] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.forge.ForgeVersion/CORE]: Found Forge version 47.4.9 -[24nov.2025 23:48:54.172] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.RegistryEventHandler to MOD -[24nov.2025 23:48:54.172] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.forge.ForgeVersion/CORE]: Found Forge spec 47.4 -[24nov.2025 23:48:54.172] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.forge.ForgeVersion/CORE]: Found Forge group net.minecraftforge -[24nov.2025 23:48:54.172] [modloading-worker-0/DEBUG] [WhiteNoise/]: Loaded technology.roughness.whitenoise.platform.ForgeConfigHelper@4e8d369a for service interface technology.roughness.whitenoise.platform.services.IConfigHelper -[24nov.2025 23:48:54.173] [modloading-worker-0/DEBUG] [WhiteNoise/]: Loaded technology.roughness.whitenoise.platform.ForgeClientPlatform@36f106ee for service interface technology.roughness.whitenoise.platform.services.IClientPlatform -[24nov.2025 23:48:54.174] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.mcp.MCPVersion/CORE]: MCP Version package package net.minecraftforge.versions.mcp, Minecraft, version 1.20.1 from cpw.mods.modlauncher.TransformingClassLoader@753fd7a1 -[24nov.2025 23:48:54.174] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.mcp.MCPVersion/CORE]: Found MC version information 1.20.1 -[24nov.2025 23:48:54.174] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.mcp.MCPVersion/CORE]: Found MCP version information 20230612.114412 -[24nov.2025 23:48:54.174] [modloading-worker-0/INFO] [net.minecraftforge.common.ForgeMod/FORGEMOD]: Forge mod loading, version 47.4.9, for MC 1.20.1 with MCP 20230612.114412 -[24nov.2025 23:48:54.175] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.RegisterParticleProvidersEventHandler to MOD -[24nov.2025 23:48:54.171] [modloading-worker-0/INFO] [projectatmosphere/]: Auroras detected – enabling seasonal aurora tuning. -[24nov.2025 23:48:54.176] [modloading-worker-0/INFO] [net.minecraftforge.common.MinecraftForge/FORGE]: MinecraftForge v47.4.9 Initialized -[24nov.2025 23:48:54.175] [modloading-worker-0/INFO] [projectatmosphere/]: Rainbows detected – enabling precipitation bridge. -[24nov.2025 23:48:54.176] [modloading-worker-0/DEBUG] [WhiteNoise/]: Loaded technology.roughness.whitenoise.platform.ForgePlatform@20ac33f3 for service interface technology.roughness.whitenoise.platform.services.IPlatform -[24nov.2025 23:48:54.176] [modloading-worker-0/INFO] [projectatmosphere/]: Tectonic detected – enabling refined ocean geometry. -[24nov.2025 23:48:54.178] [modloading-worker-0/INFO] [projectatmosphere/]: Continents mod not detected. -[24nov.2025 23:48:54.182] [modloading-worker-0/DEBUG] [mixin/]: Mixing ParticlesMixins$EngineMixin from chloride.mixin.json into net.minecraft.client.particle.ParticleEngine -[24nov.2025 23:48:54.209] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.RegisterCommandsEventHandler to FORGE -[24nov.2025 23:48:54.212] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.PlayerQuitClientEventHandler to FORGE -[24nov.2025 23:48:54.215] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file sereneseasonsplus-common.toml for sereneseasonsplus tracking -[24nov.2025 23:48:54.215] [modloading-worker-0/DEBUG] [mixin/]: Mixing BorderlessMixin$WindowMixin from chloride.mixin.json into com.mojang.blaze3d.platform.Window -[24nov.2025 23:48:54.215] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.PlayerLoggedInEventHandler to FORGE -[24nov.2025 23:48:54.216] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for sereneseasonsplus -[24nov.2025 23:48:54.217] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file rainbows-common.toml for rainbows tracking -[24nov.2025 23:48:54.217] [modloading-worker-0/DEBUG] [mixin/]: Mixing workarounds.context_creation.WindowMixin from embeddium.mixins.json into com.mojang.blaze3d.platform.Window -[24nov.2025 23:48:54.252] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinRenderTargetAccessor from simpleclouds.mixins.json into com.mojang.blaze3d.pipeline.RenderTarget -[24nov.2025 23:48:54.254] [modloading-worker-0/DEBUG] [mixin/]: Mixing vivecraft.MixinRenderTarget from simpleclouds.mixins.json into com.mojang.blaze3d.pipeline.RenderTarget -[24nov.2025 23:48:54.258] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.immediate.matrix_stack.VertexConsumerMixin from embeddium.mixins.json into com.mojang.blaze3d.vertex.VertexConsumer -[24nov.2025 23:48:54.280] [modloading-worker-0/DEBUG] [mixin/]: Mixing impl.MixinPacketHandler from glitchcore.forge.mixins.json into glitchcore.network.PacketHandler -[24nov.2025 23:48:54.281] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$init$3(Ljava/lang/String;)Ljava/lang/String; to mdd47aa8$lambda$init$3$0 in glitchcore.forge.mixins.json:impl.MixinPacketHandler -[24nov.2025 23:48:54.281] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$sendToPlayer$2(Lnet/minecraft/server/level/ServerPlayer;)Lnet/minecraft/server/level/ServerPlayer; to mdd47aa8$lambda$sendToPlayer$2$1 in glitchcore.forge.mixins.json:impl.MixinPacketHandler -[24nov.2025 23:48:54.281] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$register$1(Lglitchcore/network/CustomPacket;Lglitchcore/network/CustomPacket;Ljava/util/function/Supplier;)V to mdd47aa8$lambda$register$1$2 in glitchcore.forge.mixins.json:impl.MixinPacketHandler -[24nov.2025 23:48:54.281] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$register$0(Lglitchcore/network/CustomPacket;Lglitchcore/network/CustomPacket;Ljava/util/function/Supplier;)V to mdd47aa8$lambda$register$0$3 in glitchcore.forge.mixins.json:impl.MixinPacketHandler -[24nov.2025 23:48:54.289] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.GameRendererMixin from auroras.mixins.json into net.minecraft.client.renderer.GameRenderer -[24nov.2025 23:48:54.290] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinGameRendererAccessor from crackerslib.mixins.json into net.minecraft.client.renderer.GameRenderer -[24nov.2025 23:48:54.295] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinGameRenderer from simpleclouds.mixins.json into net.minecraft.client.renderer.GameRenderer -[24nov.2025 23:48:54.296] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$resizeRenderer_resize$0(IILdev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;)V to mdd47aa8$lambda$simpleclouds$resizeRenderer_resize$0$0 in simpleclouds.mixins.json:MixinGameRenderer -[24nov.2025 23:48:54.298] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinGameRendererAccessor from simpleclouds.mixins.json into net.minecraft.client.renderer.GameRenderer -[24nov.2025 23:48:54.300] [modloading-worker-0/DEBUG] [mixin/]: Mixing darkness.GameRendererMixin from chloride.mixin.json into net.minecraft.client.renderer.GameRenderer -[24nov.2025 23:48:54.301] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.gui.hooks.console.GameRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.GameRenderer -[24nov.2025 23:48:54.316] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.MixinGuiGraphics from glitchcore.forge.mixins.json into net.minecraft.client.gui.GuiGraphics -[24nov.2025 23:48:54.318] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.textures.animations.tracking.DrawContextMixin from embeddium.mixins.json into net.minecraft.client.gui.GuiGraphics -[24nov.2025 23:48:54.325] [modloading-worker-0/DEBUG] [mixin/]: Mixing compat.rainbows.RainbowsRendererParticleMixin from projectatmosphere.mixins.json into rainbows.util.RainbowsRendererParticle -[24nov.2025 23:48:54.330] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.LevelRenderEventHandler to FORGE -[24nov.2025 23:48:54.330] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.textures.animations.tracking.SpriteBillboardParticleMixin from embeddium.mixins.json into net.minecraft.client.particle.TextureSheetParticle -[24nov.2025 23:48:54.334] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.particle.BillboardParticleMixin from embeddium.mixins.json into net.minecraft.client.particle.SingleQuadParticle -[24nov.2025 23:48:54.335] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for auroras -[24nov.2025 23:48:54.349] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.options.overlays.InGameHudMixin from embeddium.mixins.json into net.minecraft.client.gui.Gui -[24nov.2025 23:48:54.356] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.gui.debug.ForgeGuiMixin from embeddium.mixins.json into net.minecraftforge.client.gui.overlay.ForgeGui -[24nov.2025 23:48:54.357] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$renderLinesVanilla$0(Lme/jellysquid/mods/sodium/mixin/features/render/gui/debug/DebugScreenOverlayAccessor;Lnet/minecraft/client/gui/GuiGraphics;Ljava/util/ArrayList;Ljava/util/ArrayList;)V to mdd47aa8$lambda$renderLinesVanilla$0$0 in embeddium.mixins.json:features.render.gui.debug.ForgeGuiMixin -[24nov.2025 23:48:54.359] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file forge-client.toml for forge tracking -[24nov.2025 23:48:54.359] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file forge-server.toml for forge tracking -[24nov.2025 23:48:54.359] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.ModLoadingContext/]: Attempted to register an empty config for type COMMON on mod forge -[24nov.2025 23:48:54.378] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinLightTexture from simpleclouds.mixins.json into net.minecraft.client.renderer.LightTexture -[24nov.2025 23:48:54.379] [modloading-worker-0/DEBUG] [mixin/]: Mixing darkness.LightTextureMixin from chloride.mixin.json into net.minecraft.client.renderer.LightTexture -[24nov.2025 23:48:54.382] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.Zoom$ModEvents to MOD -[24nov.2025 23:48:54.383] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for jade -[24nov.2025 23:48:54.392] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.LevelRendererMixin from auroras.mixins.json into net.minecraft.client.renderer.LevelRenderer -[24nov.2025 23:48:54.395] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.world.clouds.WorldRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.LevelRenderer -[24nov.2025 23:48:54.396] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.MixinLevelRenderer from sereneseasons.mixins.json into net.minecraft.client.renderer.LevelRenderer -[24nov.2025 23:48:54.396] [modloading-worker-0/DEBUG] [mixin/]: Mixing ParticlesMixins$LevelRendererMixin from chloride.mixin.json into net.minecraft.client.renderer.LevelRenderer -[24nov.2025 23:48:54.396] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.options.weather.WorldRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.LevelRenderer -[24nov.2025 23:48:54.396] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.gui.outlines.WorldRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.LevelRenderer -[24nov.2025 23:48:54.400] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.world.sky.WorldRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.LevelRenderer -[24nov.2025 23:48:54.401] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.render.world.WorldRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.LevelRenderer -[24nov.2025 23:48:54.401] [modloading-worker-0/WARN] [mixin/]: Static binding violation: PRIVATE @Overwrite method setSectionDirty in embeddium.mixins.json:core.render.world.WorldRendererMixin cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded. -[24nov.2025 23:48:54.408] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinLevelRenderer from simpleclouds.mixins.json into net.minecraft.client.renderer.LevelRenderer -[24nov.2025 23:48:54.495] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.render.MatrixStackMixin from embeddium.mixins.json into com.mojang.blaze3d.vertex.PoseStack -[24nov.2025 23:48:54.498] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.Overlay to FORGE -[24nov.2025 23:48:54.501] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.textures.animations.tracking.SpriteAtlasTextureMixin from embeddium.mixins.json into net.minecraft.client.renderer.texture.TextureAtlas -[24nov.2025 23:48:54.516] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.HideNametag to FORGE -[24nov.2025 23:48:54.534] [modloading-worker-0/DEBUG] [WhiteNoise/CONFIG]: Config file betterdays-client.toml for betterdays added to tracking -[24nov.2025 23:48:54.535] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinFrustumAccessor from simpleclouds.mixins.json into net.minecraft.client.renderer.culling.Frustum -[24nov.2025 23:48:54.536] [modloading-worker-0/DEBUG] [WhiteNoise/CONFIG]: Config file betterdays-common.toml for betterdays added to tracking -[24nov.2025 23:48:54.536] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for betterdays -[24nov.2025 23:48:54.538] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.render.frustum.FrustumMixin from embeddium.mixins.json into net.minecraft.client.renderer.culling.Frustum -[24nov.2025 23:48:54.542] [modloading-worker-0/DEBUG] [mixin/]: Mixing NameTagToggleMixin from chloride.mixin.json into net.minecraft.client.renderer.entity.EntityRenderer -[24nov.2025 23:48:54.542] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.entity.cull.EntityRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.entity.EntityRenderer -[24nov.2025 23:48:54.549] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.InteractionEventHandler to FORGE -[24nov.2025 23:48:54.551] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.FastBlocks to FORGE -[24nov.2025 23:48:54.551] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for rainbows -[24nov.2025 23:48:54.555] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.ColorsEventHandler to MOD -[24nov.2025 23:48:54.556] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.FastBlocks$ModEvents to MOD -[24nov.2025 23:48:54.559] [modloading-worker-0/DEBUG] [mezz.jei.common.platform.Services/]: Loaded mezz.jei.forge.platform.PlatformHelper@36e6b47c for service interface mezz.jei.common.platform.IPlatformHelper -[24nov.2025 23:48:54.562] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.ChlorideConfig to MOD -[24nov.2025 23:48:54.563] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.model.colors.ItemColorsMixin from embeddium.mixins.json into net.minecraft.client.color.item.ItemColors -[24nov.2025 23:48:54.574] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.model.colors.BlockColorsMixin from embeddium.mixins.json into net.minecraft.client.color.block.BlockColors -[24nov.2025 23:48:54.581] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file simpleclouds-client.toml for simpleclouds tracking -[24nov.2025 23:48:54.582] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file simpleclouds-common.toml for simpleclouds tracking -[24nov.2025 23:48:54.582] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file simpleclouds-server.toml for simpleclouds tracking -[24nov.2025 23:48:54.584] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for forge -[24nov.2025 23:48:54.584] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.minecraftforge.common.ForgeSpawnEggItem$CommonHandler to MOD -[24nov.2025 23:48:54.588] [modloading-worker-0/DEBUG] [mixin/]: Mixing FontShadowMixin from chloride.mixin.json into net.minecraft.client.gui.Font -[24nov.2025 23:48:54.588] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for simpleclouds -[24nov.2025 23:48:54.594] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.gui.screen.LevelLoadingScreenMixin from embeddium.mixins.json into net.minecraft.client.gui.screens.LevelLoadingScreen -[24nov.2025 23:48:54.594] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$renderChunks$0(Lit/unimi/dsi/fastutil/objects/Object2IntMap$Entry;)V to mdd47aa8$lambda$renderChunks$0$0 in embeddium.mixins.json:features.gui.screen.LevelLoadingScreenMixin -[24nov.2025 23:48:54.594] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.minecraftforge.common.ForgeSpawnEggItem$ColorRegisterHandler to MOD -[24nov.2025 23:48:54.603] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.minecraftforge.client.model.data.ModelDataManager to FORGE -[24nov.2025 23:48:54.606] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.model.ModelDataMixin from embeddium.mixins.json into net.minecraftforge.client.model.data.ModelData -[24nov.2025 23:48:54.610] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.minecraftforge.client.ForgeHooksClient$ClientEvents to MOD -[24nov.2025 23:48:54.610] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.Chloride to MOD -[24nov.2025 23:48:54.610] [modloading-worker-0/ERROR] [Embeddium/]: Failed to update fingerprint +[12mai2026 16:10:30.668] [main/DEBUG] [io.netty.util.internal.PlatformDependent0/]: java.nio.DirectByteBuffer.(long, int): unavailable +[12mai2026 16:10:30.668] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: sun.misc.Unsafe: available +[12mai2026 16:10:30.669] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: maxDirectMemory: 17112760320 bytes (maybe) +[12mai2026 16:10:30.669] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: -Dio.netty.tmpdir: C:\Users\matga\AppData\Local\Temp (java.io.tmpdir) +[12mai2026 16:10:30.669] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: -Dio.netty.bitMode: 64 (sun.arch.data.model) +[12mai2026 16:10:30.669] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: Platform: Windows +[12mai2026 16:10:30.669] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: -Dio.netty.maxDirectMemory: -1 bytes +[12mai2026 16:10:30.670] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: -Dio.netty.uninitializedArrayAllocationThreshold: -1 +[12mai2026 16:10:30.670] [main/DEBUG] [io.netty.util.internal.CleanerJava9/]: java.nio.ByteBuffer.cleaner(): available +[12mai2026 16:10:30.670] [main/DEBUG] [io.netty.util.internal.PlatformDependent/]: -Dio.netty.noPreferDirect: false +[12mai2026 16:10:30.701] [main/DEBUG] [net.minecraftforge.network.NetworkHooks/]: Loading Network data for FML net version: FML3 +[12mai2026 16:10:30.711] [main/DEBUG] [net.minecraftforge.fml.ModWorkManager/LOADING]: Using 24 threads for parallel mod-loading +[12mai2026 16:10:30.714] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.715] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for betterdays.BetterDaysForge +[12mai2026 16:10:30.717] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.717] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for nonamecrackers2.crackerslib.CrackersLib +[12mai2026 16:10:30.717] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.717] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for com.llamalad7.mixinextras.platform.forge.MixinExtrasMod +[12mai2026 16:10:30.718] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.718] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for glitchcore.forge.GlitchCoreForge +[12mai2026 16:10:30.718] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.718] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for sereneseasons.forge.core.SereneSeasonsForge +[12mai2026 16:10:30.718] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.718] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for snownee.jade.util.CommonProxy +[12mai2026 16:10:30.719] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.719] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for dev.nonamecrackers2.simpleclouds.SimpleCloudsMod +[12mai2026 16:10:30.720] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.721] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for dev.architectury.forge.ArchitecturyForge +[12mai2026 16:10:30.721] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.721] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for technology.roughness.whitenoise.WhiteNoiseForge +[12mai2026 16:10:30.721] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.721] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for com.seibel.distanthorizons.forge.ForgeMain +[12mai2026 16:10:30.722] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.722] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for mezz.jei.forge.JustEnoughItems +[12mai2026 16:10:30.722] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.722] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for dev.worldgen.lithostitched.LithostitchedForge +[12mai2026 16:10:30.722] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.722] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for me.shedaniel.clothconfig.ClothConfigForge +[12mai2026 16:10:30.723] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.723] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for me.lucko.spark.forge.ForgeSparkMod +[12mai2026 16:10:30.724] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.724] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for net.Gabou.gaboulibs.forge.GaboulibsForge +[12mai2026 16:10:30.724] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.724] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for net.minecraftforge.common.ForgeMod +[12mai2026 16:10:30.725] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.725] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for me.jellysquid.mods.sodium.client.SodiumClientMod +[12mai2026 16:10:30.727] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.727] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for me.jellysquid.mods.sodium.client.RubidiumStub +[12mai2026 16:10:30.727] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.727] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for me.srrapero720.chloride.Chloride +[12mai2026 16:10:30.727] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.727] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for net.Gabou.projectatmosphere.ProjectAtmosphere +[12mai2026 16:10:30.729] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.729] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for com.Gabou.sereneseasonsplus.SereneSeasonsPlusForge +[12mai2026 16:10:30.729] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider/LOADING]: Loading FMLModContainer from classloader cpw.mods.modlauncher.TransformingClassLoader@7c663eaf - got cpw.mods.cl.ModuleClassLoader@a64e035 +[12mai2026 16:10:30.730] [main/DEBUG] [net.minecraftforge.fml.javafmlmod.FMLModContainer/LOADING]: Creating FMLModContainer instance for dev.worldgen.tectonic.TectonicLexforge +[12mai2026 16:10:30.741] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for glitchcore +[12mai2026 16:10:30.741] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for mixinextras +[12mai2026 16:10:30.747] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for rubidium +[12mai2026 16:10:30.747] [modloading-worker-0/INFO] [chloride/Main]: Chloride is here, lets make your experience taste-able +[12mai2026 16:10:30.747] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for chloride +[12mai2026 16:10:30.747] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for distanthorizons +[12mai2026 16:10:30.747] [modloading-worker-0/INFO] [projectatmosphere/]: Project Atmosphere is loading! +[12mai2026 16:10:30.751] [modloading-worker-0/DEBUG] [Better Days/]: Loaded betterdays.platform.ForgeClientPlatform@6a7ea8a4 for service interface betterdays.platform.services.IClientPlatform +[12mai2026 16:10:30.754] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for cloth_config +[12mai2026 16:10:30.755] [modloading-worker-0/DEBUG] [mixin/]: Mixing MinecraftServerMixin from sereneseasonsplus.mixins.json into net.minecraft.server.MinecraftServer +[12mai2026 16:10:30.755] [modloading-worker-0/DEBUG] [Better Days/]: Loaded betterdays.platform.ForgePlatform@359f893c for service interface betterdays.platform.services.IPlatform +[12mai2026 16:10:30.758] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinLevelEvent from architectury.mixins.json into net.minecraftforge.event.level.LevelEvent +[12mai2026 16:10:30.771] [modloading-worker-0/DEBUG] [Better Days/]: Loaded betterdays.platform.ForgeRegistryProvider@110907bb for service interface betterdays.platform.services.IRegistryFactory +[12mai2026 16:10:30.773] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.sodium.SodiumFeatures to FORGE +[12mai2026 16:10:30.773] [modloading-worker-0/DEBUG] [mixin/]: Mixing impl.MixinEnvironment from glitchcore.forge.mixins.json into glitchcore.util.Environment +[12mai2026 16:10:30.773] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.VillagerTradesEventHandler to FORGE +[12mai2026 16:10:30.804] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.TooltipEventHandler to FORGE +[12mai2026 16:10:30.814] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for spark +[12mai2026 16:10:30.822] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.ToolModificationEventHandler to FORGE +[12mai2026 16:10:30.824] [modloading-worker-0/DEBUG] [mixin/]: Mixing vivecraft.MixinMinecraftVRMixin from simpleclouds.mixins.json into net.minecraft.client.Minecraft +[12mai2026 16:10:30.824] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$onVRStateSwitched_vivecrafft$switchVRSate$0(Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;)V to mdb27d7e$lambda$simpleclouds$onVRStateSwitched_vivecrafft$switchVRSate$0$0 in simpleclouds.mixins.json:vivecraft.MixinMinecraftVRMixin +[12mai2026 16:10:30.824] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for whitenoise +[12mai2026 16:10:30.824] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.MinecraftCrashHandlerMixin from projectatmosphere.mixins.json into net.minecraft.client.Minecraft +[12mai2026 16:10:30.827] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.MixinMinecraft from glitchcore.mixins.json into net.minecraft.client.Minecraft +[12mai2026 16:10:30.833] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinMinecraft from simpleclouds.mixins.json into net.minecraft.client.Minecraft +[12mai2026 16:10:30.833] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$onClientLevelChange_setLevel$1(Lnet/minecraft/client/multiplayer/ClientLevel;Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;)V to mdb27d7e$lambda$simpleclouds$onClientLevelChange_setLevel$1$1 in simpleclouds.mixins.json:MixinMinecraft +[12mai2026 16:10:30.833] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$appendCrashReportDetails_fillReport$0(Lnet/minecraft/CrashReport;Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;)V to mdb27d7e$lambda$simpleclouds$appendCrashReportDetails_fillReport$0$2 in simpleclouds.mixins.json:MixinMinecraft +[12mai2026 16:10:30.839] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinMinecraft from architectury.mixins.json into net.minecraft.client.Minecraft +[12mai2026 16:10:30.840] [modloading-worker-0/DEBUG] [mixin/]: Mixing MinecraftMixin from chloride.mixin.json into net.minecraft.client.Minecraft +[12mai2026 16:10:30.840] [modloading-worker-0/DEBUG] [mixin/]: Mixing OverlayMixin from chloride.mixin.json into net.minecraft.client.Minecraft +[12mai2026 16:10:30.842] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.render.MinecraftAccessor from embeddium.mixins.json into net.minecraft.client.Minecraft +[12mai2026 16:10:30.843] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.MinecraftClientMixin from embeddium.mixins.json into net.minecraft.client.Minecraft +[12mai2026 16:10:30.843] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$postInit$0(Lnet/minecraft/client/gui/screens/Screen;)Lnet/minecraft/client/gui/screens/Screen; to mdb27d7e$lambda$postInit$0$3 in embeddium.mixins.json:core.MinecraftClientMixin +[12mai2026 16:10:30.926] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.Zoom to FORGE +[12mai2026 16:10:30.926] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.TickEventHandler to FORGE +[12mai2026 16:10:30.930] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.TagsUpdatedEventHandler to FORGE +[12mai2026 16:10:30.932] [modloading-worker-0/WARN] [CompatUtils/]: Detected both Project Atmosphere and Serene Seasons Plus; running with both may be unstable. Elected host: PROJECT_ATMOSPHERE +[12mai2026 16:10:30.933] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for gaboulibs +[12mai2026 16:10:30.938] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.RegistryEventHandler to MOD +[12mai2026 16:10:30.939] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file projectatmosphere-common.toml for projectatmosphere tracking +[12mai2026 16:10:30.940] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file crackerslib-client.toml for crackerslib tracking +[12mai2026 16:10:30.940] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file jei-server.toml for jei tracking +[12mai2026 16:10:30.941] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.forge.ForgeVersion/CORE]: Forge Version package package net.minecraftforge.versions.forge, Forge, version 47.4 from cpw.mods.modlauncher.TransformingClassLoader@7c663eaf +[12mai2026 16:10:30.942] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.RegisterParticleProvidersEventHandler to MOD +[12mai2026 16:10:30.942] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.forge.ForgeVersion/CORE]: Found Forge version 47.4.13 +[12mai2026 16:10:30.942] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.forge.ForgeVersion/CORE]: Found Forge spec 47.4 +[12mai2026 16:10:30.942] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.forge.ForgeVersion/CORE]: Found Forge group net.minecraftforge +[12mai2026 16:10:30.944] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.mcp.MCPVersion/CORE]: MCP Version package package net.minecraftforge.versions.mcp, Minecraft, version 1.20.1 from cpw.mods.modlauncher.TransformingClassLoader@7c663eaf +[12mai2026 16:10:30.944] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.mcp.MCPVersion/CORE]: Found MC version information 1.20.1 +[12mai2026 16:10:30.944] [modloading-worker-0/DEBUG] [net.minecraftforge.versions.mcp.MCPVersion/CORE]: Found MCP version information 20230612.114412 +[12mai2026 16:10:30.944] [modloading-worker-0/INFO] [net.minecraftforge.common.ForgeMod/FORGEMOD]: Forge mod loading, version 47.4.13, for MC 1.20.1 with MCP 20230612.114412 +[12mai2026 16:10:30.945] [modloading-worker-0/DEBUG] [WhiteNoise/]: Loaded technology.roughness.whitenoise.platform.ForgeConfigHelper@4cebbfbc for service interface technology.roughness.whitenoise.platform.services.IConfigHelper +[12mai2026 16:10:30.945] [modloading-worker-0/INFO] [net.minecraftforge.common.MinecraftForge/FORGE]: MinecraftForge v47.4.13 Initialized +[12mai2026 16:10:30.946] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for crackerslib +[12mai2026 16:10:30.946] [modloading-worker-0/INFO] [projectatmosphere/]: No temperature mod loaded, skipping compatibility setup. +[12mai2026 16:10:30.946] [modloading-worker-0/INFO] [projectatmosphere/]: Sand Storms mod not found. +[12mai2026 16:10:30.946] [modloading-worker-0/INFO] [projectatmosphere/]: Auroras mod not detected. +[12mai2026 16:10:30.946] [modloading-worker-0/INFO] [projectatmosphere/]: Rainbows mod not detected. +[12mai2026 16:10:30.946] [modloading-worker-0/INFO] [projectatmosphere/]: Tectonic detected - enabling refined ocean geometry. +[12mai2026 16:10:30.947] [modloading-worker-0/INFO] [projectatmosphere/]: Continents mod not detected. +[12mai2026 16:10:30.947] [modloading-worker-0/INFO] [projectatmosphere/]: Dynamic Trees not detected. +[12mai2026 16:10:30.947] [modloading-worker-0/DEBUG] [WhiteNoise/]: Loaded technology.roughness.whitenoise.platform.ForgeClientPlatform@230d6764 for service interface technology.roughness.whitenoise.platform.services.IClientPlatform +[12mai2026 16:10:30.947] [modloading-worker-0/DEBUG] [mixin/]: Mixing SimpleCloudsCloudManagerMixin from projectatmosphere.mixins.json into dev.nonamecrackers2.simpleclouds.common.world.CloudManager +[12mai2026 16:10:30.953] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.particle.WindBentParticleEngineMixin from projectatmosphere.mixins.json into net.minecraft.client.particle.ParticleEngine +[12mai2026 16:10:30.953] [modloading-worker-0/DEBUG] [WhiteNoise/]: Loaded technology.roughness.whitenoise.platform.ForgePlatform@3b97c459 for service interface technology.roughness.whitenoise.platform.services.IPlatform +[12mai2026 16:10:30.954] [modloading-worker-0/DEBUG] [mixin/]: Mixing ParticlesMixins$EngineMixin from chloride.mixin.json into net.minecraft.client.particle.ParticleEngine +[12mai2026 16:10:30.972] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinRenderTargetAccessor from simpleclouds.mixins.json into com.mojang.blaze3d.pipeline.RenderTarget +[12mai2026 16:10:30.974] [modloading-worker-0/DEBUG] [mixin/]: Mixing vivecraft.MixinRenderTarget from simpleclouds.mixins.json into com.mojang.blaze3d.pipeline.RenderTarget +[12mai2026 16:10:30.978] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.RegisterCommandsEventHandler to FORGE +[12mai2026 16:10:30.981] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.PlayerQuitClientEventHandler to FORGE +[12mai2026 16:10:30.983] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.options.overlays.InGameHudMixin from embeddium.mixins.json into net.minecraft.client.gui.Gui +[12mai2026 16:10:30.988] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.PlayerLoggedInEventHandler to FORGE +[12mai2026 16:10:30.991] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.LevelRenderEventHandler to FORGE +[12mai2026 16:10:30.995] [modloading-worker-0/DEBUG] [mixin/]: Mixing BorderlessMixin$WindowMixin from chloride.mixin.json into com.mojang.blaze3d.platform.Window +[12mai2026 16:10:30.997] [modloading-worker-0/DEBUG] [mixin/]: Mixing workarounds.context_creation.WindowMixin from embeddium.mixins.json into com.mojang.blaze3d.platform.Window +[12mai2026 16:10:31.036] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.gui.debug.ForgeGuiMixin from embeddium.mixins.json into net.minecraftforge.client.gui.overlay.ForgeGui +[12mai2026 16:10:31.037] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$renderLinesVanilla$0(Lme/jellysquid/mods/sodium/mixin/features/render/gui/debug/DebugScreenOverlayAccessor;Lnet/minecraft/client/gui/GuiGraphics;Ljava/util/ArrayList;Ljava/util/ArrayList;)V to mdb27d7e$lambda$renderLinesVanilla$0$0 in embeddium.mixins.json:features.render.gui.debug.ForgeGuiMixin +[12mai2026 16:10:31.081] [modloading-worker-0/DEBUG] [mixin/]: Mixing impl.MixinPacketHandler from glitchcore.forge.mixins.json into glitchcore.network.PacketHandler +[12mai2026 16:10:31.081] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$init$3(Ljava/lang/String;)Ljava/lang/String; to mdb27d7e$lambda$init$3$0 in glitchcore.forge.mixins.json:impl.MixinPacketHandler +[12mai2026 16:10:31.081] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$sendToPlayer$2(Lnet/minecraft/server/level/ServerPlayer;)Lnet/minecraft/server/level/ServerPlayer; to mdb27d7e$lambda$sendToPlayer$2$1 in glitchcore.forge.mixins.json:impl.MixinPacketHandler +[12mai2026 16:10:31.081] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$register$1(Lglitchcore/network/CustomPacket;Lglitchcore/network/CustomPacket;Ljava/util/function/Supplier;)V to mdb27d7e$lambda$register$1$2 in glitchcore.forge.mixins.json:impl.MixinPacketHandler +[12mai2026 16:10:31.081] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$register$0(Lglitchcore/network/CustomPacket;Lglitchcore/network/CustomPacket;Ljava/util/function/Supplier;)V to mdb27d7e$lambda$register$0$3 in glitchcore.forge.mixins.json:impl.MixinPacketHandler +[12mai2026 16:10:31.083] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file sereneseasonsplus-common.toml for sereneseasonsplus tracking +[12mai2026 16:10:31.084] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for sereneseasonsplus +[12mai2026 16:10:31.090] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.MixinGuiGraphics from glitchcore.forge.mixins.json into net.minecraft.client.gui.GuiGraphics +[12mai2026 16:10:31.092] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.textures.animations.tracking.DrawContextMixin from embeddium.mixins.json into net.minecraft.client.gui.GuiGraphics +[12mai2026 16:10:31.109] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.world.clouds.WorldRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.LevelRenderer +[12mai2026 16:10:31.111] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.MixinLevelRenderer from sereneseasons.mixins.json into net.minecraft.client.renderer.LevelRenderer +[12mai2026 16:10:31.112] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.MixinLevelRenderer from DistantHorizons.forge.mixins.json into net.minecraft.client.renderer.LevelRenderer +[12mai2026 16:10:31.117] [modloading-worker-0/DEBUG] [mixin/]: Mixing ParticlesMixins$LevelRendererMixin from chloride.mixin.json into net.minecraft.client.renderer.LevelRenderer +[12mai2026 16:10:31.117] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.options.weather.WorldRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.LevelRenderer +[12mai2026 16:10:31.117] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.gui.outlines.WorldRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.LevelRenderer +[12mai2026 16:10:31.143] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.world.sky.WorldRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.LevelRenderer +[12mai2026 16:10:31.144] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.render.world.WorldRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.LevelRenderer +[12mai2026 16:10:31.145] [modloading-worker-0/WARN] [mixin/]: Static binding violation: PRIVATE @Overwrite method setSectionDirty in embeddium.mixins.json:core.render.world.WorldRendererMixin cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded. +[12mai2026 16:10:31.150] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinLevelRenderer from simpleclouds.mixins.json into net.minecraft.client.renderer.LevelRenderer +[12mai2026 16:10:31.244] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.immediate.matrix_stack.VertexConsumerMixin from embeddium.mixins.json into com.mojang.blaze3d.vertex.VertexConsumer +[12mai2026 16:10:31.248] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinLightTexture from simpleclouds.mixins.json into net.minecraft.client.renderer.LightTexture +[12mai2026 16:10:31.248] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.MixinLightTexture from DistantHorizons.forge.mixins.json into net.minecraft.client.renderer.LightTexture +[12mai2026 16:10:31.250] [modloading-worker-0/DEBUG] [mixin/]: Mixing darkness.LightTextureMixin from chloride.mixin.json into net.minecraft.client.renderer.LightTexture +[12mai2026 16:10:31.254] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for jade +[12mai2026 16:10:31.269] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinGameRendererAccessor from crackerslib.mixins.json into net.minecraft.client.renderer.GameRenderer +[12mai2026 16:10:31.281] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinGameRenderer from simpleclouds.mixins.json into net.minecraft.client.renderer.GameRenderer +[12mai2026 16:10:31.281] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$simpleclouds$resizeRenderer_resize$0(IILdev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;)V to mdb27d7e$lambda$simpleclouds$resizeRenderer_resize$0$0 in simpleclouds.mixins.json:MixinGameRenderer +[12mai2026 16:10:31.283] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinGameRendererAccessor from simpleclouds.mixins.json into net.minecraft.client.renderer.GameRenderer +[12mai2026 16:10:31.286] [modloading-worker-0/DEBUG] [mixin/]: Mixing darkness.GameRendererMixin from chloride.mixin.json into net.minecraft.client.renderer.GameRenderer +[12mai2026 16:10:31.287] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.gui.hooks.console.GameRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.GameRenderer +[12mai2026 16:10:31.307] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.render.MatrixStackMixin from embeddium.mixins.json into com.mojang.blaze3d.vertex.PoseStack +[12mai2026 16:10:31.319] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file forge-client.toml for forge tracking +[12mai2026 16:10:31.319] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file forge-server.toml for forge tracking +[12mai2026 16:10:31.319] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.ModLoadingContext/]: Attempted to register an empty config for type COMMON on mod forge +[12mai2026 16:10:31.338] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.LoadingScreenMixin from projectatmosphere.mixins.json into net.minecraft.client.gui.screens.LevelLoadingScreen +[12mai2026 16:10:31.339] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.gui.screen.LevelLoadingScreenMixin from embeddium.mixins.json into net.minecraft.client.gui.screens.LevelLoadingScreen +[12mai2026 16:10:31.339] [modloading-worker-0/DEBUG] [mixin/]: Renaming synthetic method lambda$renderChunks$0(Lit/unimi/dsi/fastutil/objects/Object2IntMap$Entry;)V to mdb27d7e$lambda$renderChunks$0$0 in embeddium.mixins.json:features.gui.screen.LevelLoadingScreenMixin +[12mai2026 16:10:31.356] [modloading-worker-0/DEBUG] [mixin/]: Mixing MixinFrustumAccessor from simpleclouds.mixins.json into net.minecraft.client.renderer.culling.Frustum +[12mai2026 16:10:31.356] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.Zoom$ModEvents to MOD +[12mai2026 16:10:31.357] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.render.frustum.FrustumMixin from embeddium.mixins.json into net.minecraft.client.renderer.culling.Frustum +[12mai2026 16:10:31.362] [modloading-worker-0/DEBUG] [mixin/]: Mixing client.LoadingOverlayMixin from projectatmosphere.mixins.json into net.minecraft.client.gui.screens.LoadingOverlay +[12mai2026 16:10:31.365] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.Overlay to FORGE +[12mai2026 16:10:31.366] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.InteractionEventHandler to FORGE +[12mai2026 16:10:31.368] [modloading-worker-0/DEBUG] [mezz.jei.common.platform.Services/]: Loaded mezz.jei.forge.platform.PlatformHelper@66f7f037 for service interface mezz.jei.common.platform.IPlatformHelper +[12mai2026 16:10:31.371] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing glitchcore.forge.handlers.ColorsEventHandler to MOD +[12mai2026 16:10:31.375] [modloading-worker-0/ERROR] [Embeddium/]: Failed to update fingerprint java.lang.NullPointerException: Cannot invoke "net.minecraft.client.Minecraft.getUser()" because the return value of "net.minecraft.client.Minecraft.getInstance()" is null - at me.jellysquid.mods.sodium.client.data.fingerprint.FingerprintMeasure.create(FingerprintMeasure.java:19) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23214!/:?] - at me.jellysquid.mods.sodium.client.SodiumClientMod.updateFingerprint(SodiumClientMod.java:108) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23214!/:?] - at me.jellysquid.mods.sodium.client.SodiumClientMod.(SodiumClientMod.java:48) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23214!/:?] + at me.jellysquid.mods.sodium.client.data.fingerprint.FingerprintMeasure.create(FingerprintMeasure.java:19) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23213!/:?] + at me.jellysquid.mods.sodium.client.SodiumClientMod.updateFingerprint(SodiumClientMod.java:108) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23213!/:?] + at me.jellysquid.mods.sodium.client.SodiumClientMod.(SodiumClientMod.java:48) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23213!/:?] at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[?:?] at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) ~[?:?] at jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[?:?] at java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500) ~[?:?] at java.lang.reflect.Constructor.newInstance(Constructor.java:481) ~[?:?] - at net.minecraftforge.fml.javafmlmod.FMLModContainer.constructMod(FMLModContainer.java:77) ~[javafmllanguage-1.20.1-47.4.9.jar%23192!/:?] - at net.minecraftforge.fml.ModContainer.lambda$buildTransitionHandler$5(ModContainer.java:126) ~[fmlcore-1.20.1-47.4.9.jar%23195!/:?] + at net.minecraftforge.fml.javafmlmod.FMLModContainer.constructMod(FMLModContainer.java:77) ~[javafmllanguage-1.20.1-47.4.13.jar%23192!/:?] + at net.minecraftforge.fml.ModContainer.lambda$buildTransitionHandler$5(ModContainer.java:126) ~[fmlcore-1.20.1-47.4.13.jar%23195!/:?] at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804) ~[?:?] at java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1796) ~[?:?] at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) ~[?:?] @@ -1103,239 +1067,271 @@ java.lang.NullPointerException: Cannot invoke "net.minecraft.client.Minecraft.ge at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) ~[?:?] at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) ~[?:?] at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) ~[?:?] -[24nov.2025 23:48:54.611] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for embeddium -[24nov.2025 23:48:54.611] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing org.embeddedt.embeddium.render.frapi.SpriteFinderCache to MOD -[24nov.2025 23:48:54.613] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.shader.uniform.ShaderProgramMixin from embeddium.mixins.json into net.minecraft.client.renderer.ShaderInstance -[24nov.2025 23:48:54.614] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for projectatmosphere -[24nov.2025 23:48:54.614] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.ClientRenderHook to FORGE -[24nov.2025 23:48:54.622] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.textures.animations.tracking.SpriteMixin from embeddium.mixins.json into net.minecraft.client.renderer.texture.TextureAtlasSprite -[24nov.2025 23:48:54.628] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.HUDOverlayRenderer to FORGE -[24nov.2025 23:48:54.631] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.render.HudRenderTest to FORGE -[24nov.2025 23:48:54.632] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing org.embeddedt.embeddium.impl.ConfigScreenHandlerHook to MOD -[24nov.2025 23:48:54.632] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for sereneseasons -[24nov.2025 23:48:54.632] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing sereneseasons.forge.datagen.DataGenerationHandler to MOD -[24nov.2025 23:48:54.633] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.TornadoShaders to MOD -[24nov.2025 23:48:54.635] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing org.embeddedt.embeddium.compat.immersive.ImmersiveCompat to MOD -[24nov.2025 23:48:54.635] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.command.CloudDumpCommand to FORGE -[24nov.2025 23:48:54.636] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing org.embeddedt.embeddium.compat.ccl.CCLCompatBootstrap to MOD -[24nov.2025 23:48:54.638] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.event.BiomeChangeManager to FORGE -[24nov.2025 23:48:54.638] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.event.EventHandler to FORGE -[24nov.2025 23:48:54.638] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.event.SimpleCloudsEventListener to FORGE -[24nov.2025 23:48:54.643] [modloading-worker-0/DEBUG] [mixin/]: Mixing CloudRegionTickEventMixin from projectatmosphere.mixins.json into dev.nonamecrackers2.simpleclouds.api.common.event.CloudRegionTickEvent -[24nov.2025 23:48:54.651] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.minecraftforge.client.ClientForgeMod to MOD -[24nov.2025 23:48:54.653] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.event.TemperatureTickHandler to FORGE -[24nov.2025 23:48:54.653] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.modules.hurricane.HurricaneCommand to FORGE -[24nov.2025 23:48:54.655] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.modules.tornado.TornadoCommand to FORGE -[24nov.2025 23:48:54.659] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.modules.tornado.TornadoDebug to FORGE -[24nov.2025 23:48:54.662] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.ProjectAtmosphere to FORGE -[24nov.2025 23:48:54.666] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.registry.ModClient to MOD -[24nov.2025 23:48:54.668] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.util.CloudSpawnScheduler to FORGE -[24nov.2025 23:48:54.670] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.util.ParticleAtlasDebugger to FORGE -[24nov.2025 23:48:54.680] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for tectonic -[24nov.2025 23:48:54.680] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.worldgen.tectonic.TectonicLexforge$ModBusEvents to MOD -[24nov.2025 23:48:54.692] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Loading config file: G:\Project-Atmosphere\run-data\config\jei\jei-debug.ini -[24nov.2025 23:48:54.695] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Saving config file: G:\Project-Atmosphere\run-data\config\jei\jei-debug.ini -[24nov.2025 23:48:54.705] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Loading config file: G:\Project-Atmosphere\run-data\config\jei\jei-mod-id-format.ini -[24nov.2025 23:48:54.706] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Saving config file: G:\Project-Atmosphere\run-data\config\jei\jei-mod-id-format.ini -[24nov.2025 23:48:54.711] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Loading config file: G:\Project-Atmosphere\run-data\config\jei\jei-colors.ini -[24nov.2025 23:48:54.715] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Saving config file: G:\Project-Atmosphere\run-data\config\jei\jei-colors.ini -[24nov.2025 23:48:54.718] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for architectury -[24nov.2025 23:48:54.718] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.registry.level.entity.trade.forge.TradeRegistryImpl to FORGE -[24nov.2025 23:48:54.719] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for lithostitched -[24nov.2025 23:48:54.721] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.registry.forge.ReloadListenerRegistryImpl to FORGE -[24nov.2025 23:48:54.722] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.registry.forge.CreativeTabRegistryImpl to MOD -[24nov.2025 23:48:54.728] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Loading config file: G:\Project-Atmosphere\run-data\config\jei\jei-client.ini -[24nov.2025 23:48:54.730] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Saving config file: G:\Project-Atmosphere\run-data\config\jei\jei-client.ini -[24nov.2025 23:48:54.733] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.registry.client.particle.forge.ParticleProviderRegistryImpl to MOD -[24nov.2025 23:48:54.736] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.registry.client.keymappings.forge.KeyMappingRegistryImpl to MOD -[24nov.2025 23:48:54.736] [modloading-worker-0/INFO] [mezz.jei.library.load.PluginCaller/]: Sending ConfigManager... -[24nov.2025 23:48:54.736] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.networking.forge.NetworkManagerImpl to FORGE -[24nov.2025 23:48:54.742] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jei:minecraft... -[24nov.2025 23:48:54.742] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jade:main... -[24nov.2025 23:48:54.742] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jei:debug... -[24nov.2025 23:48:54.742] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jei:gui... -[24nov.2025 23:48:54.742] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jei:forge_gui... -[24nov.2025 23:48:54.742] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jei:internal... -[24nov.2025 23:48:54.744] [modloading-worker-0/INFO] [mezz.jei.library.load.PluginCaller/]: Sending ConfigManager took 4.637 ms -[24nov.2025 23:48:54.746] [modloading-worker-0/INFO] [dev.architectury.networking.forge.NetworkManagerImpl/]: Registering S2C receiver with id architectury:sync_ids -[24nov.2025 23:48:54.748] [modloading-worker-0/INFO] [dev.architectury.networking.forge.NetworkManagerImpl/]: Registering C2S receiver with id architectury:sync_ids -[24nov.2025 23:48:54.750] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for jei -[24nov.2025 23:48:54.755] [main/DEBUG] [WhiteNoise/CONFIG]: Loading global configs -[24nov.2025 23:48:54.759] [main/DEBUG] [WhiteNoise/CONFIG]: Built TOML config for G:\Project-Atmosphere\run-data\config\betterdays-client.toml -[24nov.2025 23:48:54.760] [main/DEBUG] [WhiteNoise/CONFIG]: Loaded TOML config file G:\Project-Atmosphere\run-data\config\betterdays-client.toml -[24nov.2025 23:48:54.768] [main/DEBUG] [WhiteNoise/CONFIG]: Built TOML config for G:\Project-Atmosphere\run-data\config\betterdays-common.toml -[24nov.2025 23:48:54.768] [main/DEBUG] [WhiteNoise/CONFIG]: Loaded TOML config file G:\Project-Atmosphere\run-data\config\betterdays-common.toml -[24nov.2025 23:48:54.777] [main/DEBUG] [mixin/]: Mixing common.RegistryDataLoaderMixin from lithostitched.mixins.json into net.minecraft.resources.RegistryDataLoader -[24nov.2025 23:48:54.798] [main/DEBUG] [net.minecraftforge.registries.ObjectHolderRegistry/REGISTRIES]: Processing ObjectHolder annotations -[24nov.2025 23:48:54.808] [main/DEBUG] [net.minecraftforge.registries.ObjectHolderRegistry/REGISTRIES]: Found 3933 ObjectHolder annotations -[24nov.2025 23:48:54.810] [main/DEBUG] [net.minecraftforge.common.capabilities.CapabilityManager/CAPABILITIES]: Attempting to automatically register: Lnet/minecraftforge/energy/IEnergyStorage; -[24nov.2025 23:48:54.811] [main/DEBUG] [net.minecraftforge.common.capabilities.CapabilityManager/CAPABILITIES]: Attempting to automatically register: Lnet/minecraftforge/fluids/capability/IFluidHandler; -[24nov.2025 23:48:54.811] [main/DEBUG] [net.minecraftforge.common.capabilities.CapabilityManager/CAPABILITIES]: Attempting to automatically register: Lnet/minecraftforge/fluids/capability/IFluidHandlerItem; -[24nov.2025 23:48:54.811] [main/DEBUG] [net.minecraftforge.common.capabilities.CapabilityManager/CAPABILITIES]: Attempting to automatically register: Lnet/minecraftforge/items/IItemHandler; -[24nov.2025 23:48:54.811] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Unfreezing vanilla registries -[24nov.2025 23:48:54.909] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:sound_event -[24nov.2025 23:48:54.911] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:sound_event -[24nov.2025 23:48:54.911] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:fluid -[24nov.2025 23:48:54.912] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:fluid -[24nov.2025 23:48:54.920] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:block -[24nov.2025 23:48:54.921] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:block -[24nov.2025 23:48:54.921] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:attribute -[24nov.2025 23:48:54.921] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:attribute -[24nov.2025 23:48:54.922] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:mob_effect -[24nov.2025 23:48:54.922] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:mob_effect -[24nov.2025 23:48:54.924] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:particle_type -[24nov.2025 23:48:54.925] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:particle_type -[24nov.2025 23:48:54.951] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:item -[24nov.2025 23:48:54.952] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:item -[24nov.2025 23:48:54.952] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:entity_type -[24nov.2025 23:48:54.952] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:entity_type -[24nov.2025 23:48:54.952] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:sensor_type -[24nov.2025 23:48:54.952] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:sensor_type -[24nov.2025 23:48:54.953] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:memory_module_type -[24nov.2025 23:48:54.953] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:memory_module_type -[24nov.2025 23:48:54.953] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:potion -[24nov.2025 23:48:54.953] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:potion -[24nov.2025 23:48:54.954] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:game_event -[24nov.2025 23:48:54.954] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:game_event -[24nov.2025 23:48:54.954] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:enchantment -[24nov.2025 23:48:54.954] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:enchantment -[24nov.2025 23:48:54.955] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:block_entity_type -[24nov.2025 23:48:54.956] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:block_entity_type -[24nov.2025 23:48:54.956] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:painting_variant -[24nov.2025 23:48:54.956] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:painting_variant -[24nov.2025 23:48:54.956] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:stat_type -[24nov.2025 23:48:54.956] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:stat_type -[24nov.2025 23:48:54.957] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:custom_stat -[24nov.2025 23:48:54.957] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:custom_stat -[24nov.2025 23:48:54.957] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:chunk_status -[24nov.2025 23:48:54.957] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:chunk_status -[24nov.2025 23:48:54.957] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:rule_test -[24nov.2025 23:48:54.957] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:rule_test -[24nov.2025 23:48:54.958] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:rule_block_entity_modifier -[24nov.2025 23:48:54.958] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:rule_block_entity_modifier -[24nov.2025 23:48:54.958] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:pos_rule_test -[24nov.2025 23:48:54.958] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:pos_rule_test -[24nov.2025 23:48:54.958] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:menu -[24nov.2025 23:48:54.959] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:menu -[24nov.2025 23:48:54.959] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:recipe_type -[24nov.2025 23:48:54.959] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:recipe_type -[24nov.2025 23:48:54.981] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:recipe_serializer -[24nov.2025 23:48:54.982] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:recipe_serializer -[24nov.2025 23:48:54.982] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:position_source_type -[24nov.2025 23:48:54.982] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:position_source_type -[24nov.2025 23:48:54.987] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:command_argument_type -[24nov.2025 23:48:54.987] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:command_argument_type -[24nov.2025 23:48:54.987] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:villager_type -[24nov.2025 23:48:54.987] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:villager_type -[24nov.2025 23:48:54.988] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:villager_profession -[24nov.2025 23:48:54.988] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:villager_profession -[24nov.2025 23:48:54.988] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:point_of_interest_type -[24nov.2025 23:48:54.988] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:point_of_interest_type -[24nov.2025 23:48:54.988] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:schedule -[24nov.2025 23:48:54.989] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:schedule -[24nov.2025 23:48:54.989] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:activity -[24nov.2025 23:48:54.989] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:activity -[24nov.2025 23:48:54.989] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_pool_entry_type -[24nov.2025 23:48:54.989] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_pool_entry_type -[24nov.2025 23:48:54.989] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_function_type -[24nov.2025 23:48:54.989] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_function_type -[24nov.2025 23:48:54.993] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_condition_type -[24nov.2025 23:48:54.993] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_condition_type -[24nov.2025 23:48:54.993] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_number_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_number_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_nbt_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_nbt_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_score_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_score_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:float_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:float_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:int_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:int_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:height_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:height_provider_type -[24nov.2025 23:48:54.994] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:block_predicate_type -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:block_predicate_type -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/carver -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/carver -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/feature -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/feature -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/structure_processor -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/structure_processor -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/structure_placement -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/structure_placement -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/structure_piece -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/structure_piece -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/structure_type -[24nov.2025 23:48:54.995] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/structure_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/placement_modifier_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/placement_modifier_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/block_state_provider_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/block_state_provider_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/foliage_placer_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/foliage_placer_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/trunk_placer_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/trunk_placer_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/root_placer_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/root_placer_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/tree_decorator_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/tree_decorator_type -[24nov.2025 23:48:54.996] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/feature_size_type -[24nov.2025 23:48:54.997] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/feature_size_type -[24nov.2025 23:48:54.997] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/biome_source -[24nov.2025 23:48:54.997] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/biome_source -[24nov.2025 23:48:54.997] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/chunk_generator -[24nov.2025 23:48:54.997] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/chunk_generator -[24nov.2025 23:48:54.997] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/material_condition -[24nov.2025 23:48:54.997] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/material_condition -[24nov.2025 23:48:54.999] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/material_rule -[24nov.2025 23:48:55.000] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/material_rule -[24nov.2025 23:48:55.011] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/density_function_type -[24nov.2025 23:48:55.012] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/density_function_type -[24nov.2025 23:48:55.012] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/structure_pool_element -[24nov.2025 23:48:55.012] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/structure_pool_element -[24nov.2025 23:48:55.012] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:cat_variant -[24nov.2025 23:48:55.012] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:cat_variant -[24nov.2025 23:48:55.012] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:frog_variant -[24nov.2025 23:48:55.012] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:frog_variant -[24nov.2025 23:48:55.012] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:banner_pattern -[24nov.2025 23:48:55.013] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:banner_pattern -[24nov.2025 23:48:55.013] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:instrument -[24nov.2025 23:48:55.013] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:instrument -[24nov.2025 23:48:55.013] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:decorated_pot_patterns -[24nov.2025 23:48:55.013] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:decorated_pot_patterns -[24nov.2025 23:48:55.015] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:creative_mode_tab -[24nov.2025 23:48:55.015] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:creative_mode_tab -[24nov.2025 23:48:55.015] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: betterdays:time_effect -[24nov.2025 23:48:55.016] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: betterdays:time_effect -[24nov.2025 23:48:55.027] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:biome_modifier_serializers -[24nov.2025 23:48:55.027] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:biome_modifier_serializers -[24nov.2025 23:48:55.028] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:display_contexts -[24nov.2025 23:48:55.028] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:display_contexts -[24nov.2025 23:48:55.028] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:entity_data_serializers -[24nov.2025 23:48:55.028] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:entity_data_serializers -[24nov.2025 23:48:55.035] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:fluid_type -[24nov.2025 23:48:55.035] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:fluid_type -[24nov.2025 23:48:55.035] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:global_loot_modifier_serializers -[24nov.2025 23:48:55.035] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:global_loot_modifier_serializers -[24nov.2025 23:48:55.043] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:holder_set_type -[24nov.2025 23:48:55.044] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:holder_set_type -[24nov.2025 23:48:55.044] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:structure_modifier_serializers -[24nov.2025 23:48:55.045] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:structure_modifier_serializers -[24nov.2025 23:48:55.046] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: lithostitched:modifier_predicate_type -[24nov.2025 23:48:55.048] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: lithostitched:modifier_predicate_type -[24nov.2025 23:48:55.052] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: lithostitched:modifier_type -[24nov.2025 23:48:55.053] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: lithostitched:modifier_type -[24nov.2025 23:48:55.053] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: lithostitched:placement_condition_type -[24nov.2025 23:48:55.053] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: lithostitched:placement_condition_type -[24nov.2025 23:48:55.053] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: lithostitched:processor_condition_type -[24nov.2025 23:48:55.055] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: lithostitched:processor_condition_type -[24nov.2025 23:48:55.055] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/biome -[24nov.2025 23:48:55.055] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/biome -[24nov.2025 23:48:55.151] [main/DEBUG] [mixin/]: Mixing common.ServerLifecycleHooksMixin from lithostitched.forge.mixins.json into net.minecraftforge.server.ServerLifecycleHooks -[24nov.2025 23:48:55.151] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$lithostitched$injectBiomeModifers$1(Ljava/util/List;Ljava/util/Map$Entry;)V to mdd47aa8$lambda$lithostitched$injectBiomeModifers$1$0 in lithostitched.forge.mixins.json:common.ServerLifecycleHooksMixin -[24nov.2025 23:48:55.151] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$lithostitched$injectBiomeModifers$0(Ljava/util/Map$Entry;)Z to mdd47aa8$lambda$lithostitched$injectBiomeModifers$0$1 in lithostitched.forge.mixins.json:common.ServerLifecycleHooksMixin -[24nov.2025 23:48:55.188] [main/DEBUG] [mixin/]: Mixing BuiltInPackSourceAccessor from tectonic_1.20.1.mixins.json into net.minecraft.server.packs.repository.BuiltInPackSource -[24nov.2025 23:48:55.197] [main/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/C:/Users/matga/.gradle/caches/forge_gradle/minecraft_user_repo/net/minecraftforge/forge/1.20.1-47.4.9_mapped_official_1.20.1/forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar%23191!/assets/.mcassetsroot' uses unexpected schema -[24nov.2025 23:48:55.197] [main/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/C:/Users/matga/.gradle/caches/forge_gradle/minecraft_user_repo/net/minecraftforge/forge/1.20.1-47.4.9_mapped_official_1.20.1/forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar%23191!/data/.mcassetsroot' uses unexpected schema -[24nov.2025 23:48:55.412] [main/INFO] [net.minecraft.data.DataGenerator/]: All providers took: 0 ms -[24nov.2025 23:48:55.414] [main/INFO] [net.minecraft.data.HashCache/]: Caching: total files: 0, old count: 0, new count: 1, removed stale: 0, written: 0 +[12mai2026 16:10:31.377] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for embeddium +[12mai2026 16:10:31.377] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing org.embeddedt.embeddium.render.frapi.SpriteFinderCache to MOD +[12mai2026 16:10:31.380] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.model.colors.ItemColorsMixin from embeddium.mixins.json into net.minecraft.client.color.item.ItemColors +[12mai2026 16:10:31.381] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.HideNametag to FORGE +[12mai2026 16:10:31.387] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.textures.animations.tracking.SpriteMixin from embeddium.mixins.json into net.minecraft.client.renderer.texture.TextureAtlasSprite +[12mai2026 16:10:31.397] [modloading-worker-0/DEBUG] [mixin/]: Mixing NameTagToggleMixin from chloride.mixin.json into net.minecraft.client.renderer.entity.EntityRenderer +[12mai2026 16:10:31.397] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.render.entity.cull.EntityRendererMixin from embeddium.mixins.json into net.minecraft.client.renderer.entity.EntityRenderer +[12mai2026 16:10:31.400] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for forge +[12mai2026 16:10:31.400] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.minecraftforge.common.ForgeSpawnEggItem$CommonHandler to MOD +[12mai2026 16:10:31.404] [modloading-worker-0/DEBUG] [mixin/]: Mixing core.model.colors.BlockColorsMixin from embeddium.mixins.json into net.minecraft.client.color.block.BlockColors +[12mai2026 16:10:31.404] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing org.embeddedt.embeddium.impl.ConfigScreenHandlerHook to MOD +[12mai2026 16:10:31.406] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file simpleclouds-client.toml for simpleclouds tracking +[12mai2026 16:10:31.406] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file simpleclouds-common.toml for simpleclouds tracking +[12mai2026 16:10:31.406] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.config.ConfigTracker/CONFIG]: Config file simpleclouds-server.toml for simpleclouds tracking +[12mai2026 16:10:31.412] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.FastBlocks to FORGE +[12mai2026 16:10:31.415] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.minecraftforge.common.ForgeSpawnEggItem$ColorRegisterHandler to MOD +[12mai2026 16:10:31.415] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing org.embeddedt.embeddium.compat.immersive.ImmersiveCompat to MOD +[12mai2026 16:10:31.417] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing org.embeddedt.embeddium.compat.ccl.CCLCompatBootstrap to MOD +[12mai2026 16:10:31.418] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.impl.FastBlocks$ModEvents to MOD +[12mai2026 16:10:31.418] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for simpleclouds +[12mai2026 16:10:31.418] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.minecraftforge.client.model.data.ModelDataManager to FORGE +[12mai2026 16:10:31.420] [modloading-worker-0/DEBUG] [WhiteNoise/CONFIG]: Config file betterdays-client.toml for betterdays added to tracking +[12mai2026 16:10:31.421] [modloading-worker-0/DEBUG] [WhiteNoise/CONFIG]: Config file betterdays-common.toml for betterdays added to tracking +[12mai2026 16:10:31.421] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for betterdays +[12mai2026 16:10:31.422] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.model.ModelDataMixin from embeddium.mixins.json into net.minecraftforge.client.model.data.ModelData +[12mai2026 16:10:31.428] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.ChlorideConfig to MOD +[12mai2026 16:10:31.429] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.minecraftforge.client.ForgeHooksClient$ClientEvents to MOD +[12mai2026 16:10:31.433] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.shader.uniform.ShaderProgramMixin from embeddium.mixins.json into net.minecraft.client.renderer.ShaderInstance +[12mai2026 16:10:31.433] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for sereneseasons +[12mai2026 16:10:31.433] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing sereneseasons.forge.datagen.DataGenerationHandler to MOD +[12mai2026 16:10:31.452] [modloading-worker-0/DEBUG] [mixin/]: Mixing FontShadowMixin from chloride.mixin.json into net.minecraft.client.gui.Font +[12mai2026 16:10:31.463] [modloading-worker-0/DEBUG] [mixin/]: Mixing CloudGeneratorHurricaneReservationMixin from projectatmosphere.mixins.json into dev.nonamecrackers2.simpleclouds.common.cloud.spawning.CloudGenerator +[12mai2026 16:10:31.469] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing me.srrapero720.chloride.Chloride to MOD +[12mai2026 16:10:31.478] [modloading-worker-0/DEBUG] [mixin/]: Mixing features.textures.animations.tracking.SpriteAtlasTextureMixin from embeddium.mixins.json into net.minecraft.client.renderer.texture.TextureAtlas +[12mai2026 16:10:31.484] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.minecraftforge.client.ClientForgeMod to MOD +[12mai2026 16:10:31.489] [modloading-worker-0/INFO] [ProjectAtmosphere/Seasons/]: Detected Serene Seasons; using SereneSeasonsSeasonDelegate. +[12mai2026 16:10:31.496] [modloading-worker-0/INFO] [ProjectAtmosphere/SeasonTime/]: Season time delegate set to 'net.Gabou.projectatmosphere.seasons.SereneSeasonsSeasonDelegate'. +[12mai2026 16:10:31.496] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for projectatmosphere +[12mai2026 16:10:31.496] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.HUDOverlayRenderer to FORGE +[12mai2026 16:10:31.499] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.loading.ClientForecastLoadingLifecycle to FORGE +[12mai2026 16:10:31.502] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for tectonic +[12mai2026 16:10:31.502] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.loading.ClientForecastLoadingWorkQueue to FORGE +[12mai2026 16:10:31.502] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.worldgen.tectonic.TectonicLexforge$ModBusEvents to MOD +[12mai2026 16:10:31.505] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.render.HudRenderTest to FORGE +[12mai2026 16:10:31.506] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.render.HurricaneShaders to MOD +[12mai2026 16:10:31.508] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.render.SimpleCloudsDhPipelineSelector to FORGE +[12mai2026 16:10:31.512] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.render.TornadoShaders to MOD +[12mai2026 16:10:31.514] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.client.SimpleCloudsWhiteoutFogHandler to FORGE +[12mai2026 16:10:31.522] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.command.CloudDumpCommand to FORGE +[12mai2026 16:10:31.525] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.command.TelemetryDebugClientCommand to FORGE +[12mai2026 16:10:31.527] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.event.BiomeChangeManager to FORGE +[12mai2026 16:10:31.528] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.event.EventHandler to FORGE +[12mai2026 16:10:31.528] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.event.SimpleCloudsEventListener to FORGE +[12mai2026 16:10:31.531] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Loading config file: G:\Project-Atmosphere\run-data\config\jei\jei-debug.ini +[12mai2026 16:10:31.532] [modloading-worker-0/DEBUG] [mixin/]: Mixing CloudRegionTickEventMixin from projectatmosphere.mixins.json into dev.nonamecrackers2.simpleclouds.api.common.event.CloudRegionTickEvent +[12mai2026 16:10:31.534] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Saving config file: G:\Project-Atmosphere\run-data\config\jei\jei-debug.ini +[12mai2026 16:10:31.545] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.event.TemperatureTickHandler to FORGE +[12mai2026 16:10:31.545] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.modules.weather.StormShieldManager to FORGE +[12mai2026 16:10:31.548] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Loading config file: G:\Project-Atmosphere\run-data\config\jei\jei-mod-id-format.ini +[12mai2026 16:10:31.550] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Saving config file: G:\Project-Atmosphere\run-data\config\jei\jei-mod-id-format.ini +[12mai2026 16:10:31.551] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.ProjectAtmosphere to FORGE +[12mai2026 16:10:31.555] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.registry.ModClient to MOD +[12mai2026 16:10:31.557] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.telemetry.ServerTelemetrySampler to FORGE +[12mai2026 16:10:31.558] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Loading config file: G:\Project-Atmosphere\run-data\config\jei\jei-colors.ini +[12mai2026 16:10:31.562] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.util.CloudSpawnScheduler to FORGE +[12mai2026 16:10:31.562] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Saving config file: G:\Project-Atmosphere\run-data\config\jei\jei-colors.ini +[12mai2026 16:10:31.564] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing net.Gabou.projectatmosphere.util.ParticleAtlasDebugger to FORGE +[12mai2026 16:10:31.568] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for lithostitched +[12mai2026 16:10:31.577] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for architectury +[12mai2026 16:10:31.577] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.registry.level.entity.trade.forge.TradeRegistryImpl to FORGE +[12mai2026 16:10:31.579] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.registry.forge.ReloadListenerRegistryImpl to FORGE +[12mai2026 16:10:31.579] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Loading config file: G:\Project-Atmosphere\run-data\config\jei\jei-client.ini +[12mai2026 16:10:31.580] [modloading-worker-0/DEBUG] [mezz.jei.common.config.file.ConfigSerializer/]: Saving config file: G:\Project-Atmosphere\run-data\config\jei\jei-client.ini +[12mai2026 16:10:31.580] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.registry.forge.CreativeTabRegistryImpl to MOD +[12mai2026 16:10:31.590] [modloading-worker-0/INFO] [mezz.jei.library.load.PluginCaller/]: Sending ConfigManager... +[12mai2026 16:10:31.593] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.registry.client.particle.forge.ParticleProviderRegistryImpl to MOD +[12mai2026 16:10:31.597] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.registry.client.keymappings.forge.KeyMappingRegistryImpl to MOD +[12mai2026 16:10:31.598] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jei:minecraft... +[12mai2026 16:10:31.598] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jade:main... +[12mai2026 16:10:31.598] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jei:debug... +[12mai2026 16:10:31.598] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jei:gui... +[12mai2026 16:10:31.598] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jei:forge_gui... +[12mai2026 16:10:31.598] [modloading-worker-0/DEBUG] [mezz.jei.library.load.PluginCallerTimerRunnable/]: Sending ConfigManager: jei:internal... +[12mai2026 16:10:31.598] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Auto-subscribing dev.architectury.networking.forge.NetworkManagerImpl to FORGE +[12mai2026 16:10:31.600] [modloading-worker-0/INFO] [mezz.jei.library.load.PluginCaller/]: Sending ConfigManager took 6.169 ms +[12mai2026 16:10:31.609] [modloading-worker-0/DEBUG] [net.minecraftforge.fml.javafmlmod.AutomaticEventSubscriber/LOADING]: Attempting to inject @EventBusSubscriber classes into the eventbus for jei +[12mai2026 16:10:31.610] [modloading-worker-0/INFO] [dev.architectury.networking.forge.NetworkManagerImpl/]: Registering S2C receiver with id architectury:sync_ids +[12mai2026 16:10:31.613] [modloading-worker-0/INFO] [dev.architectury.networking.forge.NetworkManagerImpl/]: Registering C2S receiver with id architectury:sync_ids +[12mai2026 16:10:31.624] [main/DEBUG] [WhiteNoise/CONFIG]: Loading global configs +[12mai2026 16:10:31.626] [main/DEBUG] [WhiteNoise/CONFIG]: Built TOML config for G:\Project-Atmosphere\run-data\config\betterdays-client.toml +[12mai2026 16:10:31.626] [main/DEBUG] [WhiteNoise/CONFIG]: Loaded TOML config file G:\Project-Atmosphere\run-data\config\betterdays-client.toml +[12mai2026 16:10:31.630] [main/DEBUG] [WhiteNoise/CONFIG]: Built TOML config for G:\Project-Atmosphere\run-data\config\betterdays-common.toml +[12mai2026 16:10:31.631] [main/DEBUG] [WhiteNoise/CONFIG]: Loaded TOML config file G:\Project-Atmosphere\run-data\config\betterdays-common.toml +[12mai2026 16:10:31.639] [main/DEBUG] [mixin/]: Mixing common.RegistryDataLoaderMixin from lithostitched.mixins.json into net.minecraft.resources.RegistryDataLoader +[12mai2026 16:10:31.663] [main/DEBUG] [net.minecraftforge.registries.ObjectHolderRegistry/REGISTRIES]: Processing ObjectHolder annotations +[12mai2026 16:10:31.674] [main/DEBUG] [net.minecraftforge.registries.ObjectHolderRegistry/REGISTRIES]: Found 3936 ObjectHolder annotations +[12mai2026 16:10:31.678] [main/DEBUG] [net.minecraftforge.common.capabilities.CapabilityManager/CAPABILITIES]: Attempting to automatically register: Lnet/minecraftforge/energy/IEnergyStorage; +[12mai2026 16:10:31.678] [main/DEBUG] [net.minecraftforge.common.capabilities.CapabilityManager/CAPABILITIES]: Attempting to automatically register: Lnet/minecraftforge/fluids/capability/IFluidHandler; +[12mai2026 16:10:31.679] [main/DEBUG] [net.minecraftforge.common.capabilities.CapabilityManager/CAPABILITIES]: Attempting to automatically register: Lnet/minecraftforge/fluids/capability/IFluidHandlerItem; +[12mai2026 16:10:31.679] [main/DEBUG] [net.minecraftforge.common.capabilities.CapabilityManager/CAPABILITIES]: Attempting to automatically register: Lnet/minecraftforge/items/IItemHandler; +[12mai2026 16:10:31.679] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Unfreezing vanilla registries +[12mai2026 16:10:31.774] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:sound_event +[12mai2026 16:10:31.777] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:sound_event +[12mai2026 16:10:31.778] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:fluid +[12mai2026 16:10:31.778] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:fluid +[12mai2026 16:10:31.787] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:block +[12mai2026 16:10:31.787] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:block +[12mai2026 16:10:31.788] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:attribute +[12mai2026 16:10:31.789] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:attribute +[12mai2026 16:10:31.789] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:mob_effect +[12mai2026 16:10:31.789] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:mob_effect +[12mai2026 16:10:31.793] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:particle_type +[12mai2026 16:10:31.795] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:particle_type +[12mai2026 16:10:31.822] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:item +[12mai2026 16:10:31.824] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:item +[12mai2026 16:10:31.824] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:entity_type +[12mai2026 16:10:31.825] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:entity_type +[12mai2026 16:10:31.825] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:sensor_type +[12mai2026 16:10:31.825] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:sensor_type +[12mai2026 16:10:31.825] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:memory_module_type +[12mai2026 16:10:31.825] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:memory_module_type +[12mai2026 16:10:31.825] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:potion +[12mai2026 16:10:31.825] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:potion +[12mai2026 16:10:31.825] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:game_event +[12mai2026 16:10:31.825] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:game_event +[12mai2026 16:10:31.826] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:enchantment +[12mai2026 16:10:31.826] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:enchantment +[12mai2026 16:10:31.828] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:block_entity_type +[12mai2026 16:10:31.828] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:block_entity_type +[12mai2026 16:10:31.828] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:painting_variant +[12mai2026 16:10:31.828] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:painting_variant +[12mai2026 16:10:31.829] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:stat_type +[12mai2026 16:10:31.829] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:stat_type +[12mai2026 16:10:31.829] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:custom_stat +[12mai2026 16:10:31.829] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:custom_stat +[12mai2026 16:10:31.829] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:chunk_status +[12mai2026 16:10:31.829] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:chunk_status +[12mai2026 16:10:31.829] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:rule_test +[12mai2026 16:10:31.829] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:rule_test +[12mai2026 16:10:31.831] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:rule_block_entity_modifier +[12mai2026 16:10:31.831] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:rule_block_entity_modifier +[12mai2026 16:10:31.831] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:pos_rule_test +[12mai2026 16:10:31.831] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:pos_rule_test +[12mai2026 16:10:31.831] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:menu +[12mai2026 16:10:31.832] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:menu +[12mai2026 16:10:31.832] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:recipe_type +[12mai2026 16:10:31.832] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:recipe_type +[12mai2026 16:10:31.875] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:recipe_serializer +[12mai2026 16:10:31.875] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:recipe_serializer +[12mai2026 16:10:31.875] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:position_source_type +[12mai2026 16:10:31.876] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:position_source_type +[12mai2026 16:10:31.878] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:command_argument_type +[12mai2026 16:10:31.879] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:command_argument_type +[12mai2026 16:10:31.879] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:villager_type +[12mai2026 16:10:31.879] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:villager_type +[12mai2026 16:10:31.879] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:villager_profession +[12mai2026 16:10:31.879] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:villager_profession +[12mai2026 16:10:31.880] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:point_of_interest_type +[12mai2026 16:10:31.880] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:point_of_interest_type +[12mai2026 16:10:31.880] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:schedule +[12mai2026 16:10:31.880] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:schedule +[12mai2026 16:10:31.880] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:activity +[12mai2026 16:10:31.880] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:activity +[12mai2026 16:10:31.880] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_pool_entry_type +[12mai2026 16:10:31.880] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_pool_entry_type +[12mai2026 16:10:31.880] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_function_type +[12mai2026 16:10:31.881] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_function_type +[12mai2026 16:10:31.883] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_condition_type +[12mai2026 16:10:31.883] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_condition_type +[12mai2026 16:10:31.883] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_number_provider_type +[12mai2026 16:10:31.884] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_number_provider_type +[12mai2026 16:10:31.884] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_nbt_provider_type +[12mai2026 16:10:31.884] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_nbt_provider_type +[12mai2026 16:10:31.884] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:loot_score_provider_type +[12mai2026 16:10:31.884] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:loot_score_provider_type +[12mai2026 16:10:31.884] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:float_provider_type +[12mai2026 16:10:31.885] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:float_provider_type +[12mai2026 16:10:31.885] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:int_provider_type +[12mai2026 16:10:31.885] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:int_provider_type +[12mai2026 16:10:31.885] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:height_provider_type +[12mai2026 16:10:31.886] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:height_provider_type +[12mai2026 16:10:31.886] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:block_predicate_type +[12mai2026 16:10:31.886] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:block_predicate_type +[12mai2026 16:10:31.886] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/carver +[12mai2026 16:10:31.886] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/carver +[12mai2026 16:10:31.886] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/feature +[12mai2026 16:10:31.886] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/feature +[12mai2026 16:10:31.886] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/structure_processor +[12mai2026 16:10:31.887] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/structure_processor +[12mai2026 16:10:31.887] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/structure_placement +[12mai2026 16:10:31.887] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/structure_placement +[12mai2026 16:10:31.887] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/structure_piece +[12mai2026 16:10:31.887] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/structure_piece +[12mai2026 16:10:31.887] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/structure_type +[12mai2026 16:10:31.887] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/structure_type +[12mai2026 16:10:31.887] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/placement_modifier_type +[12mai2026 16:10:31.887] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/placement_modifier_type +[12mai2026 16:10:31.887] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/block_state_provider_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/block_state_provider_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/foliage_placer_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/foliage_placer_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/trunk_placer_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/trunk_placer_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/root_placer_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/root_placer_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/tree_decorator_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/tree_decorator_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/feature_size_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/feature_size_type +[12mai2026 16:10:31.889] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/biome_source +[12mai2026 16:10:31.890] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/biome_source +[12mai2026 16:10:31.890] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/chunk_generator +[12mai2026 16:10:31.890] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/chunk_generator +[12mai2026 16:10:31.890] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/material_condition +[12mai2026 16:10:31.890] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/material_condition +[12mai2026 16:10:31.891] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/material_rule +[12mai2026 16:10:31.892] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/material_rule +[12mai2026 16:10:31.893] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/density_function_type +[12mai2026 16:10:31.893] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/density_function_type +[12mai2026 16:10:31.894] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/structure_pool_element +[12mai2026 16:10:31.894] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/structure_pool_element +[12mai2026 16:10:31.894] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:cat_variant +[12mai2026 16:10:31.895] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:cat_variant +[12mai2026 16:10:31.895] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:frog_variant +[12mai2026 16:10:31.895] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:frog_variant +[12mai2026 16:10:31.895] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:banner_pattern +[12mai2026 16:10:31.895] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:banner_pattern +[12mai2026 16:10:31.895] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:instrument +[12mai2026 16:10:31.895] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:instrument +[12mai2026 16:10:31.895] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:decorated_pot_patterns +[12mai2026 16:10:31.895] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:decorated_pot_patterns +[12mai2026 16:10:31.897] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:creative_mode_tab +[12mai2026 16:10:31.897] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:creative_mode_tab +[12mai2026 16:10:31.898] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: betterdays:time_effect +[12mai2026 16:10:31.898] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: betterdays:time_effect +[12mai2026 16:10:31.908] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:biome_modifier_serializers +[12mai2026 16:10:31.909] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:biome_modifier_serializers +[12mai2026 16:10:31.910] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:display_contexts +[12mai2026 16:10:31.910] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:display_contexts +[12mai2026 16:10:31.910] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:entity_data_serializers +[12mai2026 16:10:31.910] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:entity_data_serializers +[12mai2026 16:10:31.915] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:fluid_type +[12mai2026 16:10:31.915] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:fluid_type +[12mai2026 16:10:31.915] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:global_loot_modifier_serializers +[12mai2026 16:10:31.916] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:global_loot_modifier_serializers +[12mai2026 16:10:31.925] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:holder_set_type +[12mai2026 16:10:31.925] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:holder_set_type +[12mai2026 16:10:31.926] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: forge:structure_modifier_serializers +[12mai2026 16:10:31.926] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: forge:structure_modifier_serializers +[12mai2026 16:10:31.928] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: lithostitched:modifier_predicate_type +[12mai2026 16:10:31.928] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: lithostitched:modifier_predicate_type +[12mai2026 16:10:31.932] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: lithostitched:modifier_type +[12mai2026 16:10:31.933] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: lithostitched:modifier_type +[12mai2026 16:10:31.933] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: lithostitched:placement_condition_type +[12mai2026 16:10:31.934] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: lithostitched:placement_condition_type +[12mai2026 16:10:31.934] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: lithostitched:processor_condition_type +[12mai2026 16:10:31.934] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: lithostitched:processor_condition_type +[12mai2026 16:10:31.934] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Applying holder lookups: minecraft:worldgen/biome +[12mai2026 16:10:31.935] [main/DEBUG] [net.minecraftforge.registries.GameData/REGISTRIES]: Holder lookups applied: minecraft:worldgen/biome +[12mai2026 16:10:31.969] [main/DEBUG] [mixin/]: Mixing common.ServerLifecycleHooksMixin from lithostitched.forge.mixins.json into net.minecraftforge.server.ServerLifecycleHooks +[12mai2026 16:10:31.969] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$lithostitched$injectBiomeModifers$1(Ljava/util/List;Ljava/util/Map$Entry;)V to mdb27d7e$lambda$lithostitched$injectBiomeModifers$1$0 in lithostitched.forge.mixins.json:common.ServerLifecycleHooksMixin +[12mai2026 16:10:31.970] [main/DEBUG] [mixin/]: Renaming synthetic method lambda$lithostitched$injectBiomeModifers$0(Ljava/util/Map$Entry;)Z to mdb27d7e$lambda$lithostitched$injectBiomeModifers$0$1 in lithostitched.forge.mixins.json:common.ServerLifecycleHooksMixin +[12mai2026 16:10:32.018] [main/DEBUG] [mixin/]: Mixing BuiltInPackSourceAccessor from tectonic_1.20.1.mixins.json into net.minecraft.server.packs.repository.BuiltInPackSource +[12mai2026 16:10:32.029] [main/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/C:/Users/matga/.gradle/caches/forge_gradle/minecraft_user_repo/net/minecraftforge/forge/1.20.1-47.4.13_mapped_official_1.20.1/forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar%23191!/assets/.mcassetsroot' uses unexpected schema +[12mai2026 16:10:32.030] [main/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/C:/Users/matga/.gradle/caches/forge_gradle/minecraft_user_repo/net/minecraftforge/forge/1.20.1-47.4.13_mapped_official_1.20.1/forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar%23191!/data/.mcassetsroot' uses unexpected schema +[12mai2026 16:10:32.274] [Worker-Main-1/DEBUG] [mixin/]: Mixing NoisesMixin from tectonic.mixins.json into net.minecraft.world.level.levelgen.Noises +[12mai2026 16:10:32.300] [main/INFO] [net.minecraft.data.DataGenerator/]: All providers took: 0 ms +[12mai2026 16:10:32.304] [main/INFO] [net.minecraft.data.HashCache/]: Caching: total files: 0, old count: 0, new count: 1, removed stale: 0, written: 0 diff --git a/run-data/logs/latest.log b/run-data/logs/latest.log index b716fb1a..0937b979 100644 --- a/run-data/logs/latest.log +++ b/run-data/logs/latest.log @@ -1,69 +1,69 @@ -[24nov.2025 23:48:47.382] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher running: args [--launchTarget, forgedatauserdev, --assetIndex, 5, --assetsDir, C:\Users\matga\.gradle\caches\forge_gradle\assets, --gameDir, ., --fml.forgeVersion, 47.4.9, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412, --mod, projectatmosphere, --all, --output, G:\Project-Atmosphere\src\generated\resources, --existing, G:\Project-Atmosphere\src\main\resources, --mixin.config, projectatmosphere.mixins.json] -[24nov.2025 23:48:47.390] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.15 by Eclipse Adoptium; OS Windows 11 arch amd64 version 10.0 -[24nov.2025 23:48:47.503] [main/INFO] [net.minecraftforge.fml.loading.ImmediateWindowHandler/]: ImmediateWindowProvider not loading because launch target is forgedatauserdev -[24nov.2025 23:48:47.534] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/C:/Users/matga/.gradle/caches/modules-2/files-2.1/org.spongepowered/mixin/0.8.5/9d1c0c3a304ae6697ecd477218fa61b850bf57fc/mixin-0.8.5.jar%23132!/ Service=ModLauncher Env=CLIENT -[24nov.2025 23:48:47.788] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\javafmllanguage\1.20.1-47.4.9\6802c410879d06cd2c16ac24988dd5893293e21c\javafmllanguage-1.20.1-47.4.9.jar is missing mods.toml file -[24nov.2025 23:48:47.790] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\lowcodelanguage\1.20.1-47.4.9\1a098871d069f3cb2ed12f392f96f462902c0350\lowcodelanguage-1.20.1-47.4.9.jar is missing mods.toml file -[24nov.2025 23:48:47.792] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\mclanguage\1.20.1-47.4.9\5279b751da7297817db4fa9729e3f3c7bf03d594\mclanguage-1.20.1-47.4.9.jar is missing mods.toml file -[24nov.2025 23:48:47.795] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\fmlcore\1.20.1-47.4.9\f4f03c369d155fb551ebf908db260a0c5600c29b\fmlcore-1.20.1-47.4.9.jar is missing mods.toml file -[24nov.2025 23:48:48.031] [main/INFO] [net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator/]: Found 3 dependencies adding them to mods collection -[24nov.2025 23:48:49.314] [main/INFO] [mixin/]: Compatibility level set to JAVA_17 -[24nov.2025 23:48:49.314] [main/ERROR] [mixin/]: Mixin config projectatmosphere.mixins.json does not specify "minVersion" property -[24nov.2025 23:48:49.336] [main/INFO] [cpw.mods.modlauncher.LaunchServiceHandler/MODLAUNCHER]: Launching target 'forgedatauserdev' with arguments [--gameDir, ., --assetsDir, C:\Users\matga\.gradle\caches\forge_gradle\assets, --assetIndex, 5, --mod, projectatmosphere, --all, --output, G:\Project-Atmosphere\src\generated\resources, --existing, G:\Project-Atmosphere\src\main\resources] -[24nov.2025 23:48:49.453] [main/WARN] [mixin/]: Reference map 'projectatmosphere.refmap.json' for projectatmosphere.mixins.json could not be read. If this is a development environment you can ignore this message -[24nov.2025 23:48:49.600] [main/INFO] [mixin/]: Remapping refMap jade.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.601] [main/INFO] [mixin/]: Remapping refMap lithostitched.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.602] [main/INFO] [mixin/]: Remapping refMap lithostitched.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.602] [main/INFO] [mixin/]: Remapping refMap sereneseasonsplus-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.602] [main/INFO] [mixin/]: Remapping refMap crackerslib.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.705] [main/INFO] [mixin/]: Remapping refMap glitchcore.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.705] [main/INFO] [mixin/]: Remapping refMap glitchcore.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.706] [main/INFO] [mixin/]: Remapping refMap sereneseasons.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.706] [main/INFO] [mixin/]: Remapping refMap sereneseasons.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.707] [main/INFO] [mixin/]: Remapping refMap simpleclouds.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.710] [main/INFO] [mixin/]: Remapping refMap architectury-forge-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.710] [main/INFO] [mixin/]: Remapping refMap architectury-common-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.711] [main/INFO] [mixin/]: Remapping refMap auroras.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.712] [main/WARN] [mixin/]: Reference map 'rainbows.refmap.json' for rainbows.mixins.json could not be read. If this is a development environment you can ignore this message -[24nov.2025 23:48:49.720] [main/INFO] [Embeddium/]: Loaded configuration file for Embeddium: 68 options available, 0 override(s) found -[24nov.2025 23:48:49.722] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Searching for graphics cards... -[24nov.2025 23:48:49.963] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Found graphics card: GraphicsAdapterInfo[vendor=UNKNOWN, name=Meta Virtual Monitor, version=DriverVersion=17.12.55.198] -[24nov.2025 23:48:49.963] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Found graphics card: GraphicsAdapterInfo[vendor=NVIDIA, name=NVIDIA GeForce RTX 4070 Ti SUPER, version=DriverVersion=32.0.15.8180] -[24nov.2025 23:48:49.967] [main/WARN] [Embeddium-Workarounds/]: Embeddium has applied one or more workarounds to prevent crashes or other issues on your system: [NVIDIA_THREADED_OPTIMIZATIONS] -[24nov.2025 23:48:49.967] [main/WARN] [Embeddium-Workarounds/]: This is not necessarily an issue, but it may result in certain features or optimizations being disabled. You can sometimes fix these issues by upgrading your graphics driver. -[24nov.2025 23:48:49.969] [main/INFO] [mixin/]: Remapping refMap embeddium-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:49.971] [main/INFO] [mixin/]: Remapping refMap chloride.mixin-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg -[24nov.2025 23:48:50.267] [main/WARN] [mixin/]: Error loading class: org/vivecraft/client_vr/provider/VRRenderer (java.lang.ClassNotFoundException: org.vivecraft.client_vr.provider.VRRenderer) -[24nov.2025 23:48:50.301] [main/WARN] [mixin/]: Error loading class: com/aetherteam/aether/client/renderer/level/AetherSkyRenderEffects (java.lang.ClassNotFoundException: com.aetherteam.aether.client.renderer.level.AetherSkyRenderEffects) -[24nov.2025 23:48:50.301] [main/WARN] [mixin/]: @Mixin target com.aetherteam.aether.client.renderer.level.AetherSkyRenderEffects was not found auroras.mixins.json:client.AetherSkyRenderEffectsMixin -[24nov.2025 23:48:50.315] [main/WARN] [mixin/]: Error loading class: dev/emi/emi/screen/EmiScreenManager (java.lang.ClassNotFoundException: dev.emi.emi.screen.EmiScreenManager) -[24nov.2025 23:48:50.317] [main/WARN] [mixin/]: Error loading class: me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl (java.lang.ClassNotFoundException: me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl) -[24nov.2025 23:48:50.741] [main/INFO] [net.minecraftforge.data.loading.DatagenModLoader/]: Initializing Data Gatherer for mods [projectatmosphere] -[24nov.2025 23:48:50.752] [main/INFO] [MixinExtras|Service/]: Initializing MixinExtras via com.llamalad7.mixinextras.service.MixinExtrasServiceImpl(version=0.3.5). -[24nov.2025 23:48:51.426] [main/WARN] [mixin/]: Method overwrite conflict for skipRendering in embeddium.mixins.json:features.options.render_layers.LeavesBlockMixin, previously written by me.srrapero720.chloride.mixins.impl.leaves_culling.LeavesBlockMixin. Skipping method. -[24nov.2025 23:48:54.017] [modloading-worker-0/INFO] [projectatmosphere/]: Project Atmosphere is loading! -[24nov.2025 23:48:54.024] [modloading-worker-0/INFO] [chloride/Main]: Chloride is here, lets make your experience taste-able -[24nov.2025 23:48:54.171] [modloading-worker-0/INFO] [projectatmosphere/]: No temperature mod loaded, skipping compatibility setup. -[24nov.2025 23:48:54.171] [modloading-worker-0/INFO] [projectatmosphere/]: Sand Storms mod not found. -[24nov.2025 23:48:54.174] [modloading-worker-0/INFO] [net.minecraftforge.common.ForgeMod/FORGEMOD]: Forge mod loading, version 47.4.9, for MC 1.20.1 with MCP 20230612.114412 -[24nov.2025 23:48:54.171] [modloading-worker-0/INFO] [projectatmosphere/]: Auroras detected – enabling seasonal aurora tuning. -[24nov.2025 23:48:54.176] [modloading-worker-0/INFO] [net.minecraftforge.common.MinecraftForge/FORGE]: MinecraftForge v47.4.9 Initialized -[24nov.2025 23:48:54.175] [modloading-worker-0/INFO] [projectatmosphere/]: Rainbows detected – enabling precipitation bridge. -[24nov.2025 23:48:54.176] [modloading-worker-0/INFO] [projectatmosphere/]: Tectonic detected – enabling refined ocean geometry. -[24nov.2025 23:48:54.178] [modloading-worker-0/INFO] [projectatmosphere/]: Continents mod not detected. -[24nov.2025 23:48:54.401] [modloading-worker-0/WARN] [mixin/]: Static binding violation: PRIVATE @Overwrite method setSectionDirty in embeddium.mixins.json:core.render.world.WorldRendererMixin cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded. -[24nov.2025 23:48:54.610] [modloading-worker-0/ERROR] [Embeddium/]: Failed to update fingerprint +[12mai2026 16:10:21.587] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher running: args [--launchTarget, forgedatauserdev, --assetIndex, 5, --assetsDir, C:\Users\matga\.gradle\caches\forge_gradle\assets, --gameDir, ., --fml.forgeVersion, 47.4.13, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412, --mod, projectatmosphere, --all, --output, G:\Project-Atmosphere\src\generated\resources, --existing, G:\Project-Atmosphere\src\main\resources, --mixin.config, projectatmosphere.mixins.json] +[12mai2026 16:10:21.591] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.17 by Eclipse Adoptium; OS Windows 11 arch amd64 version 10.0 +[12mai2026 16:10:22.009] [main/INFO] [net.minecraftforge.fml.loading.ImmediateWindowHandler/]: ImmediateWindowProvider not loading because launch target is forgedatauserdev +[12mai2026 16:10:22.144] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/C:/Users/matga/.gradle/caches/modules-2/files-2.1/org.spongepowered/mixin/0.8.5/9d1c0c3a304ae6697ecd477218fa61b850bf57fc/mixin-0.8.5.jar%23129!/ Service=ModLauncher Env=CLIENT +[12mai2026 16:10:22.499] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\javafmllanguage\1.20.1-47.4.13\cf01e2179f72bd2829c65f5226d7dd0cfb6c686\javafmllanguage-1.20.1-47.4.13.jar is missing mods.toml file +[12mai2026 16:10:22.501] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\lowcodelanguage\1.20.1-47.4.13\e3f96129ea14eba84316f2d69816253e228e6e4c\lowcodelanguage-1.20.1-47.4.13.jar is missing mods.toml file +[12mai2026 16:10:22.505] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\mclanguage\1.20.1-47.4.13\85796906112d9b621a7b2996f8bfda49da991ef0\mclanguage-1.20.1-47.4.13.jar is missing mods.toml file +[12mai2026 16:10:22.508] [main/WARN] [net.minecraftforge.fml.loading.moddiscovery.ModFileParser/LOADING]: Mod file C:\Users\matga\.gradle\caches\modules-2\files-2.1\net.minecraftforge\fmlcore\1.20.1-47.4.13\f77e3021d1eb31d61f8d1b92d18ed4fb3c2806e5\fmlcore-1.20.1-47.4.13.jar is missing mods.toml file +[12mai2026 16:10:22.759] [main/WARN] [net.minecraftforge.jarjar.selection.JarSelector/]: Attempted to select a dependency jar for JarJar which was passed in as source: crackerslib. Using Mod File: C:\Users\matga\.gradle\caches\forge_gradle\deobf_dependencies\nonamecrackers2\crackerslib-forge\1.20.1-0.4.6_mapped_official_1.20.1\crackerslib-forge-1.20.1-0.4.6_mapped_official_1.20.1.jar +[12mai2026 16:10:22.759] [main/INFO] [net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator/]: Found 4 dependencies adding them to mods collection +[12mai2026 16:10:24.512] [main/INFO] [mixin/]: Compatibility level set to JAVA_17 +[12mai2026 16:10:24.513] [main/ERROR] [mixin/]: Mixin config projectatmosphere.mixins.json does not specify "minVersion" property +[12mai2026 16:10:24.543] [main/INFO] [cpw.mods.modlauncher.LaunchServiceHandler/MODLAUNCHER]: Launching target 'forgedatauserdev' with arguments [--gameDir, ., --assetsDir, C:\Users\matga\.gradle\caches\forge_gradle\assets, --assetIndex, 5, --mod, projectatmosphere, --all, --output, G:\Project-Atmosphere\src\generated\resources, --existing, G:\Project-Atmosphere\src\main\resources] +[12mai2026 16:10:24.698] [main/WARN] [mixin/]: Reference map 'projectatmosphere.refmap.json' for projectatmosphere.mixins.json could not be read. If this is a development environment you can ignore this message +[12mai2026 16:10:24.932] [main/INFO] [mixin/]: Remapping refMap crackerslib.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.063] [main/INFO] [mixin/]: Remapping refMap glitchcore.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.064] [main/INFO] [mixin/]: Remapping refMap glitchcore.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.065] [main/INFO] [mixin/]: Remapping refMap sereneseasons.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.066] [main/INFO] [mixin/]: Remapping refMap sereneseasons.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.066] [main/INFO] [mixin/]: Remapping refMap jade.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.067] [main/INFO] [mixin/]: Remapping refMap simpleclouds.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.071] [main/INFO] [mixin/]: Remapping refMap architectury-forge-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.072] [main/INFO] [mixin/]: Remapping refMap architectury-common-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.073] [main/INFO] [mixin/]: Remapping refMap DistantHorizons.forge.mixins-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.074] [main/INFO] [mixin/]: Remapping refMap lithostitched.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.074] [main/INFO] [mixin/]: Remapping refMap lithostitched.refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.086] [main/INFO] [Embeddium/]: Loaded configuration file for Embeddium: 67 options available, 0 override(s) found +[12mai2026 16:10:25.089] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Searching for graphics cards... +[12mai2026 16:10:25.367] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Found graphics card: GraphicsAdapterInfo[vendor=UNKNOWN, name=Meta Virtual Monitor, version=DriverVersion=5.3.57.114] +[12mai2026 16:10:25.367] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Found graphics card: GraphicsAdapterInfo[vendor=NVIDIA, name=NVIDIA GeForce RTX 4070 Ti SUPER, version=DriverVersion=32.0.15.9597] +[12mai2026 16:10:25.371] [main/WARN] [Embeddium-Workarounds/]: Embeddium has applied one or more workarounds to prevent crashes or other issues on your system: [NVIDIA_THREADED_OPTIMIZATIONS] +[12mai2026 16:10:25.371] [main/WARN] [Embeddium-Workarounds/]: This is not necessarily an issue, but it may result in certain features or optimizations being disabled. You can sometimes fix these issues by upgrading your graphics driver. +[12mai2026 16:10:25.378] [main/INFO] [mixin/]: Remapping refMap embeddium-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.381] [main/INFO] [mixin/]: Remapping refMap chloride.mixin-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.388] [main/INFO] [mixin/]: Remapping refMap sereneseasonsplus-refmap.json using G:\Project-Atmosphere\build\createSrgToMcp\output.srg +[12mai2026 16:10:25.601] [main/WARN] [mixin/]: Error loading class: org/vivecraft/client_vr/provider/VRRenderer (java.lang.ClassNotFoundException: org.vivecraft.client_vr.provider.VRRenderer) +[12mai2026 16:10:25.926] [main/WARN] [mixin/]: Error loading class: dev/emi/emi/screen/EmiScreenManager (java.lang.ClassNotFoundException: dev.emi.emi.screen.EmiScreenManager) +[12mai2026 16:10:25.929] [main/WARN] [mixin/]: Error loading class: me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl (java.lang.ClassNotFoundException: me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl) +[12mai2026 16:10:26.358] [main/INFO] [net.minecraftforge.data.loading.DatagenModLoader/]: Initializing Data Gatherer for mods [projectatmosphere] +[12mai2026 16:10:26.370] [main/INFO] [MixinExtras|Service/]: Initializing MixinExtras via com.llamalad7.mixinextras.service.MixinExtrasServiceImpl(version=0.3.5). +[12mai2026 16:10:27.264] [main/WARN] [mixin/]: Method overwrite conflict for skipRendering in embeddium.mixins.json:features.options.render_layers.LeavesBlockMixin, previously written by me.srrapero720.chloride.mixins.impl.leaves_culling.LeavesBlockMixin. Skipping method. +[12mai2026 16:10:30.747] [modloading-worker-0/INFO] [chloride/Main]: Chloride is here, lets make your experience taste-able +[12mai2026 16:10:30.747] [modloading-worker-0/INFO] [projectatmosphere/]: Project Atmosphere is loading! +[12mai2026 16:10:30.932] [modloading-worker-0/WARN] [CompatUtils/]: Detected both Project Atmosphere and Serene Seasons Plus; running with both may be unstable. Elected host: PROJECT_ATMOSPHERE +[12mai2026 16:10:30.944] [modloading-worker-0/INFO] [net.minecraftforge.common.ForgeMod/FORGEMOD]: Forge mod loading, version 47.4.13, for MC 1.20.1 with MCP 20230612.114412 +[12mai2026 16:10:30.945] [modloading-worker-0/INFO] [net.minecraftforge.common.MinecraftForge/FORGE]: MinecraftForge v47.4.13 Initialized +[12mai2026 16:10:30.946] [modloading-worker-0/INFO] [projectatmosphere/]: No temperature mod loaded, skipping compatibility setup. +[12mai2026 16:10:30.946] [modloading-worker-0/INFO] [projectatmosphere/]: Sand Storms mod not found. +[12mai2026 16:10:30.946] [modloading-worker-0/INFO] [projectatmosphere/]: Auroras mod not detected. +[12mai2026 16:10:30.946] [modloading-worker-0/INFO] [projectatmosphere/]: Rainbows mod not detected. +[12mai2026 16:10:30.946] [modloading-worker-0/INFO] [projectatmosphere/]: Tectonic detected - enabling refined ocean geometry. +[12mai2026 16:10:30.947] [modloading-worker-0/INFO] [projectatmosphere/]: Continents mod not detected. +[12mai2026 16:10:30.947] [modloading-worker-0/INFO] [projectatmosphere/]: Dynamic Trees not detected. +[12mai2026 16:10:31.145] [modloading-worker-0/WARN] [mixin/]: Static binding violation: PRIVATE @Overwrite method setSectionDirty in embeddium.mixins.json:core.render.world.WorldRendererMixin cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded. +[12mai2026 16:10:31.375] [modloading-worker-0/ERROR] [Embeddium/]: Failed to update fingerprint java.lang.NullPointerException: Cannot invoke "net.minecraft.client.Minecraft.getUser()" because the return value of "net.minecraft.client.Minecraft.getInstance()" is null - at me.jellysquid.mods.sodium.client.data.fingerprint.FingerprintMeasure.create(FingerprintMeasure.java:19) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23214!/:?] - at me.jellysquid.mods.sodium.client.SodiumClientMod.updateFingerprint(SodiumClientMod.java:108) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23214!/:?] - at me.jellysquid.mods.sodium.client.SodiumClientMod.(SodiumClientMod.java:48) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23214!/:?] + at me.jellysquid.mods.sodium.client.data.fingerprint.FingerprintMeasure.create(FingerprintMeasure.java:19) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23213!/:?] + at me.jellysquid.mods.sodium.client.SodiumClientMod.updateFingerprint(SodiumClientMod.java:108) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23213!/:?] + at me.jellysquid.mods.sodium.client.SodiumClientMod.(SodiumClientMod.java:48) ~[embeddium-908741-5681725_mapped_official_1.20.1.jar%23213!/:?] at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[?:?] at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) ~[?:?] at jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[?:?] at java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500) ~[?:?] at java.lang.reflect.Constructor.newInstance(Constructor.java:481) ~[?:?] - at net.minecraftforge.fml.javafmlmod.FMLModContainer.constructMod(FMLModContainer.java:77) ~[javafmllanguage-1.20.1-47.4.9.jar%23192!/:?] - at net.minecraftforge.fml.ModContainer.lambda$buildTransitionHandler$5(ModContainer.java:126) ~[fmlcore-1.20.1-47.4.9.jar%23195!/:?] + at net.minecraftforge.fml.javafmlmod.FMLModContainer.constructMod(FMLModContainer.java:77) ~[javafmllanguage-1.20.1-47.4.13.jar%23192!/:?] + at net.minecraftforge.fml.ModContainer.lambda$buildTransitionHandler$5(ModContainer.java:126) ~[fmlcore-1.20.1-47.4.13.jar%23195!/:?] at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804) ~[?:?] at java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1796) ~[?:?] at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) ~[?:?] @@ -71,11 +71,13 @@ java.lang.NullPointerException: Cannot invoke "net.minecraft.client.Minecraft.ge at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) ~[?:?] at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) ~[?:?] at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) ~[?:?] -[24nov.2025 23:48:54.736] [modloading-worker-0/INFO] [mezz.jei.library.load.PluginCaller/]: Sending ConfigManager... -[24nov.2025 23:48:54.744] [modloading-worker-0/INFO] [mezz.jei.library.load.PluginCaller/]: Sending ConfigManager took 4.637 ms -[24nov.2025 23:48:54.746] [modloading-worker-0/INFO] [dev.architectury.networking.forge.NetworkManagerImpl/]: Registering S2C receiver with id architectury:sync_ids -[24nov.2025 23:48:54.748] [modloading-worker-0/INFO] [dev.architectury.networking.forge.NetworkManagerImpl/]: Registering C2S receiver with id architectury:sync_ids -[24nov.2025 23:48:55.197] [main/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/C:/Users/matga/.gradle/caches/forge_gradle/minecraft_user_repo/net/minecraftforge/forge/1.20.1-47.4.9_mapped_official_1.20.1/forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar%23191!/assets/.mcassetsroot' uses unexpected schema -[24nov.2025 23:48:55.197] [main/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/C:/Users/matga/.gradle/caches/forge_gradle/minecraft_user_repo/net/minecraftforge/forge/1.20.1-47.4.9_mapped_official_1.20.1/forge-1.20.1-47.4.9_mapped_official_1.20.1-recomp.jar%23191!/data/.mcassetsroot' uses unexpected schema -[24nov.2025 23:48:55.412] [main/INFO] [net.minecraft.data.DataGenerator/]: All providers took: 0 ms -[24nov.2025 23:48:55.414] [main/INFO] [net.minecraft.data.HashCache/]: Caching: total files: 0, old count: 0, new count: 1, removed stale: 0, written: 0 +[12mai2026 16:10:31.489] [modloading-worker-0/INFO] [ProjectAtmosphere/Seasons/]: Detected Serene Seasons; using SereneSeasonsSeasonDelegate. +[12mai2026 16:10:31.496] [modloading-worker-0/INFO] [ProjectAtmosphere/SeasonTime/]: Season time delegate set to 'net.Gabou.projectatmosphere.seasons.SereneSeasonsSeasonDelegate'. +[12mai2026 16:10:31.590] [modloading-worker-0/INFO] [mezz.jei.library.load.PluginCaller/]: Sending ConfigManager... +[12mai2026 16:10:31.600] [modloading-worker-0/INFO] [mezz.jei.library.load.PluginCaller/]: Sending ConfigManager took 6.169 ms +[12mai2026 16:10:31.610] [modloading-worker-0/INFO] [dev.architectury.networking.forge.NetworkManagerImpl/]: Registering S2C receiver with id architectury:sync_ids +[12mai2026 16:10:31.613] [modloading-worker-0/INFO] [dev.architectury.networking.forge.NetworkManagerImpl/]: Registering C2S receiver with id architectury:sync_ids +[12mai2026 16:10:32.029] [main/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/C:/Users/matga/.gradle/caches/forge_gradle/minecraft_user_repo/net/minecraftforge/forge/1.20.1-47.4.13_mapped_official_1.20.1/forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar%23191!/assets/.mcassetsroot' uses unexpected schema +[12mai2026 16:10:32.030] [main/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/C:/Users/matga/.gradle/caches/forge_gradle/minecraft_user_repo/net/minecraftforge/forge/1.20.1-47.4.13_mapped_official_1.20.1/forge-1.20.1-47.4.13_mapped_official_1.20.1-recomp.jar%23191!/data/.mcassetsroot' uses unexpected schema +[12mai2026 16:10:32.300] [main/INFO] [net.minecraft.data.DataGenerator/]: All providers took: 0 ms +[12mai2026 16:10:32.304] [main/INFO] [net.minecraft.data.HashCache/]: Caching: total files: 0, old count: 0, new count: 1, removed stale: 0, written: 0 diff --git a/settings.gradle b/settings.gradle index 473313ea..fcf54cfb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,7 +10,22 @@ pluginManagement { } buildscript { repositories { - maven { url = 'https://repo.spongepowered.org/repository/maven-public/' } + maven { + name = 'Sponge' + url = 'https://repo.spongepowered.org/repository/maven-public/' + content { + includeGroupByRegex('org\\.spongepowered.*') + } + } + maven { + name = 'MinecraftForge' + url = 'https://maven.minecraftforge.net/' + content { + includeGroup('cpw.mods') + includeGroupByRegex('net\\.minecraftforge.*') + } + } + mavenCentral() } } diff --git a/src/main/java/net/Gabou/projectatmosphere/ProjectAtmosphere.java b/src/main/java/net/Gabou/projectatmosphere/ProjectAtmosphere.java index 992a29eb..64afafe2 100644 --- a/src/main/java/net/Gabou/projectatmosphere/ProjectAtmosphere.java +++ b/src/main/java/net/Gabou/projectatmosphere/ProjectAtmosphere.java @@ -3,6 +3,8 @@ import dev.nonamecrackers2.simpleclouds.api.SimpleCloudsAPI; import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import net.Gabou.projectatmosphere.auth.ClientLauncherGuards; +import net.Gabou.projectatmosphere.auth.ServerAuth; import net.Gabou.projectatmosphere.compat.CompatHandler; import net.Gabou.projectatmosphere.compat.SimpleCloudsCompat; import net.Gabou.projectatmosphere.manager.ForecastGenerator; @@ -29,7 +31,6 @@ import net.minecraftforge.event.server.ServerStartingEvent; import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.config.ModConfigEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; @@ -84,12 +85,14 @@ public ProjectAtmosphere() { MinecraftForge.EVENT_BUS.register(TemperatureTickHandler.class); - MinecraftForge.EVENT_BUS.register(SeasonTracker.class); MinecraftForge.EVENT_BUS.register(BiomeChangeManager.class); MinecraftForge.EVENT_BUS.register(EventHandler.class); - MinecraftForge.EVENT_BUS.addListener((TickEvent event)-> { - if (event.phase == TickEvent.Phase.END)TickCounter.onServerTick(); + MinecraftForge.EVENT_BUS.addListener((TickEvent.ServerTickEvent event)-> { + if (event.phase == TickEvent.Phase.END) { + TickCounter.onServerTick(); + ServerAuth.onTick(event.getServer()); + } }); ModParticles.register(modEventBus); ModTabs.REGISTRY.register(modEventBus); @@ -130,6 +133,7 @@ public static void onServerStarted(ServerStartedEvent event) { @SubscribeEvent public static void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) { if (event.getEntity() instanceof ServerPlayer player) { + ServerAuth.onLogout(player); ServerLevel world = Objects.requireNonNull(player.getServer()).getLevel(ServerLevel.OVERWORLD); if (world != null) { AtmosphereManager.onPlayerLogout(world, player); @@ -138,7 +142,6 @@ public static void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) { } private void initModules() { - isSereneLoaded(); sendInfo(); } @@ -172,6 +175,7 @@ private void clientSetup(final FMLClientSetupEvent event, FMLJavaModLoadingConte event.enqueueWork(() -> { if(ProjectAtmosphere.DEBUG_MODE) LOGGER.info("Setting up Project Atmosphere (Client)"); + ClientLauncherGuards.enforce(); ClientOnlyRegistrar.registerClient(MinecraftForge.EVENT_BUS,context); Map translations = Language.getInstance().getLanguageData(); translations.put("sandstorm.debug.blocked", "Nothing to report. Stay alert."); @@ -198,9 +202,12 @@ public static void onRegisterCommands(RegisterCommandsEvent event) { @SubscribeEvent public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) { if (!(event.getEntity() instanceof ServerPlayer player)) return; + if (!ServerAuth.onLogin(player)) return; + if (!ServerAuth.enforceLocalLauncherDetection(player)) return; if(ProjectAtmosphere.DEBUG_MODE) LOGGER.info("Player logged in!"); AtmosphereManager.onPlayerLogin(player.getServer().getLevel(ServerLevel.OVERWORLD), player); + ServerAuth.sendChallenge(player); @@ -217,14 +224,7 @@ public static void onConfigLoaded(ModConfigEvent event) { private static void sendInfo() { if(ProjectAtmosphere.DEBUG_MODE) - LOGGER.info("All modules subsystems have been initialized (Serene Seasons detected)."); - } - - private static void isSereneLoaded() { - if (!ModList.get().isLoaded("sereneseasons")) { - if(ProjectAtmosphere.DEBUG_MODE) - LOGGER.info("Serene Seasons is not found—skipping all modules subsystems."); - } + LOGGER.info("All module subsystems have been initialized."); } static void initSeaLevel(Level level) { diff --git a/src/main/java/net/Gabou/projectatmosphere/auth/ClientAuth.java b/src/main/java/net/Gabou/projectatmosphere/auth/ClientAuth.java new file mode 100644 index 00000000..f5207185 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/auth/ClientAuth.java @@ -0,0 +1,28 @@ +package net.Gabou.projectatmosphere.auth; + +import net.Gabou.projectatmosphere.network.AuthChallengePacket; +import net.Gabou.projectatmosphere.network.AuthChallengeReplyPacket; +import net.Gabou.projectatmosphere.network.NetworkHandler; +import net.minecraft.client.Minecraft; + +public final class ClientAuth { + private ClientAuth() { + } + + public static void handleChallenge(AuthChallengePacket packet) { + if (packet == null) { + return; + } + + Minecraft minecraft = Minecraft.getInstance(); + if (minecraft == null || minecraft.player == null) { + return; + } + + NetworkHandler.CHANNEL.sendToServer(new AuthChallengeReplyPacket( + packet.nonce(), + SharedSecret.computeResponse(minecraft.player.getUUID(), packet.nonce()), + ClientLauncherGuards.getDetectedReason() + )); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/auth/ClientLauncherGuards.java b/src/main/java/net/Gabou/projectatmosphere/auth/ClientLauncherGuards.java new file mode 100644 index 00000000..310b2a36 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/auth/ClientLauncherGuards.java @@ -0,0 +1,37 @@ +package net.Gabou.projectatmosphere.auth; + +import net.minecraftforge.fml.loading.FMLEnvironment; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public final class ClientLauncherGuards { + private static final String SUSPICIOUS_FILE_NAME = "TLauncherAdditional.json"; + private static volatile String detectedReason; + + private ClientLauncherGuards() { + } + + public static void enforce() { + if (!FMLEnvironment.production) { + detectedReason = null; + return; + } + detectedReason = detectSuspiciousLauncher(); + } + + public static String detectSuspiciousLauncher() { + return findSuspiciousFile() ? "file:" + SUSPICIOUS_FILE_NAME : null; + } + + public static String getDetectedReason() { + return detectedReason; + } + + private static boolean findSuspiciousFile() { + Path cwd = Paths.get(System.getProperty("user.dir", ".")); + Path candidate = cwd.resolve(SUSPICIOUS_FILE_NAME); + return Files.exists(candidate); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/auth/PendingAuthManager.java b/src/main/java/net/Gabou/projectatmosphere/auth/PendingAuthManager.java new file mode 100644 index 00000000..ddfff5b8 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/auth/PendingAuthManager.java @@ -0,0 +1,54 @@ +package net.Gabou.projectatmosphere.auth; + +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.minecraft.server.level.ServerPlayer; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class PendingAuthManager { + public record PendingAuth(long nonce, long issuedAtMs) { + } + + private static final Map PENDING = new HashMap<>(); + + private PendingAuthManager() { + } + + public static PendingAuth begin(ServerPlayer player) { + PendingAuth pending = new PendingAuth(SharedSecret.createNonce(), System.currentTimeMillis()); + if (player != null) { + PENDING.put(player.getUUID(), pending); + } + return pending; + } + + public static PendingAuth get(UUID uuid) { + return uuid == null ? null : PENDING.get(uuid); + } + + public static boolean isPending(UUID uuid) { + return uuid != null && PENDING.containsKey(uuid); + } + + public static void clear(UUID uuid) { + if (uuid != null) { + PENDING.remove(uuid); + } + } + + public static void clear(ServerPlayer player) { + if (player != null) { + clear(player.getUUID()); + } + } + + public static Map snapshot() { + return new HashMap<>(PENDING); + } + + public static long getTimeoutMs() { + return Math.max(1, AtmoCommonConfig.AUTH_CHALLENGE_TIMEOUT_TICKS.get()) * 50L; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/auth/ServerAuth.java b/src/main/java/net/Gabou/projectatmosphere/auth/ServerAuth.java new file mode 100644 index 00000000..33a9af67 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/auth/ServerAuth.java @@ -0,0 +1,157 @@ +package net.Gabou.projectatmosphere.auth; + +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.Gabou.projectatmosphere.network.AuthChallengePacket; +import net.Gabou.projectatmosphere.network.AuthChallengeReplyPacket; +import net.Gabou.projectatmosphere.network.NetworkHandler; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.fml.loading.FMLEnvironment; +import net.minecraftforge.network.PacketDistributor; + +import java.util.Map; +import java.util.UUID; + +public final class ServerAuth { + private ServerAuth() { + } + + public static boolean onLogin(ServerPlayer player) { + if (player == null) { + return true; + } + + PendingAuthManager.clear(player); + + if (!FMLEnvironment.production) { + ProjectAtmosphere.LOGGER.info( + "Skipping launcher/auth checks for {} because the game is running in a development environment.", + player.getGameProfile().getName() + ); + return true; + } + + if (player.getUUID() != null + && player.getUUID().version() == 3 + && AtmoCommonConfig.AUTH_STRICT_OFFLINE_UUID_REJECT.get()) { + ProjectAtmosphere.LOGGER.warn( + "Rejected {} because strict auth mode disallows offline UUID v3 identities.", + player.getName().getString() + ); + player.connection.disconnect(Component.literal("Authentication rejected.")); + return false; + } + + PendingAuthManager.begin(player); + return true; + } + + public static void sendChallenge(ServerPlayer player) { + if (player == null) { + return; + } + + PendingAuthManager.PendingAuth pending = PendingAuthManager.get(player.getUUID()); + if (pending == null) { + return; + } + + NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), new AuthChallengePacket(pending.nonce())); + } + + public static boolean enforceLocalLauncherDetection(ServerPlayer player) { + if (player == null) { + return true; + } + + String launcherReason = ClientLauncherGuards.getDetectedReason(); + if (launcherReason == null || launcherReason.isBlank() || !(player.level() instanceof ServerLevel serverLevel)) { + return true; + } + + TLauncherDetectedHandler.handle(serverLevel, player, launcherReason); + onLogout(player); + return false; + } + + public static void onTick(MinecraftServer server) { + if (server == null) { + return; + } + + long now = System.currentTimeMillis(); + long timeoutMs = PendingAuthManager.getTimeoutMs(); + for (Map.Entry entry : PendingAuthManager.snapshot().entrySet()) { + PendingAuthManager.PendingAuth pending = entry.getValue(); + if (pending == null || now - pending.issuedAtMs() < timeoutMs) { + continue; + } + + UUID uuid = entry.getKey(); + PendingAuthManager.clear(uuid); + + ServerPlayer player = server.getPlayerList().getPlayer(uuid); + if (player == null) { + continue; + } + + ProjectAtmosphere.LOGGER.warn("Auth challenge timed out for {}", player.getName().getString()); + if (AtmoCommonConfig.AUTH_KICK_ON_FAILURE.get()) { + player.connection.disconnect(Component.literal("Authentication timed out.")); + } + } + } + + public static void onLogout(ServerPlayer player) { + if (player == null) { + return; + } + + PendingAuthManager.clear(player); + } + + public static void handleChallengeReply(ServerPlayer player, AuthChallengeReplyPacket packet) { + if (player == null || packet == null) { + return; + } + + String launcherReason = packet.launcherReason(); + if (launcherReason != null && !launcherReason.isBlank() && player.level() instanceof ServerLevel serverLevel) { + TLauncherDetectedHandler.handle(serverLevel, player, launcherReason); + PendingAuthManager.clear(player); + return; + } + + PendingAuthManager.PendingAuth pending = PendingAuthManager.get(player.getUUID()); + if (pending == null) { + markInvalid(player, "unexpected auth reply"); + return; + } + if (pending.nonce() != packet.nonce()) { + markInvalid(player, "nonce mismatch"); + return; + } + if (!SharedSecret.verifyResponse(player.getUUID(), packet.nonce(), packet.response())) { + markInvalid(player, "invalid auth response"); + return; + } + + PendingAuthManager.clear(player); + ProjectAtmosphere.LOGGER.info("Auth challenge completed for {}", player.getName().getString()); + } + + private static void markInvalid(ServerPlayer player, String reason) { + PendingAuthManager.clear(player); + ProjectAtmosphere.LOGGER.warn( + "Marked {} as failed auth because verification failed: {}", + player.getName().getString(), + reason + ); + if (AtmoCommonConfig.AUTH_KICK_ON_FAILURE.get()) { + player.connection.disconnect(Component.literal("Authentication failed.")); + } + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/auth/SharedSecret.java b/src/main/java/net/Gabou/projectatmosphere/auth/SharedSecret.java new file mode 100644 index 00000000..950e8458 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/auth/SharedSecret.java @@ -0,0 +1,39 @@ +package net.Gabou.projectatmosphere.auth; + +import net.Gabou.projectatmosphere.ProjectAtmosphere; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.HexFormat; +import java.util.UUID; + +public final class SharedSecret { + private static final SecureRandom RANDOM = new SecureRandom(); + private static final String SECRET = ProjectAtmosphere.MODID + ":auth:v1"; + + private SharedSecret() { + } + + public static long createNonce() { + return RANDOM.nextLong(); + } + + public static String computeResponse(UUID uuid, long nonce) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(SECRET.getBytes(StandardCharsets.UTF_8)); + digest.update((byte) 0); + digest.update(uuid.toString().getBytes(StandardCharsets.UTF_8)); + digest.update((byte) 0); + digest.update(Long.toString(nonce).getBytes(StandardCharsets.UTF_8)); + return HexFormat.of().formatHex(digest.digest()); + } catch (Exception exception) { + throw new IllegalStateException("Unable to compute auth response.", exception); + } + } + + public static boolean verifyResponse(UUID uuid, long nonce, String response) { + return response != null && computeResponse(uuid, nonce).equals(response); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/auth/TLauncherDetectedHandler.java b/src/main/java/net/Gabou/projectatmosphere/auth/TLauncherDetectedHandler.java new file mode 100644 index 00000000..2fe5620b --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/auth/TLauncherDetectedHandler.java @@ -0,0 +1,83 @@ +package net.Gabou.projectatmosphere.auth; + +import com.mojang.authlib.GameProfile; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.IpBanList; +import net.minecraft.server.players.IpBanListEntry; +import net.minecraft.server.players.UserBanList; +import net.minecraft.server.players.UserBanListEntry; + +import java.io.IOException; +import java.util.Date; +import java.util.UUID; + +public final class TLauncherDetectedHandler { + private static final String BAN_SOURCE = "Project Atmosphere"; + + private TLauncherDetectedHandler() { + } + + public static void handle(ServerLevel level, ServerPlayer player, String reason) { + if (level == null || player == null || reason == null || reason.isBlank()) { + return; + } + + handle(level, player.getUUID(), player.getName().getString(), reason); + banIp(level, player, reason); + disconnect(player, reason); + } + + public static void handle(ServerLevel level, UUID uuid, String playerName, String reason) { + if (level == null || uuid == null || reason == null || reason.isBlank()) { + return; + } + + GameProfile profile = new GameProfile(uuid, playerName == null || playerName.isBlank() ? uuid.toString() : playerName); + UserBanList bans = level.getServer().getPlayerList().getBans(); + if (!bans.isBanned(profile)) { + bans.add(new UserBanListEntry(profile, new Date(), BAN_SOURCE, null, reason)); + saveBanLists(level); + ProjectAtmosphere.LOGGER.error( + "Banned launcher-violating player {} ({}) on {}: {}", + profile.getName(), + profile.getId(), + level.dimension(), + reason + ); + } + } + + private static void banIp(ServerLevel level, ServerPlayer player, String reason) { + String ipAddress = player.getIpAddress(); + if (ipAddress == null || ipAddress.isBlank() || "".equals(ipAddress)) { + return; + } + + IpBanList ipBans = level.getServer().getPlayerList().getIpBans(); + if (ipBans.isBanned(ipAddress)) { + return; + } + + ipBans.add(new IpBanListEntry(ipAddress, new Date(), BAN_SOURCE, null, reason)); + saveBanLists(level); + ProjectAtmosphere.LOGGER.error("Banned launcher-violating IP {} on {}: {}", ipAddress, level.dimension(), reason); + } + + private static void saveBanLists(ServerLevel level) { + try { + level.getServer().getPlayerList().getBans().save(); + level.getServer().getPlayerList().getIpBans().save(); + } catch (IOException exception) { + ProjectAtmosphere.LOGGER.warn("Failed to persist launcher violation ban lists", exception); + } + } + + private static void disconnect(ServerPlayer player, String reason) { + if (player.connection != null) { + player.connection.disconnect(Component.literal("Launcher violation detected: " + reason)); + } + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/blocks/StormShieldBlock.java b/src/main/java/net/Gabou/projectatmosphere/blocks/StormShieldBlock.java new file mode 100644 index 00000000..ed9fa727 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/blocks/StormShieldBlock.java @@ -0,0 +1,29 @@ +package net.Gabou.projectatmosphere.blocks; + +import net.Gabou.projectatmosphere.modules.weather.StormShieldManager; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +public class StormShieldBlock extends Block { + public StormShieldBlock(Properties properties) { + super(properties); + } + + @Override + public void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) { + super.onPlace(state, level, pos, oldState, isMoving); + if (!level.isClientSide && !state.is(oldState.getBlock())) { + StormShieldManager.register(level, pos); + } + } + + @Override + public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { + if (!level.isClientSide && !state.is(newState.getBlock())) { + StormShieldManager.unregister(level, pos); + } + super.onRemove(state, level, pos, newState, isMoving); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/ClientPacketHandlers.java b/src/main/java/net/Gabou/projectatmosphere/client/ClientPacketHandlers.java index d772400b..037e0eb5 100644 --- a/src/main/java/net/Gabou/projectatmosphere/client/ClientPacketHandlers.java +++ b/src/main/java/net/Gabou/projectatmosphere/client/ClientPacketHandlers.java @@ -1,10 +1,7 @@ package net.Gabou.projectatmosphere.client; -import net.Gabou.projectatmosphere.compat.rainbows.RainbowWeatherTracker; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.Level; +import net.Gabou.projectatmosphere.client.atmosphere.AtmosphereClientState; +import net.Gabou.projectatmosphere.client.fog.AtmosphereFogState; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -14,8 +11,16 @@ public final class ClientPacketHandlers { private ClientPacketHandlers() { } - public static void handleRainfallUpdate(ResourceLocation dimensionId, float rainLevel) { - ResourceKey key = ResourceKey.create(Registries.DIMENSION, dimensionId); - RainbowWeatherTracker.applyServerUpdate(key, rainLevel); + public static void handleAtmosphereStatusUpdate(float humidityPercent, float rainIntensity, float cloudCover) { + AtmosphereClientState.applyServerUpdate(humidityPercent, rainIntensity, cloudCover); + AtmosphereFogState.applyServerUpdate(humidityPercent, rainIntensity); + } + + public static void handleFogDebugOverride(float strength, int durationTicks) { + if (durationTicks <= 0 || strength <= 0.0F) { + AtmosphereFogState.clearDebugOverride(); + return; + } + AtmosphereFogState.applyDebugOverride(strength, durationTicks); } } diff --git a/src/main/java/net/Gabou/projectatmosphere/client/ClientRenderHook.java b/src/main/java/net/Gabou/projectatmosphere/client/ClientRenderHook.java index cef4f1a2..e69de29b 100644 --- a/src/main/java/net/Gabou/projectatmosphere/client/ClientRenderHook.java +++ b/src/main/java/net/Gabou/projectatmosphere/client/ClientRenderHook.java @@ -1,72 +0,0 @@ -package net.Gabou.projectatmosphere.client; - -import com.mojang.blaze3d.vertex.PoseStack; -import dev.nonamecrackers2.simpleclouds.common.config.SimpleCloudsConfig; -import net.Gabou.projectatmosphere.ProjectAtmosphere; -import net.Gabou.projectatmosphere.modules.tornado.TornadoInstance; -import net.Gabou.projectatmosphere.modules.tornado.TornadoManager; -import net.minecraft.client.Camera; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.world.phys.Vec3; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.event.RenderLevelStageEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; - -import java.util.ArrayList; -import java.util.List; - -@Mod.EventBusSubscriber( - modid = ProjectAtmosphere.MODID, - bus = Mod.EventBusSubscriber.Bus.FORGE, - value = Dist.CLIENT -) -public class ClientRenderHook { - - - @SubscribeEvent - public static void onRender(RenderLevelStageEvent event) { - if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_PARTICLES) return; - if (Minecraft.getInstance().level == null) return; - - ClientLevel level = Minecraft.getInstance().level; - List snapshot = new ArrayList<>(TornadoManager.getActiveTornadoes()); - if (snapshot.isEmpty()) return; - PoseStack poseStack = event.getPoseStack(); - Camera camera = Minecraft.getInstance().gameRenderer.getMainCamera(); - Vec3 camPos = camera.getPosition(); - - - - poseStack.pushPose(); - poseStack.translate(-camPos.x, -camPos.y, -camPos.z); - - - for (TornadoInstance tornado : snapshot) { - try { - if (tornado.position.distanceToSqr(camPos) > 500 * 500) { - continue; - } - TornadoRenderHandler.renderTornado( - poseStack, - tornado.position.x, - Minecraft.getInstance().level.getSeaLevel(), - tornado.position.z, - tornado.getTwist(), - level, camera, Minecraft.getInstance(), tornado - - ); - - } catch (Exception e) { - ProjectAtmosphere.LOGGER.error("Error rendering tornado at position: " + tornado.position, e); - } - - } - - poseStack.popPose(); - } - - - -} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/ClientSyncLock.java b/src/main/java/net/Gabou/projectatmosphere/client/ClientSyncLock.java index d3fd4d5c..d4878bbd 100644 --- a/src/main/java/net/Gabou/projectatmosphere/client/ClientSyncLock.java +++ b/src/main/java/net/Gabou/projectatmosphere/client/ClientSyncLock.java @@ -1,6 +1,4 @@ package net.Gabou.projectatmosphere.client; - -import net.Gabou.projectatmosphere.ProjectAtmosphere; import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; @@ -24,9 +22,6 @@ public static void setReady(UUID playerUUID, boolean ready) { return; } ClientSyncLock.ready = ready; - if (ProjectAtmosphere.DEBUG_MODE) { - ProjectAtmosphere.LOGGER.info("[Atmosphere] Client forecast readiness for {} -> {}", playerUUID, ready); - } } public static void setReadyForLocalPlayer(boolean ready) { diff --git a/src/main/java/net/Gabou/projectatmosphere/client/ClientTickHandler.java b/src/main/java/net/Gabou/projectatmosphere/client/ClientTickHandler.java index 1a496f74..aa8d8218 100644 --- a/src/main/java/net/Gabou/projectatmosphere/client/ClientTickHandler.java +++ b/src/main/java/net/Gabou/projectatmosphere/client/ClientTickHandler.java @@ -1,18 +1,24 @@ package net.Gabou.projectatmosphere.client; import net.Gabou.projectatmosphere.async.PoolType; +import net.Gabou.projectatmosphere.client.atmosphere.AtmosphereClientState; +import net.Gabou.projectatmosphere.client.fog.AtmosphereFogState; +import net.Gabou.projectatmosphere.client.hurricane.ClientHurricaneStateCache; +import net.Gabou.projectatmosphere.client.TornadoClientEffects; import net.Gabou.projectatmosphere.config.AtmoCommonConfig; import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.Gabou.projectatmosphere.modules.hurricane.HurricaneManager; import net.Gabou.projectatmosphere.modules.tornado.TornadoInstance; import net.Gabou.projectatmosphere.modules.tornado.TornadoManager; import net.Gabou.projectatmosphere.client.sound.TornadoAudioClient; import net.Gabou.projectatmosphere.modules.wind.WindMath; import net.Gabou.projectatmosphere.registry.ModParticles; +import net.Gabou.projectatmosphere.client.render.SimpleCloudsRenderDiagnostics; import net.Gabou.projectatmosphere.util.AsyncAtmosphereService; import net.Gabou.projectatmosphere.util.AtmosphereUtils; import net.Gabou.projectatmosphere.util.BiomeInstanceKey; -import net.Gabou.projectatmosphere.compat.rainbows.RainbowWeatherTracker; +import net.Gabou.projectatmosphere.compat.sky.AtmosphereSkyEffectController; import net.Gabou.projectatmosphere.client.render.SkyEffectState; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; @@ -55,18 +61,25 @@ public static boolean isRegionCulled(CloudRegion region) { @SubscribeEvent public static void onClientTick(TickEvent.ClientTickEvent event) { if (event.phase != TickEvent.Phase.END) return; + Minecraft mc = Minecraft.getInstance(); + ClientHurricaneStateCache.tick(mc.level); if (!ClientSyncLock.isReady()) return; - if (Minecraft.getInstance().isPaused()) return; + if (mc.isPaused()) return; + AtmosphereClientState.tick(mc); + AtmosphereFogState.tick(mc); + if (mc.level == null) { + TornadoManager.clearClientTornadoes(); + return; + } SkyEffectState.beginFrame(); tickCounter++; - TornadoManager.tick(Minecraft.getInstance().level); - Minecraft mc = Minecraft.getInstance(); - RainbowWeatherTracker.tick(mc); + TornadoManager.tick(mc.level); + AtmosphereSkyEffectController.tick(mc); - if (mc.level != null && mc.player != null) { + if (mc.player != null) { CloudManager manager = CloudManager.get(mc.level); - List regions = manager.getCloudGenerator().getClouds(); + List regions = manager.getClouds(); double playerX = mc.player.getX(); double playerZ = mc.player.getZ(); Set nextCulled = new HashSet<>(); @@ -83,10 +96,11 @@ public static void onClientTick(TickEvent.ClientTickEvent event) { } if (mc.level != null) { - Set current = new HashSet<>(TornadoManager.getActiveTornadoes()); + Set current = new HashSet<>(TornadoManager.getClientTornadoes()); for (TornadoInstance tornado : current) { float baseVol = 0.35f + 0.45f * 0.75f; TornadoAudioClient.ensure(tornado, baseVol, 140f); + TornadoClientEffects.tickTornadoDust(tornado, mc.level, tickCounter); } for (TornadoInstance t : prevTornadoes) { if (!current.contains(t)) { @@ -97,13 +111,13 @@ public static void onClientTick(TickEvent.ClientTickEvent event) { prevTornadoes.addAll(current); } - if (mc.level != null && mc.level.getGameTime() % 2 == 0) { - for (TornadoInstance tornado : TornadoManager.getActiveTornadoes()) { - TornadoRenderHandler.spawnDebrisParticles(tornado, (ClientLevel) mc.level); - } - } if (tickCounter % 40 == 0) { - if (mc.level != null && mc.player != null) { + if (mc.player != null) { + if (mc.level != null) { + CloudManager manager = CloudManager.get(mc.level); + SimpleCloudsRenderDiagnostics.logPlayerSample(manager, mc.player.getX(), mc.player.getZ()); + } + // snapshot BlockPos pos = mc.player.blockPosition(); long gameTime = mc.level.getGameTime(); diff --git a/src/main/java/net/Gabou/projectatmosphere/client/MyShaders.java b/src/main/java/net/Gabou/projectatmosphere/client/MyShaders.java deleted file mode 100644 index c4271c6c..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/client/MyShaders.java +++ /dev/null @@ -1,10 +0,0 @@ -package net.Gabou.projectatmosphere.client; - -import net.minecraft.client.renderer.ShaderInstance; - -public class MyShaders { - - public static ShaderInstance TORNADO; - - public static ShaderInstance BOX_TORNADO; -} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/SimpleCloudsWhiteoutFogHandler.java b/src/main/java/net/Gabou/projectatmosphere/client/SimpleCloudsWhiteoutFogHandler.java new file mode 100644 index 00000000..8252c7cc --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/SimpleCloudsWhiteoutFogHandler.java @@ -0,0 +1,159 @@ +package net.Gabou.projectatmosphere.client; + +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import dev.nonamecrackers2.simpleclouds.client.renderer.WorldEffects; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudType; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.Gabou.projectatmosphere.client.fog.AtmosphereFogState; +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.Gabou.projectatmosphere.modules.fog.FogHeuristics; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.material.FogType; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.ViewportEvent; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import org.apache.commons.lang3.tuple.Pair; + +@Mod.EventBusSubscriber(modid = ProjectAtmosphere.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE) +public final class SimpleCloudsWhiteoutFogHandler { + private SimpleCloudsWhiteoutFogHandler() { + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void onRenderFog(ViewportEvent.RenderFog event) { + if (event.getType() != FogType.NONE) { + return; + } + ClientLevel level = Minecraft.getInstance().level; + if (level == null) { + return; + } + + SimpleCloudsRenderer renderer = SimpleCloudsRenderer.getOptionalInstance().orElse(null); + float partialTick = (float) event.getPartialTick(); + Vec3 cameraPos = event.getCamera().getPosition(); + FogHeuristics.FogProfile dynamicFog = AtmosphereFogState.sample(level, cameraPos, partialTick); + float cloudWhiteout = renderer == null ? 0.0F : computeCloudWhiteout(level, renderer, cameraPos); + float whiteout = cloudWhiteout; + float dynamicStrength = dynamicFog.strength(); + + if (whiteout <= 0.0F && dynamicStrength <= 0.0F) { + return; + } + + float baseNear = event.getNearPlaneDistance(); + float baseFar = event.getFarPlaneDistance(); + float nearPlane = baseNear; + float farPlane = baseFar; + + if (dynamicStrength > 0.0F && Level.OVERWORLD.equals(level.dimension())) { + float fogInfluence = Mth.clamp( + dynamicStrength * 0.55F + + dynamicStrength * dynamicStrength * 0.85F + + dynamicFog.wetBiomeFactor() * 0.20F + + dynamicFog.rainFactor() * 0.12F, + 0.0F, + 1.0F + ); + float configuredNear = AtmoCommonConfig.FOG_NEAR_DISTANCE.get().floatValue(); + float configuredFar = AtmoCommonConfig.FOG_FAR_DISTANCE.get().floatValue(); + nearPlane = Math.min(nearPlane, Mth.lerp(fogInfluence, baseNear, configuredNear)); + farPlane = Math.min(farPlane, Math.max(2.0F, Mth.lerp(fogInfluence, baseFar, configuredFar))); + } + if (whiteout > 0.0F) { + nearPlane = 0.0F; + farPlane = Math.min(farPlane, Math.max(0.35F, Mth.lerp(whiteout, baseFar, 0.85F))); + } + + event.setNearPlaneDistance(nearPlane); + event.setFarPlaneDistance(farPlane); + event.setCanceled(true); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void onComputeFogColor(ViewportEvent.ComputeFogColor event) { + ClientLevel level = Minecraft.getInstance().level; + if (level == null) { + return; + } + + SimpleCloudsRenderer renderer = SimpleCloudsRenderer.getOptionalInstance().orElse(null); + float partialTick = (float) event.getPartialTick(); + Vec3 cameraPos = event.getCamera().getPosition(); + FogHeuristics.FogProfile dynamicFog = AtmosphereFogState.sample(level, cameraPos, partialTick); + float cloudWhiteout = renderer == null ? 0.0F : computeCloudWhiteout(level, renderer, cameraPos); + float whiteout = cloudWhiteout; + float dynamicStrength = dynamicFog.strength(); + + if (whiteout <= 0.0F && dynamicStrength <= 0.0F) { + return; + } + + if (dynamicStrength > 0.0F && Level.OVERWORLD.equals(level.dimension())) { + float fogInfluence = Mth.clamp( + dynamicStrength * 0.55F + + dynamicStrength * dynamicStrength * 0.85F + + dynamicFog.wetBiomeFactor() * 0.20F + + dynamicFog.rainFactor() * 0.12F, + 0.0F, + 1.0F + ); + float colorBlend = AtmoCommonConfig.FOG_COLOR_BLEND.get().floatValue() + * Mth.clamp(dynamicStrength * 0.45F + fogInfluence * 0.70F, 0.0F, 1.0F); + float dampRed = Mth.clamp(event.getRed() * (0.88F - dynamicFog.wetBiomeFactor() * 0.06F), 0.0F, 1.0F); + float dampGreen = Mth.clamp(event.getGreen() * (0.90F + dynamicFog.wetBiomeFactor() * 0.05F), 0.0F, 1.0F); + float dampBlue = Mth.clamp(event.getBlue() * (0.95F + dynamicFog.rainFactor() * 0.05F), 0.0F, 1.0F); + event.setRed(Mth.lerp(colorBlend, event.getRed(), dampRed)); + event.setGreen(Mth.lerp(colorBlend, event.getGreen(), dampGreen)); + event.setBlue(Mth.lerp(colorBlend, event.getBlue(), dampBlue)); + } + + if (whiteout > 0.0F) { + float[] cloudColor = renderer == null ? null : renderer.getCloudColor(partialTick); + float dustyRed = cloudColor == null ? 0.30F : Mth.clamp(cloudColor[0] * 0.62F + 0.10F, 0.0F, 1.0F); + float dustyGreen = cloudColor == null ? 0.25F : Mth.clamp(cloudColor[1] * 0.55F + 0.08F, 0.0F, 1.0F); + float dustyBlue = cloudColor == null ? 0.20F : Mth.clamp(cloudColor[2] * 0.45F + 0.06F, 0.0F, 1.0F); + float dustyBlend = Mth.clamp(whiteout * 1.18F, 0.0F, 1.0F); + event.setRed(Mth.lerp(dustyBlend, event.getRed(), dustyRed)); + event.setGreen(Mth.lerp(dustyBlend, event.getGreen(), dustyGreen)); + event.setBlue(Mth.lerp(dustyBlend, event.getBlue(), dustyBlue)); + } + } + + private static float computeCloudWhiteout(ClientLevel level, SimpleCloudsRenderer renderer, Vec3 cameraPos) { + WorldEffects effects = renderer.getWorldEffectsManager(); + CloudType type = effects.getCloudTypeAtCamera(); + float fade = effects.getFadeRegionAtCamera(); + + if (type == null || type == SimpleCloudsConstants.EMPTY) { + Pair fallback = CloudManager.get(level).getCloudTypeAtWorldPos((float) cameraPos.x, (float) cameraPos.z); + type = fallback.getLeft(); + fade = fallback.getRight(); + } + + if (type == null || type == SimpleCloudsConstants.EMPTY) { + return 0.0F; + } + + float cloudBase = CloudManager.get(level).getCloudHeight(); + float bottom = cloudBase + type.noiseConfig().getStartHeight() * SimpleCloudsConstants.CLOUD_SCALE; + float top = cloudBase + type.noiseConfig().getEndHeight() * SimpleCloudsConstants.CLOUD_SCALE; + + if (cameraPos.y < bottom || cameraPos.y > top) { + return 0.0F; + } + + float horizontal = 1.0F - Mth.clamp(fade, 0.0F, 1.0F); + float distToVerticalEdge = (float) Math.min(cameraPos.y - bottom, top - cameraPos.y); + float vertical = 1.0F - Mth.clamp(distToVerticalEdge / 16.0F, 0.0F, 1.0F); + return Mth.clamp(horizontal * vertical, 0.0F, 1.0F); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/TornadoClientEffects.java b/src/main/java/net/Gabou/projectatmosphere/client/TornadoClientEffects.java new file mode 100644 index 00000000..4697cd42 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/TornadoClientEffects.java @@ -0,0 +1,116 @@ +package net.Gabou.projectatmosphere.client; + +import dev.nonamecrackers2.simpleclouds.common.config.SimpleCloudsConfig; +import net.Gabou.projectatmosphere.modules.tornado.TornadoInstance; +import net.Gabou.projectatmosphere.particles.DebrisParticleData; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.BlockParticleOption; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.util.Mth; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.phys.Vec3; + +public final class TornadoClientEffects { + private static final int LOW_BAND = 0; + private static final int MID_BAND = 1; + private static final int UPPER_BAND = 2; + private static final int DUST_SPAWN_INTERVAL_TICKS = 4; + private static final int DUST_LIFETIME_ESTIMATE_TICKS = 96; + + private TornadoClientEffects() { + } + + public static void tickTornadoDust(TornadoInstance tornado, ClientLevel level, int clientTick) { + if (clientTick % DUST_SPAWN_INTERVAL_TICKS != 0 || tornado.getNormalizedIntensity() <= 0.04F) { + return; + } + Vec3 cameraPos = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition(); + Vec3 tornadoPos = tornado.getRenderPosition(1.0F); + double visualRadius = Math.max(12.0D, tornado.getRenderRadius(1.0F)); + int targetActiveParticles = Mth.clamp( + Mth.floor(30.0F + tornado.getNormalizedIntensity() * 30.0F + (float)Math.min(visualRadius, 500.0D) / 500.0F * 18.0F), + 18, + 60 + ); + int count = Math.max(1, Mth.ceil((float)targetActiveParticles * DUST_SPAWN_INTERVAL_TICKS / DUST_LIFETIME_ESTIMATE_TICKS)); + spawnFallingDustCurtain(level, tornado, cameraPos, tornadoPos, visualRadius, count); + } + + public static void spawnDebrisParticles(TornadoInstance tornado, ClientLevel level) { + double visualHeight = Math.min(tornado.getRenderHeight(1.0F), SimpleCloudsConfig.CLIENT.cloudHeight.get()); + double maxRadius = Math.max(4.0, tornado.getRenderRadius(1.0F)); + float intensity = tornado.getNormalizedIntensity(); + float debrisScore = tornado.getRecentDebrisScore(); + + int lowCount = 7 + Math.round(intensity * 7.0F + debrisScore * 10.0F); + int midCount = 12 + Math.round(intensity * 8.0F + debrisScore * 7.0F); + int upperCount = 4 + Math.round(intensity * 4.0F + debrisScore * 2.0F); + + spawnBand(level, tornado, lowCount, maxRadius * 1.24D, visualHeight * 0.20D, 8.4F, 0.020F, 0.42F, LOW_BAND); + spawnBand(level, tornado, midCount, maxRadius * 0.76D, visualHeight * 0.74D, 16.0F, 0.046F, 0.30F, MID_BAND); + spawnBand(level, tornado, upperCount, maxRadius * 1.24D, visualHeight * 1.06D, 5.4F, 0.026F, 0.46F, UPPER_BAND); + } + + private static void spawnFallingDustCurtain(ClientLevel level, TornadoInstance tornado, Vec3 cameraPos, + Vec3 tornadoPos, double visualRadius, int count) { + Vec3 cameraToTornado = new Vec3(tornadoPos.x - cameraPos.x, 0.0D, tornadoPos.z - cameraPos.z); + if (cameraToTornado.lengthSqr() < 1.0E-4D) { + cameraToTornado = new Vec3(1.0D, 0.0D, 0.0D); + } + Vec3 behindDir = cameraToTornado.normalize(); + Vec3 lateralDir = new Vec3(-behindDir.z, 0.0D, behindDir.x); + double curtainRadius = Mth.clamp(visualRadius * 2.2D, 28.0D, 500.0D); + double nearDistance = visualRadius * 0.80D; + double farDistance = curtainRadius; + + for (int i = 0; i < count; i++) { + double distanceBehind = nearDistance + level.random.nextDouble() * Math.max(4.0D, farDistance - nearDistance); + double lateral = (level.random.nextDouble() * 2.0D - 1.0D) * curtainRadius * 0.52D; + double spawnX = tornadoPos.x + behindDir.x * distanceBehind + lateralDir.x * lateral; + double spawnZ = tornadoPos.z + behindDir.z * distanceBehind + lateralDir.z * lateral; + BlockPos column = BlockPos.containing(spawnX, tornado.getRenderBottomY(1.0F), spawnZ); + if (!level.hasChunkAt(column)) { + continue; + } + + int surfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, column.getX(), column.getZ()); + double spawnY = surfaceY + 4.0D + level.random.nextDouble() * 10.0D; + double inwardPull = 0.015D + tornado.getNormalizedIntensity() * 0.035D; + double fallSpeed = -0.035D - level.random.nextDouble() * 0.045D; + double swirl = (level.random.nextDouble() * 2.0D - 1.0D) * 0.035D; + level.addParticle( + new BlockParticleOption(ParticleTypes.FALLING_DUST, Blocks.DIRT.defaultBlockState()), + spawnX, + spawnY, + spawnZ, + -behindDir.x * inwardPull + lateralDir.x * swirl, + fallSpeed, + -behindDir.z * inwardPull + lateralDir.z * swirl + ); + } + } + + private static void spawnBand(ClientLevel level, TornadoInstance tornado, int count, double maxRadius, double maxHeight, + float angularSpeed, float verticalDrift, float radialJitter, int band) { + for (int i = 0; i < count; i++) { + double radius = Math.sqrt(level.random.nextDouble()) * Math.max(0.6D, maxRadius); + double height = level.random.nextDouble() * Math.max(1.0D, maxHeight); + float localAngularSpeed = (float) (angularSpeed * (0.82F + level.random.nextFloat() * 0.42F)); + float localVerticalDrift = verticalDrift * (0.75F + level.random.nextFloat() * 0.55F); + float localRadialJitter = radialJitter * (0.65F + level.random.nextFloat() * 0.70F); + + level.addParticle( + new DebrisParticleData(tornado, radius, height, localAngularSpeed, localVerticalDrift, localRadialJitter, band), + tornado.getRenderPosition(1.0F).x, + tornado.getRenderBottomY(1.0F), + tornado.getRenderPosition(1.0F).z, + 0.0, + localVerticalDrift, + 0.0 + ); + } + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/TornadoRenderHandler.java b/src/main/java/net/Gabou/projectatmosphere/client/TornadoRenderHandler.java deleted file mode 100644 index 67b1cb40..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/client/TornadoRenderHandler.java +++ /dev/null @@ -1,415 +0,0 @@ -package net.Gabou.projectatmosphere.client; - -import com.mojang.blaze3d.pipeline.RenderTarget; -import com.mojang.blaze3d.shaders.Uniform; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.*; -import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; -import dev.nonamecrackers2.simpleclouds.common.config.SimpleCloudsConfig; -import net.Gabou.projectatmosphere.ProjectAtmosphere; -import net.Gabou.projectatmosphere.modules.tornado.TornadoInstance; -import net.Gabou.projectatmosphere.modules.tornado.TornadoManager; -import net.Gabou.projectatmosphere.modules.tornado.TornadoLevel; -import net.Gabou.projectatmosphere.particles.DebrisParticleData; -import net.minecraft.client.Camera; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.client.renderer.ShaderInstance; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.phys.Vec3; -import org.joml.Matrix4f; - -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.lwjgl.opengl.GL11C.GL_BACK; -import static org.lwjgl.opengl.GL11C.glCullFace; - -public class TornadoRenderHandler { - - private static final ResourceLocation NOISE_TEXTURE = - ResourceLocation.fromNamespaceAndPath("projectatmosphere", "textures/effects/noise.png"); - private static final ResourceLocation TORNADO_TEXTURE = - ResourceLocation.fromNamespaceAndPath("projectatmosphere", "textures/effects/base.png"); - private static final ResourceLocation FLOWMAP_TEXTURE = - ResourceLocation.fromNamespaceAndPath("projectatmosphere", "textures/effects/flowmap.png"); - private static final ResourceLocation NORMALMAP_TEXTURE = - ResourceLocation.fromNamespaceAndPath("projectatmosphere", "textures/effects/tornado_normal.png"); - - private static final float SPAWN_DESCENT_DURATION = 10.0f; - - - public static void renderTornado(PoseStack stack, double tornadoX, double tornadoY, double tornadoZ, float twistSpeed, ClientLevel level, Camera camera, Minecraft minecraft, TornadoInstance tornado) { - ShaderInstance shader = MyShaders.TORNADO; - if (shader == null) return; - -// // Bind the tornado’s base texture: prefer SimpleClouds’ cloud color target, fallback to static texture -// AtomicBoolean boundBaseToClouds = new AtomicBoolean(false); -// SimpleCloudsRenderer.getOptionalInstance().ifPresent(scr -> { -// RenderTarget cloudRT = scr.getCloudTarget(); // offscreen clouds color -// if (cloudRT == null) { -// ProjectAtmosphere.LOGGER.warn("Cloud render target is null, cannot bind clouds as tornado base texture."); -// return; -// } -// // Replace the base sampler (Sampler0) with the cloud texture and also expose it as CloudScene -// shader.setSampler("Sampler0", cloudRT); -// shader.setSampler("CloudScene", cloudRT); -// -// // Pass size so we can compute screen-space UVs in the shader -// Uniform u = shader.getUniform("ScreenSizeX"); -// Uniform u1 = shader.getUniform("ScreenSizeY"); -// if (u != null && u1 != null) { -// u.set((float) cloudRT.width); -// u1.set((float) cloudRT.height); -// } -// boundBaseToClouds.set(true); -// }); -// -// if (!boundBaseToClouds.get()) { - // Bind the tornado base texture: prefer SimpleClouds' cloud color target, fallback to static texture - AtomicBoolean boundBaseToClouds = new AtomicBoolean(false); - SimpleCloudsRenderer.getOptionalInstance().ifPresent(scr -> { - RenderTarget cloudRT = scr.getCloudTarget(); - if (cloudRT == null) { - ProjectAtmosphere.LOGGER.warn("Cloud render target is null, cannot bind clouds as tornado base texture."); - return; - } - shader.setSampler("Sampler0", cloudRT); - shader.setSampler("CloudScene", cloudRT); - - Uniform u = shader.getUniform("ScreenSizeX"); - Uniform u1 = shader.getUniform("ScreenSizeY"); - if (u != null && u1 != null) { - u.set((float) cloudRT.width); - u1.set((float) cloudRT.height); - } - boundBaseToClouds.set(true); - }); - - if (!boundBaseToClouds.get()) { - RenderSystem.setShaderTexture(0, TORNADO_TEXTURE); - RenderSystem.setShaderTexture(4, TORNADO_TEXTURE); - } - RenderSystem.setShaderTexture(1, FLOWMAP_TEXTURE); - RenderSystem.setShaderTexture(2, NORMALMAP_TEXTURE); - RenderSystem.setShaderTexture(3, NOISE_TEXTURE); - RenderSystem.setShader(() -> shader); - shader.apply(); - int segments = 64; - int rings = 128; - float baseRadius = 20f; - float topRadius = 5f; - float height = 356f; - stack.pushPose(); - stack.translate(tornadoX, tornadoY, tornadoZ); - - Matrix4f matrix = stack.last().pose(); - - - var modelView = shader.getUniform("ModelViewMat"); - if (modelView != null) modelView.set(matrix); - - var projMat = shader.getUniform("ProjMat"); - if (projMat != null) projMat.set(RenderSystem.getProjectionMatrix()); - - var timeUniform = shader.getUniform("Time"); - if (timeUniform != null) timeUniform.set(TornadoManager.getShaderTime()); - - var twistUniform = shader.getUniform("TwistSpeed"); - if (twistUniform != null) twistUniform.set(twistSpeed); - - var baseRadiusUniform = shader.getUniform("BaseRadius"); - if (baseRadiusUniform != null) baseRadiusUniform.set(baseRadius); - - var topRadiusUniform = shader.getUniform("TopRadius"); - if (topRadiusUniform != null) topRadiusUniform.set(topRadius); - - var heightUniform = shader.getUniform("Height"); - if (heightUniform != null) heightUniform.set(height); - - - var dustUniform = shader.getUniform("DustIntensity"); - if (dustUniform != null) dustUniform.set(0.5F); - - var coreUniform = shader.getUniform("CoreTightness"); - if (coreUniform != null) coreUniform.set(0.2f); - - var flowIntensity = shader.getUniform("FlowIntensity"); - if (flowIntensity != null) flowIntensity.set(0.1f); - - var scaleUniform = shader.getUniform("Scale"); - if (scaleUniform != null) { - float scale = (float) (tornado.getLevel().getBaseDamage() / TornadoLevel.F1.getBaseDamage()); - scaleUniform.set(scale); - } - - - float partialTicks = minecraft.getFrameTime(); - float sunAngle = level.getTimeOfDay(partialTicks); - float angle = sunAngle * ((float) Math.PI * 2.0F); - - - float xLight = Mth.cos(angle); - float yLight = Mth.sin(angle); - float zLight = 0.2f; - - - float length = Mth.sqrt(xLight * xLight + yLight * yLight + zLight * zLight); - xLight /= length; - yLight /= length; - zLight /= length; - - - var lightX = shader.getUniform("LightDirX"); - var lightY = shader.getUniform("LightDirY"); - var lightZ = shader.getUniform("LightDirZ"); - - if (lightX != null) lightX.set(xLight); - if (lightY != null) lightY.set(yLight); - if (lightZ != null) lightZ.set(zLight); - - - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.disableCull(); - RenderSystem.enableDepthTest(); - RenderSystem.depthMask(true); - - - Tesselator tess = Tesselator.getInstance(); - BufferBuilder buffer = tess.getBuilder(); - buffer.begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION_TEX); - - - float windSpeed = tornado.wind.gustSpeed(); - float windAngleDeg = tornado.wind.angleRadians(); - float windAngleRad = (float) Math.toRadians(windAngleDeg); - - double windX = Math.cos(windAngleRad) * windSpeed; - double windZ = Math.sin(windAngleRad) * windSpeed; - - Vec3 horizontalWind = new Vec3(windX, 5, windZ); - - - float spawnProgress = Mth.clamp(tornado.getLifetimeSeconds() / SPAWN_DESCENT_DURATION, 0f, 1f); - float cutoffY = height * (1.0f - spawnProgress); - - float time = TornadoManager.getShaderTime(); - - for (int i = rings - 1; i >= 0; i--) { - float y0 = i * (height / rings); - float y1 = (i + 1) * (height / rings); - if (y1 < cutoffY) { - break; - } - if (y0 < cutoffY) { - y0 = cutoffY; - } - - float t0 = y0 / height; - float t1 = y1 / height; - - - for (int j = 0; j < segments; j++) { - float u0 = j / (float) segments; - float u1 = (j + 1f) / segments; - - final float U_EPS = 1e-6f; - float u0s = (j == 0) ? (u0 + U_EPS) : u0; - float u1s = (j == segments - 1) ? (1f - U_EPS) : u1; - - float twist = (float) (Math.PI * 3.5); - float angleOffset0 = twist * (1 - t0); - float angleOffset1 = twist * (1 - t1); - - float angle0_0 = (float) (2 * Math.PI * u0 + angleOffset0); - float angle0_1 = (float) (2 * Math.PI * u1 + angleOffset0); - float angle1_0 = (float) (2 * Math.PI * u0 + angleOffset1); - float angle1_1 = (float) (2 * Math.PI * u1 + angleOffset1); - - - float x00 = tornadoShapeRadius(y0, angle0_0, time) * (float) Math.cos(angle0_0); - float z00 = tornadoShapeRadius(y0, angle0_0, time) * (float) Math.sin(angle0_0); - float x01 = tornadoShapeRadius(y0, angle0_1, time) * (float) Math.cos(angle0_1); - float z01 = tornadoShapeRadius(y0, angle0_1, time) * (float) Math.sin(angle0_1); - float x10 = tornadoShapeRadius(y1, angle1_0, time) * (float) Math.cos(angle1_0); - float z10 = tornadoShapeRadius(y1, angle1_0, time) * (float) Math.sin(angle1_0); - float x11 = tornadoShapeRadius(y1, angle1_1, time) * (float) Math.cos(angle1_1); - float z11 = tornadoShapeRadius(y1, angle1_1, time) * (float) Math.sin(angle1_1); - - - float wiggleFreq = 5f; - float wiggleAmp = 0.5f; - - x00 += Math.sin(y0 * 0.1f + angle0_0 * wiggleFreq) * wiggleAmp; - z00 += Math.cos(y0 * 0.1f + angle0_0 * wiggleFreq) * wiggleAmp; - x01 += Math.sin(y0 * 0.1f + angle0_1 * wiggleFreq) * wiggleAmp; - z01 += Math.cos(y0 * 0.1f + angle0_1 * wiggleFreq) * wiggleAmp; - x10 += Math.sin(y1 * 0.1f + angle1_0 * wiggleFreq) * wiggleAmp; - z10 += Math.cos(y1 * 0.1f + angle1_0 * wiggleFreq) * wiggleAmp; - x11 += Math.sin(y1 * 0.1f + angle1_1 * wiggleFreq) * wiggleAmp; - z11 += Math.cos(y1 * 0.1f + angle1_1 * wiggleFreq) * wiggleAmp; - - float epsilon = 0.0001f; - float v0 = (y0 + epsilon) / height; - float v1 = (y1 - epsilon) / height; - - float bendScale = 1.5f; - float bendFactor0 = (y0 / height) * bendScale * windSpeed; - float bendFactor1 = (y1 / height) * bendScale * windSpeed; - - float offsetX0 = (float) horizontalWind.x * bendFactor0; - float offsetZ0 = (float) horizontalWind.z * bendFactor0; - float offsetX1 = (float) horizontalWind.x * bendFactor1; - float offsetZ1 = (float) horizontalWind.z * bendFactor1; - - x00 += offsetX0; - z00 += offsetZ0; - x01 += offsetX0; - z01 += offsetZ0; - x10 += offsetX1; - z10 += offsetZ1; - x11 += offsetX1; - z11 += offsetZ1; - - - buffer.vertex(matrix, x00, y0, z00).uv(u0s, v0).endVertex(); - buffer.vertex(matrix, x10, y1, z10).uv(u0s, v1).endVertex(); - buffer.vertex(matrix, x11, y1, z11).uv(u1s, v1).endVertex(); - - buffer.vertex(matrix, x00, y0, z00).uv(u0s, v0).endVertex(); - buffer.vertex(matrix, x11, y1, z11).uv(u1s, v1).endVertex(); - buffer.vertex(matrix, x01, y0, z01).uv(u1s, v0).endVertex(); - } - } - - -// --- Cone-like bowl (frustum) above the top --- - int bowlRings = 24; // mesh resolution - float bowlHeight = 12f; // vertical size of the cap - topRadius=topRadius-3f; - float angleDeg = 18f; // or: - float targetTopR = topRadius * 1.6f; // or: - float factor = 1.6f; - float p = 1.0f; // 1 = straight cone; 1.05–1.2 = cone-ish but slightly rounded - -// radius grows linearly with height → cone look -// slope = how many blocks of radius per 1 block of height - float flareSlope = 0.30f; // tune: 0.2 = subtle, 0.5 = wide cone - float maxBowlRadius = topRadius + flareSlope * bowlHeight; - - for (int i = 0; i < bowlRings; i++) { - float t0 = i / (float) bowlRings; - float t1 = (i + 1f) / bowlRings; - - float y0 = height + t0 * bowlHeight; - float y1 = height + t1 * bowlHeight; - - // Pure conical growth (straight sides) - float r0 = coneRadiusByAngle(y0, height, topRadius, bowlHeight, angleDeg, p); - float r1 = coneRadiusByAngle(y1, height, topRadius, bowlHeight, angleDeg, p); - - // OPTIONAL: "rounded cone" (very slight curvature, still cone-ish) - // float p = 1.10f; // 1.0 = pure cone, >1 = even straighter near seam - // r0 = topRadius + (maxBowlRadius - topRadius) * (float) Math.pow(t0, p); - // r1 = topRadius + (maxBowlRadius - topRadius) * (float) Math.pow(t1, p); - - // Keep twist continuity with the funnel - float twist = (float) (Math.PI * 3.5); - float aOff0 = twist * (1f - Math.min(1f, y0 / height)); - float aOff1 = twist * (1f - Math.min(1f, y1 / height)); - - for (int j = 0; j < segments; j++) { - float u0 = j / (float) segments; - float u1 = (j + 1f) / (float) segments; - - float a00 = (float) (2 * Math.PI * u0 + aOff0); - float a01 = (float) (2 * Math.PI * u1 + aOff0); - float a10 = (float) (2 * Math.PI * u0 + aOff1); - float a11 = (float) (2 * Math.PI * u1 + aOff1); - - float x00 = r0 * (float) Math.cos(a00); - float z00 = r0 * (float) Math.sin(a00); - float x01 = r0 * (float) Math.cos(a01); - float z01 = r0 * (float) Math.sin(a01); - float x10 = r1 * (float) Math.cos(a10); - float z10 = r1 * (float) Math.sin(a10); - float x11 = r1 * (float) Math.cos(a11); - float z11 = r1 * (float) Math.sin(a11); - - // Optional: same bend so the bowl leans with wind - float b0 = (y0 / (height + bowlHeight)) * 1.5f * windSpeed; - float b1 = (y1 / (height + bowlHeight)) * 1.5f * windSpeed; - x00 += (float) horizontalWind.x * b0; - z00 += (float) horizontalWind.z * b0; - x01 += (float) horizontalWind.x * b0; - z01 += (float) horizontalWind.z * b0; - x10 += (float) horizontalWind.x * b1; - z10 += (float) horizontalWind.z * b1; - x11 += (float) horizontalWind.x * b1; - z11 += (float) horizontalWind.z * b1; - - // Clamp V near 1 to avoid stretching if your shader expects 0..1 - float epsilon = 1e-4f; - float v0 = Math.min(1f - epsilon, y0 / height); - float v1 = Math.min(1f - epsilon, y1 / height); - - buffer.vertex(matrix, x00, y0, z00).uv(u0, v0).endVertex(); - buffer.vertex(matrix, x10, y1, z10).uv(u0, v1).endVertex(); - buffer.vertex(matrix, x11, y1, z11).uv(u1, v1).endVertex(); - - buffer.vertex(matrix, x00, y0, z00).uv(u0, v0).endVertex(); - buffer.vertex(matrix, x11, y1, z11).uv(u1, v1).endVertex(); - buffer.vertex(matrix, x01, y0, z01).uv(u1, v0).endVertex(); - } - } - - - tess.end(); - RenderSystem.enableCull(); - RenderSystem.disableBlend(); - stack.popPose(); - } - - public static void spawnDebrisParticles(TornadoInstance tornado, ClientLevel level) { - for (int i = 0; i < 10; i++) { - double maxRadius = 16.0; - double radius = Math.sqrt(level.random.nextDouble()) * maxRadius; - double height = level.random.nextDouble() * SimpleCloudsConfig.CLIENT.cloudHeight.get(); - float angularSpeed = 4f; - - level.addParticle(new DebrisParticleData(tornado, radius, height, angularSpeed), - tornado.position.x, tornado.position.y, tornado.position.z, 0, 0.01, 0); - } - } - - - - private static float tornadoShapeRadius(float y, float angle, float time) { - float yAdj = y + 45.0f; - float zcurve = (float) Math.pow(yAdj, 1.5f) * 0.03f; - float base = zcurve + 5.5f; - float scale = Mth.clamp(zcurve * 0.2f, 0.1f, 1.0f); - float radius = base + scale * Mth.sin((time - Mth.sqrt(yAdj)) + angle) * 5.0f; - float ridgedNoise = 1.0f - 2.0f * Math.abs(Mth.sin((time * 1.5f + 0.1f * yAdj) + angle)); - radius -= ridgedNoise * 1.2f; - return radius; - } - - /** - * Cone cap radius by specifying a *cone angle* (degrees). - * angleDeg = 0..89 (slope = tan(angleDeg)) - * p = 1 for pure cone; >1 slightly round; <1 flares faster. - */ - static float coneRadiusByAngle(float y, float seamY, float topRadius, float bowlHeight, - float angleDeg, float p) { - float t = Mth.clamp((y - seamY) / bowlHeight, 0f, 1f); - float slope = (float) Math.tan(Math.toRadians(angleDeg)); - float targetR = topRadius + slope * bowlHeight; - float linear = topRadius + (targetR - topRadius) * t; - return p == 1f ? linear : topRadius + (targetR - topRadius) * (float) Math.pow(t, p); - } - - -} - diff --git a/src/main/java/net/Gabou/projectatmosphere/client/TornadoShaders.java b/src/main/java/net/Gabou/projectatmosphere/client/TornadoShaders.java deleted file mode 100644 index bea52098..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/client/TornadoShaders.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.Gabou.projectatmosphere.client; - -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import net.Gabou.projectatmosphere.ProjectAtmosphere; -import net.minecraft.client.renderer.ShaderInstance; -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.event.RegisterShadersEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; - -import java.io.IOException; - -@Mod.EventBusSubscriber(modid = ProjectAtmosphere.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) -public class TornadoShaders { - - - @SubscribeEvent - public static void onRegisterShaders(RegisterShadersEvent event) throws IOException { - ShaderInstance shader = new ShaderInstance(event.getResourceProvider(), - ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "tornado"), DefaultVertexFormat.POSITION_TEX); - event.registerShader(shader, s -> MyShaders.TORNADO = s); - ShaderInstance shader1 = new ShaderInstance(event.getResourceProvider(), - ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "box_tornado"), DefaultVertexFormat.POSITION_TEX); - event.registerShader(shader1, s -> MyShaders.BOX_TORNADO = s); - } - - -} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/atmosphere/AtmosphereClientState.java b/src/main/java/net/Gabou/projectatmosphere/client/atmosphere/AtmosphereClientState.java new file mode 100644 index 00000000..3b03e194 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/atmosphere/AtmosphereClientState.java @@ -0,0 +1,167 @@ +package net.Gabou.projectatmosphere.client.atmosphere; + +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import net.Gabou.projectatmosphere.client.fog.FogBiomeClassifier; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +@OnlyIn(Dist.CLIENT) +public final class AtmosphereClientState { + private static final float HUMIDITY_TRACKING = 0.18F; + private static final float RAIN_TRACKING = 0.22F; + private static final float CLOUD_TRACKING = 0.16F; + private static final float CLEARING_TRACKING = 0.20F; + private static final float RECENT_RAIN_GAIN = 0.14F; + private static final float RECENT_RAIN_DECAY = 0.0045F; + private static final int SERVER_SAMPLE_STALE_TICKS = 120; + + private static float targetHumidityPercent; + private static float visualHumidityPercent; + private static float targetRainIntensity; + private static float visualRainIntensity; + private static float targetCloudCover; + private static float visualCloudCover; + private static float recentRainFactor; + private static float clearingTrend; + private static boolean hasServerSample; + private static int serverSampleAgeTicks; + private static ResourceKey lastDimension; + + private AtmosphereClientState() { + } + + public static void applyServerUpdate(float humidityPercent, float rainIntensity, float cloudCover) { + hasServerSample = true; + serverSampleAgeTicks = 0; + targetHumidityPercent = Mth.clamp(humidityPercent, 0.0F, 100.0F); + targetRainIntensity = Mth.clamp(rainIntensity, 0.0F, 1.0F); + targetCloudCover = Mth.clamp(cloudCover, 0.0F, 1.0F); + } + + public static void tick(Minecraft minecraft) { + ClientLevel level = minecraft.level; + if (level == null || minecraft.player == null) { + clear(); + return; + } + + ResourceKey dimension = level.dimension(); + if (lastDimension != null && !lastDimension.equals(dimension)) { + clearVisuals(); + hasServerSample = false; + serverSampleAgeTicks = 0; + } + lastDimension = dimension; + + if (serverSampleAgeTicks < Integer.MAX_VALUE) { + serverSampleAgeTicks++; + } + if (serverSampleAgeTicks > SERVER_SAMPLE_STALE_TICKS) { + hasServerSample = false; + } + + BlockPos pos = minecraft.player.blockPosition(); + float fallbackHumidity = FogBiomeClassifier.computeFallbackHumidityPercent(level, pos); + float fallbackRain = FogBiomeClassifier.computeClientRainIntensity(level, pos); + float fallbackCloud = estimateFallbackCloudCover(level, pos, fallbackRain); + + if (!Level.OVERWORLD.equals(dimension)) { + targetHumidityPercent = 0.0F; + targetRainIntensity = 0.0F; + targetCloudCover = 0.0F; + hasServerSample = false; + } else if (!hasServerSample) { + targetHumidityPercent = fallbackHumidity; + targetRainIntensity = fallbackRain; + targetCloudCover = fallbackCloud; + } else { + targetHumidityPercent = Mth.clamp(targetHumidityPercent, 0.0F, 100.0F); + targetRainIntensity = Math.max(Mth.clamp(targetRainIntensity, 0.0F, 1.0F), fallbackRain * 0.75F); + targetCloudCover = Math.max(Mth.clamp(targetCloudCover, 0.0F, 1.0F), fallbackCloud * 0.65F); + } + + float previousCloudCover = visualCloudCover; + visualHumidityPercent = Mth.lerp(HUMIDITY_TRACKING, visualHumidityPercent, targetHumidityPercent); + visualRainIntensity = Mth.lerp(RAIN_TRACKING, visualRainIntensity, targetRainIntensity); + visualCloudCover = Mth.lerp(CLOUD_TRACKING, visualCloudCover, targetCloudCover); + + float cloudClearingSample = Mth.clamp((previousCloudCover - visualCloudCover) * 7.5F, 0.0F, 1.0F); + clearingTrend = Mth.lerp(CLEARING_TRACKING, clearingTrend, cloudClearingSample); + + if (visualRainIntensity > 0.05F) { + recentRainFactor = Mth.clamp( + Math.max(recentRainFactor, visualRainIntensity) + (visualRainIntensity * RECENT_RAIN_GAIN), + 0.0F, + 1.0F + ); + } else { + recentRainFactor = Math.max(0.0F, recentRainFactor - RECENT_RAIN_DECAY); + } + } + + public static Snapshot getSnapshot() { + return new Snapshot( + visualHumidityPercent, + visualRainIntensity, + visualCloudCover, + recentRainFactor, + clearingTrend + ); + } + + public static float getRainIntensity() { + return visualRainIntensity; + } + + public static float getCloudCover() { + return visualCloudCover; + } + + public static float getHumidityPercent() { + return visualHumidityPercent; + } + + private static float estimateFallbackCloudCover(ClientLevel level, BlockPos pos, float fallbackRain) { + try { + return CloudManager.get(level).getCloudGenerator().getCloudAtWorldPosition(pos.getX() + 0.5F, pos.getZ() + 0.5F) != null + ? Math.max(0.65F, fallbackRain) + : (fallbackRain > 0.0F ? 0.72F : 0.0F); + } catch (Exception ignored) { + return fallbackRain > 0.0F ? 0.72F : 0.0F; + } + } + + private static void clearVisuals() { + targetHumidityPercent = 0.0F; + visualHumidityPercent = 0.0F; + targetRainIntensity = 0.0F; + visualRainIntensity = 0.0F; + targetCloudCover = 0.0F; + visualCloudCover = 0.0F; + recentRainFactor = 0.0F; + clearingTrend = 0.0F; + } + + private static void clear() { + clearVisuals(); + hasServerSample = false; + serverSampleAgeTicks = 0; + lastDimension = null; + } + + public record Snapshot( + float humidityPercent, + float rainIntensity, + float cloudCover, + float recentRainFactor, + float clearingTrend + ) { + public static final Snapshot NONE = new Snapshot(0.0F, 0.0F, 0.0F, 0.0F, 0.0F); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/crash/ProjectAtmosphereCrashHandler.java b/src/main/java/net/Gabou/projectatmosphere/client/crash/ProjectAtmosphereCrashHandler.java new file mode 100644 index 00000000..158d80d2 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/crash/ProjectAtmosphereCrashHandler.java @@ -0,0 +1,271 @@ +package net.Gabou.projectatmosphere.client.crash; + +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.Gabou.projectatmosphere.client.screen.ProjectAtmosphereCrashScreen; +import net.minecraft.CrashReport; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.fml.ModList; + +import javax.annotation.Nullable; +import java.io.File; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.IdentityHashMap; +import java.util.Locale; +import java.util.Set; + +public final class ProjectAtmosphereCrashHandler { + private static final String PROJECT_ATMOSPHERE_PACKAGE = "net.Gabou.projectatmosphere"; + private static final String DISCORD_INVITE_URL = "https://discord.gg/2jRhTJgYz4"; + private static final DateTimeFormatter REPORT_TIMESTAMP = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss", Locale.ROOT); + + @Nullable + private static CrashContext activeCrash; + + private ProjectAtmosphereCrashHandler() { + } + + public static boolean handleThrowable(Minecraft minecraft, Throwable throwable, String title) { + if (!isProjectAtmosphereCrash(throwable)) { + return false; + } + + return handleCrashReport(minecraft, CrashReport.forThrowable(throwable, title)); + } + + public static boolean handleCrashReport(Minecraft minecraft, CrashReport report) { + if (!isProjectAtmosphereCrash(report)) { + return false; + } + + if (activeCrash != null && minecraft.screen instanceof ProjectAtmosphereCrashScreen) { + return true; + } + + CrashReport enrichedReport = enrichReport(minecraft, report); + CrashContext crashContext = buildCrashContext(minecraft, enrichedReport); + activeCrash = crashContext; + + ProjectAtmosphere.LOGGER.error("Intercepted a Project Atmosphere client crash; opening the custom crash screen."); + + Screen crashScreen = new ProjectAtmosphereCrashScreen(crashContext); + presentCrashScreen(minecraft, crashScreen); + return true; + } + + public static String getDiscordInviteUrl() { + return DISCORD_INVITE_URL; + } + + @Nullable + public static CrashContext getActiveCrash() { + return activeCrash; + } + + public static boolean isProjectAtmosphereCrash(CrashReport report) { + return isProjectAtmosphereCrash(report.getException()); + } + + public static boolean isProjectAtmosphereCrash(@Nullable Throwable throwable) { + if (throwable == null) { + return false; + } + + Set visited = Collections.newSetFromMap(new IdentityHashMap<>()); + Deque queue = new ArrayDeque<>(); + queue.add(throwable); + + while (!queue.isEmpty()) { + Throwable current = queue.removeFirst(); + if (!visited.add(current)) { + continue; + } + + if (isProjectAtmosphereClass(current.getClass().getName()) || containsProjectAtmosphereFrame(current.getStackTrace())) { + return true; + } + + Throwable cause = current.getCause(); + if (cause != null) { + queue.addLast(cause); + } + + for (Throwable suppressed : current.getSuppressed()) { + if (suppressed != null) { + queue.addLast(suppressed); + } + } + } + + return false; + } + + private static void presentCrashScreen(Minecraft minecraft, Screen crashScreen) { + try { + if (minecraft.level != null && tryTransitionMethod(minecraft, "clearClientLevel", crashScreen)) { + return; + } + + if (minecraft.level != null && tryTransitionMethod(minecraft, "disconnect", crashScreen)) { + return; + } + + if (minecraft.level == null || minecraft.screen != crashScreen) { + minecraft.setScreen(crashScreen); + } + } catch (Throwable secondaryFailure) { + ProjectAtmosphere.LOGGER.error("Failed to clear the client level while opening the Project Atmosphere crash screen.", secondaryFailure); + minecraft.setScreen(crashScreen); + } + } + + private static boolean tryTransitionMethod(Minecraft minecraft, String methodName, Screen crashScreen) { + try { + Minecraft.class.getMethod(methodName, Screen.class).invoke(minecraft, crashScreen); + return true; + } catch (ReflectiveOperationException ignored) { + return false; + } + } + + private static CrashReport enrichReport(Minecraft minecraft, CrashReport report) { + try { + return minecraft.fillReport(report); + } catch (Throwable secondaryFailure) { + ProjectAtmosphere.LOGGER.error("Failed to enrich the Project Atmosphere crash report; falling back to the original report.", secondaryFailure); + return report; + } + } + + private static CrashContext buildCrashContext(Minecraft minecraft, CrashReport report) { + Throwable throwable = report.getException(); + StackTraceElement firstRelevantFrame = findFirstProjectAtmosphereFrame(throwable); + Path reportPath = saveCrashReport(minecraft, report); + String modVersion = ModList.get().getModContainerById(ProjectAtmosphere.MODID) + .map(container -> container.getModInfo().getVersion().toString()) + .orElse("unknown"); + String supportSummary = buildSupportSummary(report, throwable, firstRelevantFrame, reportPath, modVersion); + + return new CrashContext( + report.getTitle(), + formatThrowable(throwable), + firstRelevantFrame == null ? "Unavailable" : firstRelevantFrame.toString(), + reportPath == null ? "Unable to save crash report" : reportPath.toAbsolutePath().toString(), + supportSummary + ); + } + + @Nullable + private static Path saveCrashReport(Minecraft minecraft, CrashReport report) { + File existingSave = report.getSaveFile(); + if (existingSave != null) { + return existingSave.toPath(); + } + + File reportFile = new File( + new File(minecraft.gameDirectory, "crash-reports"), + "crash-" + REPORT_TIMESTAMP.format(LocalDateTime.now()) + "-projectatmosphere-client.txt" + ); + + return report.saveToFile(reportFile) ? reportFile.toPath() : null; + } + + @Nullable + private static StackTraceElement findFirstProjectAtmosphereFrame(@Nullable Throwable throwable) { + if (throwable == null) { + return null; + } + + Set visited = Collections.newSetFromMap(new IdentityHashMap<>()); + Deque queue = new ArrayDeque<>(); + queue.add(throwable); + + while (!queue.isEmpty()) { + Throwable current = queue.removeFirst(); + if (!visited.add(current)) { + continue; + } + + for (StackTraceElement element : current.getStackTrace()) { + if (isProjectAtmosphereClass(element.getClassName())) { + return element; + } + } + + Throwable cause = current.getCause(); + if (cause != null) { + queue.addLast(cause); + } + + for (Throwable suppressed : current.getSuppressed()) { + if (suppressed != null) { + queue.addLast(suppressed); + } + } + } + + return null; + } + + private static boolean containsProjectAtmosphereFrame(StackTraceElement[] stackTrace) { + for (StackTraceElement element : stackTrace) { + if (isProjectAtmosphereClass(element.getClassName())) { + return true; + } + } + + return false; + } + + private static boolean isProjectAtmosphereClass(String className) { + return className != null && className.startsWith(PROJECT_ATMOSPHERE_PACKAGE); + } + + private static String buildSupportSummary( + CrashReport report, + Throwable throwable, + @Nullable StackTraceElement firstRelevantFrame, + @Nullable Path reportPath, + String modVersion + ) { + StringBuilder builder = new StringBuilder(); + builder.append("Project Atmosphere support request").append('\n'); + builder.append("Mod version: ").append(modVersion).append('\n'); + builder.append("Minecraft version: ").append(SharedConstants.getCurrentVersion().getName()).append('\n'); + builder.append("Crash title: ").append(report.getTitle()).append('\n'); + builder.append("Exception: ").append(formatThrowable(throwable)).append('\n'); + builder.append("Project Atmosphere frame: ") + .append(firstRelevantFrame == null ? "Unavailable" : firstRelevantFrame) + .append('\n'); + builder.append("Crash report: ") + .append(reportPath == null ? "Unable to save crash report" : reportPath.toAbsolutePath()) + .append('\n'); + builder.append("Discord: ").append(DISCORD_INVITE_URL); + return builder.toString(); + } + + private static String formatThrowable(@Nullable Throwable throwable) { + if (throwable == null) { + return "Unknown throwable"; + } + + String message = throwable.getMessage(); + String name = throwable.getClass().getName(); + return message == null || message.isBlank() ? name : name + ": " + message; + } + + public record CrashContext( + String crashTitle, + String exceptionSummary, + String projectAtmosphereFrame, + String savedReportPath, + String supportSummary + ) { + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/fog/AtmosphereFogState.java b/src/main/java/net/Gabou/projectatmosphere/client/fog/AtmosphereFogState.java new file mode 100644 index 00000000..41a182a5 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/fog/AtmosphereFogState.java @@ -0,0 +1,150 @@ +package net.Gabou.projectatmosphere.client.fog; + +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.Gabou.projectatmosphere.modules.fog.FogHeuristics; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +@OnlyIn(Dist.CLIENT) +public final class AtmosphereFogState { + private static final float HUMIDITY_TRACKING = 0.18F; + private static final float RAIN_TRACKING = 0.22F; + private static final float BIOME_TRACKING = 0.14F; + private static final float DEBUG_TRACKING = 0.22F; + + private static float targetHumidityPercent; + private static float visualHumidityPercent; + private static float targetRainIntensity; + private static float visualRainIntensity; + private static float targetWetBiomeFactor; + private static float visualWetBiomeFactor; + private static float targetDebugStrength; + private static float visualDebugStrength; + private static int debugOverrideTicks; + private static boolean hasServerSample; + private static ResourceKey lastDimension; + + private AtmosphereFogState() { + } + + public static void applyServerUpdate(float humidityPercent, float rainIntensity) { + hasServerSample = true; + targetHumidityPercent = Mth.clamp(humidityPercent, 0.0F, 100.0F); + targetRainIntensity = Mth.clamp(rainIntensity, 0.0F, 1.0F); + } + + public static void applyDebugOverride(float strength, int durationTicks) { + targetDebugStrength = Mth.clamp(strength, 0.0F, 1.0F); + debugOverrideTicks = Math.max(durationTicks, 0); + } + + public static void clearDebugOverride() { + debugOverrideTicks = 0; + targetDebugStrength = 0.0F; + } + + public static void tick(Minecraft minecraft) { + tickDebugOverride(); + boolean fogEnabled = AtmoCommonConfig.FOG_ENABLED.get(); + boolean debugActive = hasDebugOverride(); + if (!fogEnabled && !debugActive) { + clear(); + return; + } + + ClientLevel level = minecraft.level; + if (level == null || minecraft.player == null) { + clear(); + return; + } + + ResourceKey dimension = level.dimension(); + if (lastDimension != null && !lastDimension.equals(dimension)) { + clearVisuals(); + hasServerSample = false; + } + lastDimension = dimension; + + if (!fogEnabled || !Level.OVERWORLD.equals(dimension)) { + targetHumidityPercent = 0.0F; + targetRainIntensity = 0.0F; + targetWetBiomeFactor = 0.0F; + hasServerSample = false; + trackVisuals(); + return; + } + + BlockPos pos = minecraft.player.blockPosition(); + targetWetBiomeFactor = FogBiomeClassifier.computeWetBiomeFactor(level, pos); + if (!hasServerSample) { + targetHumidityPercent = FogBiomeClassifier.computeFallbackHumidityPercent(level, pos); + targetRainIntensity = FogBiomeClassifier.computeClientRainIntensity(level, pos); + } else { + targetRainIntensity = Math.max(targetRainIntensity, FogBiomeClassifier.computeClientRainIntensity(level, pos) * 0.75F); + } + trackVisuals(); + } + + public static FogHeuristics.FogProfile sample(ClientLevel level, Vec3 cameraPos, float partialTick) { + FogHeuristics.FogProfile debugProfile = FogHeuristics.debugSample(visualDebugStrength); + boolean fogEnabled = AtmoCommonConfig.FOG_ENABLED.get(); + if (level == null) { + return debugProfile; + } + + if (!fogEnabled || !Level.OVERWORLD.equals(level.dimension())) { + return debugProfile; + } + + BlockPos samplePos = BlockPos.containing(cameraPos); + float localWetBiome = Math.max(visualWetBiomeFactor, FogBiomeClassifier.computeWetBiomeFactor(level, samplePos)); + float localRain = Math.max(visualRainIntensity, FogBiomeClassifier.computeClientRainIntensity(level, samplePos)); + FogHeuristics.FogProfile liveProfile = FogHeuristics.sample(visualHumidityPercent, localWetBiome, localRain); + return FogHeuristics.max(liveProfile, debugProfile); + } + + private static void trackVisuals() { + visualHumidityPercent = Mth.lerp(HUMIDITY_TRACKING, visualHumidityPercent, targetHumidityPercent); + visualRainIntensity = Mth.lerp(RAIN_TRACKING, visualRainIntensity, targetRainIntensity); + visualWetBiomeFactor = Mth.lerp(BIOME_TRACKING, visualWetBiomeFactor, targetWetBiomeFactor); + visualDebugStrength = Mth.lerp(DEBUG_TRACKING, visualDebugStrength, targetDebugStrength); + } + + private static void tickDebugOverride() { + if (debugOverrideTicks > 0) { + debugOverrideTicks--; + if (debugOverrideTicks == 0) { + targetDebugStrength = 0.0F; + } + } + } + + private static boolean hasDebugOverride() { + return debugOverrideTicks > 0 || targetDebugStrength > 0.001F || visualDebugStrength > 0.001F; + } + + private static void clearVisuals() { + targetHumidityPercent = 0.0F; + visualHumidityPercent = 0.0F; + targetRainIntensity = 0.0F; + visualRainIntensity = 0.0F; + targetWetBiomeFactor = 0.0F; + visualWetBiomeFactor = 0.0F; + targetDebugStrength = 0.0F; + visualDebugStrength = 0.0F; + debugOverrideTicks = 0; + } + + private static void clear() { + clearVisuals(); + hasServerSample = false; + lastDimension = null; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/fog/FogBiomeClassifier.java b/src/main/java/net/Gabou/projectatmosphere/client/fog/FogBiomeClassifier.java new file mode 100644 index 00000000..e6518e61 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/fog/FogBiomeClassifier.java @@ -0,0 +1,108 @@ +package net.Gabou.projectatmosphere.client.fog; + +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.biome.Biome; + +import java.util.List; +import java.util.Locale; + +public final class FogBiomeClassifier { + private FogBiomeClassifier() { + } + + public static float computeWetBiomeFactor(LevelReader level, BlockPos pos) { + if (level == null || pos == null) { + return 0.0F; + } + + Biome biome = level.getBiome(pos).value(); + float downfallThreshold = AtmoCommonConfig.FOG_WET_BIOME_DOWNFALL_MIN.get().floatValue(); + float factor = 0.0F; + + if (biome.getModifiedClimateSettings().hasPrecipitation()) { + factor = Math.max(factor, remapClamped(biome.getModifiedClimateSettings().downfall(), downfallThreshold, 1.0F)); + } + + ResourceLocation biomeId = level.getBiome(pos).unwrapKey() + .map(key -> key.location()) + .orElse(null); + if (biomeId == null) { + return Mth.clamp(factor, 0.0F, 1.0F); + } + + String fullId = biomeId.toString().toLowerCase(Locale.ROOT); + String path = biomeId.getPath().toLowerCase(Locale.ROOT); + + if (matchesExact(fullId, AtmoCommonConfig.FOG_WET_BIOME_IDS.get())) { + return 1.0F; + } + if (matchesKeyword(fullId, path, AtmoCommonConfig.FOG_WET_BIOME_KEYWORDS.get())) { + factor = Math.max(factor, 1.0F); + } + + return Mth.clamp(factor, 0.0F, 1.0F); + } + + public static float computeFallbackHumidityPercent(Level level, BlockPos pos) { + if (level == null || pos == null) { + return 0.0F; + } + + Biome biome = level.getBiome(pos).value(); + float humidity = biome.getModifiedClimateSettings().downfall() * 100.0F; + if (biome.getModifiedClimateSettings().hasPrecipitation() && isLocallyRaining(level, pos)) { + humidity = Math.max(humidity, 82.0F); + } + return Mth.clamp(humidity, 0.0F, 100.0F); + } + + public static float computeClientRainIntensity(Level level, BlockPos pos) { + return isLocallyRaining(level, pos) ? 0.85F : 0.0F; + } + + private static boolean isLocallyRaining(Level level, BlockPos pos) { + try { + return CloudManager.get(level).isRainingAt(pos); + } catch (Exception ignored) { + return level.isRainingAt(pos); + } + } + + private static boolean matchesExact(String fullId, List configuredIds) { + for (String configured : configuredIds) { + if (configured != null && fullId.equals(configured.toLowerCase(Locale.ROOT))) { + return true; + } + } + return false; + } + + private static boolean matchesKeyword(String fullId, String path, List keywords) { + for (String keyword : keywords) { + if (keyword == null) { + continue; + } + String lowered = keyword.toLowerCase(Locale.ROOT).trim(); + if (lowered.isEmpty()) { + continue; + } + if (path.contains(lowered) || fullId.contains(lowered)) { + return true; + } + } + return false; + } + + private static float remapClamped(float value, float start, float end) { + if (end <= start) { + return value >= end ? 1.0F : 0.0F; + } + return Mth.clamp((value - start) / (end - start), 0.0F, 1.0F); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/hurricane/ClientHurricaneStateCache.java b/src/main/java/net/Gabou/projectatmosphere/client/hurricane/ClientHurricaneStateCache.java new file mode 100644 index 00000000..9808459c --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/hurricane/ClientHurricaneStateCache.java @@ -0,0 +1,225 @@ +package net.Gabou.projectatmosphere.client.hurricane; + +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; +import net.Gabou.projectatmosphere.modules.hurricane.HurricaneSemantics; +import net.Gabou.projectatmosphere.modules.hurricane.HurricaneManager; +import net.Gabou.projectatmosphere.modules.hurricane.HurricaneRenderSnapshot; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public final class ClientHurricaneStateCache { + private static final int DEFAULT_BLEND_TICKS = 10; + private static final Map ENTRIES = new LinkedHashMap<>(); + private static final Map RESERVATION_REGIONS = new LinkedHashMap<>(); + private static long cachedSemanticTick = Long.MIN_VALUE; + private static float cachedSemanticPartialTick = Float.NaN; + private static List cachedSemanticSnapshots = List.of(); + + private ClientHurricaneStateCache() { + } + + public static void applySnapshots(List snapshots) { + Minecraft mc = Minecraft.getInstance(); + long clientTick = mc.level != null ? mc.level.getGameTime() : 0L; + + Map next = new LinkedHashMap<>(); + Map nextReservations = new LinkedHashMap<>(); + for (HurricaneRenderSnapshot snapshot : snapshots) { + Entry previous = ENTRIES.get(snapshot.id()); + if (previous == null) { + next.put(snapshot.id(), new Entry(snapshot, snapshot, clientTick)); + } else { + next.put(snapshot.id(), new Entry(previous.current, snapshot, clientTick)); + } + nextReservations.put(snapshot.id(), getReservationRegion(snapshot)); + } + + ENTRIES.clear(); + ENTRIES.putAll(next); + RESERVATION_REGIONS.clear(); + RESERVATION_REGIONS.putAll(nextReservations); + invalidateSemanticSnapshotCache(); + } + + public static void tick(ClientLevel level) { + if (level == null) { + clear(); + } + } + + public static void clear() { + ENTRIES.clear(); + RESERVATION_REGIONS.clear(); + invalidateSemanticSnapshotCache(); + } + + public static List getSemanticSnapshots() { + return getSemanticSnapshots(0.0F); + } + + public static List getSemanticSnapshots(float partialTick) { + Minecraft mc = Minecraft.getInstance(); + if (mc.level == null) { + return List.of(); + } + long clientTick = mc.level.getGameTime(); + if (clientTick == cachedSemanticTick && Float.compare(partialTick, cachedSemanticPartialTick) == 0) { + return cachedSemanticSnapshots; + } + + List snapshots = ENTRIES.isEmpty() + ? projectatmosphere$getIntegratedServerSnapshots(mc) + : buildInterpolatedSnapshots(clientTick, partialTick); + cachedSemanticTick = clientTick; + cachedSemanticPartialTick = partialTick; + cachedSemanticSnapshots = snapshots; + return snapshots; + } + + private static List buildInterpolatedSnapshots(long clientTick, float partialTick) { + List snapshots = new ArrayList<>(ENTRIES.size()); + for (Entry entry : ENTRIES.values()) { + float blend = Mth.clamp(((float)(clientTick - entry.clientUpdateTick) + partialTick) / (float)DEFAULT_BLEND_TICKS, 0.0F, 1.0F); + HurricaneRenderSnapshot start = entry.previous; + HurricaneRenderSnapshot end = entry.current; + + snapshots.add(new HurricaneRenderSnapshot( + end.id(), + Mth.lerp(blend, start.centerX(), end.centerX()), + Mth.lerp(blend, start.centerZ(), end.centerZ()), + Mth.lerp(blend, start.anchorY(), end.anchorY()), + Mth.lerp(blend, start.coreRadius(), end.coreRadius()), + Mth.lerp(blend, start.stormExtentRadius(), end.stormExtentRadius()), + Mth.lerp(blend, start.eyeRadius(), end.eyeRadius()), + Mth.lerp(blend, start.edgeFade(), end.edgeFade()), + end.bandCount(), + Mth.lerp(blend, start.bandWidth(), end.bandWidth()), + Mth.lerp(blend, start.spiralTightness(), end.spiralTightness()), + Mth.lerp(blend, start.rotationPhase(), end.rotationPhase()) + Mth.lerp(blend, start.rotationSpeed(), end.rotationSpeed()) * partialTick, + Mth.lerp(blend, start.rotationSpeed(), end.rotationSpeed()), + Mth.lerp(blend, start.transitionStart(), end.transitionStart()), + Mth.lerp(blend, start.transitionEnd(), end.transitionEnd()), + Mth.lerp(blend, start.normalizedIntensity(), end.normalizedIntensity()), + end.cloudTypeId(), + Mth.floor(Mth.lerp(blend, start.ageTicks(), end.ageTicks()) + partialTick) + )); + } + return snapshots; + } + + public static boolean hasHurricanes() { + if (!ENTRIES.isEmpty()) { + return true; + } + Minecraft mc = Minecraft.getInstance(); + if (mc.level == null || !mc.hasSingleplayerServer()) { + return false; + } + return !HurricaneManager.getActiveHurricanes().isEmpty(); + } + + public static CloudRegion getReservationRegion(HurricaneRenderSnapshot snapshot) { + CloudRegion region = RESERVATION_REGIONS.get(snapshot.id()); + if (region == null) { + region = HurricaneSemantics.createReservationRegion(snapshot); + RESERVATION_REGIONS.put(snapshot.id(), region); + } else { + HurricaneSemantics.updateReservationRegion(region, snapshot); + } + return region; + } + + private static List projectatmosphere$getIntegratedServerSnapshots(Minecraft mc) { + if (!mc.hasSingleplayerServer()) { + return List.of(); + } + var server = mc.getSingleplayerServer(); + if (server == null) { + return List.of(); + } + List active = HurricaneManager.getActiveHurricanes(); + if (active.isEmpty()) { + return List.of(); + } + List snapshots = new ArrayList<>(active.size()); + for (net.Gabou.projectatmosphere.modules.hurricane.HurricaneInstance hurricane : active) { + snapshots.add(hurricane.createRenderSnapshot()); + } + return snapshots; + } + + private static void invalidateSemanticSnapshotCache() { + cachedSemanticTick = Long.MIN_VALUE; + cachedSemanticPartialTick = Float.NaN; + cachedSemanticSnapshots = List.of(); + } + + public static List getRenderableHurricanes(float partialTick) { + Minecraft mc = Minecraft.getInstance(); + if (mc.level == null) { + return List.of(); + } + + List snapshots = getSemanticSnapshots(partialTick); + List renderables = new ArrayList<>(snapshots.size()); + for (HurricaneRenderSnapshot snapshot : snapshots) { + renderables.add(new RenderableHurricane( + snapshot.id(), + snapshot.centerX() / (double)SimpleCloudsConstants.CLOUD_SCALE, + snapshot.centerZ() / (double)SimpleCloudsConstants.CLOUD_SCALE, + snapshot.anchorY(), + snapshot.coreRadius() / (float)SimpleCloudsConstants.CLOUD_SCALE, + snapshot.stormExtentRadius() / (float)SimpleCloudsConstants.CLOUD_SCALE, + snapshot.eyeRadius() / (float)SimpleCloudsConstants.CLOUD_SCALE, + snapshot.edgeFade() / (float)SimpleCloudsConstants.CLOUD_SCALE, + snapshot.bandCount(), + snapshot.bandWidth() / (float)SimpleCloudsConstants.CLOUD_SCALE, + snapshot.spiralTightness(), + snapshot.rotationPhase(), + snapshot.rotationSpeed(), + snapshot.transitionStart() / (float)SimpleCloudsConstants.CLOUD_SCALE, + snapshot.transitionEnd() / (float)SimpleCloudsConstants.CLOUD_SCALE, + snapshot.cloudTypeId(), + snapshot.ageTicks(), + Mth.clamp(snapshot.normalizedIntensity(), 0.0F, 1.0F), + ((snapshot.id().hashCode() & 0x7fffffff) % 10000) / 10000.0F + )); + } + return renderables; + } + + private record Entry(HurricaneRenderSnapshot previous, HurricaneRenderSnapshot current, long clientUpdateTick) { + } + + public record RenderableHurricane( + UUID id, + double centerX, + double centerZ, + float anchorY, + float coreRadius, + float stormExtentRadius, + float eyeRadius, + float edgeFade, + int bandCount, + float bandWidth, + float spiralTightness, + float rotationPhase, + float rotationSpeed, + float transitionStart, + float transitionEnd, + ResourceLocation cloudTypeId, + int ageTicks, + float intensity, + float seed + ) { + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/loading/ClientForecastLoadingLifecycle.java b/src/main/java/net/Gabou/projectatmosphere/client/loading/ClientForecastLoadingLifecycle.java index c4a31ae6..97519abf 100644 --- a/src/main/java/net/Gabou/projectatmosphere/client/loading/ClientForecastLoadingLifecycle.java +++ b/src/main/java/net/Gabou/projectatmosphere/client/loading/ClientForecastLoadingLifecycle.java @@ -2,6 +2,7 @@ import net.Gabou.projectatmosphere.ProjectAtmosphere; import net.Gabou.projectatmosphere.client.ClientSyncLock; +import net.Gabou.projectatmosphere.client.hurricane.ClientHurricaneStateCache; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.ClientPlayerNetworkEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -15,6 +16,7 @@ private ClientForecastLoadingLifecycle() { @SubscribeEvent public static void onClientLogin(ClientPlayerNetworkEvent.LoggingIn event) { ClientSyncLock.clear(); + ClientHurricaneStateCache.clear(); ClientForecastLoadingWorkQueue.reset(); if (!ForecastLoadingState.snapshot().active()) { ForecastLoadingState.start( @@ -30,7 +32,9 @@ public static void onClientLogin(ClientPlayerNetworkEvent.LoggingIn event) { @SubscribeEvent public static void onClientLogout(ClientPlayerNetworkEvent.LoggingOut event) { ClientSyncLock.clear(); + ClientHurricaneStateCache.clear(); ClientForecastLoadingWorkQueue.reset(); ForecastLoadingState.reset("client_logout"); } } + diff --git a/src/main/java/net/Gabou/projectatmosphere/client/render/HurricaneShaders.java b/src/main/java/net/Gabou/projectatmosphere/client/render/HurricaneShaders.java new file mode 100644 index 00000000..e8ca3d82 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/render/HurricaneShaders.java @@ -0,0 +1,64 @@ +package net.Gabou.projectatmosphere.client.render; + +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RegisterShadersEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import java.io.IOException; + +@Mod.EventBusSubscriber(modid = ProjectAtmosphere.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) +public final class HurricaneShaders { + public static final ResourceLocation BASE_TEXTURE = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "textures/effects/base.png"); + public static final ResourceLocation NOISE_TEXTURE = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "textures/effects/noise.png"); + public static final ResourceLocation FLOW_TEXTURE = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "textures/effects/flowmap.png"); + + private static final ResourceLocation OPAQUE_SHADER_ID = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "hurricane_clouds"); + private static final ResourceLocation OPAQUE_MASK_SHADER_ID = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "hurricane_eye_mask"); + private static final ResourceLocation TRANSPARENCY_SHADER_ID = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "hurricane_clouds_transparency"); + private static final ResourceLocation TRANSPARENCY_MASK_SHADER_ID = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "hurricane_eye_mask_transparency"); + + private static ShaderInstance opaqueShader; + private static ShaderInstance opaqueMaskShader; + private static ShaderInstance transparencyShader; + private static ShaderInstance transparencyMaskShader; + + private HurricaneShaders() { + } + + @SubscribeEvent + public static void onRegisterShaders(RegisterShadersEvent event) throws IOException { + event.registerShader(new ShaderInstance(event.getResourceProvider(), OPAQUE_SHADER_ID, DefaultVertexFormat.POSITION_TEX), loaded -> opaqueShader = loaded); + event.registerShader(new ShaderInstance(event.getResourceProvider(), OPAQUE_MASK_SHADER_ID, DefaultVertexFormat.POSITION_TEX), loaded -> opaqueMaskShader = loaded); + event.registerShader(new ShaderInstance(event.getResourceProvider(), TRANSPARENCY_SHADER_ID, DefaultVertexFormat.POSITION_TEX), loaded -> transparencyShader = loaded); + event.registerShader(new ShaderInstance(event.getResourceProvider(), TRANSPARENCY_MASK_SHADER_ID, DefaultVertexFormat.POSITION_TEX), loaded -> transparencyMaskShader = loaded); + } + + public static ShaderInstance getOpaqueShader() { + return opaqueShader; + } + + public static ShaderInstance getOpaqueMaskShader() { + return opaqueMaskShader; + } + + public static ShaderInstance getTransparencyShader() { + return transparencyShader; + } + + public static ShaderInstance getTransparencyMaskShader() { + return transparencyMaskShader; + } + + public static boolean isOpaqueReady() { + return opaqueShader != null && opaqueMaskShader != null; + } + + public static boolean isTransparencyReady() { + return transparencyShader != null && transparencyMaskShader != null; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsDhPipelineSelector.java b/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsDhPipelineSelector.java new file mode 100644 index 00000000..29785418 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsDhPipelineSelector.java @@ -0,0 +1,22 @@ +package net.Gabou.projectatmosphere.client.render; + +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import dev.nonamecrackers2.simpleclouds.client.dh.pipeline.DhSupportPipeline; +import dev.nonamecrackers2.simpleclouds.client.event.impl.DetermineCloudRenderPipelineEvent; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = ProjectAtmosphere.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE) +public final class SimpleCloudsDhPipelineSelector { + private SimpleCloudsDhPipelineSelector() { + } + + @SubscribeEvent + public static void selectDhPipeline(DetermineCloudRenderPipelineEvent event) { + if (SimpleCloudsMod.dhLoaded()) { + event.overridePipeline(DhSupportPipeline.INSTANCE); + } + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsHurricaneRenderer.java b/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsHurricaneRenderer.java new file mode 100644 index 00000000..06fab59e --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsHurricaneRenderer.java @@ -0,0 +1,581 @@ +package net.Gabou.projectatmosphere.client.render; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.pipeline.TextureTarget; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexBuffer; +import com.mojang.blaze3d.vertex.VertexFormat; +import dev.nonamecrackers2.simpleclouds.client.framebuffer.WeightedBlendingTarget; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import net.Gabou.projectatmosphere.client.hurricane.ClientHurricaneStateCache; +import net.Gabou.projectatmosphere.modules.hurricane.HurricaneCloudVolume; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL14; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL40; + +import java.util.ArrayList; +import java.util.List; + +public final class SimpleCloudsHurricaneRenderer { + public static final SimpleCloudsHurricaneRenderer INSTANCE = new SimpleCloudsHurricaneRenderer(); + + private static final int MAX_STORMS = 4; + private static final float MAX_RAY_DISTANCE_CLOUD = 1100.0F; + + private ClientLevel preparedLevel; + private long preparedGameTime = Long.MIN_VALUE; + private float preparedPartialTick = Float.NaN; + private final List preparedHurricanes = new ArrayList<>(); + private boolean initialized; + private VertexBuffer fullscreenQuad; + private final VolumeBoxMesh volumeBox = new VolumeBoxMesh(); + private TextureTarget opaqueScratchTarget; + private WeightedBlendingTarget transparencyScratchTarget; + + private SimpleCloudsHurricaneRenderer() { + } + + public void prepareFrame(ClientLevel level, float partialTick) { + if (this.preparedLevel == level + && this.preparedGameTime == level.getGameTime() + && Float.compare(this.preparedPartialTick, partialTick) == 0) { + return; + } + + this.ensureInitialized(); + this.preparedLevel = level; + this.preparedGameTime = level.getGameTime(); + this.preparedPartialTick = partialTick; + this.preparedHurricanes.clear(); + + for (ClientHurricaneStateCache.RenderableHurricane hurricane : ClientHurricaneStateCache.getRenderableHurricanes(partialTick)) { + if (this.preparedHurricanes.size() >= MAX_STORMS) { + break; + } + this.preparedHurricanes.add(HurricaneCloudVolume.from(hurricane, partialTick)); + } + } + + public void renderOpaque(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB) { + if (this.preparedHurricanes.isEmpty() || !HurricaneShaders.isOpaqueReady()) { + return; + } + + this.ensureScratchTargets(renderer); + StormUniforms uniforms = StormUniforms.from(this.preparedHurricanes); + + this.runOpaqueEyeMaskPass(renderer, stack, projMat, uniforms, this.opaqueScratchTarget, + renderer.getCloudTarget().getColorTextureId(), renderer.getCloudTarget().getDepthTextureId(), false); + this.runOpaqueEyeMaskPass(renderer, stack, projMat, uniforms, renderer.getCloudTarget(), + this.opaqueScratchTarget.getColorTextureId(), this.opaqueScratchTarget.getDepthTextureId(), true); + this.runOpaqueVolumePass(renderer, stack, projMat, partialTick, cloudR, cloudG, cloudB); + } + + public void renderTransparency(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB) { + if (this.preparedHurricanes.isEmpty() || !HurricaneShaders.isTransparencyReady()) { + return; + } + + this.ensureScratchTargets(renderer); + StormUniforms uniforms = StormUniforms.from(this.preparedHurricanes); + + this.runTransparencyMaskPass(renderer, stack, projMat, uniforms, this.transparencyScratchTarget, + renderer.getCloudTransparencyTarget().getColorTextureId(), + renderer.getCloudTransparencyTarget().getRevealageTextureId(), + renderer.getCloudTransparencyTarget().getDepthTextureId(), + false); + this.runTransparencyMaskPass(renderer, stack, projMat, uniforms, renderer.getCloudTransparencyTarget(), + this.transparencyScratchTarget.getColorTextureId(), + this.transparencyScratchTarget.getRevealageTextureId(), + this.transparencyScratchTarget.getDepthTextureId(), + true); + this.runTransparencyVolumePass(renderer, stack, projMat, partialTick, cloudR, cloudG, cloudB); + } + + public void close() { + this.preparedLevel = null; + this.preparedGameTime = Long.MIN_VALUE; + this.preparedPartialTick = Float.NaN; + this.preparedHurricanes.clear(); + if (this.fullscreenQuad != null) { + this.fullscreenQuad.close(); + this.fullscreenQuad = null; + } + this.volumeBox.close(); + if (this.opaqueScratchTarget != null) { + this.opaqueScratchTarget.destroyBuffers(); + this.opaqueScratchTarget = null; + } + if (this.transparencyScratchTarget != null) { + this.transparencyScratchTarget.destroyBuffers(); + this.transparencyScratchTarget = null; + } + this.initialized = false; + } + + private void ensureInitialized() { + if (this.initialized) { + return; + } + BufferBuilder builder = Tesselator.getInstance().getBuilder(); + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); + builder.vertex(-1.0F, -1.0F, 0.0F).uv(0.0F, 0.0F).endVertex(); + builder.vertex(1.0F, -1.0F, 0.0F).uv(1.0F, 0.0F).endVertex(); + builder.vertex(1.0F, 1.0F, 0.0F).uv(1.0F, 1.0F).endVertex(); + builder.vertex(-1.0F, 1.0F, 0.0F).uv(0.0F, 1.0F).endVertex(); + this.fullscreenQuad = new VertexBuffer(VertexBuffer.Usage.STATIC); + this.fullscreenQuad.bind(); + this.fullscreenQuad.upload(builder.end()); + VertexBuffer.unbind(); + this.initialized = true; + } + + private void ensureScratchTargets(SimpleCloudsRenderer renderer) { + RenderTarget cloudTarget = renderer.getCloudTarget(); + if (this.opaqueScratchTarget == null + || this.opaqueScratchTarget.width != cloudTarget.width + || this.opaqueScratchTarget.height != cloudTarget.height) { + if (this.opaqueScratchTarget != null) { + this.opaqueScratchTarget.destroyBuffers(); + } + this.opaqueScratchTarget = new TextureTarget(cloudTarget.width, cloudTarget.height, true, Minecraft.ON_OSX); + } + + WeightedBlendingTarget transparencyTarget = renderer.getCloudTransparencyTarget(); + if (this.transparencyScratchTarget == null + || this.transparencyScratchTarget.width != transparencyTarget.width + || this.transparencyScratchTarget.height != transparencyTarget.height) { + if (this.transparencyScratchTarget != null) { + this.transparencyScratchTarget.destroyBuffers(); + } + this.transparencyScratchTarget = new WeightedBlendingTarget( + transparencyTarget.width, + transparencyTarget.height, + true, + false + ); + } + } + + private void runOpaqueEyeMaskPass(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, StormUniforms uniforms, + RenderTarget destination, int sourceColorTextureId, int sourceDepthTextureId, + boolean protectionEnabled) { + ShaderInstance shader = HurricaneShaders.getOpaqueMaskShader(); + if (shader == null) { + return; + } + + destination.bindWrite(false); + RenderSystem.disableBlend(); + RenderSystem.disableCull(); + RenderSystem.disableDepthTest(); + RenderSystem.depthMask(true); + RenderSystem.setShader(() -> shader); + + shader.setSampler("SourceColorSampler", sourceColorTextureId); + shader.setSampler("SourceDepthSampler", sourceDepthTextureId); + this.applyCommonUniforms(shader, renderer, stack, projMat); + this.applyStormUniforms(shader, uniforms); + shader.safeGetUniform("ProtectionEnabled").set(protectionEnabled ? 1 : 0); + shader.apply(); + + this.fullscreenQuad.bind(); + this.fullscreenQuad.drawWithShader(new Matrix4f(), new Matrix4f(), shader); + VertexBuffer.unbind(); + shader.clear(); + + RenderSystem.enableDepthTest(); + RenderSystem.enableCull(); + } + + private void runTransparencyMaskPass(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, StormUniforms uniforms, + WeightedBlendingTarget destination, int sourceAccumTextureId, + int sourceRevealageTextureId, int sourceDepthTextureId, + boolean protectionEnabled) { + ShaderInstance shader = HurricaneShaders.getTransparencyMaskShader(); + if (shader == null) { + return; + } + + destination.bindWrite(false); + RenderSystem.disableBlend(); + RenderSystem.disableCull(); + RenderSystem.disableDepthTest(); + RenderSystem.depthMask(true); + RenderSystem.setShader(() -> shader); + + shader.setSampler("SourceAccumSampler", sourceAccumTextureId); + shader.setSampler("SourceRevealageSampler", sourceRevealageTextureId); + shader.setSampler("SourceDepthSampler", sourceDepthTextureId); + this.applyCommonUniforms(shader, renderer, stack, projMat); + this.applyStormUniforms(shader, uniforms); + shader.safeGetUniform("ProtectionEnabled").set(protectionEnabled ? 1 : 0); + shader.apply(); + + this.fullscreenQuad.bind(); + this.fullscreenQuad.drawWithShader(new Matrix4f(), new Matrix4f(), shader); + VertexBuffer.unbind(); + shader.clear(); + + RenderSystem.enableDepthTest(); + RenderSystem.enableCull(); + } + + private void runOpaqueVolumePass(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB) { + ShaderInstance shader = HurricaneShaders.getOpaqueShader(); + if (shader == null) { + return; + } + + Minecraft mc = Minecraft.getInstance(); + renderer.getCloudTarget().bindWrite(false); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + RenderSystem.depthMask(true); + RenderSystem.depthFunc(GL11.GL_LEQUAL); + RenderSystem.disableCull(); + RenderSystem.setShader(() -> shader); + + AbstractTexture baseTexture = mc.getTextureManager().getTexture(HurricaneShaders.BASE_TEXTURE); + AbstractTexture noiseTexture = mc.getTextureManager().getTexture(HurricaneShaders.NOISE_TEXTURE); + AbstractTexture flowTexture = mc.getTextureManager().getTexture(HurricaneShaders.FLOW_TEXTURE); + shader.setSampler("BaseSampler", baseTexture); + shader.setSampler("NoiseSampler", noiseTexture); + shader.setSampler("FlowSampler", flowTexture); + shader.setSampler("DepthSampler", renderer.getCloudTarget().getDepthTextureId()); + + this.applyCommonUniforms(shader, renderer, stack, projMat); + shader.safeGetUniform("CloudColor").set(cloudR, cloudG, cloudB, 1.0F); + List renderOrder = new ArrayList<>(this.preparedHurricanes); + Vec3 cameraPos = mc.gameRenderer.getMainCamera().getPosition(); + renderOrder.sort((left, right) -> Double.compare( + right.centerWorld().distanceToSqr(cameraPos), + left.centerWorld().distanceToSqr(cameraPos) + )); + + for (HurricaneCloudVolume hurricane : renderOrder) { + this.applySingleStormUniforms(shader, hurricane); + shader.safeGetUniform("VolumeMin").set( + (float) hurricane.boundsMinCloud().x, + (float) hurricane.boundsMinCloud().y, + (float) hurricane.boundsMinCloud().z + ); + shader.safeGetUniform("VolumeMax").set( + (float) hurricane.boundsMaxCloud().x, + (float) hurricane.boundsMaxCloud().y, + (float) hurricane.boundsMaxCloud().z + ); + shader.apply(); + this.volumeBox.draw(shader, stack.last().pose(), projMat); + shader.clear(); + } + + RenderSystem.depthMask(true); + RenderSystem.enableDepthTest(); + RenderSystem.disableBlend(); + RenderSystem.enableCull(); + } + + private void runTransparencyVolumePass(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB) { + ShaderInstance shader = HurricaneShaders.getTransparencyShader(); + if (shader == null) { + return; + } + + Minecraft mc = Minecraft.getInstance(); + renderer.getCloudTransparencyTarget().bindWrite(false); + RenderSystem.disableCull(); + RenderSystem.disableDepthTest(); + RenderSystem.depthMask(false); + RenderSystem.setShader(() -> shader); + + AbstractTexture baseTexture = mc.getTextureManager().getTexture(HurricaneShaders.BASE_TEXTURE); + AbstractTexture noiseTexture = mc.getTextureManager().getTexture(HurricaneShaders.NOISE_TEXTURE); + AbstractTexture flowTexture = mc.getTextureManager().getTexture(HurricaneShaders.FLOW_TEXTURE); + shader.setSampler("BaseSampler", baseTexture); + shader.setSampler("NoiseSampler", noiseTexture); + shader.setSampler("FlowSampler", flowTexture); + shader.setSampler("DepthSampler", renderer.getCloudTarget().getDepthTextureId()); + + this.applyCommonUniforms(shader, renderer, stack, projMat); + shader.safeGetUniform("CloudColor").set(cloudR, cloudG, cloudB, 1.0F); + + GL30.glEnablei(GL11.GL_BLEND, 0); + GL30.glEnablei(GL11.GL_BLEND, 1); + GL40.glBlendEquationi(0, GL14.GL_FUNC_ADD); + GL40.glBlendEquationi(1, GL14.GL_FUNC_ADD); + GL40.glBlendFunci(0, GL11.GL_ONE, GL11.GL_ONE); + GL40.glBlendFunci(1, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_COLOR); + + List renderOrder = new ArrayList<>(this.preparedHurricanes); + Vec3 cameraPos = mc.gameRenderer.getMainCamera().getPosition(); + renderOrder.sort((left, right) -> Double.compare( + right.centerWorld().distanceToSqr(cameraPos), + left.centerWorld().distanceToSqr(cameraPos) + )); + + for (HurricaneCloudVolume hurricane : renderOrder) { + this.applySingleStormUniforms(shader, hurricane); + shader.safeGetUniform("VolumeMin").set( + (float) hurricane.boundsMinCloud().x, + (float) hurricane.boundsMinCloud().y, + (float) hurricane.boundsMinCloud().z + ); + shader.safeGetUniform("VolumeMax").set( + (float) hurricane.boundsMaxCloud().x, + (float) hurricane.boundsMaxCloud().y, + (float) hurricane.boundsMaxCloud().z + ); + shader.apply(); + this.volumeBox.draw(shader, stack.last().pose(), projMat); + shader.clear(); + } + + GL30.glDisablei(GL11.GL_BLEND, 0); + GL30.glDisablei(GL11.GL_BLEND, 1); + GL40.glBlendFuncSeparatei(0, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + GL40.glBlendFuncSeparatei(1, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + + RenderSystem.depthMask(true); + RenderSystem.enableCull(); + RenderSystem.enableDepthTest(); + } + + private void applyCommonUniforms(ShaderInstance shader, SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat) { + Minecraft mc = Minecraft.getInstance(); + shader.safeGetUniform("ModelViewMat").set(stack.last().pose()); + shader.safeGetUniform("ProjMat").set(projMat); + shader.safeGetUniform("InverseProjMat").set(new Matrix4f(projMat).invert()); + shader.safeGetUniform("InverseModelViewMat").set(new Matrix4f(stack.last().pose()).invert()); + + float scale = SimpleCloudsConstants.CLOUD_SCALE; + float cloudHeight = this.preparedLevel == null ? 0.0F : dev.nonamecrackers2.simpleclouds.common.world.CloudManager.get(this.preparedLevel).getCloudHeight(); + Vec3 cameraPos = mc.gameRenderer.getMainCamera().getPosition(); + shader.safeGetUniform("CameraPos").set( + (float) cameraPos.x / scale, + ((float) cameraPos.y - cloudHeight) / scale, + (float) cameraPos.z / scale + ); + shader.safeGetUniform("AnimationTime").set((this.preparedGameTime + this.preparedPartialTick) * 0.065F); + shader.safeGetUniform("MaxDistance").set(MAX_RAY_DISTANCE_CLOUD); + shader.safeGetUniform("OutSize").set((float) mc.getWindow().getWidth(), (float) mc.getWindow().getHeight()); + shader.safeGetUniform("FogStart").set(renderer.getFogStart()); + shader.safeGetUniform("FogEnd").set(renderer.getFogEnd()); + float[] fogColor = RenderSystem.getShaderFogColor(); + shader.safeGetUniform("FogColor").set(fogColor[0], fogColor[1], fogColor[2], fogColor[3]); + } + + private void applyStormUniforms(ShaderInstance shader, StormUniforms uniforms) { + shader.safeGetUniform("StormCount").set(uniforms.stormCount()); + shader.safeGetUniform("StormPositions").set(uniforms.stormPositions()); + shader.safeGetUniform("StormHeights").set(uniforms.stormHeights()); + shader.safeGetUniform("EyeRadii").set(uniforms.eyeRadii()); + shader.safeGetUniform("EyeClearRadii").set(uniforms.eyeClearRadii()); + shader.safeGetUniform("EyeSlopes").set(uniforms.eyeSlopes()); + shader.safeGetUniform("EyewallThicknesses").set(uniforms.eyewallThicknesses()); + shader.safeGetUniform("CanopyRadii").set(uniforms.canopyRadii()); + shader.safeGetUniform("ShieldRadii").set(uniforms.shieldRadii()); + shader.safeGetUniform("CanopyBaseFactors").set(uniforms.canopyBaseFactors()); + shader.safeGetUniform("CanopyTopFactors").set(uniforms.canopyTopFactors()); + shader.safeGetUniform("ShieldBaseFactors").set(uniforms.shieldBaseFactors()); + shader.safeGetUniform("ShieldTopFactors").set(uniforms.shieldTopFactors()); + shader.safeGetUniform("BandStartRadii").set(uniforms.bandStartRadii()); + shader.safeGetUniform("BandEndRadii").set(uniforms.bandEndRadii()); + shader.safeGetUniform("BandWidths").set(uniforms.bandWidths()); + shader.safeGetUniform("BandStrengths").set(uniforms.bandStrengths()); + shader.safeGetUniform("BandCounts").set(uniforms.bandCounts()); + shader.safeGetUniform("FringeStrengths").set(uniforms.fringeStrengths()); + shader.safeGetUniform("StormSpins").set(uniforms.stormSpins()); + shader.safeGetUniform("StormIntensities").set(uniforms.stormIntensities()); + shader.safeGetUniform("StormSeeds").set(uniforms.stormSeeds()); + } + + private void applySingleStormUniforms(ShaderInstance shader, HurricaneCloudVolume hurricane) { + float[] stormPositions = new float[MAX_STORMS * 3]; + float[] stormHeights = new float[MAX_STORMS]; + float[] eyeRadii = new float[MAX_STORMS]; + float[] eyeClearRadii = new float[MAX_STORMS]; + float[] eyeSlopes = new float[MAX_STORMS]; + float[] eyewallThicknesses = new float[MAX_STORMS]; + float[] canopyRadii = new float[MAX_STORMS]; + float[] shieldRadii = new float[MAX_STORMS]; + float[] canopyBaseFactors = new float[MAX_STORMS]; + float[] canopyTopFactors = new float[MAX_STORMS]; + float[] shieldBaseFactors = new float[MAX_STORMS]; + float[] shieldTopFactors = new float[MAX_STORMS]; + float[] bandStartRadii = new float[MAX_STORMS]; + float[] bandEndRadii = new float[MAX_STORMS]; + float[] bandWidths = new float[MAX_STORMS]; + float[] bandStrengths = new float[MAX_STORMS]; + float[] bandCounts = new float[MAX_STORMS]; + float[] fringeStrengths = new float[MAX_STORMS]; + float[] stormSpins = new float[MAX_STORMS]; + float[] stormIntensities = new float[MAX_STORMS]; + float[] stormSeeds = new float[MAX_STORMS]; + + stormPositions[0] = hurricane.centerX(); + stormPositions[1] = hurricane.baseY(); + stormPositions[2] = hurricane.centerZ(); + stormHeights[0] = hurricane.height(); + eyeRadii[0] = hurricane.eyeRadius(); + eyeClearRadii[0] = hurricane.eyeClearRadius(); + eyeSlopes[0] = hurricane.eyeSlope(); + eyewallThicknesses[0] = hurricane.eyewallThickness(); + canopyRadii[0] = hurricane.canopyRadius(); + shieldRadii[0] = hurricane.shieldRadius(); + canopyBaseFactors[0] = hurricane.canopyBaseFactor(); + canopyTopFactors[0] = hurricane.canopyTopFactor(); + shieldBaseFactors[0] = hurricane.shieldBaseFactor(); + shieldTopFactors[0] = hurricane.shieldTopFactor(); + bandStartRadii[0] = hurricane.bandStartRadius(); + bandEndRadii[0] = hurricane.bandEndRadius(); + bandWidths[0] = hurricane.bandWidth(); + bandStrengths[0] = hurricane.bandStrength(); + bandCounts[0] = hurricane.bandCount(); + fringeStrengths[0] = hurricane.fringeStrength(); + stormSpins[0] = hurricane.spin(); + stormIntensities[0] = hurricane.intensity(); + stormSeeds[0] = hurricane.seed(); + + shader.safeGetUniform("StormCount").set(1); + shader.safeGetUniform("StormPositions").set(stormPositions); + shader.safeGetUniform("StormHeights").set(stormHeights); + shader.safeGetUniform("EyeRadii").set(eyeRadii); + shader.safeGetUniform("EyeClearRadii").set(eyeClearRadii); + shader.safeGetUniform("EyeSlopes").set(eyeSlopes); + shader.safeGetUniform("EyewallThicknesses").set(eyewallThicknesses); + shader.safeGetUniform("CanopyRadii").set(canopyRadii); + shader.safeGetUniform("ShieldRadii").set(shieldRadii); + shader.safeGetUniform("CanopyBaseFactors").set(canopyBaseFactors); + shader.safeGetUniform("CanopyTopFactors").set(canopyTopFactors); + shader.safeGetUniform("ShieldBaseFactors").set(shieldBaseFactors); + shader.safeGetUniform("ShieldTopFactors").set(shieldTopFactors); + shader.safeGetUniform("BandStartRadii").set(bandStartRadii); + shader.safeGetUniform("BandEndRadii").set(bandEndRadii); + shader.safeGetUniform("BandWidths").set(bandWidths); + shader.safeGetUniform("BandStrengths").set(bandStrengths); + shader.safeGetUniform("BandCounts").set(bandCounts); + shader.safeGetUniform("FringeStrengths").set(fringeStrengths); + shader.safeGetUniform("StormSpins").set(stormSpins); + shader.safeGetUniform("StormIntensities").set(stormIntensities); + shader.safeGetUniform("StormSeeds").set(stormSeeds); + } + + private record StormUniforms( + int stormCount, + float[] stormPositions, + float[] stormHeights, + float[] eyeRadii, + float[] eyeClearRadii, + float[] eyeSlopes, + float[] eyewallThicknesses, + float[] canopyRadii, + float[] shieldRadii, + float[] canopyBaseFactors, + float[] canopyTopFactors, + float[] shieldBaseFactors, + float[] shieldTopFactors, + float[] bandStartRadii, + float[] bandEndRadii, + float[] bandWidths, + float[] bandStrengths, + float[] bandCounts, + float[] fringeStrengths, + float[] stormSpins, + float[] stormIntensities, + float[] stormSeeds + ) { + static StormUniforms from(List hurricanes) { + float[] stormPositions = new float[MAX_STORMS * 3]; + float[] stormHeights = new float[MAX_STORMS]; + float[] eyeRadii = new float[MAX_STORMS]; + float[] eyeClearRadii = new float[MAX_STORMS]; + float[] eyeSlopes = new float[MAX_STORMS]; + float[] eyewallThicknesses = new float[MAX_STORMS]; + float[] canopyRadii = new float[MAX_STORMS]; + float[] shieldRadii = new float[MAX_STORMS]; + float[] canopyBaseFactors = new float[MAX_STORMS]; + float[] canopyTopFactors = new float[MAX_STORMS]; + float[] shieldBaseFactors = new float[MAX_STORMS]; + float[] shieldTopFactors = new float[MAX_STORMS]; + float[] bandStartRadii = new float[MAX_STORMS]; + float[] bandEndRadii = new float[MAX_STORMS]; + float[] bandWidths = new float[MAX_STORMS]; + float[] bandStrengths = new float[MAX_STORMS]; + float[] bandCounts = new float[MAX_STORMS]; + float[] fringeStrengths = new float[MAX_STORMS]; + float[] stormSpins = new float[MAX_STORMS]; + float[] stormIntensities = new float[MAX_STORMS]; + float[] stormSeeds = new float[MAX_STORMS]; + + for (int i = 0; i < hurricanes.size(); i++) { + HurricaneCloudVolume hurricane = hurricanes.get(i); + stormPositions[i * 3] = hurricane.centerX(); + stormPositions[i * 3 + 1] = hurricane.baseY(); + stormPositions[i * 3 + 2] = hurricane.centerZ(); + stormHeights[i] = hurricane.height(); + eyeRadii[i] = hurricane.eyeRadius(); + eyeClearRadii[i] = hurricane.eyeClearRadius(); + eyeSlopes[i] = hurricane.eyeSlope(); + eyewallThicknesses[i] = hurricane.eyewallThickness(); + canopyRadii[i] = hurricane.canopyRadius(); + shieldRadii[i] = hurricane.shieldRadius(); + canopyBaseFactors[i] = hurricane.canopyBaseFactor(); + canopyTopFactors[i] = hurricane.canopyTopFactor(); + shieldBaseFactors[i] = hurricane.shieldBaseFactor(); + shieldTopFactors[i] = hurricane.shieldTopFactor(); + bandStartRadii[i] = hurricane.bandStartRadius(); + bandEndRadii[i] = hurricane.bandEndRadius(); + bandWidths[i] = hurricane.bandWidth(); + bandStrengths[i] = hurricane.bandStrength(); + bandCounts[i] = hurricane.bandCount(); + fringeStrengths[i] = hurricane.fringeStrength(); + stormSpins[i] = hurricane.spin(); + stormIntensities[i] = hurricane.intensity(); + stormSeeds[i] = hurricane.seed(); + } + + return new StormUniforms( + hurricanes.size(), + stormPositions, + stormHeights, + eyeRadii, + eyeClearRadii, + eyeSlopes, + eyewallThicknesses, + canopyRadii, + shieldRadii, + canopyBaseFactors, + canopyTopFactors, + shieldBaseFactors, + shieldTopFactors, + bandStartRadii, + bandEndRadii, + bandWidths, + bandStrengths, + bandCounts, + fringeStrengths, + stormSpins, + stormIntensities, + stormSeeds + ); + } + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsRenderDiagnostics.java b/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsRenderDiagnostics.java new file mode 100644 index 00000000..d9636c5a --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsRenderDiagnostics.java @@ -0,0 +1,303 @@ +package net.Gabou.projectatmosphere.client.render; + +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudType; +import dev.nonamecrackers2.simpleclouds.common.noise.NoiseSettings; +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import net.minecraft.resources.ResourceLocation; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public final class SimpleCloudsRenderDiagnostics { + private static final Logger LOGGER = LogManager.getLogger("ProjectAtmosphere/SimpleCloudsRender"); + private static final boolean ENABLED = Boolean.getBoolean("projectatmosphere.simpleclouds.debugRender"); + private static final ThreadLocal CURRENT_PASS = ThreadLocal.withInitial(PassStats::new); + private static final AtomicBoolean PLAYER_SAMPLE_LOGGED = new AtomicBoolean(); + private static final AtomicBoolean SHADER_LOAD_LOGGED = new AtomicBoolean(); + private static final AtomicBoolean REGION_UPLOAD_LOGGED = new AtomicBoolean(); + private static final AtomicBoolean CHUNK_DECISION_LOGGED = new AtomicBoolean(); + private static final AtomicBoolean PREPARE_MESH_LOGGED = new AtomicBoolean(); + private static final AtomicBoolean PIPELINE_STAGE_LOGGED = new AtomicBoolean(); + private static final AtomicBoolean ALPHA_FALLBACK_LOGGED = new AtomicBoolean(); + private static final AtomicBoolean FINALIZE_MESH_LOGGED = new AtomicBoolean(); + private static final AtomicBoolean PASS_SUMMARY_LOGGED = new AtomicBoolean(); + private static final AtomicBoolean DH_PIPELINE_FALLBACK_LOGGED = new AtomicBoolean(); + private static final AtomicBoolean DH_PASS_SUMMARY_LOGGED = new AtomicBoolean(); + private static final ThreadLocal DH_PIPELINE_ACTIVE = ThreadLocal.withInitial(() -> false); + + private SimpleCloudsRenderDiagnostics() { + } + + public static boolean isEnabled() { + return ENABLED; + } + + public static void beginPass(String passName, int totalChunks, int opaqueBytes, int transparentBytes, int opaqueElements, int transparentElements, boolean canRender, boolean transparencyEnabled, Object meshStatus) { + if (Boolean.TRUE.equals(DH_PIPELINE_ACTIVE.get())) { + return; + } + setPassStats(passName, totalChunks, opaqueBytes, transparentBytes, opaqueElements, transparentElements, canRender, transparencyEnabled, meshStatus); + } + + private static void setPassStats(String passName, int totalChunks, int opaqueBytes, int transparentBytes, int opaqueElements, int transparentElements, boolean canRender, boolean transparencyEnabled, Object meshStatus) { + PassStats stats = CURRENT_PASS.get(); + stats.passName = passName; + stats.totalChunks = totalChunks; + stats.opaqueBytes = opaqueBytes; + stats.transparentBytes = transparentBytes; + stats.opaqueElements = opaqueElements; + stats.transparentElements = transparentElements; + stats.canRender = canRender; + stats.transparencyEnabled = transparencyEnabled; + stats.meshStatus = meshStatus; + stats.drawCalls = 0; + stats.totalElements = 0; + stats.alphaFallbacks = 0; + } + + public static void beginDhPipelinePass(int totalChunks, int opaqueBytes, int transparentBytes, int opaqueElements, int transparentElements, boolean canRender, boolean transparencyEnabled, Object meshStatus) { + DH_PIPELINE_ACTIVE.set(true); + setPassStats( + "dh_after_distant_horizons_render", + totalChunks, + opaqueBytes, + transparentBytes, + opaqueElements, + transparentElements, + canRender, + transparencyEnabled, + meshStatus + ); + } + + public static void recordDraw(String passName, int elementCount) { + PassStats stats = CURRENT_PASS.get(); + if (stats.passName == null || "unknown".equals(stats.passName)) { + stats.passName = passName; + } + stats.drawCalls++; + stats.totalElements += Math.max(0, elementCount); + } + + public static void noteAlphaFallback(int elementCount, int ticksSinceLastGen) { + PassStats stats = CURRENT_PASS.get(); + stats.alphaFallbacks++; + if (ENABLED || ALPHA_FALLBACK_LOGGED.compareAndSet(false, true)) { + LOGGER.info( + "[SimpleCloudsRender] alpha fallback triggered elementCount={} ticksSinceLastGen={} pass={} drawCalls={} totalElements={}", + elementCount, + ticksSinceLastGen, + stats.passName, + stats.drawCalls, + stats.totalElements + ); + } + } + + public static void logPlayerSample(CloudManager manager, double playerX, double playerZ) { + if (manager == null) { + return; + } + + var sample = manager.getCloudTypeAtPosition((float)playerX, (float)playerZ); + CloudType type = sample.getLeft(); + float fade = sample.getRight(); + float coverage = 1.0F - fade; + NoiseSettings noise = type != null ? type.noiseConfig() : null; + float[] packedNoise = noise != null ? noise.packForShader() : new float[0]; + if (ENABLED || PLAYER_SAMPLE_LOGGED.compareAndSet(false, true)) { + LOGGER.info( + "[SimpleCloudsRender] playerSample world=({}, {}) cloudHeight={} cloudMode={} cloudCount={} selectedType={} coverage={} fade={} weather={} stormStart={} noiseStartHeight={} noiseEndHeight={} noisePacked={}", + fmt(playerX), + fmt(playerZ), + manager.getCloudHeight(), + manager.getCloudMode(), + manager.getClouds().size(), + type != null ? type.id() : "null", + fmt(coverage), + fmt(fade), + type != null ? type.weatherType() : "null", + type != null ? fmt(type.stormStart()) : "null", + noise != null ? noise.getStartHeight() : -1, + noise != null ? noise.getEndHeight() : -1, + Arrays.toString(packedNoise) + ); + } + } + + public static void logShaderLoad(String stage, ResourceLocation requested, ResourceLocation actual, String shaderName, int shaderId, boolean valid) { + if (ENABLED || SHADER_LOAD_LOGGED.compareAndSet(false, true)) { + LOGGER.info( + "[SimpleCloudsRender] shaderLoad stage={} requested={} actual={} shaderName={} shaderId={} valid={}", + stage, + requested, + actual, + shaderName, + shaderId, + valid + ); + } + } + + public static void logRegionUpload(int cloudRegions, int filteredRegions, int uploadedRegions, int cachedTypes, int shaderId, String shaderName) { + if (ENABLED || REGION_UPLOAD_LOGGED.compareAndSet(false, true)) { + LOGGER.info( + "[SimpleCloudsRender] regionUpload cloudRegions={} filteredRegions={} uploadedRegions={} cachedTypes={} shaderId={} shaderName={}", + cloudRegions, + filteredRegions, + uploadedRegions, + cachedTypes, + shaderId, + shaderName + ); + } + } + + public static void logChunkGenDecision(float minX, float minZ, float maxX, float maxZ, List cornerSamples, String resultDescription) { + if (ENABLED || CHUNK_DECISION_LOGGED.compareAndSet(false, true)) { + LOGGER.info( + "[SimpleCloudsRender] chunkGenDecision bounds=({}, {}) -> ({}, {}) samples={} result={}", + fmt(minX), + fmt(minZ), + fmt(maxX), + fmt(maxZ), + cornerSamples, + resultDescription + ); + } + } + + public static void logPrepareMeshGen(int queuedTasks, int chunkCount, int tasksPerTick, int meshGenInterval, double originX, double originY, double originZ, float meshOffsetX, float meshOffsetZ, boolean frustumCulled) { + if (ENABLED || PREPARE_MESH_LOGGED.compareAndSet(false, true)) { + LOGGER.info( + "[SimpleCloudsRender] prepareMeshGen queuedTasks={} chunkCount={} tasksPerTick={} meshGenInterval={} origin=({}, {}, {}) meshOffset=({}, {}) frustumCulled={}", + queuedTasks, + chunkCount, + tasksPerTick, + meshGenInterval, + fmt(originX), + fmt(originY), + fmt(originZ), + fmt(meshOffsetX), + fmt(meshOffsetZ), + frustumCulled + ); + } + } + + public static void logPipelineStage(String pipelineName, String stage, int chunkCount, int queuedTasks, int completedTasks, boolean canRender, boolean transparencyEnabled, Object meshStatus) { + if (ENABLED || PIPELINE_STAGE_LOGGED.compareAndSet(false, true)) { + LOGGER.info( + "[SimpleCloudsRender] pipeline stage={} pipeline={} chunks={} queuedTasks={} completedTasks={} canRender={} transparencyEnabled={} meshStatus={}", + stage, + pipelineName, + chunkCount, + queuedTasks, + completedTasks, + canRender, + transparencyEnabled, + meshStatus + ); + } + } + + public static void logDhPipelineFallback(String selectedPipeline, int chunkCount, int queuedTasks, int completedTasks, boolean canRender, boolean transparencyEnabled, Object meshStatus) { + if (ENABLED || DH_PIPELINE_FALLBACK_LOGGED.compareAndSet(false, true)) { + LOGGER.info( + "[SimpleCloudsRender] dhPipelineFallback selectedPipeline={} chunks={} queuedTasks={} completedTasks={} canRender={} transparencyEnabled={} meshStatus={}", + selectedPipeline, + chunkCount, + queuedTasks, + completedTasks, + canRender, + transparencyEnabled, + meshStatus + ); + } + } + + public static void logFinalizeMeshGen(int completedTasks, int opaqueElements, int transparentElements, int opaqueBytes, int transparentBytes, Object meshStatus) { + if (ENABLED || FINALIZE_MESH_LOGGED.compareAndSet(false, true)) { + LOGGER.info( + "[SimpleCloudsRender] finalizeMeshGen completedTasks={} opaqueElements={} transparentElements={} opaqueBytes={} transparentBytes={} meshStatus={}", + completedTasks, + opaqueElements, + transparentElements, + opaqueBytes, + transparentBytes, + meshStatus + ); + } + } + + public static void endPass() { + if (Boolean.TRUE.equals(DH_PIPELINE_ACTIVE.get())) { + return; + } + PassStats stats = CURRENT_PASS.get(); + if (ENABLED || PASS_SUMMARY_LOGGED.compareAndSet(false, true)) { + LOGGER.info( + "[SimpleCloudsRender] pass={} totalChunks={} drawCalls={} totalElements={} alphaFallbacks={} opaqueBytes={} transparentBytes={} opaqueElements={} transparentElements={} canRender={} transparencyEnabled={} meshStatus={}", + stats.passName, + stats.totalChunks, + stats.drawCalls, + stats.totalElements, + stats.alphaFallbacks, + stats.opaqueBytes, + stats.transparentBytes, + stats.opaqueElements, + stats.transparentElements, + stats.canRender, + stats.transparencyEnabled, + stats.meshStatus + ); + } + } + + public static void endDhPipelinePass() { + PassStats stats = CURRENT_PASS.get(); + if (ENABLED || DH_PASS_SUMMARY_LOGGED.compareAndSet(false, true)) { + LOGGER.info( + "[SimpleCloudsRender] dhPass totalChunks={} drawCalls={} totalElements={} alphaFallbacks={} opaqueBytes={} transparentBytes={} opaqueElements={} transparentElements={} canRender={} transparencyEnabled={} meshStatus={}", + stats.totalChunks, + stats.drawCalls, + stats.totalElements, + stats.alphaFallbacks, + stats.opaqueBytes, + stats.transparentBytes, + stats.opaqueElements, + stats.transparentElements, + stats.canRender, + stats.transparencyEnabled, + stats.meshStatus + ); + } + DH_PIPELINE_ACTIVE.set(false); + } + + public static boolean isDhPipelineActive() { + return Boolean.TRUE.equals(DH_PIPELINE_ACTIVE.get()); + } + + private static String fmt(double value) { + return String.format(java.util.Locale.ROOT, "%.3f", value); + } + + private static final class PassStats { + private String passName = "unknown"; + private int totalChunks; + private int opaqueBytes; + private int transparentBytes; + private int opaqueElements; + private int transparentElements; + private boolean canRender; + private boolean transparencyEnabled; + private Object meshStatus; + private int drawCalls; + private int totalElements; + private int alphaFallbacks; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsTornadoRenderer.java b/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsTornadoRenderer.java new file mode 100644 index 00000000..f82a59e2 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsTornadoRenderer.java @@ -0,0 +1,1185 @@ +package net.Gabou.projectatmosphere.client.render; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.pipeline.TextureTarget; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexBuffer; +import com.mojang.blaze3d.vertex.VertexFormat; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.Gabou.projectatmosphere.modules.tornado.TornadoInstance; +import net.Gabou.projectatmosphere.modules.tornado.TornadoManager; +import net.Gabou.projectatmosphere.modules.weather.StormLifecyclePhase; +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import org.joml.Vector4f; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL43; +import org.lwjgl.system.MemoryStack; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +public final class SimpleCloudsTornadoRenderer { + public static final SimpleCloudsTornadoRenderer INSTANCE = new SimpleCloudsTornadoRenderer(); + + private static final int MAX_STORMS = 8; + private static final float CLOUD_BLEND_PAD_ABOVE_CLOUD_BASE_WORLD = 28.0F; + private static final float GROUND_CONTACT_PADDING_WORLD = 8.0F; + private static final float GROUND_VISUAL_SINK_WORLD = 1.5F; + private static final float MIN_VISUAL_WORLD_WIDTH = 28.0F; + private static final float MIN_VISUAL_WORLD_STORM_SIZE = 140.0F; + private static final float MIN_VISUAL_WORLD_HEIGHT = 120.0F; + private static final float MAX_RAY_DISTANCE_CLOUD = 420.0F; + private static final float WHITEOUT_STRENGTH = 1.0F; + private static final float WHITEOUT_THRESHOLD = 0.015F; + private static final float RAY_STEP_CLOUD = 0.42F; + private static final float WALLCLOUD_LOWER_WORLD = 15.0F; + private static final float FUNNEL_TOP_OFFSET_WORLD = 13.125F; + private static final float FUNNEL_BASE_PADDING_WORLD = 3.75F; + private static final float WALLCLOUD_GATE_BELOW_ORIGIN_WORLD = 8.5F; + private static final float TOUCHDOWN_TOP_BLEND_WORLD = 3.75F; + private static final float CONNECTION_BLEND_WORLD = 1.8F; + private static final float DIRECT_RENDER_DOWNSAMPLE_THRESHOLD = 1.01F; + + private ClientLevel preparedLevel; + private long preparedGameTime = Long.MIN_VALUE; + private float preparedPartialTick = Float.NaN; + private final List preparedTornadoes = new ArrayList<>(); + private final VolumeBoxMesh volumeBox = new VolumeBoxMesh(); + private boolean initialized; + private VertexBuffer fullscreenQuad; + private TextureTarget downsampleTarget; + private int resolvedDebugStormIndex = -1; + private long lastRenderOpaqueLogGameTime = Long.MIN_VALUE; + private long lastDiagnosticReportGameTime = Long.MIN_VALUE; + private long lastShaderBindingLogGameTime = Long.MIN_VALUE; + + private SimpleCloudsTornadoRenderer() { + } + + public void prepareFrame(ClientLevel level, float partialTick) { + if (this.preparedLevel == level + && this.preparedGameTime == level.getGameTime() + && Float.compare(this.preparedPartialTick, partialTick) == 0) { + return; + } + + this.preparedLevel = level; + this.preparedGameTime = level.getGameTime(); + this.preparedPartialTick = partialTick; + this.preparedTornadoes.clear(); + + float animationTime = TornadoManager.getShaderTime() + partialTick * 0.05F; + for (TornadoInstance tornado : TornadoManager.getClientTornadoes()) { + if (this.preparedTornadoes.size() >= MAX_STORMS) { + break; + } + this.preparedTornadoes.add(PreparedTornado.from(level, tornado, animationTime, partialTick)); + } + + Camera camera = Minecraft.getInstance().gameRenderer.getMainCamera(); + if (camera != null && TornadoRenderDebugState.isActive()) { + this.resolvedDebugStormIndex = this.resolveDebugStormIndex( + camera.getPosition(), + new Vec3(camera.getLookVector()) + ); + } else { + this.resolvedDebugStormIndex = -1; + } + + if (shouldDebugLog(level)) { + debug( + "prepareFrame complete gameTime={} tornadoes={} debugState={} resolvedDebugStorm={}", + level.getGameTime(), + this.preparedTornadoes.size(), + TornadoRenderDebugState.describe(), + this.resolvedDebugStormIndex + ); + } + } + + public void renderOpaque(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB) { + this.renderOpaque(renderer, stack, projMat, partialTick, cloudR, cloudG, cloudB, + null, renderer.getCloudTarget().getDepthTextureId(), -1, true, false); + } + + public void renderOpaque(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB, + int depthTextureId, int secondaryDepthTextureId, boolean writeDepth) { + this.renderOpaque(renderer, stack, projMat, partialTick, cloudR, cloudG, cloudB, + null, depthTextureId, secondaryDepthTextureId, writeDepth, false); + } + + public void renderOpaque(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB, + Frustum frustum, int depthTextureId, int secondaryDepthTextureId, boolean writeDepth) { + this.renderOpaque(renderer, stack, projMat, partialTick, cloudR, cloudG, cloudB, + frustum, depthTextureId, secondaryDepthTextureId, writeDepth, false); + } + + public void renderOpaque(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB, + Frustum frustum, int depthTextureId, int secondaryDepthTextureId, + boolean writeDepth, boolean distantHorizonsDepthMode) { + this.renderOpaqueToTarget(renderer, null, stack, projMat, partialTick, cloudR, cloudG, cloudB, + frustum, depthTextureId, secondaryDepthTextureId, writeDepth, distantHorizonsDepthMode); + } + + public void renderOpaqueToTarget(SimpleCloudsRenderer renderer, RenderTarget target, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB, + Frustum frustum, int depthTextureId, int secondaryDepthTextureId, + boolean writeDepth, boolean distantHorizonsDepthMode) { + this.renderOpaqueToTarget(renderer, target, stack, projMat, partialTick, cloudR, cloudG, cloudB, + frustum, depthTextureId, secondaryDepthTextureId, writeDepth, distantHorizonsDepthMode, false); + } + + public void renderOpaqueToTarget(SimpleCloudsRenderer renderer, RenderTarget target, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB, + Frustum frustum, int depthTextureId, int secondaryDepthTextureId, + boolean writeDepth, boolean distantHorizonsDepthMode, + boolean forceDisableFramebufferDepth) { + ClientLevel level = Minecraft.getInstance().level; + boolean pathLog = shouldPathLog(level); + if (pathLog && this.lastRenderOpaqueLogGameTime != level.getGameTime()) { + this.lastRenderOpaqueLogGameTime = level.getGameTime(); + path( + "renderOpaque called gameTime={} tornadoes={} shaderReady={} debugState={} resolvedDebugStorm={} targetOverride={} dhMode={} depth={} secondaryDepth={} writeDepth={} forceNoFbDepth={}", + level.getGameTime(), + this.preparedTornadoes.size(), + TornadoShaders.isReady(), + TornadoRenderDebugState.describe(), + this.resolvedDebugStormIndex, + target != null, + distantHorizonsDepthMode, + depthTextureId, + secondaryDepthTextureId, + writeDepth, + forceDisableFramebufferDepth + ); + } + if (this.preparedTornadoes.isEmpty()) { + if (pathLog) { + path("renderOpaque skipped: no prepared tornadoes"); + } + return; + } + if (!TornadoShaders.isReady()) { + if (pathLog) { + path("renderOpaque skipped: tornado shaders are not ready"); + } + return; + } + + Minecraft mc = Minecraft.getInstance(); + ShaderInstance shader = TornadoShaders.getShader(); + if (shader == null) { + if (pathLog) { + path("renderOpaque skipped: tornado shader instance is null"); + } + return; + } + + TornadoRenderDebugState.Mode debugMode = TornadoRenderDebugState.isActive() + ? TornadoRenderDebugState.getMode() + : TornadoRenderDebugState.Mode.OFF; + TornadoRenderDebugState.Mode shaderMode = !distantHorizonsDepthMode && debugMode == TornadoRenderDebugState.Mode.OFF + ? TornadoRenderDebugState.Mode.FULL + : debugMode; + float downsample = getConfiguredDownsample(); + boolean useTargetOverride = target != null; + RenderTarget destinationTarget = useTargetOverride ? target : renderer.getCloudTarget(); + boolean useDownsample = this.canUseDownsamplePath() + && (!useTargetOverride || distantHorizonsDepthMode); + RenderTarget renderTarget = useDownsample ? this.prepareDownsampleTarget(destinationTarget, downsample) : destinationTarget; + if (renderTarget == null) { + if (pathLog) { + path("renderOpaque skipped: render target is null useDownsample={} targetOverride={}", useDownsample, useTargetOverride); + } + return; + } + boolean disableFramebufferDepth = debugMode == TornadoRenderDebugState.Mode.DEPTH_NO_FRAMEBUFFER + || debugMode == TornadoRenderDebugState.Mode.OCCLUSION + || debugMode == TornadoRenderDebugState.Mode.LATE + || forceDisableFramebufferDepth + || useDownsample; + boolean deferSceneDepthReject = useDownsample && !distantHorizonsDepthMode; + boolean writeDownsampleDepth = deferSceneDepthReject; + if (pathLog) { + path( + "renderOpaque state debugMode={} shaderMode={} configuredDownsample={} canDownsample={} useDownsample={} targetOverride={} destination={}x{} renderTarget={}x{} disableFramebufferDepth={} deferSceneDepthReject={} writeDownsampleDepth={}", + debugMode, + shaderMode, + downsample, + this.canUseDownsamplePath(), + useDownsample, + useTargetOverride, + destinationTarget.width, + destinationTarget.height, + renderTarget.width, + renderTarget.height, + disableFramebufferDepth, + deferSceneDepthReject, + writeDownsampleDepth + ); + } + + this.pushGpuDebugGroup("ProjectAtmosphere Tornado Opaque"); + try { + if (useDownsample) { + this.clearDownsampleTarget(); + } + + renderTarget.bindWrite(useDownsample); + if (useDownsample && !distantHorizonsDepthMode) { + RenderSystem.disableBlend(); + } else { + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + } + if (writeDownsampleDepth) { + RenderSystem.enableDepthTest(); + RenderSystem.depthMask(true); + } else if (disableFramebufferDepth) { + RenderSystem.disableDepthTest(); + RenderSystem.depthMask(false); + } else { + RenderSystem.enableDepthTest(); + RenderSystem.depthMask(writeDepth); + } + RenderSystem.depthFunc(writeDownsampleDepth ? GL11.GL_ALWAYS : GL11.GL_LEQUAL); + RenderSystem.setShader(() -> shader); + + AbstractTexture tornadoTexture = mc.getTextureManager().getTexture(TornadoShaders.TORNADO_TEXTURE); + AbstractTexture noiseTexture = mc.getTextureManager().getTexture(TornadoShaders.NOISE_TEXTURE); + AbstractTexture flowTexture = mc.getTextureManager().getTexture(TornadoShaders.FLOW_TEXTURE); + shader.setSampler("TornadoSampler", tornadoTexture); + shader.setSampler("NoiseSampler", noiseTexture); + shader.setSampler("FlowSampler", flowTexture); + shader.setSampler("DepthSampler", depthTextureId); + shader.setSampler("SecondaryDepthSampler", secondaryDepthTextureId > 0 ? secondaryDepthTextureId : depthTextureId); + + shader.safeGetUniform("ModelViewMat").set(stack.last().pose()); + shader.safeGetUniform("ProjMat").set(projMat); + Matrix4f inverseProj = new Matrix4f(projMat).invert(); + Matrix4f inverseModelView = new Matrix4f(stack.last().pose()).invert(); + shader.safeGetUniform("InverseProjMat").set(inverseProj); + shader.safeGetUniform("InverseModelViewMat").set(inverseModelView); + + float scale = SimpleCloudsConstants.CLOUD_SCALE; + float cloudHeight = CloudManager.get(level).getCloudHeight(); + Vec3 cameraPos = mc.gameRenderer.getMainCamera().getPosition(); + Vec3 cameraPosCloud = new Vec3( + cameraPos.x / scale, + (cameraPos.y - cloudHeight) / scale, + cameraPos.z / scale + ); + shader.safeGetUniform("CameraPos").set((float) cameraPosCloud.x, (float) cameraPosCloud.y, (float) cameraPosCloud.z); + + shader.safeGetUniform("CloudScale").set(scale); + shader.safeGetUniform("RenderQuality").set((float) AtmoCommonConfig.TORNADO_RENDER_QUALITY.get().doubleValue()); + shader.safeGetUniform("UseSecondaryDepthSampler").set(secondaryDepthTextureId > 0 && secondaryDepthTextureId != depthTextureId ? 1 : 0); + shader.safeGetUniform("DistantHorizonsDepthMode").set(distantHorizonsDepthMode ? 1 : 0); + shader.safeGetUniform("DeferSceneDepthReject").set(deferSceneDepthReject ? 1 : 0); + + shader.safeGetUniform("CloudColor").set(cloudR, cloudG, cloudB, 1.0F); + shader.safeGetUniform("AnimationTime").set(TornadoManager.getShaderTime() + partialTick * 0.05F); + shader.safeGetUniform("MaxDistance").set(MAX_RAY_DISTANCE_CLOUD); + shader.safeGetUniform("OutSize").set((float) renderTarget.width, (float) renderTarget.height); + shader.safeGetUniform("FogStart").set(renderer.getFogStart()); + shader.safeGetUniform("FogEnd").set(renderer.getFogEnd()); + float[] fogColor = RenderSystem.getShaderFogColor(); + shader.safeGetUniform("FogColor").set(fogColor[0], fogColor[1], fogColor[2], fogColor[3]); + + this.maybeLogShaderBindings(level, shader, depthTextureId, secondaryDepthTextureId); + this.maybeEmitDiagnosticReport(level, inverseProj, inverseModelView, stack.last().pose(), projMat, cameraPos, cameraPosCloud, writeDepth); + + + List renderOrder = new ArrayList<>(); + for (int i = 0; i < this.preparedTornadoes.size(); i++) { + if (debugMode != TornadoRenderDebugState.Mode.OFF + && this.resolvedDebugStormIndex >= 0 + && i != this.resolvedDebugStormIndex) { + continue; + } + renderOrder.add(i); + } + renderOrder.sort((left, right) -> Double.compare( + this.preparedTornadoes.get(right).centerWorld().distanceToSqr(cameraPos), + this.preparedTornadoes.get(left).centerWorld().distanceToSqr(cameraPos) + )); + + int drawn = 0; + for (int index : renderOrder) { + PreparedTornado tornado = this.preparedTornadoes.get(index); + if (!this.isVisible(tornado, frustum)) { + continue; + } + this.setProxyCullState(cameraPos, tornado); + this.applyStormUniforms(shader, tornado); + shader.safeGetUniform("DebugMode").set(shaderMode.shaderValue()); + shader.safeGetUniform("DebugSelectedStorm").set(debugMode == TornadoRenderDebugState.Mode.OFF ? -1 : 0); + shader.safeGetUniform("DebugFreeze").set(TornadoRenderDebugState.isFreezeEnabled() ? 1 : 0); + shader.safeGetUniform("VolumeMin").set( + (float) tornado.boundsMinCloud().x, + (float) tornado.boundsMinCloud().y, + (float) tornado.boundsMinCloud().z + ); + shader.safeGetUniform("VolumeMax").set( + (float) tornado.boundsMaxCloud().x, + (float) tornado.boundsMaxCloud().y, + (float) tornado.boundsMaxCloud().z + ); + shader.apply(); + this.volumeBox.draw(shader, stack.last().pose(), projMat); + shader.clear(); + drawn++; + } + if (pathLog) { + path("renderOpaque submitted renderOrder={} drawn={} debugFilterIndex={}", renderOrder.size(), drawn, this.resolvedDebugStormIndex); + } + + if (useDownsample) { + this.compositeDownsampleTarget(destinationTarget, depthTextureId, deferSceneDepthReject); + } + } finally { + this.popGpuDebugGroup(); + } + + RenderSystem.depthMask(true); + RenderSystem.depthFunc(GL11.GL_LEQUAL); + RenderSystem.enableDepthTest(); + RenderSystem.disableBlend(); + RenderSystem.enableCull(); + } + + public boolean hasVisibleTornado(Frustum frustum) { + if (TornadoRenderDebugState.isActive()) { + return !this.preparedTornadoes.isEmpty(); + } + for (PreparedTornado tornado : this.preparedTornadoes) { + if (this.isVisible(tornado, frustum)) { + return true; + } + } + return false; + } + + public int preparedTornadoCount() { + return this.preparedTornadoes.size(); + } + + public boolean hasPreparedTornadoes() { + return !this.preparedTornadoes.isEmpty(); + } + + public boolean usesDownsamplePath() { + if (SimpleCloudsMod.dhLoaded()) { + return false; + } + + return this.canUseDownsamplePath(); + } + + private void ensureInitialized() { + if (this.initialized) { + return; + } + + BufferBuilder builder = Tesselator.getInstance().getBuilder(); + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); + builder.vertex(-1.0F, -1.0F, 0.0F).uv(0.0F, 0.0F).endVertex(); + builder.vertex(1.0F, -1.0F, 0.0F).uv(1.0F, 0.0F).endVertex(); + builder.vertex(1.0F, 1.0F, 0.0F).uv(1.0F, 1.0F).endVertex(); + builder.vertex(-1.0F, 1.0F, 0.0F).uv(0.0F, 1.0F).endVertex(); + this.fullscreenQuad = new VertexBuffer(VertexBuffer.Usage.STATIC); + this.fullscreenQuad.bind(); + this.fullscreenQuad.upload(builder.end()); + VertexBuffer.unbind(); + this.initialized = true; + } + + private RenderTarget prepareDownsampleTarget(RenderTarget destination, float downsample) { + this.ensureInitialized(); + int width = Math.max(1, Mth.ceil(destination.width / downsample)); + int height = Math.max(1, Mth.ceil(destination.height / downsample)); + + if (this.downsampleTarget == null + || this.downsampleTarget.width != width + || this.downsampleTarget.height != height) { + if (this.downsampleTarget != null) { + this.downsampleTarget.destroyBuffers(); + } + this.downsampleTarget = new TextureTarget(width, height, true, Minecraft.ON_OSX); + this.downsampleTarget.setFilterMode(GL11.GL_LINEAR); + } + + return this.downsampleTarget; + } + + private void clearDownsampleTarget() { + if (this.downsampleTarget == null) { + return; + } + this.downsampleTarget.setClearColor(0.0F, 0.0F, 0.0F, 0.0F); + this.downsampleTarget.clear(Minecraft.ON_OSX); + } + + private void compositeDownsampleTarget(RenderTarget destination, int sceneDepthTextureId, boolean writeCompositeDepth) { + ShaderInstance shader = TornadoShaders.getCompositeShader(); + if (shader == null || this.downsampleTarget == null) { + return; + } + + destination.bindWrite(true); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + if (writeCompositeDepth) { + RenderSystem.enableDepthTest(); + RenderSystem.depthMask(true); + RenderSystem.depthFunc(GL11.GL_ALWAYS); + } else { + RenderSystem.disableDepthTest(); + RenderSystem.depthMask(false); + } + RenderSystem.disableCull(); + RenderSystem.setShader(() -> shader); + shader.setSampler("TornadoColorSampler", this.downsampleTarget.getColorTextureId()); + shader.setSampler("TornadoDepthSampler", this.downsampleTarget.getDepthTextureId()); + shader.setSampler("SceneDepthSampler", sceneDepthTextureId); + shader.safeGetUniform("WriteCompositeDepth").set(writeCompositeDepth ? 1 : 0); + shader.apply(); + + this.fullscreenQuad.bind(); + this.fullscreenQuad.drawWithShader(new Matrix4f(), new Matrix4f(), shader); + VertexBuffer.unbind(); + shader.clear(); + } + + private void setProxyCullState(Vec3 cameraPos, PreparedTornado tornado) { + if (tornado.boundsWorld().contains(cameraPos)) { + RenderSystem.disableCull(); + } else { + RenderSystem.enableCull(); + } + } + + private static float getConfiguredDownsample() { + return (float) Mth.clamp(AtmoCommonConfig.TORNADO_RENDER_DOWNSAMPLE.get(), 1.0D, 4.0D); + } + + private boolean canUseDownsamplePath() { + return getConfiguredDownsample() > DIRECT_RENDER_DOWNSAMPLE_THRESHOLD + && !TornadoRenderDebugState.isActive() + && TornadoShaders.getCompositeShader() != null; + } + + private void applyStormUniforms(ShaderInstance shader, PreparedTornado tornado) { + float[] stormPositions = new float[MAX_STORMS * 3]; + float[] stormHeights = new float[MAX_STORMS]; + float[] stormWidths = new float[MAX_STORMS]; + float[] stormSizes = new float[MAX_STORMS]; + float[] stormSpins = new float[MAX_STORMS]; + float[] stormIntensities = new float[MAX_STORMS]; + float[] stormShapes = new float[MAX_STORMS]; + float[] stormProgress = new float[MAX_STORMS]; + + stormPositions[0] = tornado.centerX(); + stormPositions[1] = tornado.bottomY(); + stormPositions[2] = tornado.centerZ(); + stormHeights[0] = tornado.height(); + stormWidths[0] = tornado.width(); + stormSizes[0] = tornado.stormSize(); + stormSpins[0] = tornado.spin(); + stormIntensities[0] = tornado.intensity(); + stormShapes[0] = tornado.shape(); + stormProgress[0] = tornado.touchdownProgress(); + + shader.safeGetUniform("StormCount").set(1); + shader.safeGetUniform("StormPositions").set(stormPositions); + shader.safeGetUniform("StormHeights").set(stormHeights); + shader.safeGetUniform("StormWidths").set(stormWidths); + shader.safeGetUniform("StormSizes").set(stormSizes); + shader.safeGetUniform("StormSpins").set(stormSpins); + shader.safeGetUniform("StormIntensities").set(stormIntensities); + shader.safeGetUniform("StormShapes").set(stormShapes); + shader.safeGetUniform("StormProgress").set(stormProgress); + } + + public float sampleWhiteoutAtCamera(ClientLevel level, Vec3 cameraPos, float partialTick) { + if (TornadoRenderDebugState.isActive()) { + return 0.0F; + } + + float scale = SimpleCloudsConstants.CLOUD_SCALE; + float cloudHeight = CloudManager.get(level).getCloudHeight(); + float sampleX = (float) cameraPos.x / scale; + float sampleY = ((float) cameraPos.y - cloudHeight) / scale; + float sampleZ = (float) cameraPos.z / scale; + float strongest = 0.0F; + float animationTime = TornadoManager.getShaderTime() + partialTick * 0.05F; + + for (TornadoInstance tornado : TornadoManager.getClientTornadoes()) { + PreparedTornado prepared = PreparedTornado.from(level, tornado, animationTime, partialTick); + float density = sampleAnalyticalDensity(sampleX, sampleY, sampleZ, prepared); + double horizontal = cameraPos.distanceTo(new Vec3(prepared.renderPosWorld().x, cameraPos.y, prepared.renderPosWorld().z)); + float dustyRadius = Math.max(prepared.widthWorld() * 2.8F, prepared.stormSizeWorld() * 0.48F); + float interiorDust = 1.0F - Mth.clamp(((float) horizontal - dustyRadius * 0.35F) / Math.max(dustyRadius * 0.65F, 1.0F), 0.0F, 1.0F); + interiorDust *= Mth.clamp((float) (cameraPos.y - prepared.bottomWorld()) / 8.0F, 0.0F, 1.0F); + interiorDust *= 1.0F - Mth.clamp((float) (cameraPos.y - prepared.bottomWorld() - prepared.heightWorld() * 0.82F) / 32.0F, 0.0F, 1.0F); + float whiteout = Math.max( + Mth.clamp((density - WHITEOUT_THRESHOLD) / 0.075F, 0.0F, 1.0F), + interiorDust + ) * WHITEOUT_STRENGTH; + strongest = Math.max(strongest, whiteout); + } + return strongest; + } + + public void close() { + this.preparedLevel = null; + this.preparedGameTime = Long.MIN_VALUE; + this.preparedPartialTick = Float.NaN; + this.preparedTornadoes.clear(); + this.resolvedDebugStormIndex = -1; + this.volumeBox.close(); + if (this.fullscreenQuad != null) { + this.fullscreenQuad.close(); + this.fullscreenQuad = null; + } + if (this.downsampleTarget != null) { + this.downsampleTarget.destroyBuffers(); + this.downsampleTarget = null; + } + this.initialized = false; + } + + private int resolveDebugStormIndex(Vec3 cameraPosWorld, Vec3 cameraLook) { + int requestedStormIndex = TornadoRenderDebugState.getRequestedStormIndex(); + if (requestedStormIndex >= 0 && requestedStormIndex < this.preparedTornadoes.size()) { + return requestedStormIndex; + } + if (this.preparedTornadoes.isEmpty()) { + return -1; + } + + int bestVisible = -1; + double bestVisibleDistanceSqr = Double.MAX_VALUE; + int bestOverall = 0; + double bestOverallDistanceSqr = Double.MAX_VALUE; + Vec3 normalizedLook = cameraLook.lengthSqr() > 0.0D ? cameraLook.normalize() : new Vec3(0.0D, 0.0D, 1.0D); + + for (int i = 0; i < this.preparedTornadoes.size(); i++) { + PreparedTornado tornado = this.preparedTornadoes.get(i); + Vec3 tornadoCenter = tornado.centerWorld(); + Vec3 toStorm = tornadoCenter.subtract(cameraPosWorld); + double distanceSqr = toStorm.lengthSqr(); + if (distanceSqr < bestOverallDistanceSqr) { + bestOverallDistanceSqr = distanceSqr; + bestOverall = i; + } + if (distanceSqr <= 0.0001D) { + continue; + } + + double dot = toStorm.normalize().dot(normalizedLook); + if (dot > 0.15D && distanceSqr < bestVisibleDistanceSqr) { + bestVisibleDistanceSqr = distanceSqr; + bestVisible = i; + } + } + return bestVisible >= 0 ? bestVisible : bestOverall; + } + + private void maybeEmitDiagnosticReport(ClientLevel level, Matrix4f inverseProj, Matrix4f inverseModelView, + Matrix4f modelView, Matrix4f projMat, + Vec3 cameraPosWorld, Vec3 cameraPosCloud, boolean writeDepth) { + boolean requested = TornadoRenderDebugState.consumeDiagnosticReportRequest(); + boolean periodic = TornadoRenderDebugState.isActive() + && shouldDebugLog(level) + && this.lastDiagnosticReportGameTime != level.getGameTime(); + if (!requested && !periodic) { + return; + } + + this.lastDiagnosticReportGameTime = level.getGameTime(); + debug( + "renderState mode={} freeze={} writeDepth={} blend=srcalpha,1-srcalpha depthFunc=LEQUAL cull=outside-only downsample={} proxyVolume=true resolvedStorm={}", + TornadoRenderDebugState.getMode().token(), + TornadoRenderDebugState.isFreezeEnabled(), + writeDepth, + getConfiguredDownsample(), + this.resolvedDebugStormIndex + ); + + if (this.resolvedDebugStormIndex < 0 || this.resolvedDebugStormIndex >= this.preparedTornadoes.size()) { + debug("diagnostic skipped: no selected tornado. preparedCount={}", this.preparedTornadoes.size()); + return; + } + + PreparedTornado tornado = this.preparedTornadoes.get(this.resolvedDebugStormIndex); + CenterRayDiagnostic diagnostic = sampleCenterRayDiagnostic(tornado, inverseProj, inverseModelView, modelView, projMat, cameraPosCloud); + debug( + "selectedStorm index={} id={} renderPosWorld=({}, {}, {}) cloudHeightWorld={} cloudScale={} renderBottomWorld={} terrainSurfaceWorld={} terrainLoadedSamples={} terrainFallbackUsed={} bottomWorld={} baseOffsetWorld={} topWorld={} bottomYCloud={} heightCloud={} heightWorld={} widthCloud={} widthWorld={} stormSizeCloud={} stormSizeWorld={} boundsRadiusCloud={} boundsRadiusWorld={} wallcloudRadiusWorld={}", + this.resolvedDebugStormIndex, + tornado.id(), + fmt(tornado.renderPosWorld().x), fmt(tornado.renderPosWorld().y), fmt(tornado.renderPosWorld().z), + fmt(tornado.cloudHeightWorld()), + fmt(tornado.scale()), + fmt(tornado.renderBottomWorld()), + fmt(tornado.terrainSurfaceWorld()), + tornado.terrainLoadedSamples(), + tornado.terrainFallbackUsed(), + fmt(tornado.bottomWorld()), + fmt(tornado.renderBottomWorld() - tornado.bottomWorld()), + fmt(tornado.topWorld()), + fmt(tornado.bottomY()), + fmt(tornado.height()), + fmt(tornado.heightWorld()), + fmt(tornado.width()), + fmt(tornado.widthWorld()), + fmt(tornado.stormSize()), + fmt(tornado.stormSizeWorld()), + fmt(tornado.boundsRadiusCloud()), + fmt(tornado.boundsRadiusWorld()), + fmt(tornado.wallcloudRadiusWorld()) + ); + + if (diagnostic == null) { + debug( + "centerRay cameraWorld=({}, {}, {}) cameraCloud=({}, {}, {}) note=center ray did not intersect selected tornado AABB", + fmt(cameraPosWorld.x), fmt(cameraPosWorld.y), fmt(cameraPosWorld.z), + fmt(cameraPosCloud.x), fmt(cameraPosCloud.y), fmt(cameraPosCloud.z) + ); + return; + } + + debug( + "centerRay cameraWorld=({}, {}, {}) cameraCloud=({}, {}, {}) rayEndCloud=({}, {}, {}) rayDirCloud=({}, {}, {}) tNear={} tFar={} stepSize={} sampleT={} projectedDepth={} sceneDepthAtSample={} sceneDepthDelta={} sampleScreen=({}, {}) samplePosCloud=({}, {}, {}) samplePosWorld=({}, {}, {}) tornadoOriginCloud=({}, {}, {}) tornadoOriginWorld=({}, {}, {}) localPosCloud=({}, {}, {}) localPosWorld=({}, {}, {}) radialDistanceWorld={} height01={} heightMask={} funnelRadiusWorld={} density={} alpha={} wallcloudRadiusWorld={} wallcloudLowerWorld={} connectionRadiusWorld={}", + fmt(cameraPosWorld.x), fmt(cameraPosWorld.y), fmt(cameraPosWorld.z), + fmt(cameraPosCloud.x), fmt(cameraPosCloud.y), fmt(cameraPosCloud.z), + fmt(diagnostic.rayEndCloud().x), fmt(diagnostic.rayEndCloud().y), fmt(diagnostic.rayEndCloud().z), + fmt(diagnostic.rayDirectionCloud().x), fmt(diagnostic.rayDirectionCloud().y), fmt(diagnostic.rayDirectionCloud().z), + fmt(diagnostic.tNear()), + fmt(diagnostic.tFar()), + fmt(diagnostic.stepSize()), + fmt(diagnostic.sampleT()), + fmt(diagnostic.projectedDepth()), + fmt(diagnostic.sceneDepth()), + fmt(diagnostic.projectedDepth() - diagnostic.sceneDepth()), + fmt(diagnostic.sampleScreenX()), + fmt(diagnostic.sampleScreenY()), + fmt(diagnostic.samplePosCloud().x), fmt(diagnostic.samplePosCloud().y), fmt(diagnostic.samplePosCloud().z), + fmt(diagnostic.samplePosWorld().x), fmt(diagnostic.samplePosWorld().y), fmt(diagnostic.samplePosWorld().z), + fmt(diagnostic.tornadoOriginCloud().x), fmt(diagnostic.tornadoOriginCloud().y), fmt(diagnostic.tornadoOriginCloud().z), + fmt(diagnostic.tornadoOriginWorld().x), fmt(diagnostic.tornadoOriginWorld().y), fmt(diagnostic.tornadoOriginWorld().z), + fmt(diagnostic.localPosCloud().x), fmt(diagnostic.localPosCloud().y), fmt(diagnostic.localPosCloud().z), + fmt(diagnostic.localPosWorld().x), fmt(diagnostic.localPosWorld().y), fmt(diagnostic.localPosWorld().z), + fmt(diagnostic.funnelSample().radialDistanceWorld()), + fmt(diagnostic.funnelSample().height01()), + fmt(diagnostic.funnelSample().heightMask()), + fmt(diagnostic.funnelSample().funnelRadiusWorld()), + fmt(diagnostic.funnelSample().density()), + fmt(diagnostic.funnelSample().alpha()), + fmt(diagnostic.funnelSample().wallcloudRadiusWorld()), + fmt(diagnostic.funnelSample().wallcloudLowerWorld()), + fmt(diagnostic.funnelSample().connectionRadiusWorld()) + ); + } + + private static CenterRayDiagnostic sampleCenterRayDiagnostic(PreparedTornado tornado, Matrix4f inverseProj, + Matrix4f inverseModelView, Matrix4f modelView, + Matrix4f projMat, Vec3 cameraPosCloud) { + Vec3 rayEndCloud = reconstructPosition(0.5F, 0.5F, 1.0F, inverseProj, inverseModelView); + Vec3 rayDirectionCloud = rayEndCloud.subtract(cameraPosCloud); + if (rayDirectionCloud.lengthSqr() <= 0.000001D) { + return null; + } + rayDirectionCloud = rayDirectionCloud.normalize(); + + Vec3 boundsMin = new Vec3( + tornado.centerX() - tornado.boundsRadiusCloud(), + tornado.bottomY() - (8.0F / tornado.scale()), + tornado.centerZ() - tornado.boundsRadiusCloud() + ); + Vec3 boundsMax = new Vec3( + tornado.centerX() + tornado.boundsRadiusCloud(), + tornado.bottomY() + tornado.height() + (12.0F / tornado.scale()), + tornado.centerZ() + tornado.boundsRadiusCloud() + ); + AabbHit hit = intersectAabb(cameraPosCloud, rayDirectionCloud, boundsMin, boundsMax); + if (hit == null) { + return null; + } + + float interval = hit.far() - hit.near(); + int steps = Mth.clamp(Mth.floor(interval / RAY_STEP_CLOUD), 18, 52); + float stepSize = interval / Math.max(steps, 1); + float jitter = hash1(0.5F * Minecraft.getInstance().getWindow().getWidth() + + 0.5F * Minecraft.getInstance().getWindow().getHeight() + + tornado.seed() * 17.13F); + float t = hit.near() + stepSize * (0.20F + jitter * 0.80F); + + Vec3 samplePosCloud = cameraPosCloud.add(rayDirectionCloud.scale(t)); + Vec3 samplePosWorld = new Vec3( + samplePosCloud.x * tornado.scale(), + samplePosCloud.y * tornado.scale() + tornado.cloudHeightWorld(), + samplePosCloud.z * tornado.scale() + ); + Vec3 tornadoOriginCloud = tornado.originCloud(); + Vec3 tornadoOriginWorld = tornado.originWorld(); + Vec3 localPosCloud = samplePosCloud.subtract(tornadoOriginCloud); + Vec3 localPosWorld = samplePosWorld.subtract(tornadoOriginWorld); + ScreenDepthSample projectedDepth = projectDepthSample(samplePosCloud, modelView, projMat); + return new CenterRayDiagnostic( + rayEndCloud, + rayDirectionCloud, + hit.near(), + hit.far(), + stepSize, + t, + projectedDepth.projectedDepth(), + projectedDepth.sceneDepth(), + projectedDepth.screenX(), + projectedDepth.screenY(), + samplePosCloud, + samplePosWorld, + tornadoOriginCloud, + tornadoOriginWorld, + localPosCloud, + localPosWorld, + sampleDeterministicFunnel(samplePosCloud, tornado) + ); + } + + private static Vec3 reconstructPosition(float u, float v, float depth, Matrix4f inverseProj, Matrix4f inverseModelView) { + Vector4f ndc = new Vector4f(u * 2.0F - 1.0F, v * 2.0F - 1.0F, depth * 2.0F - 1.0F, 1.0F); + inverseProj.transform(ndc); + ndc.div(ndc.w); + inverseModelView.transform(ndc); + ndc.div(ndc.w); + return new Vec3(ndc.x, ndc.y, ndc.z); + } + + private static ScreenDepthSample projectDepthSample(Vec3 samplePosCloud, Matrix4f modelView, Matrix4f projMat) { + Minecraft mc = Minecraft.getInstance(); + Vector4f clip = new Vector4f((float) samplePosCloud.x, (float) samplePosCloud.y, (float) samplePosCloud.z, 1.0F); + modelView.transform(clip); + projMat.transform(clip); + if (Math.abs(clip.w) <= 0.000001F) { + return new ScreenDepthSample(1.0F, 1.0F, -1.0F, -1.0F); + } + + float invW = 1.0F / clip.w; + float ndcX = clip.x * invW; + float ndcY = clip.y * invW; + float ndcZ = clip.z * invW; + float projectedDepth = ndcZ * 0.5F + 0.5F; + + float screenX = ((ndcX * 0.5F) + 0.5F) * mc.getWindow().getWidth(); + float screenY = ((ndcY * 0.5F) + 0.5F) * mc.getWindow().getHeight(); + float sceneDepth = readFramebufferDepth(screenX, screenY, mc.getWindow().getWidth(), mc.getWindow().getHeight()); + return new ScreenDepthSample(projectedDepth, sceneDepth, screenX, screenY); + } + + private static float readFramebufferDepth(float screenX, float screenY, int width, int height) { + int pixelX = Mth.clamp(Mth.floor(screenX), 0, Math.max(width - 1, 0)); + int pixelY = Mth.clamp(Mth.floor(screenY), 0, Math.max(height - 1, 0)); + int glY = Math.max(height - 1 - pixelY, 0); + try (MemoryStack stack = MemoryStack.stackPush()) { + var depthValue = stack.mallocFloat(1); + GL11.glReadPixels(pixelX, glY, 1, 1, GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, depthValue); + return depthValue.get(0); + } + } + + private static AabbHit intersectAabb(Vec3 ro, Vec3 rd, Vec3 bmin, Vec3 bmax) { + double invX = 1.0D / rd.x; + double invY = 1.0D / rd.y; + double invZ = 1.0D / rd.z; + + double t0x = (bmin.x - ro.x) * invX; + double t1x = (bmax.x - ro.x) * invX; + double t0y = (bmin.y - ro.y) * invY; + double t1y = (bmax.y - ro.y) * invY; + double t0z = (bmin.z - ro.z) * invZ; + double t1z = (bmax.z - ro.z) * invZ; + + double minX = Math.min(t0x, t1x); + double minY = Math.min(t0y, t1y); + double minZ = Math.min(t0z, t1z); + double maxX = Math.max(t0x, t1x); + double maxY = Math.max(t0y, t1y); + double maxZ = Math.max(t0z, t1z); + + double tNear = Math.max(Math.max(minX, minY), minZ); + double tFar = Math.min(Math.min(maxX, maxY), maxZ); + if (tFar <= Math.max(tNear, 0.0D)) { + return null; + } + return new AabbHit((float) Math.max(tNear, 0.0D), (float) tFar); + } + + private static float hash1(float p) { + float value = Mth.frac(p * 0.1031F); + value *= value + 33.33F; + value *= value + value; + return Mth.frac(value); + } + + private static float sampleAnalyticalDensity(float sampleX, float sampleY, float sampleZ, PreparedTornado tornado) { + return sampleDeterministicFunnel(new Vec3(sampleX, sampleY, sampleZ), tornado).density(); + } + + private static DeterministicFunnelSample sampleDeterministicFunnel(Vec3 samplePosCloud, PreparedTornado tornado) { + float sampleXWorld = (float) samplePosCloud.x * tornado.scale(); + float sampleYWorld = (float) samplePosCloud.y * tornado.scale() + tornado.cloudHeightWorld(); + float sampleZWorld = (float) samplePosCloud.z * tornado.scale(); + float localXWorld = sampleXWorld - (float) tornado.renderPosWorld().x; + float localZWorld = sampleZWorld - (float) tornado.renderPosWorld().z; + float localYWorld = sampleYWorld - tornado.bottomWorld(); + float topWorld = tornado.topWorld(); + float funnelTopWorld = Math.max(topWorld - FUNNEL_TOP_OFFSET_WORLD, tornado.bottomWorld() + FUNNEL_BASE_PADDING_WORLD); + float height01 = Mth.clamp(localYWorld / Math.max(tornado.heightWorld(), 0.001F), 0.0F, 1.0F); + float heightMask = Mth.clamp((sampleYWorld - tornado.bottomWorld()) / Math.max(funnelTopWorld - tornado.bottomWorld(), 0.001F), 0.0F, 1.0F); + float funnelRadiusWorld = sampleFunnelRadiusWorld(tornado, sampleYWorld, funnelTopWorld); + float radialDistanceWorld = Mth.sqrt(localXWorld * localXWorld + localZWorld * localZWorld); + float radialMask = 1.0F - Mth.clamp(radialDistanceWorld / Math.max(funnelRadiusWorld, 0.001F), 0.0F, 1.0F); + float density = 0.0F; + if (sampleYWorld >= tornado.bottomWorld() && sampleYWorld <= funnelTopWorld) { + density = radialMask; + } + float alpha = Mth.clamp(density * 0.85F, 0.0F, 1.0F); + float wallcloudLowerWorld = WALLCLOUD_LOWER_WORLD + * (float) Math.pow(Math.max(0.0F, 1.0F - Mth.clamp(radialDistanceWorld / Math.max(tornado.wallcloudRadiusWorld(), 0.001F), 0.0F, 1.0F)), 0.25F) + * Mth.clamp((tornado.intensity() - 0.45F) * 2.2F, 0.0F, 1.0F); + float connectionRadiusWorld = Math.max( + funnelRadiusWorld * Mth.lerp(tornado.intensity(), 1.8F, 2.5F), + tornado.stormSizeWorld() * 0.28F + ); + return new DeterministicFunnelSample(height01, heightMask, radialMask, funnelRadiusWorld, density, alpha, + tornado.wallcloudRadiusWorld(), wallcloudLowerWorld, connectionRadiusWorld); + } + + private static float sampleFunnelRadiusWorld(PreparedTornado tornado, float sampleYWorld, float funnelTopWorld) { + float widthWorld = tornado.widthWorld(); + float stormSizeWorld = tornado.stormSizeWorld(); + float percFunnelHeight = Mth.clamp( + (sampleYWorld - tornado.bottomWorld()) / Math.max(funnelTopWorld - tornado.bottomWorld(), 0.001F), + 0.0F, + 1.0F + ); + float torShape = Mth.lerp(Mth.clamp(widthWorld / 62.5F, 0.0F, 1.0F), tornado.shape(), 20.0F); + float funnelRadiusWorld = (widthWorld / 2.5F) + + ((widthWorld / 2.5F) * percFunnelHeight * tornado.touchdownProgress()) + + ((stormSizeWorld / Math.max(Mth.lerp(tornado.touchdownProgress(), torShape + 2.0F, torShape), 0.001F)) + * percFunnelHeight * percFunnelHeight * percFunnelHeight * percFunnelHeight); + return Mth.lerp((1.0F - percFunnelHeight) * (1.0F - tornado.touchdownProgress()), funnelRadiusWorld, 0.0F); + } + + private static String fmt(double value) { + return String.format(Locale.ROOT, "%.3f", value); + } + + private static boolean shouldDebugLog(ClientLevel level) { + return TornadoRenderDebugState.isActive() && level != null && level.getGameTime() % 20L == 0L; + } + + public static boolean shouldPathLog(ClientLevel level) { + return level != null + && level.getGameTime() % 20L == 0L + && (TornadoRenderDebugState.isActive() || AtmoCommonConfig.TORNADO_DEBUG_LOGGING.get()); + } + + private void maybeLogShaderBindings(ClientLevel level, ShaderInstance shader, int depthTextureId, int secondaryDepthTextureId) { + if (!shouldDebugLog(level) || this.lastShaderBindingLogGameTime == level.getGameTime()) { + return; + } + this.lastShaderBindingLogGameTime = level.getGameTime(); + + try { + int programId = shader.getId(); + String shaderName = shader.getName(); + Object fragmentProgram = getFieldValue(shader, "fragmentProgram"); + String fragmentName = invokeStringMethod(fragmentProgram, "getName"); + Integer fragmentId = invokeIntMethod(fragmentProgram, "getId"); + List samplerNames = getFieldValue(shader, "samplerNames"); + List samplerLocations = getFieldValue(shader, "samplerLocations"); + Map samplerMap = getFieldValue(shader, "samplerMap"); + + debug( + "shaderBinding shaderName={} programId={} fragmentName={} fragmentId={} requestedDepthSampler={} requestedSecondaryDepthSampler={} samplerNames={} samplerLocations={} samplerKeys={} hasDepthSampler={} hasSecondaryDepthSampler={}", + shaderName, + programId, + fragmentName, + fragmentId, + depthTextureId, + secondaryDepthTextureId, + samplerNames, + samplerLocations, + samplerMap == null ? "null" : samplerMap.keySet(), + samplerNames != null && samplerNames.contains("DepthSampler"), + samplerNames != null && samplerNames.contains("SecondaryDepthSampler") + ); + } catch (Exception exception) { + debug("shaderBinding reflection failed: {}", exception.toString()); + } + } + + @SuppressWarnings("unchecked") + private static T getFieldValue(Object target, String fieldName) throws ReflectiveOperationException { + if (target == null) { + return null; + } + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(target); + } + + private static String invokeStringMethod(Object target, String methodName) throws ReflectiveOperationException { + if (target == null) { + return "null"; + } + return (String) target.getClass().getMethod(methodName).invoke(target); + } + + private static Integer invokeIntMethod(Object target, String methodName) throws ReflectiveOperationException { + if (target == null) { + return null; + } + return (Integer) target.getClass().getMethod(methodName).invoke(target); + } + + private boolean isVisible(PreparedTornado tornado, Frustum frustum) { + return frustum == null || frustum.isVisible(tornado.boundsWorld()); + } + + private static TerrainSurfaceSample sampleTerrainSurfaceY(ClientLevel level, Vec3 renderPos, float radius, float renderBottomY) { + int centerX = Mth.floor(renderPos.x); + int centerZ = Mth.floor(renderPos.z); + int sampleOffset = Math.max(2, Mth.floor(Math.min(radius * 0.45F, 10.0F))); + int minBuildHeight = level.getMinBuildHeight(); + int[][] sampleColumns = { + {centerX, centerZ}, + {centerX + sampleOffset, centerZ}, + {centerX - sampleOffset, centerZ}, + {centerX, centerZ + sampleOffset}, + {centerX, centerZ - sampleOffset} + }; + + float highestSurface = Float.NEGATIVE_INFINITY; + int loadedSamples = 0; + for (int[] sampleColumn : sampleColumns) { + if (!level.hasChunkAt(new net.minecraft.core.BlockPos(sampleColumn[0], Mth.floor(renderBottomY), sampleColumn[1]))) { + continue; + } + float surfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, sampleColumn[0], sampleColumn[1]) - 1.0F; + if (surfaceY <= minBuildHeight) { + continue; + } + highestSurface = Math.max(highestSurface, surfaceY); + loadedSamples++; + } + + if (loadedSamples <= 0) { + return new TerrainSurfaceSample(renderBottomY - GROUND_CONTACT_PADDING_WORLD, 0, true); + } + + return new TerrainSurfaceSample(highestSurface, loadedSamples, false); + } + + private static void debug(String message, Object... args) { + if (TornadoRenderDebugState.isActive()) { + ProjectAtmosphere.LOGGER.info("[TornadoDebug] " + message, args); + } + } + + public static void path(String message, Object... args) { + ProjectAtmosphere.LOGGER.info("[TornadoPath] " + message, args); + } + + private void pushGpuDebugGroup(String label) { + if (GL.getCapabilities() != null && GL.getCapabilities().GL_KHR_debug) { + GL43.glPushDebugGroup(GL43.GL_DEBUG_SOURCE_APPLICATION, 0, label); + } + } + + private void popGpuDebugGroup() { + if (GL.getCapabilities() != null && GL.getCapabilities().GL_KHR_debug) { + GL43.glPopDebugGroup(); + } + } + + private record PreparedTornado(UUID id, float centerX, float centerZ, float bottomY, float height, + float width, float stormSize, float spin, float intensity, + float shape, float touchdownProgress, float seed, float animationTime, + Vec3 renderPosWorld, float renderBottomWorld, float terrainSurfaceWorld, + int terrainLoadedSamples, boolean terrainFallbackUsed, + float bottomWorld, float topWorld, float cloudHeightWorld, float scale, + float boundsRadiusCloud, float boundsRadiusWorld, float wallcloudRadiusWorld) { + static PreparedTornado from(ClientLevel level, TornadoInstance tornado, float animationTime, float partialTick) { + float scale = SimpleCloudsConstants.CLOUD_SCALE; + float cloudHeight = CloudManager.get(level).getCloudHeight(); + Vec3 renderPos = tornado.getRenderPosition(partialTick); + float renderBottomY = tornado.getRenderBottomY(partialTick); + float renderRadius = tornado.getRenderRadius(partialTick); + TerrainSurfaceSample terrainSurface = sampleTerrainSurfaceY(level, renderPos, renderRadius, renderBottomY); + float terrainSurfaceY = terrainSurface.surfaceY(); + float centerX = (float) renderPos.x / scale; + float centerZ = (float) renderPos.z / scale; + float bottomWorld; + if (terrainSurface.fallbackUsed()) { + bottomWorld = renderBottomY - GROUND_VISUAL_SINK_WORLD; + } else { + bottomWorld = terrainSurfaceY - GROUND_VISUAL_SINK_WORLD; + } + float topWorld = Math.max( + renderBottomY + tornado.getRenderHeight(partialTick), + cloudHeight + CLOUD_BLEND_PAD_ABOVE_CLOUD_BASE_WORLD + ); + float bottomY = (bottomWorld - cloudHeight) / scale; + float height = Math.max((topWorld - bottomWorld) / scale, MIN_VISUAL_WORLD_HEIGHT / scale); + float width = Math.max(renderRadius * 2.0F, MIN_VISUAL_WORLD_WIDTH) / scale; + float stormSize = Math.max(MIN_VISUAL_WORLD_STORM_SIZE / scale, Math.max(width * 3.75F, height * 0.30F)); + float boundsRadiusCloud = Math.max(width * 4.25F, stormSize * 0.50F); + float boundsRadiusWorld = boundsRadiusCloud * scale; + float wallcloudRadiusWorld = stormSize * scale * 0.35F; + float intensity = Mth.clamp(tornado.getNormalizedIntensity(), 0.0F, 1.0F); + float touchdownProgress = switch (tornado.getPhase()) { + case FORMING -> Mth.clamp(intensity * 1.35F, 0.0F, 0.92F); + case ACTIVE -> Mth.clamp(0.72F + intensity * 0.35F, 0.0F, 1.0F); + case DISSIPATING -> Mth.clamp(intensity * 1.10F, 0.0F, 1.0F); + default -> 0.0F; + }; + float seed = (Math.abs(tornado.getId().hashCode()) % 10000) / 10000.0F; + float shape = 8.0F + seed * 10.0F; + return new PreparedTornado( + tornado.getId(), + centerX, + centerZ, + bottomY, + height, + width, + stormSize, + tornado.getVisualSpin(partialTick), + intensity, + shape, + touchdownProgress, + seed, + animationTime, + renderPos, + renderBottomY, + terrainSurfaceY, + terrainSurface.loadedSamples(), + terrainSurface.fallbackUsed(), + bottomWorld, + topWorld, + cloudHeight, + scale, + boundsRadiusCloud, + boundsRadiusWorld, + wallcloudRadiusWorld + ); + } + + float heightWorld() { + return this.height * this.scale; + } + + float widthWorld() { + return this.width * this.scale; + } + + float stormSizeWorld() { + return this.stormSize * this.scale; + } + + Vec3 originCloud() { + return new Vec3(this.centerX, this.bottomY, this.centerZ); + } + + Vec3 boundsMinCloud() { + return new Vec3( + this.centerX - this.boundsRadiusCloud, + this.bottomY - (16.0F / this.scale), + this.centerZ - this.boundsRadiusCloud + ); + } + + Vec3 boundsMaxCloud() { + return new Vec3( + this.centerX + this.boundsRadiusCloud, + this.bottomY + this.height + (12.0F / this.scale), + this.centerZ + this.boundsRadiusCloud + ); + } + + AABB boundsWorld() { + return new AABB( + this.renderPosWorld.x - this.boundsRadiusWorld, + this.bottomWorld - 16.0F, + this.renderPosWorld.z - this.boundsRadiusWorld, + this.renderPosWorld.x + this.boundsRadiusWorld, + this.bottomWorld + this.heightWorld() + 12.0F, + this.renderPosWorld.z + this.boundsRadiusWorld + ); + } + + Vec3 originWorld() { + return new Vec3(this.renderPosWorld.x, this.bottomWorld, this.renderPosWorld.z); + } + + Vec3 centerWorld() { + return new Vec3(this.renderPosWorld.x, (this.bottomWorld + this.topWorld) * 0.5F, this.renderPosWorld.z); + } + } + + private record AabbHit(float near, float far) { + } + + private record CenterRayDiagnostic(Vec3 rayEndCloud, Vec3 rayDirectionCloud, float tNear, float tFar, + float stepSize, float sampleT, float projectedDepth, float sceneDepth, + float sampleScreenX, float sampleScreenY, Vec3 samplePosCloud, Vec3 samplePosWorld, + Vec3 tornadoOriginCloud, Vec3 tornadoOriginWorld, + Vec3 localPosCloud, Vec3 localPosWorld, + DeterministicFunnelSample funnelSample) { + } + + private record ScreenDepthSample(float projectedDepth, float sceneDepth, float screenX, float screenY) { + } + + private record TerrainSurfaceSample(float surfaceY, int loadedSamples, boolean fallbackUsed) { + } + + private record DeterministicFunnelSample(float height01, float heightMask, float radialMask, + float funnelRadiusWorld, float density, float alpha, + float wallcloudRadiusWorld, float wallcloudLowerWorld, + float connectionRadiusWorld) { + float radialDistanceWorld() { + return this.radialMask >= 1.0F ? 0.0F : this.funnelRadiusWorld * (1.0F - this.radialMask); + } + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoLateRenderDiagnostics.java b/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoLateRenderDiagnostics.java new file mode 100644 index 00000000..fac8dce3 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoLateRenderDiagnostics.java @@ -0,0 +1,89 @@ +package net.Gabou.projectatmosphere.client.render; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RenderLevelStageEvent; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL30; + +@Mod.EventBusSubscriber(modid = ProjectAtmosphere.MODID, value = Dist.CLIENT) +public final class TornadoLateRenderDiagnostics { + private TornadoLateRenderDiagnostics() { + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void onRenderLevelStage(RenderLevelStageEvent event) { + if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_LEVEL) { + return; + } + if (!SimpleCloudsMod.dhLoaded()) { + return; + } + + Minecraft mc = Minecraft.getInstance(); + ClientLevel level = mc.level; + if (level == null) { + return; + } + + SimpleCloudsRenderer renderer = SimpleCloudsRenderer.getOptionalInstance().orElse(null); + if (renderer == null) { + return; + } + + SimpleCloudsTornadoRenderer.INSTANCE.prepareFrame(level, event.getPartialTick()); + if (!SimpleCloudsTornadoRenderer.INSTANCE.hasVisibleTornado(null)) { + return; + } + + Camera camera = event.getCamera(); + PoseStack stack = new PoseStack(); + stack.mulPose(Axis.XP.rotationDegrees(camera.getXRot())); + stack.mulPose(Axis.YP.rotationDegrees(camera.getYRot() + 180.0F)); + renderer.translateClouds(stack, camera.getPosition().x, camera.getPosition().y, camera.getPosition().z); + + float[] cloudColor = renderer.getCloudColor(event.getPartialTick()); + mc.getMainRenderTarget().bindWrite(false); + int mainDepthTexture = mc.getMainRenderTarget().getDepthTextureId(); + boolean detachedMainDepth = mainDepthTexture > 0; + if (detachedMainDepth) { + GlStateManager._glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, + GL11.GL_TEXTURE_2D, 0, 0); + } + + try { + SimpleCloudsTornadoRenderer.INSTANCE.renderOpaqueToTarget( + renderer, + mc.getMainRenderTarget(), + stack, + event.getProjectionMatrix(), + event.getPartialTick(), + cloudColor[0], + cloudColor[1], + cloudColor[2], + null, + mainDepthTexture, + -1, + true, + true, + true + ); + } finally { + if (detachedMainDepth) { + GlStateManager._glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, + GL11.GL_TEXTURE_2D, mainDepthTexture, 0); + } + } + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoMesh.java b/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoMesh.java deleted file mode 100644 index ffcdb871..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoMesh.java +++ /dev/null @@ -1,94 +0,0 @@ -package net.Gabou.projectatmosphere.client.render; - -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.BufferBuilder; -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import com.mojang.blaze3d.vertex.Tesselator; -import com.mojang.blaze3d.vertex.VertexFormat; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.world.phys.Vec3; -import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.*; - -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; - -public class TornadoMesh { - private static int vao = -1; - private static int vbo = -1; - - public static void init() { - if (vao != -1) return; - - - FloatBuffer vertexData = BufferUtils.createFloatBuffer(3); - vertexData.put(0f).put(0f).put(0f).flip(); - - vbo = GL15.glGenBuffers(); - GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo); - GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexData, GL15.GL_STATIC_DRAW); - - vao = GL30.glGenVertexArrays(); - GL30.glBindVertexArray(vao); - GL20.glEnableVertexAttribArray(0); - GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0); - GL30.glBindVertexArray(0); - } - - public static void drawInstanced(int count) { - GL30.glBindVertexArray(vao); - GL31.glDrawArraysInstanced(GL11.GL_POINTS, 0, 1, count); - GL30.glBindVertexArray(0); - } - - public static int uploadTornadoSSBO(Vec3 pos, float radius, int count) { - ByteBuffer buf = BufferUtils.createByteBuffer(6 * 4 * count); - - for (int i = 0; i < count; i++) { - buf.putInt(i); - buf.putFloat((float) pos.x); - buf.putFloat((float) pos.y); - buf.putFloat((float) pos.z); - buf.putFloat(radius); - buf.putFloat(1.0f); - } - - buf.flip(); - - int ssbo = GL15.glGenBuffers(); - GL15.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, ssbo); - GL15.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, buf, GL15.GL_STATIC_DRAW); - return ssbo; - } - public static void drawTestTriangle() { - Tesselator tesselator = Tesselator.getInstance(); - BufferBuilder buffer = tesselator.getBuilder(); - - RenderSystem.setShader(GameRenderer::getPositionColorShader); - RenderSystem.setShaderColor(1f, 1f, 1f, 1f); - - buffer.begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION_COLOR); - buffer.vertex(0, 1, 0).color(255, 0, 0, 255).endVertex(); - buffer.vertex(-1, -1, 0).color(0, 255, 0, 255).endVertex(); - buffer.vertex(1, -1, 0).color(0, 0, 255, 255).endVertex(); - tesselator.end(); - } - - public static void drawCone() { - Tesselator tesselator = Tesselator.getInstance(); - BufferBuilder buffer = tesselator.getBuilder(); - - buffer.begin(VertexFormat.Mode.TRIANGLE_STRIP, DefaultVertexFormat.POSITION); - int segments = 20; - for (int i = 0; i <= segments; i++) { - float angle = (float) (i * Math.PI * 2.0 / segments); - float x = (float) Math.cos(angle); - float z = (float) Math.sin(angle); - buffer.vertex(x, 0, z).endVertex(); - buffer.vertex(0, 1.5f, 0).endVertex(); - } - tesselator.end(); - } - -} - diff --git a/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoRenderDebugState.java b/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoRenderDebugState.java new file mode 100644 index 00000000..ac92109e --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoRenderDebugState.java @@ -0,0 +1,125 @@ +package net.Gabou.projectatmosphere.client.render; + +import net.Gabou.projectatmosphere.ProjectAtmosphere; + +import java.util.Arrays; +import java.util.Locale; +import java.util.stream.Collectors; + +public final class TornadoRenderDebugState { + public enum Mode { + OFF("off", 0), + BOX("box", 1), + HIT("hit", 2), + FILL("fill", 3), + FUNNEL("funnel", 4), + HEIGHT("height", 5), + RADIAL("radial", 6), + RADIUS("radius", 7), + DENSITY("density", 8), + ALPHA("alpha", 9), + WALLCLOUD("wallcloud", 10), + CONNECTION("connection", 11), + GROUND_SKIRT("groundskirt", 12), + FULL("full", 13), + DEPTH("depth", 14), + DEPTH_NO_FRAMEBUFFER("depth_nofb", 15), + DEPTH_MAIN_FRAMEBUFFER("depth_mainfb", 14), + OCCLUSION("occlusion", 16), + COVERAGE("coverage", 17), + LATE("late", 0); + + private final String token; + private final int shaderValue; + + Mode(String token, int shaderValue) { + this.token = token; + this.shaderValue = shaderValue; + } + + public String token() { + return this.token; + } + + public int shaderValue() { + return this.shaderValue; + } + + public static Mode fromToken(String token) { + if (token == null) { + return OFF; + } + String normalized = token.trim().toLowerCase(Locale.ROOT); + if (normalized.equals("aabb")) { + return BOX; + } + for (Mode mode : values()) { + if (mode.token.equals(normalized)) { + return mode; + } + } + return OFF; + } + } + + private static Mode mode = Mode.OFF; + private static boolean freeze; + private static int requestedStormIndex = -1; + private static boolean diagnosticReportRequested; + + private TornadoRenderDebugState() { + } + + public static synchronized Mode getMode() { + return mode; + } + + public static synchronized void setMode(Mode newMode) { + mode = newMode == null ? Mode.OFF : newMode; + } + + public static synchronized boolean isFreezeEnabled() { + return freeze; + } + + public static synchronized void setFreezeEnabled(boolean enabled) { + freeze = enabled; + } + + public static synchronized int getRequestedStormIndex() { + return requestedStormIndex; + } + + public static synchronized void setRequestedStormIndex(int index) { + requestedStormIndex = Math.max(-1, index); + } + + public static synchronized boolean isActive() { + return ProjectAtmosphere.DEBUG_MODE && mode != Mode.OFF; + } + + public static synchronized boolean isCommandAvailable() { + return ProjectAtmosphere.DEBUG_MODE; + } + + public static synchronized void requestDiagnosticReport() { + diagnosticReportRequested = true; + } + + public static synchronized boolean consumeDiagnosticReportRequest() { + boolean requested = diagnosticReportRequested; + diagnosticReportRequested = false; + return requested; + } + + public static String supportedModes() { + return Arrays.stream(Mode.values()) + .map(Mode::token) + .collect(Collectors.joining(", ")); + } + + public static synchronized String describe() { + String storm = requestedStormIndex < 0 ? "auto" : Integer.toString(requestedStormIndex); + return "mode=" + mode.token() + ", freeze=" + freeze + ", storm=" + storm; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoShaders.java b/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoShaders.java new file mode 100644 index 00000000..a445d451 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/render/TornadoShaders.java @@ -0,0 +1,47 @@ +package net.Gabou.projectatmosphere.client.render; + +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RegisterShadersEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import java.io.IOException; + +@Mod.EventBusSubscriber(modid = ProjectAtmosphere.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) +public final class TornadoShaders { + public static final ResourceLocation TORNADO_TEXTURE = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "textures/effects/tornado.png"); + public static final ResourceLocation BASE_TEXTURE = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "textures/effects/base.png"); + public static final ResourceLocation NOISE_TEXTURE = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "textures/effects/noise.png"); + public static final ResourceLocation FLOW_TEXTURE = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "textures/effects/flowmap.png"); + + private static final ResourceLocation SHADER_ID = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "tornado_round"); + private static final ResourceLocation COMPOSITE_SHADER_ID = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "tornado_composite"); + + private static ShaderInstance shader; + private static ShaderInstance compositeShader; + + private TornadoShaders() { + } + + @SubscribeEvent + public static void onRegisterShaders(RegisterShadersEvent event) throws IOException { + event.registerShader(new ShaderInstance(event.getResourceProvider(), SHADER_ID, DefaultVertexFormat.POSITION_TEX), loaded -> shader = loaded); + event.registerShader(new ShaderInstance(event.getResourceProvider(), COMPOSITE_SHADER_ID, DefaultVertexFormat.POSITION_TEX), loaded -> compositeShader = loaded); + } + + public static ShaderInstance getShader() { + return shader; + } + + public static ShaderInstance getCompositeShader() { + return compositeShader; + } + + public static boolean isReady() { + return shader != null; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/render/VolumeBoxMesh.java b/src/main/java/net/Gabou/projectatmosphere/client/render/VolumeBoxMesh.java new file mode 100644 index 00000000..ee06bed2 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/render/VolumeBoxMesh.java @@ -0,0 +1,82 @@ +package net.Gabou.projectatmosphere.client.render; + +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexBuffer; +import com.mojang.blaze3d.vertex.VertexFormat; +import net.minecraft.client.renderer.ShaderInstance; +import org.joml.Matrix4f; + +public final class VolumeBoxMesh implements AutoCloseable { + private VertexBuffer buffer; + + public void ensureInitialized() { + if (this.buffer != null) { + return; + } + + BufferBuilder builder = Tesselator.getInstance().getBuilder(); + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); + + // Front (+Z) + vertex(builder, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F); + vertex(builder, 1.0F, 0.0F, 1.0F, 1.0F, 0.0F); + vertex(builder, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F); + vertex(builder, 0.0F, 1.0F, 1.0F, 0.0F, 1.0F); + + // Back (-Z) + vertex(builder, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F); + vertex(builder, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F); + vertex(builder, 0.0F, 1.0F, 0.0F, 1.0F, 1.0F); + vertex(builder, 1.0F, 1.0F, 0.0F, 0.0F, 1.0F); + + // Left (-X) + vertex(builder, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F); + vertex(builder, 0.0F, 0.0F, 1.0F, 1.0F, 0.0F); + vertex(builder, 0.0F, 1.0F, 1.0F, 1.0F, 1.0F); + vertex(builder, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F); + + // Right (+X) + vertex(builder, 1.0F, 0.0F, 1.0F, 0.0F, 0.0F); + vertex(builder, 1.0F, 0.0F, 0.0F, 1.0F, 0.0F); + vertex(builder, 1.0F, 1.0F, 0.0F, 1.0F, 1.0F); + vertex(builder, 1.0F, 1.0F, 1.0F, 0.0F, 1.0F); + + // Top (+Y) + vertex(builder, 0.0F, 1.0F, 1.0F, 0.0F, 0.0F); + vertex(builder, 1.0F, 1.0F, 1.0F, 1.0F, 0.0F); + vertex(builder, 1.0F, 1.0F, 0.0F, 1.0F, 1.0F); + vertex(builder, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F); + + // Bottom (-Y) + vertex(builder, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F); + vertex(builder, 1.0F, 0.0F, 0.0F, 1.0F, 0.0F); + vertex(builder, 1.0F, 0.0F, 1.0F, 1.0F, 1.0F); + vertex(builder, 0.0F, 0.0F, 1.0F, 0.0F, 1.0F); + + this.buffer = new VertexBuffer(VertexBuffer.Usage.STATIC); + this.buffer.bind(); + this.buffer.upload(builder.end()); + VertexBuffer.unbind(); + } + + public void draw(ShaderInstance shader, Matrix4f modelViewMat, Matrix4f projMat) { + this.ensureInitialized(); + this.buffer.bind(); + this.buffer.drawWithShader(modelViewMat, projMat, shader); + VertexBuffer.unbind(); + } + + @Override + public void close() { + if (this.buffer != null) { + this.buffer.close(); + this.buffer = null; + } + } + + private static void vertex(BufferBuilder builder, float x, float y, float z, float u, float v) { + builder.vertex(x, y, z).uv(u, v).endVertex(); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/screen/ProjectAtmosphereCrashScreen.java b/src/main/java/net/Gabou/projectatmosphere/client/screen/ProjectAtmosphereCrashScreen.java new file mode 100644 index 00000000..f5ae92c2 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/client/screen/ProjectAtmosphereCrashScreen.java @@ -0,0 +1,103 @@ +package net.Gabou.projectatmosphere.client.screen; + +import net.Gabou.projectatmosphere.client.crash.ProjectAtmosphereCrashHandler; +import net.minecraft.Util; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.util.FormattedCharSequence; + +import java.util.List; + +public class ProjectAtmosphereCrashScreen extends Screen { + private static final Component TITLE = Component.literal("Project Atmosphere Crash Detected"); + private static final Component MESSAGE = Component.literal("It looks like Project Atmosphere crashed. Please join the Discord server to submit your issue."); + + private final ProjectAtmosphereCrashHandler.CrashContext crashContext; + private Component statusMessage = Component.empty(); + + public ProjectAtmosphereCrashScreen(ProjectAtmosphereCrashHandler.CrashContext crashContext) { + super(TITLE); + this.crashContext = crashContext; + } + + @Override + protected void init() { + int buttonWidth = Math.min(240, this.width - 40); + int left = (this.width - buttonWidth) / 2; + int baseY = this.height - 86; + + this.addRenderableWidget(Button.builder(Component.literal("Copy Support Summary"), button -> { + if (this.minecraft != null) { + this.minecraft.keyboardHandler.setClipboard(this.crashContext.supportSummary()); + this.statusMessage = Component.literal("Support summary copied to clipboard."); + } + }).bounds(left, baseY, buttonWidth, 20).build()); + + this.addRenderableWidget(Button.builder(Component.literal("Open Discord"), button -> { + Util.getPlatform().openUri(ProjectAtmosphereCrashHandler.getDiscordInviteUrl()); + this.statusMessage = Component.literal("Opened the Discord invite in your browser."); + }).bounds(left, baseY + 24, buttonWidth, 20).build()); + + this.addRenderableWidget(Button.builder(Component.literal("Close Game"), button -> { + if (this.minecraft != null) { + this.minecraft.stop(); + } + }).bounds(left, baseY + 48, buttonWidth, 20).build()); + } + + @Override + public boolean isPauseScreen() { + return true; + } + + @Override + public boolean shouldCloseOnEsc() { + return false; + } + + @Override + public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + this.renderBackground(guiGraphics); + guiGraphics.fillGradient(0, 0, this.width, this.height, 0xFF200D12, 0xFF070A10); + + int contentWidth = Math.min(420, this.width - 48); + int contentLeft = (this.width - contentWidth) / 2; + int panelTop = 24; + int panelBottom = this.height - 16; + guiGraphics.fill(contentLeft - 12, panelTop - 12, contentLeft + contentWidth + 12, panelBottom, 0xB0141C24); + guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, panelTop, 0xFFF4F4F4); + + int y = panelTop + 24; + y = drawWrapped(guiGraphics, MESSAGE, contentLeft, y, contentWidth, 0xFFE9D7D7); + y += 10; + y = drawField(guiGraphics, "Crash", this.crashContext.crashTitle(), contentLeft, y, contentWidth); + y = drawField(guiGraphics, "Exception", this.crashContext.exceptionSummary(), contentLeft, y, contentWidth); + y = drawField(guiGraphics, "PA Frame", this.crashContext.projectAtmosphereFrame(), contentLeft, y, contentWidth); + drawField(guiGraphics, "Report", this.crashContext.savedReportPath(), contentLeft, y, contentWidth); + + if (!this.statusMessage.getString().isBlank()) { + guiGraphics.drawCenteredString(this.font, this.statusMessage, this.width / 2, this.height - 104, 0xFFACF59B); + } + + super.render(guiGraphics, mouseX, mouseY, partialTick); + } + + private int drawField(GuiGraphics guiGraphics, String label, String value, int left, int top, int width) { + guiGraphics.drawString(this.font, label + ":", left, top, 0xFFFFC5C5, false); + return drawWrapped(guiGraphics, Component.literal(value), left, top + 12, width, 0xFFF4F4F4) + 8; + } + + private int drawWrapped(GuiGraphics guiGraphics, Component text, int left, int top, int width, int color) { + List lines = this.font.split(text, width); + int y = top; + + for (FormattedCharSequence line : lines) { + guiGraphics.drawString(this.font, line, left, y, color, false); + y += 10; + } + + return y; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/client/screen/WeatherRadarScreen.java b/src/main/java/net/Gabou/projectatmosphere/client/screen/WeatherRadarScreen.java index d0c4103a..cc614106 100644 --- a/src/main/java/net/Gabou/projectatmosphere/client/screen/WeatherRadarScreen.java +++ b/src/main/java/net/Gabou/projectatmosphere/client/screen/WeatherRadarScreen.java @@ -2,8 +2,8 @@ import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import net.Gabou.projectatmosphere.client.hurricane.ClientHurricaneStateCache; import net.Gabou.projectatmosphere.modules.core.CloudLibrary; -import net.Gabou.projectatmosphere.modules.hurricane.HurricaneInstance; import net.Gabou.projectatmosphere.modules.hurricane.HurricaneManager; import net.Gabou.projectatmosphere.modules.tornado.TornadoInstance; import net.Gabou.projectatmosphere.modules.tornado.TornadoManager; @@ -97,7 +97,7 @@ public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partia } // Overlay: Tornadoes (purple) and Hurricanes (black) - for (TornadoInstance t : TornadoManager.getActiveTornadoes()) { + for (TornadoInstance t : TornadoManager.getClientTornadoes()) { float dx = (float) ((t.position.x - player.getX()) / scale); float dz = (float) ((t.position.z - player.getZ()) / scale); int r = Math.max(2, Math.round(t.radius / scale)); @@ -107,10 +107,10 @@ public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partia fillEllipse(guiGraphics, x, y, Math.max(2, r), Math.max(2, (int) (r * 0.7f)), color); } - for (HurricaneInstance h : HurricaneManager.getActiveHurricanes()) { - float dx = (float) ((h.position.x - player.getX()) / scale); - float dz = (float) ((h.position.z - player.getZ()) / scale); - int r = Math.max(3, Math.round(h.radius / scale)); + for (var h : ClientHurricaneStateCache.getSemanticSnapshots(partialTick)) { + float dx = (float) ((h.centerX() - player.getX()) / scale); + float dz = (float) ((h.centerZ() - player.getZ()) / scale); + int r = Math.max(3, Math.round(h.coreRadius() / scale)); int x = left + MAP_SIZE / 2 + Math.round(dx); int y = top + MAP_SIZE / 2 + Math.round(dz); int color = (0xC0 << 24) | 0x000000; // semi-opaque black diff --git a/src/main/java/net/Gabou/projectatmosphere/command/DebugAtmoCommand.java b/src/main/java/net/Gabou/projectatmosphere/command/DebugAtmoCommand.java index 50feba80..14386c65 100644 --- a/src/main/java/net/Gabou/projectatmosphere/command/DebugAtmoCommand.java +++ b/src/main/java/net/Gabou/projectatmosphere/command/DebugAtmoCommand.java @@ -8,11 +8,15 @@ import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; import dev.nonamecrackers2.simpleclouds.common.cloud.spawning.CloudGenerator; import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.Gabou.projectatmosphere.api.AtmoApi; import net.Gabou.projectatmosphere.compat.SimpleCloudsCompat; +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; import net.Gabou.projectatmosphere.manager.SimpleCloudSpawner; import net.Gabou.projectatmosphere.modules.core.CloudLibrary; import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.Gabou.projectatmosphere.client.fog.FogBiomeClassifier; +import net.Gabou.projectatmosphere.modules.fog.FogHeuristics; import net.Gabou.projectatmosphere.modules.snowstorm.SnowstormManager; import net.Gabou.projectatmosphere.modules.temperature.command.TemperatureCommandHelper; import net.Gabou.projectatmosphere.util.AtmosphereUtils; @@ -92,6 +96,12 @@ public static void appendTo(LiteralArgumentBuilder root) { }) ) ); + root.then(Commands.literal("fog") + .executes(DebugAtmoCommand::sendFogDebug) + .then(Commands.argument("pos", BlockPosArgument.blockPos()) + .executes(DebugAtmoCommand::sendFogDebug) + ) + ); root.then(Commands.literal("cpu") .executes(ctx -> { @@ -204,6 +214,50 @@ private static CloudRegion spawnCloud(ServerLevel level, BlockPos pos, String cl return SimpleCloudsCompat.spawnCloudInBiome(cloudId, key, level, null, wind); } + private static int sendFogDebug(CommandContext ctx) { + ServerLevel level = ctx.getSource().getLevel(); + if (!TemperatureCommandHelper.isInOverworld(level)) { + ctx.getSource().sendFailure(Component.literal("Fog debug is only available in the Overworld.")); + return 0; + } + + BlockPos pos; + try { + pos = BlockPosArgument.getBlockPos(ctx, "pos"); + } catch (IllegalArgumentException e) { + pos = BlockPos.containing(ctx.getSource().getPosition()); + } + final BlockPos samplePos = pos; + + long tick = level.getGameTime(); + float humidity = ForecastOrchestrator.getCurrentHumidity(level, samplePos, tick); + float rainIntensity = AtmoApi.getInstance().getWeatherSnapshot(level, samplePos, tick).rainIntensity(); + float wetBiomeFactor = FogBiomeClassifier.computeWetBiomeFactor(level, samplePos); + FogHeuristics.FogProfile fog = FogHeuristics.sample(humidity, wetBiomeFactor, rainIntensity); + ResourceLocation biome = level.registryAccess() + .registryOrThrow(Registries.BIOME) + .getKey(level.getBiome(samplePos).value()); + + ctx.getSource().sendSuccess(() -> Component.literal( + "Fog Debug: " + samplePos.getX() + ", " + samplePos.getY() + ", " + samplePos.getZ() + + "\n Enabled: " + AtmoCommonConfig.FOG_ENABLED.get() + + "\n Biome: " + biome + + "\n Humidity: " + UnitFormatter.formatHumidity(humidity) + + "\n Rain intensity: " + String.format("%.2f", rainIntensity) + + "\n Wet biome factor: " + String.format("%.2f", wetBiomeFactor) + + "\n Humidity factor: " + String.format("%.2f", fog.humidityFactor()) + + "\n Fog strength: " + String.format("%.2f", fog.strength()) + + "\n Thresholds: start=" + String.format("%.0f", AtmoCommonConfig.FOG_HUMIDITY_START_PERCENT.get()) + + "% full=" + String.format("%.0f", AtmoCommonConfig.FOG_HUMIDITY_FULL_PERCENT.get()) + "%" + + "\n Tunables: wetBiome=" + String.format("%.2f", AtmoCommonConfig.FOG_WET_BIOME_BASE_STRENGTH.get()) + + " rainBoost=" + String.format("%.2f", AtmoCommonConfig.FOG_RAIN_BOOST.get()) + + " near=" + String.format("%.1f", AtmoCommonConfig.FOG_NEAR_DISTANCE.get()) + + " far=" + String.format("%.1f", AtmoCommonConfig.FOG_FAR_DISTANCE.get()) + + " colorBlend=" + String.format("%.2f", AtmoCommonConfig.FOG_COLOR_BLEND.get()) + ), false); + return 1; + } + private static int spawnRain(CommandContext ctx) { ServerLevel level = ctx.getSource().getLevel(); if (!TemperatureCommandHelper.isInOverworld(level)) { diff --git a/src/main/java/net/Gabou/projectatmosphere/command/ProjectAtmosphereCommands.java b/src/main/java/net/Gabou/projectatmosphere/command/ProjectAtmosphereCommands.java index 0c396274..e0640085 100644 --- a/src/main/java/net/Gabou/projectatmosphere/command/ProjectAtmosphereCommands.java +++ b/src/main/java/net/Gabou/projectatmosphere/command/ProjectAtmosphereCommands.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.Gabou.projectatmosphere.modules.fog.FogCommand; import net.Gabou.projectatmosphere.modules.humidity.HumidityCommand; import net.Gabou.projectatmosphere.modules.pressure.PressureCommand; import net.Gabou.projectatmosphere.modules.temperature.command.TemperatureCommands; @@ -24,6 +25,7 @@ public static void register(CommandDispatcher dispatcher) { root.then(PressureCommand.build()); root.then(WindCommand.build()); root.then(SpawnCloudCommand.build()); + root.then(FogCommand.build()); LiteralArgumentBuilder weatherDebug = Commands.literal("weatherdebug"); DebugAtmoCommand.appendTo(weatherDebug); diff --git a/src/main/java/net/Gabou/projectatmosphere/command/TelemetryDebugClientCommand.java b/src/main/java/net/Gabou/projectatmosphere/command/TelemetryDebugClientCommand.java index fea09d4f..695f37ba 100644 --- a/src/main/java/net/Gabou/projectatmosphere/command/TelemetryDebugClientCommand.java +++ b/src/main/java/net/Gabou/projectatmosphere/command/TelemetryDebugClientCommand.java @@ -22,6 +22,7 @@ public static void onRegisterClientCommand(RegisterClientCommandsEvent event) { dispatcher.register( LiteralArgumentBuilder.literal("pa") .then(Commands.literal("debug") + .then(TornadoRenderDebugClientCommand.build()) .then(Commands.literal("export") .executes(ctx -> { if (!AtmoCommonConfig.TELEMETRY_ENABLED.get()) { diff --git a/src/main/java/net/Gabou/projectatmosphere/command/TornadoRenderDebugClientCommand.java b/src/main/java/net/Gabou/projectatmosphere/command/TornadoRenderDebugClientCommand.java new file mode 100644 index 00000000..31a51266 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/command/TornadoRenderDebugClientCommand.java @@ -0,0 +1,85 @@ +package net.Gabou.projectatmosphere.command; + +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.Gabou.projectatmosphere.client.render.TornadoRenderDebugState; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; + +public final class TornadoRenderDebugClientCommand { + private TornadoRenderDebugClientCommand() { + } + + public static LiteralArgumentBuilder build() { + return Commands.literal("tornado") + .requires(source -> TornadoRenderDebugState.isCommandAvailable()) + .then(Commands.literal("render") + .executes(ctx -> sendStatus(ctx.getSource())) + .then(Commands.literal("status") + .executes(ctx -> sendStatus(ctx.getSource()))) + .then(Commands.literal("inspect") + .executes(ctx -> requestInspect(ctx.getSource()))) + .then(Commands.literal("mode") + .then(Commands.argument("value", StringArgumentType.word()) + .suggests((context, builder) -> + SharedSuggestionProvider.suggest(TornadoRenderDebugState.supportedModes().split(", "), builder)) + .executes(ctx -> setMode( + ctx.getSource(), + StringArgumentType.getString(ctx, "value") + )))) + .then(Commands.literal("freeze") + .then(Commands.argument("enabled", BoolArgumentType.bool()) + .executes(ctx -> setFreeze( + ctx.getSource(), + BoolArgumentType.getBool(ctx, "enabled") + )))) + .then(Commands.literal("storm") + .then(Commands.literal("auto") + .executes(ctx -> setStormIndex(ctx.getSource(), -1))) + .then(Commands.argument("index", IntegerArgumentType.integer(0)) + .executes(ctx -> setStormIndex( + ctx.getSource(), + IntegerArgumentType.getInteger(ctx, "index") + ))))); + } + + private static int sendStatus(CommandSourceStack source) { + source.sendSuccess(() -> Component.literal("Tornado render debug: " + TornadoRenderDebugState.describe()), false); + return 1; + } + + private static int setMode(CommandSourceStack source, String token) { + TornadoRenderDebugState.Mode mode = TornadoRenderDebugState.Mode.fromToken(token); + TornadoRenderDebugState.setMode(mode); + source.sendSuccess(() -> Component.literal( + "Tornado render debug mode set to '" + mode.token() + "'. Supported modes: " + TornadoRenderDebugState.supportedModes() + ), false); + return 1; + } + + private static int setFreeze(CommandSourceStack source, boolean enabled) { + TornadoRenderDebugState.setFreezeEnabled(enabled); + source.sendSuccess(() -> Component.literal("Tornado render debug freeze set to " + enabled + "."), false); + return 1; + } + + private static int setStormIndex(CommandSourceStack source, int stormIndex) { + TornadoRenderDebugState.setRequestedStormIndex(stormIndex); + source.sendSuccess(() -> Component.literal( + stormIndex < 0 + ? "Tornado render debug storm selection set to auto." + : "Tornado render debug storm index set to " + stormIndex + "." + ), false); + return 1; + } + + private static int requestInspect(CommandSourceStack source) { + TornadoRenderDebugState.requestDiagnosticReport(); + source.sendSuccess(() -> Component.literal("Requested tornado render diagnostic report. Check the log output."), false); + return 1; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/compat/CompatHandler.java b/src/main/java/net/Gabou/projectatmosphere/compat/CompatHandler.java index bf276cfe..a16769d1 100644 --- a/src/main/java/net/Gabou/projectatmosphere/compat/CompatHandler.java +++ b/src/main/java/net/Gabou/projectatmosphere/compat/CompatHandler.java @@ -51,9 +51,11 @@ public static TemperatureMod getActiveTemperatureMod() { public static boolean isLegendarySurvivalLoaded() { return getActiveTemperatureMod() == TemperatureMod.LEGENDARY_SURVIVAL; } + public static boolean isToughAsNailsLoaded() { return getActiveTemperatureMod() == TemperatureMod.TOUGH_AS_NAILS; } + public static boolean isColdSweatLoaded() { return getActiveTemperatureMod() == TemperatureMod.COLD_SWEAT; } @@ -64,8 +66,9 @@ public static boolean isATemperatureModLoaded() { public static void init() { TemperatureMod mod = getActiveTemperatureMod(); - if(!ProjectAtmosphere.DEBUG_MODE) + if (!ProjectAtmosphere.DEBUG_MODE) { return; + } switch (mod) { case LEGENDARY_SURVIVAL -> LOGGER.info("Legendary Survival Overhaul loaded"); @@ -77,16 +80,16 @@ public static void init() { ? "Sand Storms mod loaded, enabling compatibility." : "Sand Storms mod not found."); LOGGER.info(isAurorasLoaded() - ? "Auroras detected – enabling seasonal aurora tuning." + ? "Auroras detected - enabling Project Atmosphere aurora bridge." : "Auroras mod not detected."); LOGGER.info(isRainbowsLoaded() - ? "Rainbows detected – enabling precipitation bridge." + ? "Rainbows detected - enabling Project Atmosphere rainbow bridge." : "Rainbows mod not detected."); LOGGER.info(isTectonicLoaded() - ? "Tectonic detected – enabling refined ocean geometry." + ? "Tectonic detected - enabling refined ocean geometry." : "Tectonic mod not detected."); LOGGER.info(isContinentsLoaded() - ? "Continents detected – enabling refined shoreline geometry." + ? "Continents detected - enabling refined shoreline geometry." : "Continents mod not detected."); LOGGER.info(isDynamicTreesLoaded() ? "Dynamic Trees detected - enabling seasonal tree integration." diff --git a/src/main/java/net/Gabou/projectatmosphere/compat/SimpleCloudsTornadoSupport.java b/src/main/java/net/Gabou/projectatmosphere/compat/SimpleCloudsTornadoSupport.java deleted file mode 100644 index cd93c91e..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/compat/SimpleCloudsTornadoSupport.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.Gabou.projectatmosphere.compat; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Tracks whether the SimpleClouds shader pack exposes the CloudTornadoes SSBO. - * Updated on the client when the compute shader is inspected so server logic can - * decide whether to fall back to the legacy mesh-based tornado. - */ -public final class SimpleCloudsTornadoSupport { - private static final AtomicBoolean TORNADO_SSBO_SUPPORTED = new AtomicBoolean(true); - - private SimpleCloudsTornadoSupport() { - } - - public static boolean isTornadoSsboSupported() { - return TORNADO_SSBO_SUPPORTED.get(); - } - - public static void setTornadoSsboSupported(boolean supported) { - TORNADO_SSBO_SUPPORTED.set(supported); - } -} diff --git a/src/main/java/net/Gabou/projectatmosphere/compat/auroras/AuroraCompatController.java b/src/main/java/net/Gabou/projectatmosphere/compat/auroras/AuroraCompatController.java new file mode 100644 index 00000000..b4161d01 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/compat/auroras/AuroraCompatController.java @@ -0,0 +1,27 @@ +package net.Gabou.projectatmosphere.compat.auroras; + +import net.Gabou.projectatmosphere.compat.sky.AtmosphereSkyEffectController; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +@OnlyIn(Dist.CLIENT) +public final class AuroraCompatController { + private AuroraCompatController() { + } + + public static void setEnabled(boolean value) { + AtmosphereSkyEffectController.setAuroraEnabled(value); + } + + public static float scaleBrightness(float vanillaBrightness) { + return AtmosphereSkyEffectController.scaleAuroraBrightness(vanillaBrightness); + } + + public static float overrideRainLevel(float vanillaRainLevel) { + return AtmosphereSkyEffectController.getRainLevelOverride(vanillaRainLevel); + } + + public static boolean isActive() { + return AtmosphereSkyEffectController.isAuroraActive(); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/compat/rainbows/RainbowRainBridge.java b/src/main/java/net/Gabou/projectatmosphere/compat/rainbows/RainbowRainBridge.java deleted file mode 100644 index 48999c12..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/compat/rainbows/RainbowRainBridge.java +++ /dev/null @@ -1,73 +0,0 @@ -package net.Gabou.projectatmosphere.compat.rainbows; - -import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; -import dev.nonamecrackers2.simpleclouds.common.cloud.spawning.CloudGenerator; -import net.Gabou.projectatmosphere.modules.core.CloudLibrary; -import net.Gabou.projectatmosphere.network.NetworkHandler; -import net.Gabou.projectatmosphere.network.RainfallUpdatePacket; -import net.Gabou.projectatmosphere.util.WeatherType; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.util.Mth; -import net.minecraft.world.level.Level; -import net.minecraftforge.network.PacketDistributor; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public final class RainbowRainBridge { - - private static final Map, Float> LAST_LEVELS = new HashMap<>(); - private static final float EPSILON = 0.02f; - - private RainbowRainBridge() { - } - - public static void sync(ServerLevel level, CloudGenerator generator) { - float rainLevel = computeRainLevel(generator); - ResourceKey dimension = level.dimension(); - Float previous = LAST_LEVELS.get(dimension); - if (previous != null && Math.abs(previous - rainLevel) <= EPSILON) { - return; - } - LAST_LEVELS.put(dimension, rainLevel); - RainfallUpdatePacket packet = new RainfallUpdatePacket(dimension.location(), rainLevel); - NetworkHandler.CHANNEL.send(PacketDistributor.DIMENSION.with(() -> dimension), packet); - } - - public static void clear(ResourceKey dimension) { - LAST_LEVELS.remove(dimension); - } - - public static void sendSnapshot(ServerPlayer player, ServerLevel level, CloudGenerator generator) { - ResourceKey dimension = level.dimension(); - float rainLevel = LAST_LEVELS.getOrDefault(dimension, computeRainLevel(generator)); - LAST_LEVELS.put(dimension, rainLevel); - RainfallUpdatePacket packet = new RainfallUpdatePacket(dimension.location(), rainLevel); - NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), packet); - } - - private static float computeRainLevel(CloudGenerator generator) { - List clouds = generator.getClouds(); - if (clouds.isEmpty()) { - return 0.0f; - } - float total = 0.0f; - int rainyCount = 0; - for (CloudRegion region : clouds) { - ResourceLocation type = region.getCloudTypeId(); - if (WeatherType.isRainy(type)) { - rainyCount++; - int severity = CloudLibrary.getSeverityFromRessourceLocation(type); - total += Math.max(1, severity) / 7.0f; - } - } - if (rainyCount == 0) { - return 0.0f; - } - return Mth.clamp(total / rainyCount, 0.0f, 1.0f); - } -} diff --git a/src/main/java/net/Gabou/projectatmosphere/compat/rainbows/RainbowWeatherTracker.java b/src/main/java/net/Gabou/projectatmosphere/compat/rainbows/RainbowWeatherTracker.java index c7be1a2f..7d1e4d55 100644 --- a/src/main/java/net/Gabou/projectatmosphere/compat/rainbows/RainbowWeatherTracker.java +++ b/src/main/java/net/Gabou/projectatmosphere/compat/rainbows/RainbowWeatherTracker.java @@ -1,141 +1,39 @@ package net.Gabou.projectatmosphere.compat.rainbows; -import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.core.BlockPos; -import net.minecraft.resources.ResourceKey; -import net.minecraft.util.Mth; -import net.minecraft.world.level.Level; +import net.Gabou.projectatmosphere.compat.sky.AtmosphereSkyEffectController; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; -import java.util.HashMap; -import java.util.Map; - -/** - * Mirrors Serene Seasons Plus' rain state helper on the client so Rainbows can react to - * Project Atmosphere precipitation (which bypasses vanilla rain). - */ @OnlyIn(Dist.CLIENT) public final class RainbowWeatherTracker { - - private static final Map, TrackerState> STATES = new HashMap<>(); - private static boolean enabled; - private RainbowWeatherTracker() { } public static void setEnabled(boolean value) { - enabled = value; - if (!value) { - STATES.clear(); - } + AtmosphereSkyEffectController.setRainbowEnabled(value); } public static boolean isEnabled() { - return enabled; + return AtmosphereSkyEffectController.isRainbowEnabled(); } - public static void tick(Minecraft minecraft) { - if (!enabled) { - return; - } - if (minecraft.level == null || minecraft.player == null) { - if (minecraft.level == null) { - STATES.clear(); - } - return; - } - ClientLevel level = minecraft.level; - ResourceKey dimension = level.dimension(); - TrackerState state = STATES.computeIfAbsent(dimension, key -> new TrackerState()); - if (!state.isServerAuthoritative()) { - boolean raining = isRainingAt(level, minecraft.player.blockPosition()); - state.applyFallback(raining); - } - state.step(); + public static float getRainLevelOverride(float vanillaRainLevel) { + return AtmosphereSkyEffectController.getRainLevelOverride(vanillaRainLevel); } - private static boolean isRainingAt(ClientLevel level, BlockPos pos) { - try { - return CloudManager.get(level).isRainingAt(pos); - } catch (Exception ignored) { - return false; - } + public static double scaleBrightness(double vanillaBrightness) { + return AtmosphereSkyEffectController.scaleRainbowBrightness(vanillaBrightness); } - public static float getRainLevel(ResourceKey dimension) { - TrackerState state = STATES.get(dimension); - return state != null ? state.rainVisual : 0.0f; + public static boolean shouldRender() { + return AtmosphereSkyEffectController.shouldRenderRainbow(); } - public static boolean consumeRainStop(ResourceKey dimension) { - TrackerState state = STATES.get(dimension); - return state != null && state.consumeStopFlag(); + public static boolean consumeActivationPulse() { + return AtmosphereSkyEffectController.consumeRainbowActivationPulse(); } - public static void applyServerUpdate(ResourceKey dimension, float rainLevel) { - if (!enabled) { - return; - } - TrackerState state = STATES.computeIfAbsent(dimension, key -> new TrackerState()); - state.setServerTarget(rainLevel); - } - - private static final class TrackerState { - private static final float STEP = 0.05f; - - private float target; - private float rainVisual; - private boolean stopFlag; - private boolean serverAuthoritative; - private boolean wasTargetRaining; - - void setServerTarget(float value) { - serverAuthoritative = true; - applyTarget(value); - } - - void applyFallback(boolean raining) { - if (serverAuthoritative) { - return; - } - applyTarget(raining ? 1.0f : 0.0f); - } - - private void applyTarget(float value) { - float clamped = Mth.clamp(value, 0.0f, 1.0f); - if (Math.abs(clamped - target) <= 0.0005f) { - return; - } - boolean newRaining = clamped > 0.01f; - if (wasTargetRaining && !newRaining) { - stopFlag = true; - } - wasTargetRaining = newRaining; - target = clamped; - } - - void step() { - float delta = target - rainVisual; - if (Math.abs(delta) > STEP) { - rainVisual += Math.copySign(STEP, delta); - } else { - rainVisual = target; - } - } - - boolean consumeStopFlag() { - if (stopFlag) { - stopFlag = false; - return true; - } - return false; - } - - boolean isServerAuthoritative() { - return serverAuthoritative; - } + public static float getVisualStrength() { + return AtmosphereSkyEffectController.getRainbowStrength(); } } diff --git a/src/main/java/net/Gabou/projectatmosphere/compat/sky/AtmosphereSkyEffectController.java b/src/main/java/net/Gabou/projectatmosphere/compat/sky/AtmosphereSkyEffectController.java new file mode 100644 index 00000000..8f82d09e --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/compat/sky/AtmosphereSkyEffectController.java @@ -0,0 +1,193 @@ +package net.Gabou.projectatmosphere.compat.sky; + +import net.Gabou.projectatmosphere.compat.auroras.AuroraSeasonHelper; +import net.Gabou.projectatmosphere.client.atmosphere.AtmosphereClientState; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceKey; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +@OnlyIn(Dist.CLIENT) +public final class AtmosphereSkyEffectController { + private static boolean rainbowEnabled; + private static boolean auroraEnabled; + private static ResourceKey lastDimension; + + private static AtmosphereSkySample lastSample = AtmosphereSkySample.NONE; + + private static float rainbowStrength; + private static boolean rainbowActive; + private static boolean rainbowActivationPulse; + + private static float auroraStrength; + private static boolean auroraActive; + + private AtmosphereSkyEffectController() { + } + + public static void setRainbowEnabled(boolean enabled) { + rainbowEnabled = enabled; + if (!enabled) { + rainbowStrength = 0.0F; + rainbowActive = false; + rainbowActivationPulse = false; + } + } + + public static void setAuroraEnabled(boolean enabled) { + auroraEnabled = enabled; + if (!enabled) { + auroraStrength = 0.0F; + auroraActive = false; + } + } + + public static boolean isRainbowEnabled() { + return rainbowEnabled; + } + + public static boolean isAuroraEnabled() { + return auroraEnabled; + } + + public static void tick(Minecraft minecraft) { + if ((!rainbowEnabled && !auroraEnabled) || minecraft.level == null || minecraft.player == null) { + clear(); + return; + } + + ResourceKey dimension = minecraft.level.dimension(); + if (lastDimension != null && !lastDimension.equals(dimension)) { + clearVisualState(); + } + lastDimension = dimension; + + lastSample = AtmosphereSkySampler.sample(minecraft, 0.0F); + if (lastSample == AtmosphereSkySample.NONE) { + rainbowActivationPulse = false; + rainbowStrength = Mth.lerp(0.08F, rainbowStrength, 0.0F); + auroraStrength = Mth.lerp(0.08F, auroraStrength, 0.0F); + rainbowActive = rainbowEnabled && rainbowStrength >= 0.07F; + auroraActive = auroraEnabled && auroraStrength >= 0.03F; + return; + } + + updateRainbowState(lastSample); + updateAuroraState(minecraft, lastSample); + } + + public static float getRainLevelOverride(float vanillaRainLevel) { + if (!rainbowEnabled && !auroraEnabled) { + return vanillaRainLevel; + } + return AtmosphereClientState.getRainIntensity(); + } + + public static double scaleRainbowBrightness(double vanillaBrightness) { + return rainbowEnabled ? vanillaBrightness * rainbowStrength : vanillaBrightness; + } + + public static float scaleAuroraBrightness(float vanillaBrightness) { + return auroraEnabled ? vanillaBrightness * auroraStrength : vanillaBrightness; + } + + public static boolean shouldRenderRainbow() { + return rainbowEnabled && rainbowActive; + } + + public static boolean consumeRainbowActivationPulse() { + boolean pulse = rainbowActivationPulse; + rainbowActivationPulse = false; + return pulse; + } + + public static float getRainbowStrength() { + return rainbowStrength; + } + + public static boolean isAuroraActive() { + return auroraEnabled && auroraActive; + } + + public static float getAuroraStrength() { + return auroraStrength; + } + + public static AtmosphereSkySample getLastSample() { + return lastSample; + } + + private static void updateRainbowState(AtmosphereSkySample sample) { + if (!rainbowEnabled) { + rainbowStrength = 0.0F; + rainbowActive = false; + rainbowActivationPulse = false; + return; + } + + float humidityFactor = SkyConditionMath.remapClamped(sample.humidityPercent(), 58.0F, 96.0F); + float recentRainFactor = sample.recentRainFactor(); + float cloudBreakFactor = Mth.clamp(sample.cloudBreakup() * 0.72F + sample.clearingTrend() * 0.55F, 0.0F, 1.0F); + float sunVisibilityFactor = SkyConditionMath.remapClamped(sample.sunVisibility(), 0.10F, 0.60F); + float sunAngleFactor = SkyConditionMath.peakedFactor(sample.daylightFactor(), 0.42F, 0.34F); + float activeRainPenalty = 1.0F - SkyConditionMath.remapClamped(sample.rainIntensity(), 0.05F, 0.24F); + float targetStrength = 0.0F; + + if (sample.canSeeSky()) { + targetStrength = humidityFactor + * recentRainFactor + * cloudBreakFactor + * sunVisibilityFactor + * sunAngleFactor + * activeRainPenalty; + } + + float tracking = targetStrength > rainbowStrength ? 0.18F : 0.07F; + rainbowStrength = Mth.lerp(tracking, rainbowStrength, targetStrength); + + boolean newActive = rainbowStrength >= 0.15F || (rainbowActive && rainbowStrength >= 0.07F); + rainbowActivationPulse = !rainbowActive && newActive; + rainbowActive = newActive; + } + + private static void updateAuroraState(Minecraft minecraft, AtmosphereSkySample sample) { + if (!auroraEnabled) { + auroraStrength = 0.0F; + auroraActive = false; + return; + } + + float seasonal = AuroraSeasonHelper.computeSeasonalFactor(minecraft.level); + float thermal = AuroraSeasonHelper.computeTemperatureFactor(minecraft.level, minecraft.player.blockPosition()); + float nightGate = SkyConditionMath.remapClamped(sample.nightFactor(), 0.08F, 0.35F); + float darknessGate = SkyConditionMath.reverseRemapClamped(sample.daylightFactor(), 0.06F, 0.22F); + float cloudPenalty = 1.0F - SkyConditionMath.remapClamped(sample.cloudCover(), 0.30F, 0.92F) * 0.82F; + float humidityHaze = SkyConditionMath.remapClamped(sample.humidityPercent(), 82.0F, 100.0F); + float wetBiomePenalty = 1.0F - sample.wetBiomeFactor() * 0.12F; + float clarity = sample.canSeeSky() + ? Mth.clamp(sample.atmosphericClarity() * cloudPenalty * (1.0F - humidityHaze * 0.35F) * wetBiomePenalty, 0.0F, 1.0F) + : 0.0F; + float climateBoost = Mth.clamp(seasonal * thermal, 0.35F, 1.45F); + float targetStrength = nightGate * darknessGate * clarity * climateBoost; + + float tracking = targetStrength > auroraStrength ? 0.12F : 0.06F; + auroraStrength = Mth.lerp(tracking, auroraStrength, targetStrength); + auroraActive = auroraStrength >= 0.08F || (auroraActive && auroraStrength >= 0.03F); + } + + private static void clearVisualState() { + lastSample = AtmosphereSkySample.NONE; + rainbowStrength = 0.0F; + rainbowActive = false; + rainbowActivationPulse = false; + auroraStrength = 0.0F; + auroraActive = false; + } + + private static void clear() { + clearVisualState(); + lastDimension = null; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/compat/sky/AtmosphereSkySample.java b/src/main/java/net/Gabou/projectatmosphere/compat/sky/AtmosphereSkySample.java new file mode 100644 index 00000000..e34d5fb2 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/compat/sky/AtmosphereSkySample.java @@ -0,0 +1,33 @@ +package net.Gabou.projectatmosphere.compat.sky; + +public record AtmosphereSkySample( + float humidityPercent, + float rainIntensity, + float cloudCover, + float recentRainFactor, + float clearingTrend, + float wetBiomeFactor, + float temperatureC, + float daylightFactor, + float nightFactor, + float sunVisibility, + float atmosphericClarity, + float cloudBreakup, + boolean canSeeSky +) { + public static final AtmosphereSkySample NONE = new AtmosphereSkySample( + 0.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F, + false + ); +} diff --git a/src/main/java/net/Gabou/projectatmosphere/compat/sky/AtmosphereSkySampler.java b/src/main/java/net/Gabou/projectatmosphere/compat/sky/AtmosphereSkySampler.java new file mode 100644 index 00000000..acb08b0c --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/compat/sky/AtmosphereSkySampler.java @@ -0,0 +1,68 @@ +package net.Gabou.projectatmosphere.compat.sky; + +import net.Gabou.projectatmosphere.client.atmosphere.AtmosphereClientState; +import net.Gabou.projectatmosphere.client.fog.FogBiomeClassifier; +import net.Gabou.projectatmosphere.compat.temperature.ClientTemperatureResolver; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +@OnlyIn(Dist.CLIENT) +public final class AtmosphereSkySampler { + private AtmosphereSkySampler() { + } + + public static AtmosphereSkySample sample(Minecraft minecraft, float partialTick) { + ClientLevel level = minecraft.level; + if (level == null || minecraft.player == null || !Level.OVERWORLD.equals(level.dimension())) { + return AtmosphereSkySample.NONE; + } + + BlockPos pos = minecraft.player.blockPosition(); + AtmosphereClientState.Snapshot snapshot = AtmosphereClientState.getSnapshot(); + float humidityPercent = snapshot.humidityPercent(); + float rainIntensity = snapshot.rainIntensity(); + float cloudCover = snapshot.cloudCover(); + float recentRainFactor = snapshot.recentRainFactor(); + float clearingTrend = snapshot.clearingTrend(); + float wetBiomeFactor = FogBiomeClassifier.computeWetBiomeFactor(level, pos); + float temperatureC = ClientTemperatureResolver.getCelsius(level, pos); + boolean canSeeSky = level.canSeeSky(pos.above()); + + float daylightFactor = Mth.clamp((float) (Math.cos(level.getTimeOfDay(partialTick) * (Math.PI * 2.0D)) * 3.0D), 0.0F, 1.0F); + float nightFactor = Mth.clamp(level.getStarBrightness(partialTick) * 2.0F, 0.0F, 1.0F); + + float sunVisibility = 0.0F; + float atmosphericClarity = 0.0F; + if (canSeeSky) { + float sunOcclusion = Mth.clamp(1.0F - cloudCover * 0.82F - rainIntensity * 0.95F, 0.0F, 1.0F); + sunVisibility = daylightFactor * sunOcclusion; + + float humidityHaze = SkyConditionMath.remapClamped(humidityPercent, 74.0F, 100.0F); + atmosphericClarity = Mth.clamp(1.0F - cloudCover * 0.75F - rainIntensity * 0.95F - humidityHaze * 0.25F, 0.0F, 1.0F); + } + + float cloudBreakup = SkyConditionMath.peakedFactor(cloudCover, 0.32F, 0.34F); + cloudBreakup = Mth.clamp(cloudBreakup * 0.72F + clearingTrend * 0.68F, 0.0F, 1.0F); + + return new AtmosphereSkySample( + humidityPercent, + rainIntensity, + cloudCover, + recentRainFactor, + clearingTrend, + wetBiomeFactor, + temperatureC, + daylightFactor, + nightFactor, + sunVisibility, + atmosphericClarity, + cloudBreakup, + canSeeSky + ); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/compat/sky/SkyConditionMath.java b/src/main/java/net/Gabou/projectatmosphere/compat/sky/SkyConditionMath.java new file mode 100644 index 00000000..8b812ff7 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/compat/sky/SkyConditionMath.java @@ -0,0 +1,26 @@ +package net.Gabou.projectatmosphere.compat.sky; + +import net.minecraft.util.Mth; + +public final class SkyConditionMath { + private SkyConditionMath() { + } + + public static float remapClamped(float value, float start, float end) { + if (end <= start) { + return value >= end ? 1.0F : 0.0F; + } + return Mth.clamp((value - start) / (end - start), 0.0F, 1.0F); + } + + public static float reverseRemapClamped(float value, float start, float end) { + return 1.0F - remapClamped(value, start, end); + } + + public static float peakedFactor(float value, float center, float radius) { + if (radius <= 0.0F) { + return value == center ? 1.0F : 0.0F; + } + return Mth.clamp(1.0F - Math.abs(value - center) / radius, 0.0F, 1.0F); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/config/AtmoCommonConfig.java b/src/main/java/net/Gabou/projectatmosphere/config/AtmoCommonConfig.java index fc6207ff..b325f84c 100644 --- a/src/main/java/net/Gabou/projectatmosphere/config/AtmoCommonConfig.java +++ b/src/main/java/net/Gabou/projectatmosphere/config/AtmoCommonConfig.java @@ -21,10 +21,15 @@ public class AtmoCommonConfig { public static final ForgeConfigSpec.BooleanValue FORCE_SHARED_EXECUTOR; public static final ForgeConfigSpec.BooleanValue DISPLAY_UNITS_IMPERIAL; public static final ForgeConfigSpec.BooleanValue ENABLE_TORNADOES; + public static final ForgeConfigSpec.BooleanValue ENABLE_TORNADO_DESTRUCTION; public static final ForgeConfigSpec.BooleanValue ENABLE_STORM_DEBRIS; public static final ForgeConfigSpec.IntValue MAX_STORM_DEBRIS_PER_CHUNK; public static final ForgeConfigSpec.BooleanValue AUTO_REPAIR_GLASS; public static final ForgeConfigSpec.BooleanValue DAMAGE_GLASS_ON_TORNADO; + public static final ForgeConfigSpec.BooleanValue ENABLE_HURRICANE_DESTRUCTION; + public static final ForgeConfigSpec.DoubleValue HURRICANE_DESTRUCTION_STRENGTH; + public static final ForgeConfigSpec.BooleanValue HURRICANE_DROP_BROKEN_BLOCKS; + public static final ForgeConfigSpec.BooleanValue HURRICANE_DAMAGE_TREES; public static final ForgeConfigSpec.DoubleValue TORNADO_CHECK_INTERVAL_SEC; public static final ForgeConfigSpec.DoubleValue TORNADO_BASE_SPAWN_RADIUS_M; public static final ForgeConfigSpec.DoubleValue TORNADO_MIN_TEMP_CONTRAST_C; @@ -42,7 +47,10 @@ public class AtmoCommonConfig { public static final ForgeConfigSpec.DoubleValue TORNADO_INTENSITY_MAX; public static final ForgeConfigSpec.IntValue TORNADO_CELL_COOLDOWN_MINUTES; public static final ForgeConfigSpec.BooleanValue TORNADO_ALLOW_LEGACY_FALLBACK; + public static final ForgeConfigSpec.BooleanValue DISABLE_SIMPLE_CLOUDS_TORNADO_SSBO; public static final ForgeConfigSpec.BooleanValue TORNADO_DEBUG_LOGGING; + public static final ForgeConfigSpec.DoubleValue TORNADO_RENDER_QUALITY; + public static final ForgeConfigSpec.DoubleValue TORNADO_RENDER_DOWNSAMPLE; public static final ForgeConfigSpec.DoubleValue STORM_SEVERITY_BOOSTER; @@ -105,8 +113,23 @@ public class AtmoCommonConfig { public static final ForgeConfigSpec.IntValue CLOUD_FIRE_DAMP_TICKS; public static final ForgeConfigSpec.DoubleValue FIRE_EXTINGUISH_BASE_CHANCE; public static final ForgeConfigSpec.DoubleValue CAULDRON_FILL_BASE_CHANCE; + public static final ForgeConfigSpec.BooleanValue FOG_ENABLED; + public static final ForgeConfigSpec.IntValue FOG_SYNC_INTERVAL_TICKS; + public static final ForgeConfigSpec.DoubleValue FOG_HUMIDITY_START_PERCENT; + public static final ForgeConfigSpec.DoubleValue FOG_HUMIDITY_FULL_PERCENT; + public static final ForgeConfigSpec.DoubleValue FOG_WET_BIOME_BASE_STRENGTH; + public static final ForgeConfigSpec.DoubleValue FOG_RAIN_BOOST; + public static final ForgeConfigSpec.DoubleValue FOG_NEAR_DISTANCE; + public static final ForgeConfigSpec.DoubleValue FOG_FAR_DISTANCE; + public static final ForgeConfigSpec.DoubleValue FOG_COLOR_BLEND; + public static final ForgeConfigSpec.DoubleValue FOG_WET_BIOME_DOWNFALL_MIN; + public static final ForgeConfigSpec.ConfigValue> FOG_WET_BIOME_IDS; + public static final ForgeConfigSpec.ConfigValue> FOG_WET_BIOME_KEYWORDS; public static final ForgeConfigSpec.BooleanValue TELEMETRY_ENABLED; public static final ForgeConfigSpec.IntValue TELEMETRY_RETENTION_DAYS; + public static final ForgeConfigSpec.BooleanValue AUTH_STRICT_OFFLINE_UUID_REJECT; + public static final ForgeConfigSpec.BooleanValue AUTH_KICK_ON_FAILURE; + public static final ForgeConfigSpec.IntValue AUTH_CHALLENGE_TIMEOUT_TICKS; static { ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder(); @@ -131,6 +154,9 @@ public class AtmoCommonConfig { ENABLE_TORNADOES = builder .comment("Enable tornado spawning and commands") .define("enableTornadoes", true); + ENABLE_TORNADO_DESTRUCTION = builder + .comment("Enable tornado block destruction and terrain scouring") + .define("enableTornadoDestruction", true); ENABLE_STORM_DEBRIS = builder .comment("Enable random debris spawning during storms") .define("enableStormDebris", false); @@ -143,6 +169,18 @@ public class AtmoCommonConfig { DAMAGE_GLASS_ON_TORNADO = builder .comment("Enable glass damage when a tornado passes over it") .define("damageGlassOnTornado", true); + ENABLE_HURRICANE_DESTRUCTION = builder + .comment("Enable limited hurricane block destruction") + .define("enableHurricaneDestruction", true); + HURRICANE_DESTRUCTION_STRENGTH = builder + .comment("Overall hurricane destruction strength. Higher values increase checks, break chance, and tree damage.") + .defineInRange("hurricaneDestructionStrength", 1.0d, 0.0d, 3.0d); + HURRICANE_DROP_BROKEN_BLOCKS = builder + .comment("Drop items from blocks broken by hurricanes") + .define("hurricaneDropBrokenBlocks", false); + HURRICANE_DAMAGE_TREES = builder + .comment("Allow hurricanes to damage leaves and occasionally break logs") + .define("hurricaneDamageTrees", true); builder.push("tornado"); TORNADO_CHECK_INTERVAL_SEC = builder .comment("Seconds between tornado spawn checks") @@ -193,11 +231,20 @@ public class AtmoCommonConfig { .comment("Cooldown in minutes before a cell can spawn another tornado") .defineInRange("cellCooldownMinutes", 20, 0, Integer.MAX_VALUE); TORNADO_ALLOW_LEGACY_FALLBACK = builder - .comment("Allow falling back to the legacy mesh tornado when the SimpleClouds shader pack lacks the CloudTornadoes SSBO. Leave false to require the shader-driven funnel.") + .comment("Allow falling back to the legacy mesh tornado when the SimpleClouds shader pack lacks the CloudStorms SSBO. Leave false to require the shader-driven funnel.") .define("allowLegacyTornadoFallback", false); + DISABLE_SIMPLE_CLOUDS_TORNADO_SSBO = builder + .comment("Disable Project Atmosphere's Simple Clouds storm SSBO integration. Tornado cloud carving uses safer uniforms; hurricane cloud shaping falls back off when this is enabled.") + .define("disableSimpleCloudsTornadoSSBO", false); TORNADO_DEBUG_LOGGING = builder .comment("Enable verbose tornado logging (SSBO detection, fallback decisions, command outcomes).") .define("debugTornadoLogging", false); + TORNADO_RENDER_QUALITY = builder + .comment("Client tornado volume quality multiplier. Lower values reduce tornado shader step count and detail for better FPS.") + .defineInRange("renderQuality", 0.72d, 0.25d, 1.0d); + TORNADO_RENDER_DOWNSAMPLE = builder + .comment("Client tornado volume downsample factor. 1.0 renders at full resolution; higher values reduce shaded pixels before upsampling.") + .defineInRange("renderDownsample", 2.5d, 1.0d, 4.0d); builder.pop(); builder.pop(); @@ -374,6 +421,49 @@ public class AtmoCommonConfig { .defineInRange("cauldronFillBaseChance", 0.06d, 0d, 1d); builder.pop(); + builder.push("fog"); + FOG_ENABLED = builder + .comment("Enable humidity-driven dynamic fog rendering on the client") + .define("enabled", true); + FOG_SYNC_INTERVAL_TICKS = builder + .comment("Ticks between lightweight server->client atmosphere sync updates used by fog and sky-effect compatibility sampling") + .defineInRange("syncIntervalTicks", 20, 1, 200); + FOG_HUMIDITY_START_PERCENT = builder + .comment("Humidity percentage where dynamic fog starts to form") + .defineInRange("humidityStartPercent", 72d, 0d, 100d); + FOG_HUMIDITY_FULL_PERCENT = builder + .comment("Humidity percentage where the humidity contribution reaches full strength") + .defineInRange("humidityFullPercent", 96d, 0d, 100d); + FOG_WET_BIOME_BASE_STRENGTH = builder + .comment("Base fog strength added by moisture-heavy biomes such as swamps or rainforests") + .defineInRange("wetBiomeBaseStrength", 0.18d, 0d, 1d); + FOG_RAIN_BOOST = builder + .comment("Additional fog strength contributed by active rain intensity") + .defineInRange("rainBoost", 0.22d, 0d, 1d); + FOG_NEAR_DISTANCE = builder + .comment("Near fog plane used when dynamic fog is fully saturated") + .defineInRange("nearDistance", 0.5d, 0d, 64d); + FOG_FAR_DISTANCE = builder + .comment("Far fog plane used when dynamic fog is fully saturated") + .defineInRange("farDistance", 72d, 4d, 512d); + FOG_COLOR_BLEND = builder + .comment("Color tint blend applied by dynamic fog") + .defineInRange("colorBlend", 0.45d, 0d, 1d); + FOG_WET_BIOME_DOWNFALL_MIN = builder + .comment("Biome downfall value that starts contributing wet-biome fog weighting") + .defineInRange("wetBiomeDownfallMin", 0.75d, 0d, 1d); + FOG_WET_BIOME_IDS = builder + .comment("Explicit biome ids that should always count as moisture-heavy for fog") + .defineListAllowEmpty("wetBiomeIds", + List.of("minecraft:swamp", "minecraft:mangrove_swamp"), + value -> value instanceof String); + FOG_WET_BIOME_KEYWORDS = builder + .comment("Biome path keywords that should count as moisture-heavy for fog") + .defineListAllowEmpty("wetBiomeKeywords", + List.of("swamp", "marsh", "bog", "fen", "rainforest", "jungle", "mangrove", "wetland", "bayou"), + value -> value instanceof String); + builder.pop(); + builder.push("telemetry"); TELEMETRY_ENABLED = builder .comment("Enable lightweight telemetry collection for diagnostics") @@ -383,6 +473,18 @@ public class AtmoCommonConfig { .defineInRange("telemetryRetentionDays", 14, 0, 365); builder.pop(); + builder.push("auth"); + AUTH_STRICT_OFFLINE_UUID_REJECT = builder + .comment("Reject offline UUID v3 identities during the launcher auth check") + .define("strictOfflineUuidReject", true); + AUTH_KICK_ON_FAILURE = builder + .comment("Kick players who fail or timeout the launcher auth challenge") + .define("kickOnFailure", true); + AUTH_CHALLENGE_TIMEOUT_TICKS = builder + .comment("Ticks before a pending launcher auth challenge times out") + .defineInRange("challengeTimeoutTicks", 200, 1, Integer.MAX_VALUE); + builder.pop(); + builder.push("debug"); DEBUG_MODE = builder .comment("Enable debug mode for verbose logging and diagnostics") diff --git a/src/main/java/net/Gabou/projectatmosphere/config/AtmoConfigScreen.java b/src/main/java/net/Gabou/projectatmosphere/config/AtmoConfigScreen.java index a5b403a6..7c8a42f3 100644 --- a/src/main/java/net/Gabou/projectatmosphere/config/AtmoConfigScreen.java +++ b/src/main/java/net/Gabou/projectatmosphere/config/AtmoConfigScreen.java @@ -24,10 +24,16 @@ public class AtmoConfigScreen extends Screen { private boolean forceSharedExecutor; private boolean displayUnitsImperial; private boolean enableTornadoes; + private boolean enableTornadoDestruction; private boolean enableStormDebris; + private boolean fogEnabled; private int maxStormDebrisPerChunk; private boolean autoRepairGlass; private boolean damageGlassOnTornado; + private boolean enableHurricaneDestruction; + private double hurricaneDestructionStrength; + private boolean hurricaneDropBrokenBlocks; + private boolean hurricaneDamageTrees; private double tornadoCheckIntervalSec; private double tornadoBaseSpawnRadiusM; private double tornadoMinTempContrastC; @@ -46,6 +52,8 @@ public class AtmoConfigScreen extends Screen { private int tornadoCellCooldownMinutes; private boolean tornadoAllowLegacyFallback; private boolean tornadoDebugLogging; + private double tornadoRenderQuality; + private double tornadoRenderDownsample; private double windBaseRetargetSec; private double windDirRetargetSec; private double windGustMeanSec; @@ -54,6 +62,12 @@ public class AtmoConfigScreen extends Screen { private double windPushThresholdMps; private double windPlayerPushScale; private double windEntityPushScale; + private double fogHumidityStartPercent; + private double fogHumidityFullPercent; + private double fogWetBiomeBaseStrength; + private double fogRainBoost; + private double fogFarDistance; + private double fogColorBlend; private double stormBoostMultiplier; private boolean debugMode; @@ -76,6 +90,15 @@ public class AtmoConfigScreen extends Screen { private EditBox tornadoIntensityMinBox; private EditBox tornadoIntensityMaxBox; private EditBox tornadoCellCooldownBox; + private EditBox tornadoRenderQualityBox; + private EditBox tornadoRenderDownsampleBox; + private EditBox hurricaneDestructionStrengthBox; + private EditBox fogHumidityStartBox; + private EditBox fogHumidityFullBox; + private EditBox fogWetBiomeStrengthBox; + private EditBox fogRainBoostBox; + private EditBox fogFarDistanceBox; + private EditBox fogColorBlendBox; private EditBox windBaseRetargetBox; private EditBox windDirRetargetBox; private EditBox windGustMeanBox; @@ -134,10 +157,16 @@ protected void init() { this.stormBoostMultiplier = AtmoCommonConfig.STORM_SEVERITY_BOOSTER.get(); this.displayUnitsImperial = AtmoCommonConfig.DISPLAY_UNITS_IMPERIAL.get(); this.enableTornadoes = AtmoCommonConfig.ENABLE_TORNADOES.get(); + this.enableTornadoDestruction = AtmoCommonConfig.ENABLE_TORNADO_DESTRUCTION.get(); this.enableStormDebris = AtmoCommonConfig.ENABLE_STORM_DEBRIS.get(); + this.fogEnabled = AtmoCommonConfig.FOG_ENABLED.get(); this.maxStormDebrisPerChunk = AtmoCommonConfig.MAX_STORM_DEBRIS_PER_CHUNK.get(); this.autoRepairGlass = AtmoCommonConfig.AUTO_REPAIR_GLASS.get(); this.damageGlassOnTornado = AtmoCommonConfig.DAMAGE_GLASS_ON_TORNADO.get(); + this.enableHurricaneDestruction = AtmoCommonConfig.ENABLE_HURRICANE_DESTRUCTION.get(); + this.hurricaneDestructionStrength = AtmoCommonConfig.HURRICANE_DESTRUCTION_STRENGTH.get(); + this.hurricaneDropBrokenBlocks = AtmoCommonConfig.HURRICANE_DROP_BROKEN_BLOCKS.get(); + this.hurricaneDamageTrees = AtmoCommonConfig.HURRICANE_DAMAGE_TREES.get(); this.cloudRenderDistance = AtmoCommonConfig.CLOUD_RENDER_DISTANCE.get(); this.tornadoCheckIntervalSec = AtmoCommonConfig.TORNADO_CHECK_INTERVAL_SEC.get(); this.tornadoBaseSpawnRadiusM = AtmoCommonConfig.TORNADO_BASE_SPAWN_RADIUS_M.get(); @@ -157,6 +186,8 @@ protected void init() { this.tornadoCellCooldownMinutes = AtmoCommonConfig.TORNADO_CELL_COOLDOWN_MINUTES.get(); this.tornadoAllowLegacyFallback = AtmoCommonConfig.TORNADO_ALLOW_LEGACY_FALLBACK.get(); this.tornadoDebugLogging = AtmoCommonConfig.TORNADO_DEBUG_LOGGING.get(); + this.tornadoRenderQuality = AtmoCommonConfig.TORNADO_RENDER_QUALITY.get(); + this.tornadoRenderDownsample = AtmoCommonConfig.TORNADO_RENDER_DOWNSAMPLE.get(); this.windBaseRetargetSec = AtmoCommonConfig.WIND_BASE_RETARGET_SEC.get(); this.windDirRetargetSec = AtmoCommonConfig.WIND_DIR_RETARGET_SEC.get(); this.windGustMeanSec = AtmoCommonConfig.WIND_GUST_MEAN_SEC.get(); @@ -165,6 +196,12 @@ protected void init() { this.windPushThresholdMps = AtmoCommonConfig.WIND_PUSH_THRESHOLD_MPS.get(); this.windPlayerPushScale = AtmoCommonConfig.WIND_PLAYER_PUSH_SCALE.get(); this.windEntityPushScale = AtmoCommonConfig.WIND_ENTITY_PUSH_SCALE.get(); + this.fogHumidityStartPercent = AtmoCommonConfig.FOG_HUMIDITY_START_PERCENT.get(); + this.fogHumidityFullPercent = AtmoCommonConfig.FOG_HUMIDITY_FULL_PERCENT.get(); + this.fogWetBiomeBaseStrength = AtmoCommonConfig.FOG_WET_BIOME_BASE_STRENGTH.get(); + this.fogRainBoost = AtmoCommonConfig.FOG_RAIN_BOOST.get(); + this.fogFarDistance = AtmoCommonConfig.FOG_FAR_DISTANCE.get(); + this.fogColorBlend = AtmoCommonConfig.FOG_COLOR_BLEND.get(); int center = this.width / 2; int y = 40; @@ -176,6 +213,10 @@ protected void init() { b.setMessage(toggleLabel("Force Shared Executor", forceSharedExecutor)); }).bounds(center - 100, y, 200, 20).build(), y); y += 32; + tornadoRenderQualityBox = addNumberField(center, y, "Tornado Render Quality", Double.toString(tornadoRenderQuality)); + y += 34; + tornadoRenderDownsampleBox = addNumberField(center, y, "Tornado Render Downsample", Double.toString(tornadoRenderDownsample)); + y += 34; addTitle("Display", y); y += 18; @@ -185,6 +226,26 @@ protected void init() { }).bounds(center - 100, y, 200, 20).build(), y); y += 32; + addTitle("Fog", y); + y += 18; + addConfigWidget(Button.builder(toggleLabel("Dynamic Fog", fogEnabled), b -> { + fogEnabled = !fogEnabled; + b.setMessage(toggleLabel("Dynamic Fog", fogEnabled)); + }).bounds(center - 100, y, 200, 20).build(), y); + y += 32; + fogHumidityStartBox = addNumberField(center, y, "Humidity Start Percent", Double.toString(fogHumidityStartPercent)); + y += 34; + fogHumidityFullBox = addNumberField(center, y, "Humidity Full Percent", Double.toString(fogHumidityFullPercent)); + y += 34; + fogWetBiomeStrengthBox = addNumberField(center, y, "Wet Biome Strength", Double.toString(fogWetBiomeBaseStrength)); + y += 34; + fogRainBoostBox = addNumberField(center, y, "Rain Boost", Double.toString(fogRainBoost)); + y += 34; + fogFarDistanceBox = addNumberField(center, y, "Fog Far Distance", Double.toString(fogFarDistance)); + y += 34; + fogColorBlendBox = addNumberField(center, y, "Fog Color Blend", Double.toString(fogColorBlend)); + y += 34; + addTitle("Storms", y); y += 18; addConfigWidget(Button.builder(toggleLabel("Tornadoes", enableTornadoes), b -> { @@ -192,6 +253,11 @@ protected void init() { b.setMessage(toggleLabel("Tornadoes", enableTornadoes)); }).bounds(center - 100, y, 200, 20).build(), y); y += 32; + addConfigWidget(Button.builder(toggleLabel("Tornado Destruction", enableTornadoDestruction), b -> { + enableTornadoDestruction = !enableTornadoDestruction; + b.setMessage(toggleLabel("Tornado Destruction", enableTornadoDestruction)); + }).bounds(center - 100, y, 200, 20).build(), y); + y += 32; addConfigWidget(Button.builder(toggleLabel("Storm Debris", enableStormDebris), b -> { enableStormDebris = !enableStormDebris; b.setMessage(toggleLabel("Storm Debris", enableStormDebris)); @@ -216,6 +282,23 @@ protected void init() { b.setMessage(toggleLabel("Damage Glass On Tornado", damageGlassOnTornado)); }).bounds(center - 100, y, 200, 20).build(), y); y += 32; + addConfigWidget(Button.builder(toggleLabel("Hurricane Destruction", enableHurricaneDestruction), b -> { + enableHurricaneDestruction = !enableHurricaneDestruction; + b.setMessage(toggleLabel("Hurricane Destruction", enableHurricaneDestruction)); + }).bounds(center - 100, y, 200, 20).build(), y); + y += 32; + hurricaneDestructionStrengthBox = addNumberField(center, y, "Hurricane Destruction Strength", Double.toString(hurricaneDestructionStrength)); + y += 34; + addConfigWidget(Button.builder(toggleLabel("Drop Hurricane Blocks", hurricaneDropBrokenBlocks), b -> { + hurricaneDropBrokenBlocks = !hurricaneDropBrokenBlocks; + b.setMessage(toggleLabel("Drop Hurricane Blocks", hurricaneDropBrokenBlocks)); + }).bounds(center - 100, y, 200, 20).build(), y); + y += 32; + addConfigWidget(Button.builder(toggleLabel("Hurricane Tree Damage", hurricaneDamageTrees), b -> { + hurricaneDamageTrees = !hurricaneDamageTrees; + b.setMessage(toggleLabel("Hurricane Tree Damage", hurricaneDamageTrees)); + }).bounds(center - 100, y, 200, 20).build(), y); + y += 32; addTitle("Tornado", y); y += 18; @@ -415,7 +498,16 @@ private void saveChanges() { tornadoIntensityMin = parseDouble(tornadoIntensityMinBox, tornadoIntensityMin); tornadoIntensityMax = parseDouble(tornadoIntensityMaxBox, tornadoIntensityMax); tornadoCellCooldownMinutes = parseInt(tornadoCellCooldownBox, tornadoCellCooldownMinutes); + tornadoRenderQuality = Mth.clamp(parseDouble(tornadoRenderQualityBox, tornadoRenderQuality), 0.25d, 1.0d); + tornadoRenderDownsample = Mth.clamp(parseDouble(tornadoRenderDownsampleBox, tornadoRenderDownsample), 1.0d, 4.0d); + hurricaneDestructionStrength = Mth.clamp(parseDouble(hurricaneDestructionStrengthBox, hurricaneDestructionStrength), 0.0d, 3.0d); // Buttons already toggled booleans; nothing to parse. + fogHumidityStartPercent = parseDouble(fogHumidityStartBox, fogHumidityStartPercent); + fogHumidityFullPercent = parseDouble(fogHumidityFullBox, fogHumidityFullPercent); + fogWetBiomeBaseStrength = parseDouble(fogWetBiomeStrengthBox, fogWetBiomeBaseStrength); + fogRainBoost = parseDouble(fogRainBoostBox, fogRainBoost); + fogFarDistance = parseDouble(fogFarDistanceBox, fogFarDistance); + fogColorBlend = parseDouble(fogColorBlendBox, fogColorBlend); windBaseRetargetSec = parseDouble(windBaseRetargetBox, windBaseRetargetSec); windDirRetargetSec = parseDouble(windDirRetargetBox, windDirRetargetSec); windGustMeanSec = parseDouble(windGustMeanBox, windGustMeanSec); @@ -428,11 +520,17 @@ private void saveChanges() { AtmoCommonConfig.FORCE_SHARED_EXECUTOR.set(forceSharedExecutor); AtmoCommonConfig.DISPLAY_UNITS_IMPERIAL.set(displayUnitsImperial); AtmoCommonConfig.ENABLE_TORNADOES.set(enableTornadoes); + AtmoCommonConfig.ENABLE_TORNADO_DESTRUCTION.set(enableTornadoDestruction); AtmoCommonConfig.ENABLE_STORM_DEBRIS.set(enableStormDebris); + AtmoCommonConfig.FOG_ENABLED.set(fogEnabled); AtmoCommonConfig.MAX_STORM_DEBRIS_PER_CHUNK.set(maxStormDebrisPerChunk); AtmoCommonConfig.CLOUD_RENDER_DISTANCE.set(cloudRenderDistance); AtmoCommonConfig.AUTO_REPAIR_GLASS.set(autoRepairGlass); AtmoCommonConfig.DAMAGE_GLASS_ON_TORNADO.set(damageGlassOnTornado); + AtmoCommonConfig.ENABLE_HURRICANE_DESTRUCTION.set(enableHurricaneDestruction); + AtmoCommonConfig.HURRICANE_DESTRUCTION_STRENGTH.set(hurricaneDestructionStrength); + AtmoCommonConfig.HURRICANE_DROP_BROKEN_BLOCKS.set(hurricaneDropBrokenBlocks); + AtmoCommonConfig.HURRICANE_DAMAGE_TREES.set(hurricaneDamageTrees); AtmoCommonConfig.TORNADO_CHECK_INTERVAL_SEC.set(tornadoCheckIntervalSec); AtmoCommonConfig.TORNADO_BASE_SPAWN_RADIUS_M.set(tornadoBaseSpawnRadiusM); AtmoCommonConfig.TORNADO_MIN_TEMP_CONTRAST_C.set(tornadoMinTempContrastC); @@ -451,6 +549,14 @@ private void saveChanges() { AtmoCommonConfig.TORNADO_CELL_COOLDOWN_MINUTES.set(tornadoCellCooldownMinutes); AtmoCommonConfig.TORNADO_ALLOW_LEGACY_FALLBACK.set(tornadoAllowLegacyFallback); AtmoCommonConfig.TORNADO_DEBUG_LOGGING.set(tornadoDebugLogging); + AtmoCommonConfig.TORNADO_RENDER_QUALITY.set(tornadoRenderQuality); + AtmoCommonConfig.TORNADO_RENDER_DOWNSAMPLE.set(tornadoRenderDownsample); + AtmoCommonConfig.FOG_HUMIDITY_START_PERCENT.set(fogHumidityStartPercent); + AtmoCommonConfig.FOG_HUMIDITY_FULL_PERCENT.set(fogHumidityFullPercent); + AtmoCommonConfig.FOG_WET_BIOME_BASE_STRENGTH.set(fogWetBiomeBaseStrength); + AtmoCommonConfig.FOG_RAIN_BOOST.set(fogRainBoost); + AtmoCommonConfig.FOG_FAR_DISTANCE.set(fogFarDistance); + AtmoCommonConfig.FOG_COLOR_BLEND.set(fogColorBlend); AtmoCommonConfig.WIND_BASE_RETARGET_SEC.set(windBaseRetargetSec); AtmoCommonConfig.WIND_DIR_RETARGET_SEC.set(windDirRetargetSec); AtmoCommonConfig.WIND_GUST_MEAN_SEC.set(windGustMeanSec); diff --git a/src/main/java/net/Gabou/projectatmosphere/event/BiomeChangeManager.java b/src/main/java/net/Gabou/projectatmosphere/event/BiomeChangeManager.java index 68e4a754..fe7fc960 100644 --- a/src/main/java/net/Gabou/projectatmosphere/event/BiomeChangeManager.java +++ b/src/main/java/net/Gabou/projectatmosphere/event/BiomeChangeManager.java @@ -23,7 +23,7 @@ @Mod.EventBusSubscriber public class BiomeChangeManager { - private static final Map regionTrack = new HashMap<>(); + private static final Map> regionTrack = new HashMap<>(); private static final Map> lastBiome = new HashMap<>(); private static final int RUN_INTERVAL_TICKS = 2000; // Threshold to move the tracked center: 4/5 of default region size. @@ -48,22 +48,22 @@ public static void onPlayerTick(TickEvent.PlayerTickEvent ev) { UUID uuid = player.getUUID(); BlockPos pos = player.blockPosition(); RegionInstanceKey region = RegionInstanceKey.from(pos); - RegionTrack track = regionTrack.computeIfAbsent(uuid, id -> new RegionTrack(region, pos)); + Pair track = regionTrack.computeIfAbsent(uuid, id -> Pair.of(region, pos)); ResourceLocation nowBiome = getBiomeKeyAt(player); Pair last = lastBiome.get(uuid); if (last == null || !last.getKey().equals(nowBiome)) { lastBiome.put(uuid, Pair.of(nowBiome, isDesert(nowBiome))); } // If we enter a new region, update and trigger regen. - if (!track.region().equals(region)) { - track = new RegionTrack(region, pos); + if (!track.getLeft().equals(region)) { + track = Pair.of(region, pos); regionTrack.put(uuid, track); onRegionChanged(player, region, nowBiome); return; } // If we moved far from the tracked center within the same region, update center and regen. - if (track.center().distManhattan(pos) > MOVE_THRESHOLD) { - track = new RegionTrack(region, pos); + if (track.getRight().distManhattan(pos) > MOVE_THRESHOLD) { + track = Pair.of(region, pos); regionTrack.put(uuid, track); onRegionChanged(player, region, nowBiome); } @@ -118,6 +118,4 @@ private static void onRegionChanged(ServerPlayer player, RegionInstanceKey regio "[Atmosphere] Entered region " + region + ". Forecast regenerated." )); } - - private record RegionTrack(RegionInstanceKey region, BlockPos center) {} } diff --git a/src/main/java/net/Gabou/projectatmosphere/event/EventHandler.java b/src/main/java/net/Gabou/projectatmosphere/event/EventHandler.java index ee0b184a..5b530887 100644 --- a/src/main/java/net/Gabou/projectatmosphere/event/EventHandler.java +++ b/src/main/java/net/Gabou/projectatmosphere/event/EventHandler.java @@ -7,13 +7,13 @@ import net.Gabou.projectatmosphere.ProjectAtmosphere; import net.Gabou.projectatmosphere.blocks.BlockManager; import net.Gabou.projectatmosphere.compat.CompatHandler; -import net.Gabou.projectatmosphere.compat.rainbows.RainbowRainBridge; import net.Gabou.projectatmosphere.config.AtmoCommonConfig; import net.Gabou.projectatmosphere.manager.AtmosphereManager; import net.Gabou.projectatmosphere.manager.AtmosphereWorldEffectsManager; import net.Gabou.projectatmosphere.manager.ForecastGenerator; import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; import net.Gabou.projectatmosphere.manager.SimpleCloudSpawner; +import net.Gabou.projectatmosphere.modules.atmosphere.AtmosphereStatusSyncManager; import net.Gabou.projectatmosphere.modules.core.CloudLibrary; import net.Gabou.projectatmosphere.modules.wind.WindForces; import net.minecraft.core.BlockPos; @@ -77,9 +77,7 @@ public static void onLevelTick(TickEvent.LevelTickEvent event) { cloudBoosterTicks = 0; } - if (CompatHandler.isRainbowsLoaded()) { - RainbowRainBridge.sync(serverLevel, generator); - } + AtmosphereStatusSyncManager.syncPlayers(serverLevel); if(!serverLevel.players().isEmpty() && !hasDisplayedMessage) { hasDisplayedMessage = true; serverLevel.players().forEach(player -> {player.sendSystemMessage(Component.literal(ForecastGenerator.message) );}); @@ -116,9 +114,8 @@ public static void onPlayerChangedDimension(PlayerEvent.PlayerChangedDimensionEv return; } ServerLevel level = player.serverLevel(); - if (CompatHandler.isRainbowsLoaded() && level.dimension().equals(event.getTo())) { - ServerCloudManager cloudManager = (ServerCloudManager) CloudManager.get(level); - RainbowRainBridge.sendSnapshot(player, level, cloudManager.getCloudGenerator()); + if (level.dimension().equals(event.getTo())) { + AtmosphereStatusSyncManager.syncPlayer(player); } } diff --git a/src/main/java/net/Gabou/projectatmosphere/event/SimpleCloudsEventListener.java b/src/main/java/net/Gabou/projectatmosphere/event/SimpleCloudsEventListener.java index 4b1f58a2..5d4ef7a1 100644 --- a/src/main/java/net/Gabou/projectatmosphere/event/SimpleCloudsEventListener.java +++ b/src/main/java/net/Gabou/projectatmosphere/event/SimpleCloudsEventListener.java @@ -85,9 +85,11 @@ private static float getFinalSpeed(float stormFactor, float currentSpeed, ScAPIC float tornadoBoost = 0f; for (var t : TornadoManager.getActiveTornadoes()) { var cr = t.getCloudRegion(); - // Compare by proximity of cloud region centers - double dx = cr.getWorldX() - region.getWorldX(); - double dz = cr.getWorldZ() - region.getWorldZ(); + double centerX = cr != null ? cr.getWorldX() : t.position.x; + double centerZ = cr != null ? cr.getWorldZ() : t.position.z; + // Standalone tornadoes do not have a Simple Clouds region; compare against the tornado position instead. + double dx = centerX - region.getWorldX(); + double dz = centerZ - region.getWorldZ(); double dist = Math.sqrt(dx * dx + dz * dz); if (dist <= (double) (t.radius + 150f)) { // generous overlap threshold // Amplify based on tornado level diff --git a/src/main/java/net/Gabou/projectatmosphere/manager/AtmosphereManager.java b/src/main/java/net/Gabou/projectatmosphere/manager/AtmosphereManager.java index a56fffed..d4067c60 100644 --- a/src/main/java/net/Gabou/projectatmosphere/manager/AtmosphereManager.java +++ b/src/main/java/net/Gabou/projectatmosphere/manager/AtmosphereManager.java @@ -4,23 +4,19 @@ import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; -import dev.nonamecrackers2.simpleclouds.common.world.ServerCloudManager; import net.Gabou.projectatmosphere.ProjectAtmosphere; import net.Gabou.projectatmosphere.command.ProjectAtmosphereCommands; import net.Gabou.projectatmosphere.compat.CompatHandler; -import net.Gabou.projectatmosphere.compat.rainbows.RainbowRainBridge; import net.Gabou.projectatmosphere.client.loading.ForecastLoadingStage; import net.Gabou.projectatmosphere.event.EventHandler; +import net.Gabou.projectatmosphere.modules.atmosphere.AtmosphereStatusSyncManager; import net.Gabou.projectatmosphere.modules.hurricane.HurricaneManager; import net.Gabou.projectatmosphere.modules.snowstorm.SnowstormManager; import net.Gabou.projectatmosphere.modules.tornado.TornadoManager; import net.Gabou.projectatmosphere.network.ForecastLoadingStatusPacket; import net.Gabou.projectatmosphere.network.NetworkHandler; -import net.Gabou.projectatmosphere.seasons.SeasonBootstrap; -import net.Gabou.projectatmosphere.seasons.SeasonProviderRegistry; import net.Gabou.projectatmosphere.seasons.SeasonTimeHelper; import net.Gabou.projectatmosphere.seasons.SeasonStage; -import net.Gabou.projectatmosphere.seasons.SereneSeasonsSeasonDelegate; import net.Gabou.projectatmosphere.util.AsyncAtmosphereService; import net.Gabou.projectatmosphere.util.CloudRegionQueue; import net.Gabou.projectatmosphere.util.ICloudRegionId; @@ -85,8 +81,6 @@ public static void onServerStopping(ServerLevel world) { } public static void updateForecastAround(ServerLevel world, BlockPos center) { - if(ProjectAtmosphere.DEBUG_MODE) - ProjectAtmosphere.LOGGER.info("Updating forecast Around"); AsyncAtmosphereService.runWeather(() -> { ForecastOrchestrator.updateForecast(world, center); }); @@ -121,10 +115,8 @@ public static void onPlayerLogin(ServerLevel world, ServerPlayer player) { PacketDistributor.PLAYER.with(() -> player), ForecastLoadingStatusPacket.ready("player_login_ready") ); - if (CompatHandler.isRainbowsLoaded()) { - ServerCloudManager cloudManager = (ServerCloudManager) CloudManager.get(world); - RainbowRainBridge.sendSnapshot(player, world, cloudManager.getCloudGenerator()); - } + AtmosphereStatusSyncManager.syncPlayer(player); + HurricaneManager.syncToPlayer(player); future.complete(null); }); } @@ -140,8 +132,6 @@ public static boolean isPlayerReady(UUID uuid) { } public static void onSwapProfiles(ServerLevel world) { - if(ProjectAtmosphere.DEBUG_MODE) - ProjectAtmosphere.LOGGER.info("Swapping profiles and updating weather"); AsyncAtmosphereService.runWeather(() -> { ForecastOrchestrator.onSwapDay(world); }); @@ -149,9 +139,6 @@ public static void onSwapProfiles(ServerLevel world) { public static void onRegenerate(ServerLevel world) { ProjectAtmosphere.LOGGER.info("Regenerating weather data for all players"); - if (CompatHandler.isRainbowsLoaded()) { - RainbowRainBridge.clear(world.dimension()); - } AsyncAtmosphereService.runWeather(() -> { EventHandler.onRegenerate(); CloudManager.get(world).getCloudGenerator().removeAllClouds(); @@ -252,8 +239,9 @@ private static void handleCloudRegionToQueue(ServerLevel level, CloudRegion clou return; } cloudRegions.add(cloudRegion); - if (WeatherType.isRainy(cloudRegion.getCloudTypeId()) && SeasonTimeHelper.isSereneSeasonsPresent()) - SereneSeasonsSeasonDelegate.handleRainStarted(level, cloudRegion); + if (WeatherType.isRainy(cloudRegion.getCloudTypeId())) { + SeasonTimeHelper.onRainStarted(level, cloudRegion); + } } @@ -263,8 +251,9 @@ private static void handleCloudRegionToRemove(ServerLevel level, CloudRegion clo } cloudRegions.remove(cloudRegion); - if (WeatherType.isRainy(cloudRegion.getCloudTypeId()) && SeasonTimeHelper.isSereneSeasonsPresent()) - SereneSeasonsSeasonDelegate.handleRainEnded(level, cloudRegion); + if (WeatherType.isRainy(cloudRegion.getCloudTypeId())) { + SeasonTimeHelper.onRainEnded(level, cloudRegion); + } } diff --git a/src/main/java/net/Gabou/projectatmosphere/manager/ForecastOrchestrator.java b/src/main/java/net/Gabou/projectatmosphere/manager/ForecastOrchestrator.java index 04cb1893..670dcd42 100644 --- a/src/main/java/net/Gabou/projectatmosphere/manager/ForecastOrchestrator.java +++ b/src/main/java/net/Gabou/projectatmosphere/manager/ForecastOrchestrator.java @@ -14,6 +14,8 @@ import net.Gabou.projectatmosphere.modules.core.WindVector; import net.Gabou.projectatmosphere.modules.ocean.OceanBasinManager; import net.Gabou.projectatmosphere.modules.tornado.GlassDamageManager; +import net.Gabou.projectatmosphere.modules.weather.RegionalWeatherPhase; +import net.Gabou.projectatmosphere.modules.weather.ServerWeatherStateResolver; import net.Gabou.projectatmosphere.modules.region.ForecastRegion; import net.Gabou.projectatmosphere.modules.region.RegionForecastOrchestrator; import net.Gabou.projectatmosphere.modules.region.RegionOrchestratorBootstrap; @@ -21,9 +23,7 @@ import net.Gabou.projectatmosphere.util.BiomeInstanceKey; import net.Gabou.projectatmosphere.util.RegionInstanceKey; import net.Gabou.projectatmosphere.data.TornadoStorageManager; -import net.Gabou.projectatmosphere.modules.hurricane.HurricaneState; import net.Gabou.projectatmosphere.modules.tornado.TornadoProbabilityManager; -import net.Gabou.projectatmosphere.modules.hurricane.HurricaneManager; import net.Gabou.projectatmosphere.config.AtmoCommonConfig; import net.Gabou.projectatmosphere.modules.wind.WindEngine; @@ -190,7 +190,6 @@ public static void onPlayerLogin(ServerPlayer player, ServerLevel level) { BlockPos playerPos = player.blockPosition(); sendLoginStage(player, "Collecting nearby forecast regions", 0.16F, "player_login_collect_regions"); getNearbyRegions(level, player, 1000); - long start = System.nanoTime(); if (!ForecastDataStorage.playerData.containsKey(uuid)) { boolean shouldGenerate = true; for (BlockPos center : ForecastDataStorage.playerData.values()) { @@ -201,8 +200,6 @@ public static void onPlayerLogin(ServerPlayer player, ServerLevel level) { } if (shouldGenerate) { - if(ProjectAtmosphere.DEBUG_MODE) - ProjectAtmosphere.LOGGER.info("[Atmosphere] Player " + player.getName().getString()); ForecastDataStorage.playerData.put(uuid, playerPos); sendLoginStage(player, "Seeding local weather systems", 0.28F, "player_login_seed_weather"); SimpleCloudsCompat.doInitialGenWithWeather(playerPos.getX(), playerPos.getZ(), level); @@ -212,11 +209,6 @@ public static void onPlayerLogin(ServerPlayer player, ServerLevel level) { SimpleCloudsCompat.setIsInit(true); sendLoginStage(player, "Forecast regions ready", 0.38F, "player_login_regions_ready"); - long end = System.nanoTime(); - long durationMs = (end - start) / 1_000_000; - if(ProjectAtmosphere.DEBUG_MODE) - ProjectAtmosphere.LOGGER.info("[Atmosphere] Forecast data prepared for player {} in {} ms", player.getName().getString(), durationMs); - } /** @@ -617,6 +609,10 @@ public static float getCurrentStormChance(RegionInstanceKey regionKey, long tick return 0f; } + public static RegionalWeatherPhase getWeatherPhase(ServerLevel level, RegionInstanceKey key, long tick) { + return ServerWeatherStateResolver.resolve(level, key, tick); + } + private static float computeStormChance(RegionAtmosphereState state) { float rain = Math.min(1f, state.getRainIntensity()); float cloud = state.getCloudCover(); @@ -663,8 +659,6 @@ public static void tick(ServerLevel level) { if (REGENERATING) { runAfterRegen(() -> AsyncAtmosphereService.runStorm(() -> TornadoProbabilityManager.onScheduledCheck(level))); } else { - if(ProjectAtmosphere.DEBUG_MODE) - ProjectAtmosphere.LOGGER.info("[Atmosphere] Checking for tornadoes..."); AsyncAtmosphereService.runStorm(() -> TornadoProbabilityManager.onScheduledCheck(level)); } } @@ -730,17 +724,6 @@ private static void initializeDynamicSystems(ServerLevel level) { OceanBasinManager.initialize(level); } - - public static HurricaneState getActiveHurricane() { - var hurricanes = HurricaneManager.getActiveHurricanes(); - if (hurricanes.isEmpty()) { - return null; - } - - var h = hurricanes.get(0); - return new HurricaneState(h.position.x, h.position.z, h.radius, h.radius * 0.5); - } - private static void sendLoginStage(ServerPlayer player, String subtext, float progress, String source) { NetworkHandler.CHANNEL.send( PacketDistributor.PLAYER.with(() -> player), diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/CloudGeneratorHurricaneReservationMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/CloudGeneratorHurricaneReservationMixin.java new file mode 100644 index 00000000..7ff1a121 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/CloudGeneratorHurricaneReservationMixin.java @@ -0,0 +1,69 @@ +package net.Gabou.projectatmosphere.mixin; + +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudTypeSource; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; +import dev.nonamecrackers2.simpleclouds.common.cloud.spawning.CloudGenerator; +import dev.nonamecrackers2.simpleclouds.api.common.cloud.spawning.SpawnInfo; +import net.Gabou.projectatmosphere.modules.hurricane.HurricaneSemantics; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Optional; + +@Mixin(value = CloudGenerator.class, remap = false) +public abstract class CloudGeneratorHurricaneReservationMixin { + @Shadow @Final protected CloudTypeSource cloudGetter; + + @Inject(method = "createRegion", at = @At("HEAD"), cancellable = true) + private void projectatmosphere$blockHurricaneRegionCreation(SpawnInfo info, float playerX, float playerZ, float x, float z, RandomSource random, boolean growTime, CallbackInfoReturnable> cir) { + Level level = HurricaneSemantics.resolveLevel(this.cloudGetter); + if (level == null) { + return; + } + + if (HurricaneSemantics.intersectsReservation(level, x, z, SimpleCloudsConstants.MIN_SPAWN_DIST_BETWEEN_REGIONS)) { + cir.setReturnValue(Optional.empty()); + } + } + + @Inject(method = "addCloud", at = @At("HEAD"), cancellable = true) + private void projectatmosphere$blockCloudsInsideHurricane(CloudRegion region, CloudGenerator.Order order, CallbackInfoReturnable cir) { + Level level = HurricaneSemantics.resolveLevel(this.cloudGetter); + if (level == null) { + return; + } + + double padding = region.getWorldRadius() + SimpleCloudsConstants.MIN_SPAWN_DIST_BETWEEN_REGIONS; + if (HurricaneSemantics.intersectsReservation(level, region.getWorldX(), region.getWorldZ(), padding)) { + cir.setReturnValue(false); + } + } + + @Inject(method = "getCloudAtPosition", at = @At("RETURN"), cancellable = true) + private void projectatmosphere$returnHurricaneReservationRegion(float x, float z, CallbackInfoReturnable cir) { + if (cir.getReturnValue() != null) { + return; + } + + Level level = HurricaneSemantics.resolveLevel(this.cloudGetter); + if (level == null) { + return; + } + + CloudRegion reservation = HurricaneSemantics.getReservationRegionAt( + level, + x * (double)SimpleCloudsConstants.CLOUD_SCALE, + z * (double)SimpleCloudsConstants.CLOUD_SCALE + ); + if (reservation != null) { + cir.setReturnValue(reservation); + } + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/CloudMeshGeneratorDiagnosticsAccessor.java b/src/main/java/net/Gabou/projectatmosphere/mixin/CloudMeshGeneratorDiagnosticsAccessor.java new file mode 100644 index 00000000..21457f34 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/CloudMeshGeneratorDiagnosticsAccessor.java @@ -0,0 +1,34 @@ +package net.Gabou.projectatmosphere.mixin; + +import dev.nonamecrackers2.simpleclouds.client.mesh.chunk.MeshChunk; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator; +import org.apache.commons.lang3.tuple.Pair; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; +import java.util.Queue; + +@Mixin(value = CloudMeshGenerator.class, remap = false) +public interface CloudMeshGeneratorDiagnosticsAccessor { + @Accessor("chunks") + List projectatmosphere$getChunks(); + + @Accessor("completedGenTasks") + List projectatmosphere$getCompletedGenTasks(); + + @Accessor("chunkGenTasks") + Queue projectatmosphere$getChunkGenTasks(); + + @Accessor("tasksPerTick") + int projectatmosphere$getTasksPerTick(); + + @Accessor("opaqueBufferSize") + int projectatmosphere$getOpaqueBufferSize(); + + @Accessor("transparentBufferSize") + int projectatmosphere$getTransparentBufferSize(); + + @Accessor("meshGenStatus") + Pair projectatmosphere$getMeshGenStatus(); +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/CloudMeshGeneratorDiagnosticsMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/CloudMeshGeneratorDiagnosticsMixin.java new file mode 100644 index 00000000..c3abbb1c --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/CloudMeshGeneratorDiagnosticsMixin.java @@ -0,0 +1,112 @@ +package net.Gabou.projectatmosphere.mixin; + +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator; +import net.Gabou.projectatmosphere.client.render.SimpleCloudsRenderDiagnostics; +import org.apache.commons.lang3.tuple.Pair; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.List; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicBoolean; + +@Mixin(value = CloudMeshGenerator.class, remap = false) +public abstract class CloudMeshGeneratorDiagnosticsMixin { + @Unique + private static final AtomicBoolean PROJECTATMOSPHERE$GEN_TICK_EMPTY_LOGGED = new AtomicBoolean(); + @Unique + private static final AtomicBoolean PROJECTATMOSPHERE$GEN_TICK_ACTIVE_LOGGED = new AtomicBoolean(); + @Unique + private static final AtomicBoolean PROJECTATMOSPHERE$FINALIZE_EMPTY_LOGGED = new AtomicBoolean(); + @Unique + private static final AtomicBoolean PROJECTATMOSPHERE$FINALIZE_ACTIVE_LOGGED = new AtomicBoolean(); + + @Inject(method = "genTick", at = @At("HEAD")) + private void projectatmosphere$logGenTickStart(double originX, double originY, double originZ, net.minecraft.client.renderer.culling.Frustum frustum, float partialTick, CallbackInfo ci) { + CloudMeshGeneratorDiagnosticsAccessor accessor = (CloudMeshGeneratorDiagnosticsAccessor)(Object)this; + int queuedTasks = projectatmosphere$count(accessor.projectatmosphere$getChunkGenTasks()); + int completedTasks = projectatmosphere$count(accessor.projectatmosphere$getCompletedGenTasks()); + int tasksPerTick = accessor.projectatmosphere$getTasksPerTick(); + if ((queuedTasks > 0 || completedTasks > 0 || tasksPerTick > 0) && PROJECTATMOSPHERE$GEN_TICK_ACTIVE_LOGGED.compareAndSet(false, true)) { + SimpleCloudsRenderDiagnostics.logPipelineStage( + "mesh_generator", + "genTick_active", + accessor.projectatmosphere$getChunks() == null ? 0 : accessor.projectatmosphere$getChunks().size(), + queuedTasks, + completedTasks, + true, + true, + accessor.projectatmosphere$getMeshGenStatus() + ); + } else if (PROJECTATMOSPHERE$GEN_TICK_EMPTY_LOGGED.compareAndSet(false, true)) { + SimpleCloudsRenderDiagnostics.logPipelineStage( + "mesh_generator", + "genTick_empty", + accessor.projectatmosphere$getChunks() == null ? 0 : accessor.projectatmosphere$getChunks().size(), + queuedTasks, + completedTasks, + true, + true, + accessor.projectatmosphere$getMeshGenStatus() + ); + } + } + + @Inject(method = "finalizeMeshGen", at = @At("RETURN")) + private void projectatmosphere$logFinalizeMeshGen(CallbackInfoReturnable> cir) { + CloudMeshGeneratorDiagnosticsAccessor accessor = (CloudMeshGeneratorDiagnosticsAccessor)(Object)this; + int completedTasks = projectatmosphere$count(accessor.projectatmosphere$getCompletedGenTasks()); + int opaqueElements = projectatmosphere$countElements(accessor.projectatmosphere$getChunks(), false); + int transparentElements = projectatmosphere$countElements(accessor.projectatmosphere$getChunks(), true); + boolean active = completedTasks > 0 || opaqueElements > 0 || transparentElements > 0; + if (active && PROJECTATMOSPHERE$FINALIZE_ACTIVE_LOGGED.compareAndSet(false, true)) { + SimpleCloudsRenderDiagnostics.logFinalizeMeshGen( + completedTasks, + opaqueElements, + transparentElements, + accessor.projectatmosphere$getOpaqueBufferSize(), + accessor.projectatmosphere$getTransparentBufferSize(), + cir.getReturnValue() + ); + } else if (PROJECTATMOSPHERE$FINALIZE_EMPTY_LOGGED.compareAndSet(false, true)) { + SimpleCloudsRenderDiagnostics.logFinalizeMeshGen( + completedTasks, + opaqueElements, + transparentElements, + accessor.projectatmosphere$getOpaqueBufferSize(), + accessor.projectatmosphere$getTransparentBufferSize(), + cir.getReturnValue() + ); + } + } + + @Unique + private static int projectatmosphere$count(List values) { + return values == null ? 0 : values.size(); + } + + @Unique + private static int projectatmosphere$count(Queue values) { + return values == null ? 0 : values.size(); + } + + @Unique + private static int projectatmosphere$countElements(List chunks, boolean transparent) { + if (chunks == null) { + return 0; + } + int total = 0; + for (dev.nonamecrackers2.simpleclouds.client.mesh.chunk.MeshChunk chunk : chunks) { + if (transparent) { + total += chunk.getTransparentBuffers().map(dev.nonamecrackers2.simpleclouds.client.mesh.chunk.MeshChunk.BufferSet::getElementCount).orElse(0); + } else { + total += chunk.getOpaqueBuffers().getElementCount(); + } + } + return total; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/CloudMeshGeneratorShaderMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/CloudMeshGeneratorShaderMixin.java new file mode 100644 index 00000000..35e0f39a --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/CloudMeshGeneratorShaderMixin.java @@ -0,0 +1,41 @@ +package net.Gabou.projectatmosphere.mixin; + +import com.google.common.collect.ImmutableMap; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.shader.compute.ComputeShader; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.Gabou.projectatmosphere.client.render.SimpleCloudsRenderDiagnostics; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceProvider; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.io.IOException; + +@Mixin(value = CloudMeshGenerator.class, remap = false) +public abstract class CloudMeshGeneratorShaderMixin { + @Redirect( + method = "createShader", + at = @At( + value = "INVOKE", + target = "Ldev/nonamecrackers2/simpleclouds/client/shader/compute/ComputeShader;loadShader(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/server/packs/resources/ResourceProvider;IIILcom/google/common/collect/ImmutableMap;)Ldev/nonamecrackers2/simpleclouds/client/shader/compute/ComputeShader;" + ) + ) + private ComputeShader projectatmosphere$useProjectAtmosphereCubeMeshShader(ResourceLocation loc, ResourceProvider provider, int localX, int localY, int localZ, ImmutableMap parameters) throws IOException { + ResourceLocation shaderLoc = loc; + if (loc != null && "simpleclouds".equals(loc.getNamespace()) && "cube_mesh".equals(loc.getPath())) { + shaderLoc = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "cube_mesh"); + } + ComputeShader shader = ComputeShader.loadShader(shaderLoc, provider, localX, localY, localZ, parameters); + SimpleCloudsRenderDiagnostics.logShaderLoad( + "cube_mesh", + loc, + shaderLoc, + shader == null ? "null" : shader.getName(), + shader == null ? -1 : shader.getId(), + shader != null && shader.isValid() + ); + return shader; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/MultiRegionCloudMeshGeneratorMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/MultiRegionCloudMeshGeneratorMixin.java index d12dc5bb..4de6c8f8 100644 --- a/src/main/java/net/Gabou/projectatmosphere/mixin/MultiRegionCloudMeshGeneratorMixin.java +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/MultiRegionCloudMeshGeneratorMixin.java @@ -1,76 +1,212 @@ package net.Gabou.projectatmosphere.mixin; -import net.Gabou.projectatmosphere.api.common.cloud.region.ITornadoRegion; -import net.Gabou.projectatmosphere.api.common.cloud.region.TornadoDescriptor; +import com.google.common.collect.ImmutableMap; import dev.nonamecrackers2.simpleclouds.client.mesh.generator.MultiRegionCloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.BindingManager; import dev.nonamecrackers2.simpleclouds.client.shader.buffer.ShaderStorageBufferObject; import dev.nonamecrackers2.simpleclouds.client.shader.compute.ComputeShader; import dev.nonamecrackers2.simpleclouds.common.cloud.CloudInfo; import dev.nonamecrackers2.simpleclouds.common.cloud.CloudType; import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudGetter; import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.Gabou.projectatmosphere.api.common.cloud.region.ITornadoRegion; +import net.Gabou.projectatmosphere.api.common.cloud.region.TornadoDescriptor; +import net.Gabou.projectatmosphere.client.hurricane.ClientHurricaneStateCache; +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.Gabou.projectatmosphere.modules.hurricane.HurricaneInstance; +import net.Gabou.projectatmosphere.util.HurricaneUpload; +import net.Gabou.projectatmosphere.util.RegionUpload; +import net.Gabou.projectatmosphere.util.TornadoUpload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceProvider; import net.minecraft.util.Mth; -import org.lwjgl.opengl.GL41; -import org.lwjgl.opengl.GL43; -import org.joml.Matrix2f; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.joml.Matrix2f; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL41; +import org.lwjgl.opengl.GL43; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.io.IOException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Objects; @Mixin(value = MultiRegionCloudMeshGenerator.class, remap = false) public abstract class MultiRegionCloudMeshGeneratorMixin { - @Unique private static final Logger PROJECTATMOSPHERE$LOGGER = LogManager.getLogger("ProjectAtmosphere/CloudMeshGenerator"); - @Unique private static final String PROJECTATMOSPHERE$TORNADO_BUFFER_NAME = "CloudTornadoes"; + @Unique + private static final Logger PROJECTATMOSPHERE$LOGGER = LogManager.getLogger("ProjectAtmosphere/CloudMeshGenerator"); + @Unique + private static final String PROJECTATMOSPHERE$STORM_BUFFER_NAME = "CloudStorms"; + @Unique + private static final ResourceLocation PROJECTATMOSPHERE$HURRICANE_CLOUD_ID = HurricaneInstance.HURRICANE_CLOUD_TYPE_ID; @Shadow @Final public static int MAX_CLOUD_FORMATIONS; - @Shadow protected ComputeShader regionTextureGenerator; - @Shadow protected CloudGetter cloudGetter; - @Shadow protected CloudInfo[] cachedTypes; - @Unique private static final int PROJECTATMOSPHERE$MAX_TORNADOES = Math.max(1, Integer.getInteger("projectatmosphere.simpleclouds.maxTornadoes", 64)); - @Unique private static final int PROJECTATMOSPHERE$TORNADO_STRIDE = 32; + @Unique + private static final int PROJECTATMOSPHERE$MAX_TORNADOES = 64; + @Unique + private static final int PROJECTATMOSPHERE$MAX_UNIFORM_TORNADOES = 16; + @Unique + private static final int PROJECTATMOSPHERE$MAX_HURRICANES = 8; + @Unique + private static final int PROJECTATMOSPHERE$TORNADO_STRIDE = 32; + @Unique + private static final int PROJECTATMOSPHERE$HURRICANE_STRIDE = 64; - @Unique private ShaderStorageBufferObject projectatmosphere$tornadoBuffer; + @Unique private ShaderStorageBufferObject projectatmosphere$stormBuffer; + @Unique private boolean projectatmosphere$stormBufferUsesBindingManager; @Unique private int projectatmosphere$currentTornadoCount; - @Unique private boolean projectatmosphere$hasTornadoBlock; - @Unique private int projectatmosphere$lastTornadoShaderId = -1; + @Unique private int projectatmosphere$currentHurricaneCount; + @Unique private boolean projectatmosphere$hasStormBlock; + @Unique private boolean projectatmosphere$stormBufferUnavailableLogged; + @Unique private int projectatmosphere$lastStormShaderId = -1; - @Inject(method = "setupShader", at = @At("TAIL")) - private void projectatmosphere$setupTornadoSsbo(CallbackInfo ci) { - this.projectatmosphere$ensureTornadoBuffer(); + @Inject(method = "uploadCloudRegionData", at = @At("TAIL")) + private void projectatmosphere$uploadNativeStormData(float partialTick, CallbackInfo ci) { + this.projectatmosphere$uploadStormData(partialTick); } - @Inject(method = "uploadCloudRegionData", at = @At("TAIL")) - private void projectatmosphere$uploadTornadoData(float partialTick, CallbackInfo ci) { - if (this.regionTextureGenerator == null || !this.regionTextureGenerator.isValid()) { + @Redirect( + method = "initExtra", + at = @At( + value = "INVOKE", + target = "Ldev/nonamecrackers2/simpleclouds/client/shader/compute/ComputeShader;loadShader(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/server/packs/resources/ResourceProvider;IIILcom/google/common/collect/ImmutableMap;)Ldev/nonamecrackers2/simpleclouds/client/shader/compute/ComputeShader;" + ) + ) + private ComputeShader projectatmosphere$useProjectAtmosphereCloudRegionShader(ResourceLocation loc, ResourceProvider provider, int localX, int localY, int localZ, ImmutableMap parameters) throws IOException { + ResourceLocation shaderLoc = loc; + if (loc != null && "simpleclouds".equals(loc.getNamespace()) && "cloud_regions".equals(loc.getPath())) { + shaderLoc = ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "cloud_regions"); + } + return ComputeShader.loadShader(shaderLoc, provider, localX, localY, localZ, parameters); + } + + @Inject(method = "determineChunkGenSettings", at = @At("HEAD"), cancellable = true) + private void projectatmosphere$includeHurricanesInChunkHeights(float minX, float minZ, float maxX, float maxZ, CallbackInfoReturnable cir) { + List hurricanes = ClientHurricaneStateCache.getRenderableHurricanes(0.0F); + if (hurricanes.isEmpty() || this.cloudGetter == null) { return; } - this.projectatmosphere$ensureTornadoBuffer(); - if (this.projectatmosphere$tornadoBuffer == null) { - if (this.projectatmosphere$currentTornadoCount != 0) { - this.projectatmosphere$currentTornadoCount = 0; - this.projectatmosphere$updateTornadoUniforms(0); + + CloudType hurricaneType = this.cloudGetter.getCloudTypeForId(PROJECTATMOSPHERE$HURRICANE_CLOUD_ID); + if (hurricaneType == null) { + return; + } + + float[][] positions = new float[][]{{minX, minZ}, {minX, maxZ}, {maxX, minZ}, {maxX, maxZ}}; + int smallestStartHeight = 0; + int largestEndHeight = 0; + boolean hasRenderableContent = false; + boolean initializedHeights = false; + + for (float[] pos : positions) { + var typeAt = this.cloudGetter.getCloudTypeAtPosition(pos[0], pos[1]); + if (typeAt.getRight() < 1.0F) { + hasRenderableContent = true; + } + CloudType type = typeAt.getLeft(); + if (type == null) { + continue; + } + int startHeight = type.noiseConfig().getStartHeight(); + int endHeight = type.noiseConfig().getEndHeight(); + if (!initializedHeights || smallestStartHeight > startHeight) { + smallestStartHeight = startHeight; + } + if (!initializedHeights || largestEndHeight < endHeight) { + largestEndHeight = endHeight; + } + initializedHeights = true; + } + + for (ClientHurricaneStateCache.RenderableHurricane hurricane : hurricanes) { + if (!this.projectatmosphere$hurricaneIntersects(hurricane, minX, minZ, maxX, maxZ)) { + continue; + } + int startHeight = Mth.floor(hurricane.anchorY() + hurricaneType.noiseConfig().getStartHeight()); + int endHeight = Mth.ceil(hurricane.anchorY() + hurricaneType.noiseConfig().getEndHeight()); + if (!initializedHeights || smallestStartHeight > startHeight) { + smallestStartHeight = startHeight; } + if (!initializedHeights || largestEndHeight < endHeight) { + largestEndHeight = endHeight; + } + hasRenderableContent = true; + initializedHeights = true; + } + + if (!hasRenderableContent || !initializedHeights || smallestStartHeight == largestEndHeight) { + cir.setReturnValue(this.projectatmosphere$invokeSkipSettings()); + } else { + cir.setReturnValue(this.projectatmosphere$invokeHeightSettings(smallestStartHeight, largestEndHeight)); + } + } + + @Inject(method = "close", at = @At("HEAD")) + private void projectatmosphere$resetStormBuffers(CallbackInfo ci) { + this.projectatmosphere$closeManualStormBuffer(); + this.projectatmosphere$stormBuffer = null; + this.projectatmosphere$stormBufferUsesBindingManager = false; + this.projectatmosphere$currentTornadoCount = 0; + this.projectatmosphere$currentHurricaneCount = 0; + this.projectatmosphere$hasStormBlock = false; + this.projectatmosphere$stormBufferUnavailableLogged = false; + this.projectatmosphere$lastStormShaderId = -1; + } + + @Unique + private void projectatmosphere$uploadStormData(float partialTick) { + if (this.regionTextureGenerator == null || !this.regionTextureGenerator.isValid()) { return; } + List uploads = this.projectatmosphere$collectTornadoUploads(partialTick); - int count = Math.min(uploads.size(), PROJECTATMOSPHERE$MAX_TORNADOES); - if (count > 0) { - this.projectatmosphere$tornadoBuffer.writeData(buffer -> { - for (int i = 0; i < count; i++) { + List hurricaneUploads = this.projectatmosphere$collectHurricaneUploads(partialTick); + int tornadoCount = Math.min(uploads.size(), PROJECTATMOSPHERE$MAX_TORNADOES); + int uniformTornadoCount = Math.min(tornadoCount, PROJECTATMOSPHERE$MAX_UNIFORM_TORNADOES); + int hurricaneCount = Math.min(hurricaneUploads.size(), PROJECTATMOSPHERE$MAX_HURRICANES); + if (tornadoCount <= 0 && hurricaneCount <= 0) { + this.projectatmosphere$updateStormUniforms(0, 0); + this.projectatmosphere$currentTornadoCount = 0; + this.projectatmosphere$currentHurricaneCount = 0; + return; + } + + this.projectatmosphere$updateTornadoUniformData(uploads, uniformTornadoCount); + if (hurricaneCount <= 0) { + this.projectatmosphere$updateStormUniforms(uniformTornadoCount, 0); + this.projectatmosphere$currentTornadoCount = uniformTornadoCount; + this.projectatmosphere$currentHurricaneCount = 0; + return; + } + + this.projectatmosphere$ensureStormBuffer(); + if (this.projectatmosphere$stormBuffer == null) { + this.projectatmosphere$updateStormUniforms(uniformTornadoCount, 0); + this.projectatmosphere$currentTornadoCount = uniformTornadoCount; + this.projectatmosphere$currentHurricaneCount = 0; + return; + } + + this.projectatmosphere$bindStormBufferToShaders(); + this.projectatmosphere$stormBuffer.writeData(buffer -> { + for (int i = 0; i < PROJECTATMOSPHERE$MAX_TORNADOES; i++) { + if (i < tornadoCount) { TornadoUpload upload = uploads.get(i); buffer.putFloat(upload.typeIndex); buffer.putFloat(upload.centerX); @@ -80,28 +216,95 @@ public abstract class MultiRegionCloudMeshGeneratorMixin { buffer.putFloat(upload.height); buffer.putFloat(0.0F); buffer.putFloat(0.0F); + } else { + for (int j = 0; j < PROJECTATMOSPHERE$TORNADO_STRIDE / Float.BYTES; j++) { + buffer.putFloat(0.0F); + } } - buffer.flip(); - }, count * PROJECTATMOSPHERE$TORNADO_STRIDE, false); - } - this.projectatmosphere$currentTornadoCount = count; - this.projectatmosphere$updateTornadoUniforms(count); + } + for (int i = 0; i < PROJECTATMOSPHERE$MAX_HURRICANES; i++) { + if (i < hurricaneCount) { + HurricaneUpload upload = hurricaneUploads.get(i); + buffer.putFloat(upload.typeIndex()); + buffer.putFloat(upload.centerX()); + buffer.putFloat(upload.centerZ()); + buffer.putFloat(upload.anchorY()); + buffer.putFloat(upload.coreRadius()); + buffer.putFloat(upload.stormExtentRadius()); + buffer.putFloat(upload.eyeRadius()); + buffer.putFloat(upload.edgeFade()); + buffer.putFloat(upload.bandCount()); + buffer.putFloat(upload.bandWidth()); + buffer.putFloat(upload.spiralTightness()); + buffer.putFloat(upload.rotationPhase()); + buffer.putFloat(upload.rotationSpeed()); + buffer.putFloat(upload.transitionStart()); + buffer.putFloat(upload.transitionEnd()); + buffer.putFloat(0.0F); + } else { + for (int j = 0; j < PROJECTATMOSPHERE$HURRICANE_STRIDE / Float.BYTES; j++) { + buffer.putFloat(0.0F); + } + } + } + buffer.flip(); + }, this.projectatmosphere$stormBufferSize(), false); + + this.projectatmosphere$currentTornadoCount = uniformTornadoCount; + this.projectatmosphere$currentHurricaneCount = hurricaneCount; + this.projectatmosphere$updateStormUniforms(uniformTornadoCount, hurricaneCount); } @Unique - private void projectatmosphere$ensureTornadoBuffer() { - if (this.regionTextureGenerator == null || this.projectatmosphere$tornadoBuffer != null) { + private void projectatmosphere$ensureStormBuffer() { + if (this.projectatmosphere$stormBuffer != null && this.projectatmosphere$stormBuffer.getId() == -1) { + this.projectatmosphere$stormBuffer = null; + this.projectatmosphere$stormBufferUsesBindingManager = false; + } + if (this.regionTextureGenerator == null || this.projectatmosphere$stormBuffer != null) { return; } - if (!this.projectatmosphere$supportsTornadoBuffer()) { + if (!this.projectatmosphere$supportsStormBuffer()) { + return; + } + ShaderStorageBufferObject newBuffer = null; + try { + newBuffer = this.projectatmosphere$createStormBuffer(); + if (newBuffer != null) { + newBuffer.allocateBuffer(this.projectatmosphere$stormBufferSize()); + this.projectatmosphere$stormBuffer = newBuffer; + this.projectatmosphere$bindStormBufferToShaders(); + } + } catch (Throwable e) { + if (newBuffer != null) { + this.projectatmosphere$closeFailedStormBuffer(newBuffer); + } + this.projectatmosphere$stormBuffer = null; + this.projectatmosphere$stormBufferUsesBindingManager = false; + if (!this.projectatmosphere$stormBufferUnavailableLogged) { + this.projectatmosphere$stormBufferUnavailableLogged = true; + PROJECTATMOSPHERE$LOGGER.warn("Unable to allocate Simple Clouds storm SSBO '{}'; tornado cloud carving and hurricane cloud shaping are disabled for this client. Cause: {}", PROJECTATMOSPHERE$STORM_BUFFER_NAME, e.getMessage()); + } + } + } + + @Unique + private void projectatmosphere$bindStormBufferToShaders() { + if (this.projectatmosphere$stormBuffer == null || this.projectatmosphere$stormBuffer.getId() == -1) { return; } - this.projectatmosphere$tornadoBuffer = this.regionTextureGenerator.createAndBindSSBO(PROJECTATMOSPHERE$TORNADO_BUFFER_NAME, GL43.GL_DYNAMIC_DRAW); - if (this.projectatmosphere$tornadoBuffer != null) { - this.projectatmosphere$tornadoBuffer.allocateBuffer(PROJECTATMOSPHERE$MAX_TORNADOES * PROJECTATMOSPHERE$TORNADO_STRIDE); + ComputeShader shader = this.projectatmosphere$getShader(); + if (shader != null && shader.isValid()) { + this.projectatmosphere$stormBuffer.optionalBindToProgram(PROJECTATMOSPHERE$STORM_BUFFER_NAME, shader.getId()); } } + @Unique + private int projectatmosphere$stormBufferSize() { + return PROJECTATMOSPHERE$MAX_TORNADOES * PROJECTATMOSPHERE$TORNADO_STRIDE + + PROJECTATMOSPHERE$MAX_HURRICANES * PROJECTATMOSPHERE$HURRICANE_STRIDE; + } + @Unique private List projectatmosphere$collectTornadoUploads(float partialTick) { List uploads = new ArrayList<>(); @@ -133,6 +336,42 @@ public abstract class MultiRegionCloudMeshGeneratorMixin { return uploads; } + @Unique + private List projectatmosphere$collectHurricaneUploads(float partialTick) { + List uploads = new ArrayList<>(); + int hurricaneTypeIndex = this.projectatmosphere$findCloudTypeIndex(PROJECTATMOSPHERE$HURRICANE_CLOUD_ID); + if (hurricaneTypeIndex < 0) { + return uploads; + } + + for (ClientHurricaneStateCache.RenderableHurricane hurricane : ClientHurricaneStateCache.getRenderableHurricanes(partialTick)) { + if (!Objects.equals(hurricane.cloudTypeId(), PROJECTATMOSPHERE$HURRICANE_CLOUD_ID)) { + continue; + } + uploads.add(new HurricaneUpload( + hurricaneTypeIndex, + (float)hurricane.centerX(), + (float)hurricane.centerZ(), + hurricane.anchorY(), + hurricane.coreRadius(), + hurricane.stormExtentRadius(), + hurricane.eyeRadius(), + Math.max(1.0F, hurricane.edgeFade()), + Math.max(1, hurricane.bandCount()), + Math.max(1.0F, hurricane.bandWidth()), + hurricane.spiralTightness(), + hurricane.rotationPhase(), + hurricane.rotationSpeed(), + hurricane.transitionStart(), + hurricane.transitionEnd() + )); + if (uploads.size() >= PROJECTATMOSPHERE$MAX_HURRICANES) { + break; + } + } + return uploads; + } + @Unique private List projectatmosphere$getRenderableRegions(float partialTick) { List regions = new ArrayList<>(); @@ -155,16 +394,16 @@ public abstract class MultiRegionCloudMeshGeneratorMixin { @Unique private float[] projectatmosphere$buildRegionUpload(float partialTick, CloudRegion region) { Matrix2f transform = region.createTransform(partialTick); - int typeIndex = this.projectatmosphere$findCloudTypeIndex(region); + int typeIndex = this.projectatmosphere$findCloudTypeIndex(region.getCloudTypeId()); return new float[]{ - region.getPosX(partialTick), - region.getPosZ(partialTick), - typeIndex, - region.getRadius(partialTick), - transform.m00, - transform.m01, - transform.m10, - transform.m11 + region.getPosX(partialTick), + region.getPosZ(partialTick), + typeIndex, + region.getRadius(partialTick), + transform.m00, + transform.m01, + transform.m10, + transform.m11 }; } @@ -174,11 +413,14 @@ public abstract class MultiRegionCloudMeshGeneratorMixin { } @Unique - private int projectatmosphere$findCloudTypeIndex(CloudRegion region) { - if (this.cachedTypes == null || this.cachedTypes.length == 0) { + private int projectatmosphere$findCloudTypeIndex(ResourceLocation cloudTypeId) { + if (this.cachedTypes == null || this.cachedTypes.length == 0 || this.cloudGetter == null) { + return -1; + } + CloudType type = this.cloudGetter.getCloudTypeForId(cloudTypeId); + if (type == null) { return -1; } - CloudType type = this.cloudGetter.getCloudTypeForId(region.getCloudTypeId()); for (int i = 0; i < this.cachedTypes.length; i++) { if (Objects.equals(this.cachedTypes[i], type)) { return i; @@ -188,37 +430,155 @@ public abstract class MultiRegionCloudMeshGeneratorMixin { } @Unique - private boolean projectatmosphere$supportsTornadoBuffer() { + private boolean projectatmosphere$hurricaneIntersects(ClientHurricaneStateCache.RenderableHurricane hurricane, float minX, float minZ, float maxX, float maxZ) { + float closestX = Mth.clamp((float)hurricane.centerX(), minX, maxX); + float closestZ = Mth.clamp((float)hurricane.centerZ(), minZ, maxZ); + float dx = closestX - (float)hurricane.centerX(); + float dz = closestZ - (float)hurricane.centerZ(); + float radius = hurricane.stormExtentRadius() + hurricane.edgeFade(); + return dx * dx + dz * dz <= radius * radius; + } + + @Unique + private boolean projectatmosphere$supportsStormBuffer() { if (this.regionTextureGenerator == null || !this.regionTextureGenerator.isValid()) { return false; } + if (AtmoCommonConfig.DISABLE_SIMPLE_CLOUDS_TORNADO_SSBO.get()) { + if (!this.projectatmosphere$stormBufferUnavailableLogged) { + this.projectatmosphere$stormBufferUnavailableLogged = true; + PROJECTATMOSPHERE$LOGGER.warn("Simple Clouds storm SSBO integration is disabled by config; tornado cloud carving will use uniforms and hurricane cloud shaping is disabled."); + } + return false; + } + int maxBindings = GL11.glGetInteger(GL43.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS); + if (maxBindings <= 16) { + if (!this.projectatmosphere$stormBufferUnavailableLogged) { + this.projectatmosphere$stormBufferUnavailableLogged = true; + PROJECTATMOSPHERE$LOGGER.warn("Disabling Simple Clouds storm SSBO integration because this GPU exposes only {} shader storage buffer bindings. Tornado cloud carving will use uniforms and hurricane cloud shaping is disabled.", maxBindings); + } + return false; + } int shaderId = this.regionTextureGenerator.getId(); if (shaderId <= 0) { return false; } - if (shaderId != this.projectatmosphere$lastTornadoShaderId) { - this.projectatmosphere$lastTornadoShaderId = shaderId; - int index = GL43.glGetProgramResourceIndex(shaderId, GL43.GL_SHADER_STORAGE_BLOCK, PROJECTATMOSPHERE$TORNADO_BUFFER_NAME); - this.projectatmosphere$hasTornadoBlock = index != GL43.GL_INVALID_INDEX; - if (!this.projectatmosphere$hasTornadoBlock) { - PROJECTATMOSPHERE$LOGGER.warn("Missing '{}' SSBO on shader '{}'; tornado rendering disabled until the shader is updated.", PROJECTATMOSPHERE$TORNADO_BUFFER_NAME, this.regionTextureGenerator.getName()); + if (shaderId != this.projectatmosphere$lastStormShaderId) { + this.projectatmosphere$lastStormShaderId = shaderId; + int index = GL43.glGetProgramResourceIndex(shaderId, GL43.GL_SHADER_STORAGE_BLOCK, PROJECTATMOSPHERE$STORM_BUFFER_NAME); + this.projectatmosphere$hasStormBlock = index != GL43.GL_INVALID_INDEX; + if (!this.projectatmosphere$hasStormBlock) { + PROJECTATMOSPHERE$LOGGER.warn("Missing '{}' SSBO on shader '{}'; tornado cloud carving and hurricane cloud shaping are disabled until the shader is updated.", PROJECTATMOSPHERE$STORM_BUFFER_NAME, this.regionTextureGenerator.getName()); } } - return this.projectatmosphere$hasTornadoBlock; + return this.projectatmosphere$hasStormBlock; + } + + @Unique + private ShaderStorageBufferObject projectatmosphere$createStormBuffer() { + this.projectatmosphere$stormBufferUsesBindingManager = true; + return this.regionTextureGenerator.createAndBindSSBO(PROJECTATMOSPHERE$STORM_BUFFER_NAME, GL43.GL_DYNAMIC_DRAW); + } + + @Unique + private void projectatmosphere$closeFailedStormBuffer(ShaderStorageBufferObject buffer) { + if (this.projectatmosphere$stormBufferUsesBindingManager) { + BindingManager.freeSSBO(buffer); + } else { + buffer.close(); + } + this.projectatmosphere$stormBufferUsesBindingManager = false; } @Unique - private void projectatmosphere$updateTornadoUniforms(int count) { + private void projectatmosphere$closeManualStormBuffer() { + if (this.projectatmosphere$stormBuffer != null && !this.projectatmosphere$stormBufferUsesBindingManager && this.projectatmosphere$stormBuffer.getId() != -1) { + this.projectatmosphere$stormBuffer.close(); + } + } + + @Unique + private void projectatmosphere$updateStormUniforms(int tornadoCount, int hurricaneCount) { if (this.regionTextureGenerator != null && this.regionTextureGenerator.isValid()) { - this.regionTextureGenerator.forUniform("TotalCloudTornadoes", (program, location) -> GL41.glProgramUniform1i(program, location, count)); + this.regionTextureGenerator.forUniform("TotalCloudTornadoes", (program, location) -> GL41.glProgramUniform1i(program, location, tornadoCount)); + this.regionTextureGenerator.forUniform("TotalCloudHurricanes", (program, location) -> GL41.glProgramUniform1i(program, location, hurricaneCount)); } ComputeShader shader = this.projectatmosphere$getShader(); if (shader != null && shader.isValid()) { - shader.forUniform("TotalCloudTornadoes", (program, location) -> GL41.glProgramUniform1i(program, location, count)); + shader.forUniform("TotalCloudTornadoes", (program, location) -> GL41.glProgramUniform1i(program, location, tornadoCount)); + shader.forUniform("TotalCloudHurricanes", (program, location) -> GL41.glProgramUniform1i(program, location, hurricaneCount)); + } + } + + @Unique + private void projectatmosphere$updateTornadoUniformData(List uploads, int tornadoCount) { + float[] data0 = new float[PROJECTATMOSPHERE$MAX_UNIFORM_TORNADOES * 4]; + float[] data1 = new float[PROJECTATMOSPHERE$MAX_UNIFORM_TORNADOES * 4]; + for (int i = 0; i < tornadoCount; i++) { + TornadoUpload upload = uploads.get(i); + int base = i * 4; + data0[base] = upload.typeIndex; + data0[base + 1] = upload.centerX; + data0[base + 2] = upload.centerZ; + data0[base + 3] = upload.radius; + data1[base] = upload.bottom; + data1[base + 1] = upload.height; + } + + this.projectatmosphere$setTornadoRegionUniforms(this.regionTextureGenerator, data0); + this.projectatmosphere$setTornadoMeshUniforms(this.projectatmosphere$getShader(), data0, data1); + } + + @Unique + private void projectatmosphere$setTornadoRegionUniforms(ComputeShader shader, float[] data0) { + if (shader == null || !shader.isValid()) { + return; + } + shader.forUniform("CloudTornadoData0[0]", (program, location) -> GL41.glProgramUniform4fv(program, location, data0)); + } + + @Unique + private void projectatmosphere$setTornadoMeshUniforms(ComputeShader shader, float[] data0, float[] data1) { + if (shader == null || !shader.isValid()) { + return; } + shader.forUniform("CloudTornadoData0[0]", (program, location) -> GL41.glProgramUniform4fv(program, location, data0)); + shader.forUniform("CloudTornadoData1[0]", (program, location) -> GL41.glProgramUniform4fv(program, location, data1)); } + + @Unique + private static Method projectatmosphere$skipSettingsMethod; + @Unique + private static Method projectatmosphere$heightSettingsMethod; + @Unique private ComputeShader projectatmosphere$getShader() { - return ((CloudMeshGeneratorAccessor) (Object) this).projectatmosphere$getShader(); + return ((CloudMeshGeneratorAccessor)(Object)this).projectatmosphere$getShader(); + } + + @Unique + private Object projectatmosphere$invokeSkipSettings() { + try { + if (projectatmosphere$skipSettingsMethod == null) { + projectatmosphere$skipSettingsMethod = dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator.class.getDeclaredMethod("skip"); + projectatmosphere$skipSettingsMethod.setAccessible(true); + } + return projectatmosphere$skipSettingsMethod.invoke(null); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to invoke Simple Clouds skip() helper", e); + } + } + + @Unique + private Object projectatmosphere$invokeHeightSettings(int minHeight, int maxHeight) { + try { + if (projectatmosphere$heightSettingsMethod == null) { + projectatmosphere$heightSettingsMethod = dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator.class.getDeclaredMethod("heights", int.class, int.class); + projectatmosphere$heightSettingsMethod.setAccessible(true); + } + return projectatmosphere$heightSettingsMethod.invoke(null, minHeight, maxHeight); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to invoke Simple Clouds heights(...) helper", e); + } } } diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/RegionUpload.java b/src/main/java/net/Gabou/projectatmosphere/mixin/RegionUpload.java deleted file mode 100644 index 066f9059..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/mixin/RegionUpload.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.Gabou.projectatmosphere.mixin; - -import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; - -final class RegionUpload { - final CloudRegion region; - final float[] data; - - RegionUpload(CloudRegion region, float[] data) { - this.region = region; - this.data = data; - } -} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/SimpleCloudsCloudManagerMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/SimpleCloudsCloudManagerMixin.java new file mode 100644 index 00000000..912982e5 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/SimpleCloudsCloudManagerMixin.java @@ -0,0 +1,102 @@ +package net.Gabou.projectatmosphere.mixin; + +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudType; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import net.Gabou.projectatmosphere.modules.hurricane.HurricaneSemanticSample; +import net.Gabou.projectatmosphere.modules.hurricane.HurricaneSemantics; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import org.apache.commons.lang3.tuple.Pair; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = CloudManager.class, remap = false) +public abstract class SimpleCloudsCloudManagerMixin { + @Shadow protected T level; + + @Shadow public abstract CloudType getCloudTypeForId(ResourceLocation id); + + @Inject(method = "getCloudTypeAtPosition", at = @At("RETURN"), cancellable = true) + private void projectatmosphere$surfaceHurricaneType(float x, float z, CallbackInfoReturnable> cir) { + HurricaneSemanticSample sample = HurricaneSemantics.sampleBest(this.level, x * (double)SimpleCloudsConstants.CLOUD_SCALE, z * (double)SimpleCloudsConstants.CLOUD_SCALE); + CloudType hurricaneType = this.getCloudTypeForId(sample.cloudTypeId()); + if (hurricaneType == null) { + return; + } + + if (sample.inEye()) { + cir.setReturnValue(Pair.of(hurricaneType, 1.0F)); + return; + } + if (!sample.isPresent()) { + return; + } + + Pair current = cir.getReturnValue(); + float currentCoverage = 1.0F - current.getRight(); + if (sample.coverage() >= currentCoverage || current.getLeft() == SimpleCloudsConstants.EMPTY) { + cir.setReturnValue(Pair.of(hurricaneType, 1.0F - sample.coverage())); + } + } + + @Inject(method = "getRainLevel", at = @At("HEAD"), cancellable = true) + private void projectatmosphere$forceHurricaneRain(float x, float y, float z, CallbackInfoReturnable cir) { + HurricaneSemanticSample sample = HurricaneSemantics.sampleBest(this.level, x, z); + if (sample.inEye()) { + cir.setReturnValue(0.0F); + return; + } + if (!sample.isPresent()) { + return; + } + + CloudType hurricaneType = this.getCloudTypeForId(sample.cloudTypeId()); + if (hurricaneType == null || !hurricaneType.weatherType().includesRain()) { + return; + } + + float stormStartY = sample.anchorY() + hurricaneType.stormStart() * SimpleCloudsConstants.CLOUD_SCALE; + float verticalFade = 1.0F - Mth.clamp((y - stormStartY) / SimpleCloudsConstants.RAIN_VERTICAL_FADE, 0.0F, 1.0F); + cir.setReturnValue(Mth.clamp(sample.rainStrength() * verticalFade, 0.0F, 1.0F)); + } + + @Inject(method = "getPrecipitationAt", at = @At("HEAD"), cancellable = true) + private void projectatmosphere$forceHurricanePrecipitation(BlockPos pos, CallbackInfoReturnable> cir) { + if (!this.level.canSeeSky(pos) || this.level.getHeightmapPos(net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING, pos).getY() > pos.getY()) { + cir.setReturnValue(Pair.of(false, Biome.Precipitation.NONE)); + return; + } + + HurricaneSemanticSample sample = HurricaneSemantics.sampleBest(this.level, pos.getX() + 0.5D, pos.getZ() + 0.5D); + if (sample.inEye()) { + cir.setReturnValue(Pair.of(false, Biome.Precipitation.NONE)); + return; + } + if (!sample.isPresent()) { + return; + } + + CloudType hurricaneType = this.getCloudTypeForId(sample.cloudTypeId()); + if (hurricaneType == null || !hurricaneType.weatherType().includesRain()) { + return; + } + + float stormStartY = sample.anchorY() + hurricaneType.stormStart() * SimpleCloudsConstants.CLOUD_SCALE; + if ((float)pos.getY() + 0.5F > stormStartY || sample.rainStrength() <= 0.12F) { + cir.setReturnValue(Pair.of(false, Biome.Precipitation.NONE)); + return; + } + + Biome.Precipitation precipitation = this.level.getBiome(pos).value().coldEnoughToSnow(pos) + ? Biome.Precipitation.SNOW + : Biome.Precipitation.RAIN; + cir.setReturnValue(Pair.of(true, precipitation)); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/SimpleCloudsRendererMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/SimpleCloudsRendererMixin.java deleted file mode 100644 index f7753b6e..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/mixin/SimpleCloudsRendererMixin.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.Gabou.projectatmosphere.mixin; - -import com.mojang.blaze3d.vertex.PoseStack; -import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; -import net.Gabou.projectatmosphere.render.HurricaneMeshRenderer; -import org.joml.Matrix4f; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(SimpleCloudsRenderer.class) -public abstract class SimpleCloudsRendererMixin { - - @Inject( - method = "renderBeforeWeather(Lcom/mojang/blaze3d/vertex/PoseStack;Lorg/joml/Matrix4f;FDDD)V", - at = @At("TAIL"), - remap = false - ) - private void pa$afterBeforeWeather(PoseStack stack, Matrix4f projMat, float partialTick, - double camX, double camY, double camZ, CallbackInfo ci) { - SimpleCloudsRenderer self = (SimpleCloudsRenderer)(Object)this; - - // Enter Simple Clouds' cloud space (translate + scale + cloud height) - stack.pushPose(); - self.translateClouds(stack, camX, camY, camZ); - - // Draw hurricane ring in cloud space (y≈0 is cloud plane) - HurricaneMeshRenderer.renderCloudSpace(self, stack, projMat, partialTick, camX, camZ); - - stack.popPose(); - } -} - diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/TornadoUpload.java b/src/main/java/net/Gabou/projectatmosphere/mixin/TornadoUpload.java deleted file mode 100644 index 5abd27d9..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/mixin/TornadoUpload.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.Gabou.projectatmosphere.mixin; - -final class TornadoUpload { - final float typeIndex; - final float centerX; - final float centerZ; - final float radius; - final float bottom; - final float height; - - TornadoUpload(float typeIndex, float centerX, float centerZ, float radius, float bottom, float height) { - this.typeIndex = typeIndex; - this.centerX = centerX; - this.centerZ = centerZ; - this.radius = radius; - this.bottom = bottom; - this.height = height; - } -} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/client/BindingManagerAccessor.java b/src/main/java/net/Gabou/projectatmosphere/mixin/client/BindingManagerAccessor.java new file mode 100644 index 00000000..4b0f6b5d --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/client/BindingManagerAccessor.java @@ -0,0 +1,14 @@ +package net.Gabou.projectatmosphere.mixin.client; + +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.BindingManager; +import it.unimi.dsi.fastutil.ints.IntList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(value = BindingManager.class, remap = false) +public interface BindingManagerAccessor { + @Accessor("ALL_SHADER_STORAGE_BINDINGS") + static IntList projectatmosphere$getShaderStorageBindings() { + throw new AssertionError(); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/client/DefaultPipelineHurricaneMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/client/DefaultPipelineHurricaneMixin.java new file mode 100644 index 00000000..3c148084 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/client/DefaultPipelineHurricaneMixin.java @@ -0,0 +1,73 @@ +package net.Gabou.projectatmosphere.mixin.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import dev.nonamecrackers2.simpleclouds.client.renderer.pipeline.DefaultPipeline; +import net.Gabou.projectatmosphere.client.hurricane.ClientHurricaneStateCache; +import net.Gabou.projectatmosphere.client.render.SimpleCloudsHurricaneRenderer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.culling.Frustum; +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = DefaultPipeline.class, remap = false) +public abstract class DefaultPipelineHurricaneMixin { + @Inject( + method = "afterSky", + at = @At( + value = "INVOKE", + target = "Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;copyDepthFromCloudsToMain()V" + ), + require = 0 + ) + private void projectatmosphere$renderHurricaneOpaque(Minecraft mc, SimpleCloudsRenderer renderer, + PoseStack stack, Matrix4f projMat, float partialTick, + double camX, double camY, double camZ, Frustum frustum, + CallbackInfo ci) { + ClientLevel level = mc.level; + if (level == null || !ClientHurricaneStateCache.hasHurricanes()) { + return; + } + + float[] cloudColor = renderer.getCloudColor(partialTick); + mc.getProfiler().push("projectatmosphere_hurricane_opaque"); + SimpleCloudsHurricaneRenderer.INSTANCE.prepareFrame(level, partialTick); + SimpleCloudsHurricaneRenderer.INSTANCE.renderOpaque( + renderer, stack, projMat, partialTick, cloudColor[0], cloudColor[1], cloudColor[2] + ); + mc.getProfiler().pop(); + } + + @Inject( + method = "afterSky", + at = @At( + value = "INVOKE", + target = "Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;doFinalCompositePass(Lcom/mojang/blaze3d/vertex/PoseStack;FLorg/joml/Matrix4f;)V", + shift = At.Shift.BEFORE + ), + require = 0 + ) + private void projectatmosphere$renderHurricaneTransparency(Minecraft mc, SimpleCloudsRenderer renderer, + PoseStack stack, Matrix4f projMat, float partialTick, + double camX, double camY, double camZ, Frustum frustum, + CallbackInfo ci) { + ClientLevel level = mc.level; + if (level == null || !ClientHurricaneStateCache.hasHurricanes()) { + return; + } + + float[] cloudColor = renderer.getCloudColor(partialTick); + mc.getProfiler().push("projectatmosphere_hurricane_transparency"); + SimpleCloudsHurricaneRenderer.INSTANCE.prepareFrame(level, partialTick); + renderer.copyDepthFromCloudsToTransparency(); + renderer.getCloudTransparencyTarget().bindWrite(false); + SimpleCloudsHurricaneRenderer.INSTANCE.renderTransparency( + renderer, stack, projMat, partialTick, cloudColor[0], cloudColor[1], cloudColor[2] + ); + mc.getProfiler().pop(); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/client/DefaultPipelineTornadoMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/client/DefaultPipelineTornadoMixin.java new file mode 100644 index 00000000..bbbf2139 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/client/DefaultPipelineTornadoMixin.java @@ -0,0 +1,96 @@ +package net.Gabou.projectatmosphere.mixin.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import dev.nonamecrackers2.simpleclouds.client.renderer.pipeline.DefaultPipeline; +import net.Gabou.projectatmosphere.client.render.SimpleCloudsTornadoRenderer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.culling.Frustum; +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = DefaultPipeline.class, remap = false) +public abstract class DefaultPipelineTornadoMixin { + @Inject( + method = "afterSky", + at = @At( + value = "INVOKE", + target = "Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;copyDepthFromCloudsToMain()V" + ), + require = 0 + ) + private void projectatmosphere$renderTornadoOpaque(Minecraft mc, SimpleCloudsRenderer renderer, + PoseStack stack, Matrix4f projMat, float partialTick, + double camX, double camY, double camZ, Frustum frustum, + CallbackInfo ci) { + ClientLevel level = mc.level; + if (level == null) { + return; + } + boolean pathLog = SimpleCloudsTornadoRenderer.shouldPathLog(level); + if (pathLog) { + SimpleCloudsTornadoRenderer.path( + "DefaultPipeline hook entered gameTime={} dhLoaded={} mainDepth={} cloudDepth={} transparencyDepth={}", + level.getGameTime(), + SimpleCloudsMod.dhLoaded(), + mc.getMainRenderTarget().getDepthTextureId(), + renderer.getCloudTarget().getDepthTextureId(), + renderer.getCloudTransparencyTarget().getDepthTextureId() + ); + } + if (SimpleCloudsMod.dhLoaded()) { + if (pathLog) { + SimpleCloudsTornadoRenderer.path("DefaultPipeline skipped: SimpleClouds reports DH loaded"); + } + return; + } + float[] cloudColor = renderer.getCloudColor(partialTick); + mc.getProfiler().push("projectatmosphere_tornado_opaque"); + SimpleCloudsTornadoRenderer.INSTANCE.prepareFrame(level, partialTick); + boolean hasPreparedTornado = SimpleCloudsTornadoRenderer.INSTANCE.hasPreparedTornadoes(); + if (pathLog) { + SimpleCloudsTornadoRenderer.path( + "DefaultPipeline prepared tornadoes={} hasPrepared={} frustumGate=disabled", + SimpleCloudsTornadoRenderer.INSTANCE.preparedTornadoCount(), + hasPreparedTornado + ); + } + if (!hasPreparedTornado) { + if (pathLog) { + SimpleCloudsTornadoRenderer.path("DefaultPipeline skipped: no prepared tornado"); + } + mc.getProfiler().pop(); + return; + } + boolean downsampled = SimpleCloudsTornadoRenderer.INSTANCE.usesDownsamplePath(); + renderer.copyDepthFromCloudsToTransparency(); + int primaryDepth = renderer.getCloudTransparencyTarget().getDepthTextureId(); + int secondaryDepth = mc.getMainRenderTarget().getDepthTextureId(); + if (pathLog) { + SimpleCloudsTornadoRenderer.path( + "DefaultPipeline drawing downsampled={} copiedTransparencyDepth=true primaryDepth={} secondaryDepth={} cloudTarget={}x{} transparencyTarget={}x{}", + downsampled, + primaryDepth, + secondaryDepth, + renderer.getCloudTarget().width, + renderer.getCloudTarget().height, + renderer.getCloudTransparencyTarget().width, + renderer.getCloudTransparencyTarget().height + ); + } + renderer.getCloudTarget().bindWrite(false); + SimpleCloudsTornadoRenderer.INSTANCE.renderOpaque( + renderer, stack, projMat, partialTick, cloudColor[0], cloudColor[1], cloudColor[2], + null, + primaryDepth, + secondaryDepth, + true + ); + mc.getProfiler().pop(); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/client/DhSupportPipelineDiagnosticsMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/client/DhSupportPipelineDiagnosticsMixin.java new file mode 100644 index 00000000..0de14e24 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/client/DhSupportPipelineDiagnosticsMixin.java @@ -0,0 +1,55 @@ +package net.Gabou.projectatmosphere.mixin.client; + +import dev.nonamecrackers2.simpleclouds.client.dh.pipeline.DhSupportPipeline; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import net.Gabou.projectatmosphere.client.render.SimpleCloudsRenderDiagnostics; +import net.Gabou.projectatmosphere.mixin.CloudMeshGeneratorDiagnosticsAccessor; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.culling.Frustum; +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = DhSupportPipeline.class, remap = false) +public abstract class DhSupportPipelineDiagnosticsMixin { + @Inject(method = "afterDistantHorizonsRender", at = @At("HEAD")) + private void projectatmosphere$beginDhPass(Minecraft mc, SimpleCloudsRenderer renderer, com.mojang.blaze3d.vertex.PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ, Frustum frustum, int dhFbo, CallbackInfo ci) { + CloudMeshGenerator generator = renderer.getMeshGenerator(); + CloudMeshGeneratorDiagnosticsAccessor accessor = (CloudMeshGeneratorDiagnosticsAccessor)(Object)generator; + SimpleCloudsRenderDiagnostics.beginDhPipelinePass( + accessor.projectatmosphere$getChunks() == null ? 0 : accessor.projectatmosphere$getChunks().size(), + accessor.projectatmosphere$getOpaqueBufferSize(), + accessor.projectatmosphere$getTransparentBufferSize(), + countElements(accessor, false), + countElements(accessor, true), + generator.canRender(), + generator.transparencyEnabled(), + accessor.projectatmosphere$getMeshGenStatus() + ); + } + + @Inject(method = "afterDistantHorizonsRender", at = @At("RETURN")) + private void projectatmosphere$endDhPass(Minecraft mc, SimpleCloudsRenderer renderer, com.mojang.blaze3d.vertex.PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ, Frustum frustum, int dhFbo, CallbackInfo ci) { + SimpleCloudsRenderDiagnostics.endDhPipelinePass(); + } + + private static int countElements(CloudMeshGeneratorDiagnosticsAccessor accessor, boolean transparent) { + if (accessor.projectatmosphere$getChunks() == null) { + return 0; + } + + int total = 0; + for (var chunk : accessor.projectatmosphere$getChunks()) { + if (transparent) { + total += chunk.getTransparentBuffers().map(dev.nonamecrackers2.simpleclouds.client.mesh.chunk.MeshChunk.BufferSet::getElementCount).orElse(0); + } else { + total += chunk.getOpaqueBuffers().getElementCount(); + } + } + return total; + } + +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/client/InstanceableMeshDiagnosticsMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/client/InstanceableMeshDiagnosticsMixin.java new file mode 100644 index 00000000..08f717df --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/client/InstanceableMeshDiagnosticsMixin.java @@ -0,0 +1,16 @@ +package net.Gabou.projectatmosphere.mixin.client; + +import dev.nonamecrackers2.simpleclouds.client.mesh.instancing.InstanceableMesh; +import net.Gabou.projectatmosphere.client.render.SimpleCloudsRenderDiagnostics; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = InstanceableMesh.class, remap = false) +public abstract class InstanceableMeshDiagnosticsMixin { + @Inject(method = "drawInstanced", at = @At("HEAD")) + private void projectatmosphere$recordDrawCount(int count, CallbackInfo ci) { + SimpleCloudsRenderDiagnostics.recordDraw("simpleclouds", count); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/client/MinecraftCrashHandlerMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/client/MinecraftCrashHandlerMixin.java new file mode 100644 index 00000000..e75b3b58 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/client/MinecraftCrashHandlerMixin.java @@ -0,0 +1,40 @@ +package net.Gabou.projectatmosphere.mixin.client; + +import net.Gabou.projectatmosphere.client.crash.ProjectAtmosphereCrashHandler; +import net.minecraft.CrashReport; +import net.minecraft.ReportedException; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public abstract class MinecraftCrashHandlerMixin { + @Shadow + protected abstract void runTick(boolean renderLevel); + + @Redirect(method = "run", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;runTick(Z)V")) + private void projectatmosphere$wrapRunTick(Minecraft minecraft, boolean renderLevel) { + try { + this.runTick(renderLevel); + } catch (ReportedException reportedException) { + if (!ProjectAtmosphereCrashHandler.handleCrashReport(minecraft, reportedException.getReport())) { + throw reportedException; + } + } catch (Throwable throwable) { + if (!ProjectAtmosphereCrashHandler.handleThrowable(minecraft, throwable, "Unexpected error")) { + throw throwable; + } + } + } + + @Inject(method = {"delayCrash", "delayCrashRaw"}, at = @At("HEAD"), cancellable = true) + private void projectatmosphere$handleDelayedCrash(CrashReport report, CallbackInfo ci) { + if (ProjectAtmosphereCrashHandler.handleCrashReport((Minecraft) (Object) this, report)) { + ci.cancel(); + } + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/client/ShaderSupportPipelineHurricaneMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/client/ShaderSupportPipelineHurricaneMixin.java new file mode 100644 index 00000000..2adc39e7 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/client/ShaderSupportPipelineHurricaneMixin.java @@ -0,0 +1,74 @@ +package net.Gabou.projectatmosphere.mixin.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import dev.nonamecrackers2.simpleclouds.client.renderer.pipeline.ShaderSupportPipeline; +import net.Gabou.projectatmosphere.client.hurricane.ClientHurricaneStateCache; +import net.Gabou.projectatmosphere.client.render.SimpleCloudsHurricaneRenderer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.culling.Frustum; +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = ShaderSupportPipeline.class, remap = false) +public abstract class ShaderSupportPipelineHurricaneMixin { + @Inject( + method = "afterLevel", + at = @At( + value = "INVOKE", + target = "Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;getCloudTransparencyTarget()Ldev/nonamecrackers2/simpleclouds/client/framebuffer/WeightedBlendingTarget;" + ), + require = 0 + ) + private void projectatmosphere$renderHurricaneOpaque(Minecraft mc, SimpleCloudsRenderer renderer, + PoseStack stack, Matrix4f projMat, float partialTick, + double camX, double camY, double camZ, Frustum frustum, + CallbackInfo ci) { + ClientLevel level = mc.level; + if (level == null || !ClientHurricaneStateCache.hasHurricanes()) { + return; + } + + float[] cloudColor = renderer.getCloudColor(partialTick); + mc.getProfiler().push("projectatmosphere_hurricane_opaque"); + SimpleCloudsHurricaneRenderer.INSTANCE.prepareFrame(level, partialTick); + renderer.getCloudTarget().bindWrite(false); + SimpleCloudsHurricaneRenderer.INSTANCE.renderOpaque( + renderer, stack, projMat, partialTick, cloudColor[0], cloudColor[1], cloudColor[2] + ); + mc.getProfiler().pop(); + } + + @Inject( + method = "afterLevel", + at = @At( + value = "INVOKE", + target = "Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;doFinalCompositePass(Lcom/mojang/blaze3d/vertex/PoseStack;FLorg/joml/Matrix4f;)V", + shift = At.Shift.BEFORE + ), + require = 0 + ) + private void projectatmosphere$renderHurricaneTransparency(Minecraft mc, SimpleCloudsRenderer renderer, + PoseStack stack, Matrix4f projMat, float partialTick, + double camX, double camY, double camZ, Frustum frustum, + CallbackInfo ci) { + ClientLevel level = mc.level; + if (level == null || !ClientHurricaneStateCache.hasHurricanes()) { + return; + } + + float[] cloudColor = renderer.getCloudColor(partialTick); + mc.getProfiler().push("projectatmosphere_hurricane_transparency"); + SimpleCloudsHurricaneRenderer.INSTANCE.prepareFrame(level, partialTick); + renderer.copyDepthFromCloudsToTransparency(); + renderer.getCloudTransparencyTarget().bindWrite(false); + SimpleCloudsHurricaneRenderer.INSTANCE.renderTransparency( + renderer, stack, projMat, partialTick, cloudColor[0], cloudColor[1], cloudColor[2] + ); + mc.getProfiler().pop(); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/client/ShaderSupportPipelineTornadoMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/client/ShaderSupportPipelineTornadoMixin.java new file mode 100644 index 00000000..555ffc78 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/client/ShaderSupportPipelineTornadoMixin.java @@ -0,0 +1,96 @@ +package net.Gabou.projectatmosphere.mixin.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import dev.nonamecrackers2.simpleclouds.client.renderer.pipeline.ShaderSupportPipeline; +import net.Gabou.projectatmosphere.client.render.SimpleCloudsTornadoRenderer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.culling.Frustum; +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = ShaderSupportPipeline.class, remap = false) +public abstract class ShaderSupportPipelineTornadoMixin { + @Inject( + method = "afterLevel", + at = @At( + value = "INVOKE", + target = "Ldev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer;getCloudTransparencyTarget()Ldev/nonamecrackers2/simpleclouds/client/framebuffer/WeightedBlendingTarget;" + ), + require = 0 + ) + private void projectatmosphere$renderTornadoOpaque(Minecraft mc, SimpleCloudsRenderer renderer, + PoseStack stack, Matrix4f projMat, float partialTick, + double camX, double camY, double camZ, Frustum frustum, + CallbackInfo ci) { + ClientLevel level = mc.level; + if (level == null) { + return; + } + boolean pathLog = SimpleCloudsTornadoRenderer.shouldPathLog(level); + if (pathLog) { + SimpleCloudsTornadoRenderer.path( + "ShaderSupportPipeline hook entered gameTime={} dhLoaded={} mainDepth={} cloudDepth={} transparencyDepth={}", + level.getGameTime(), + SimpleCloudsMod.dhLoaded(), + mc.getMainRenderTarget().getDepthTextureId(), + renderer.getCloudTarget().getDepthTextureId(), + renderer.getCloudTransparencyTarget().getDepthTextureId() + ); + } + if (SimpleCloudsMod.dhLoaded()) { + if (pathLog) { + SimpleCloudsTornadoRenderer.path("ShaderSupportPipeline skipped: SimpleClouds reports DH loaded"); + } + return; + } + float[] cloudColor = renderer.getCloudColor(partialTick); + mc.getProfiler().push("projectatmosphere_tornado_opaque"); + SimpleCloudsTornadoRenderer.INSTANCE.prepareFrame(level, partialTick); + boolean hasPreparedTornado = SimpleCloudsTornadoRenderer.INSTANCE.hasPreparedTornadoes(); + if (pathLog) { + SimpleCloudsTornadoRenderer.path( + "ShaderSupportPipeline prepared tornadoes={} hasPrepared={} frustumGate=disabled", + SimpleCloudsTornadoRenderer.INSTANCE.preparedTornadoCount(), + hasPreparedTornado + ); + } + if (!hasPreparedTornado) { + if (pathLog) { + SimpleCloudsTornadoRenderer.path("ShaderSupportPipeline skipped: no prepared tornado"); + } + mc.getProfiler().pop(); + return; + } + boolean downsampled = SimpleCloudsTornadoRenderer.INSTANCE.usesDownsamplePath(); + renderer.copyDepthFromCloudsToTransparency(); + int primaryDepth = renderer.getCloudTransparencyTarget().getDepthTextureId(); + int secondaryDepth = -1; + if (pathLog) { + SimpleCloudsTornadoRenderer.path( + "ShaderSupportPipeline drawing downsampled={} copiedTransparencyDepth=true primaryDepth={} secondaryDepth={} cloudTarget={}x{} transparencyTarget={}x{}", + downsampled, + primaryDepth, + secondaryDepth, + renderer.getCloudTarget().width, + renderer.getCloudTarget().height, + renderer.getCloudTransparencyTarget().width, + renderer.getCloudTransparencyTarget().height + ); + } + renderer.getCloudTarget().bindWrite(false); + SimpleCloudsTornadoRenderer.INSTANCE.renderOpaque( + renderer, stack, projMat, partialTick, cloudColor[0], cloudColor[1], cloudColor[2], + null, + primaryDepth, + secondaryDepth, + true + ); + mc.getProfiler().pop(); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/client/SimpleCloudsRendererDhFallbackMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/client/SimpleCloudsRendererDhFallbackMixin.java new file mode 100644 index 00000000..d204476d --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/client/SimpleCloudsRendererDhFallbackMixin.java @@ -0,0 +1,45 @@ +package net.Gabou.projectatmosphere.mixin.client; + +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import dev.nonamecrackers2.simpleclouds.client.dh.pipeline.DhSupportPipeline; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import dev.nonamecrackers2.simpleclouds.client.renderer.pipeline.CloudsRenderPipeline; +import net.Gabou.projectatmosphere.client.render.SimpleCloudsRenderDiagnostics; +import net.Gabou.projectatmosphere.mixin.CloudMeshGeneratorDiagnosticsAccessor; +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import javax.annotation.Nullable; + +@Mixin(value = SimpleCloudsRenderer.class, remap = false) +public abstract class SimpleCloudsRendererDhFallbackMixin { + @Shadow + @Nullable + private CloudsRenderPipeline renderPipelineThisPass; + + @Inject(method = "renderBeforeLevel", at = @At("TAIL")) + private void projectatmosphere$forceDhSupportPipeline(com.mojang.blaze3d.vertex.PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ, CallbackInfo ci) { + if (!SimpleCloudsMod.dhLoaded() || this.renderPipelineThisPass == DhSupportPipeline.INSTANCE) { + return; + } + + this.renderPipelineThisPass = DhSupportPipeline.INSTANCE; + + CloudMeshGenerator generator = ((SimpleCloudsRenderer)(Object)this).getMeshGenerator(); + CloudMeshGeneratorDiagnosticsAccessor accessor = (CloudMeshGeneratorDiagnosticsAccessor)(Object)generator; + SimpleCloudsRenderDiagnostics.logDhPipelineFallback( + "dh_support", + accessor.projectatmosphere$getChunks() == null ? 0 : accessor.projectatmosphere$getChunks().size(), + accessor.projectatmosphere$getChunkGenTasks() == null ? 0 : accessor.projectatmosphere$getChunkGenTasks().size(), + accessor.projectatmosphere$getCompletedGenTasks() == null ? 0 : accessor.projectatmosphere$getCompletedGenTasks().size(), + generator.canRender(), + generator.transparencyEnabled(), + accessor.projectatmosphere$getMeshGenStatus() + ); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/client/SimpleCloudsRendererDiagnosticsMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/client/SimpleCloudsRendererDiagnosticsMixin.java new file mode 100644 index 00000000..a7d1d19d --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/client/SimpleCloudsRendererDiagnosticsMixin.java @@ -0,0 +1,107 @@ +package net.Gabou.projectatmosphere.mixin.client; + +import dev.nonamecrackers2.simpleclouds.client.mesh.chunk.MeshChunk; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import net.Gabou.projectatmosphere.client.hurricane.ClientHurricaneStateCache; +import net.Gabou.projectatmosphere.client.render.SimpleCloudsRenderDiagnostics; +import net.Gabou.projectatmosphere.mixin.CloudMeshGeneratorDiagnosticsAccessor; +import net.minecraft.client.renderer.culling.Frustum; +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = SimpleCloudsRenderer.class, remap = false) +public abstract class SimpleCloudsRendererDiagnosticsMixin { + @Inject( + method = "renderCloudsOpaque(Ldev/nonamecrackers2/simpleclouds/client/mesh/generator/CloudMeshGenerator;Lcom/mojang/blaze3d/vertex/PoseStack;Lorg/joml/Matrix4f;FFFFFFLnet/minecraft/client/renderer/culling/Frustum;Z)V", + at = @At("HEAD"), + cancellable = true + ) + private static void projectatmosphere$beginOpaquePass(CloudMeshGenerator generator, com.mojang.blaze3d.vertex.PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, Frustum frustum, boolean ditherFade, CallbackInfo ci) { + if (ditherFade && ClientHurricaneStateCache.hasHurricanes()) { + SimpleCloudsRenderer.renderCloudsOpaque(generator, stack, projMat, fogStart, fogEnd, partialTick, r, g, b, frustum, false); + ci.cancel(); + return; + } + if (generator == null || SimpleCloudsRenderDiagnostics.isDhPipelineActive()) { + return; + } + + CloudMeshGeneratorDiagnosticsAccessor accessor = (CloudMeshGeneratorDiagnosticsAccessor)(Object)generator; + SimpleCloudsRenderDiagnostics.beginPass( + "opaque", + accessor.projectatmosphere$getChunks() == null ? 0 : accessor.projectatmosphere$getChunks().size(), + accessor.projectatmosphere$getOpaqueBufferSize(), + accessor.projectatmosphere$getTransparentBufferSize(), + projectatmosphere$countElements(accessor, false), + projectatmosphere$countElements(accessor, true), + generator.canRender(), + generator.transparencyEnabled(), + accessor.projectatmosphere$getMeshGenStatus() + ); + } + + @Inject(method = "renderCloudsOpaque(Ldev/nonamecrackers2/simpleclouds/client/mesh/generator/CloudMeshGenerator;Lcom/mojang/blaze3d/vertex/PoseStack;Lorg/joml/Matrix4f;FFFFFFLnet/minecraft/client/renderer/culling/Frustum;Z)V", at = @At("RETURN")) + private static void projectatmosphere$endOpaquePass(CloudMeshGenerator generator, com.mojang.blaze3d.vertex.PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, Frustum frustum, boolean ditherFade, CallbackInfo ci) { + if (SimpleCloudsRenderDiagnostics.isDhPipelineActive()) { + return; + } + SimpleCloudsRenderDiagnostics.endPass(); + } + + @Inject( + method = "renderCloudsTransparency(Ldev/nonamecrackers2/simpleclouds/client/mesh/generator/CloudMeshGenerator;Lcom/mojang/blaze3d/vertex/PoseStack;Lorg/joml/Matrix4f;FFFFFFLnet/minecraft/client/renderer/culling/Frustum;Z)V", + at = @At("HEAD"), + cancellable = true + ) + private static void projectatmosphere$beginTransparencyPass(CloudMeshGenerator generator, com.mojang.blaze3d.vertex.PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, Frustum frustum, boolean ditherFade, CallbackInfo ci) { + if (ditherFade && ClientHurricaneStateCache.hasHurricanes()) { + SimpleCloudsRenderer.renderCloudsTransparency(generator, stack, projMat, fogStart, fogEnd, partialTick, r, g, b, frustum, false); + ci.cancel(); + return; + } + if (generator == null || SimpleCloudsRenderDiagnostics.isDhPipelineActive()) { + return; + } + + CloudMeshGeneratorDiagnosticsAccessor accessor = (CloudMeshGeneratorDiagnosticsAccessor)(Object)generator; + SimpleCloudsRenderDiagnostics.beginPass( + "transparent", + accessor.projectatmosphere$getChunks() == null ? 0 : accessor.projectatmosphere$getChunks().size(), + accessor.projectatmosphere$getOpaqueBufferSize(), + accessor.projectatmosphere$getTransparentBufferSize(), + projectatmosphere$countElements(accessor, false), + projectatmosphere$countElements(accessor, true), + generator.canRender(), + generator.transparencyEnabled(), + accessor.projectatmosphere$getMeshGenStatus() + ); + } + + @Inject(method = "renderCloudsTransparency(Ldev/nonamecrackers2/simpleclouds/client/mesh/generator/CloudMeshGenerator;Lcom/mojang/blaze3d/vertex/PoseStack;Lorg/joml/Matrix4f;FFFFFFLnet/minecraft/client/renderer/culling/Frustum;Z)V", at = @At("RETURN")) + private static void projectatmosphere$endTransparencyPass(CloudMeshGenerator generator, com.mojang.blaze3d.vertex.PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, Frustum frustum, boolean ditherFade, CallbackInfo ci) { + if (SimpleCloudsRenderDiagnostics.isDhPipelineActive()) { + return; + } + SimpleCloudsRenderDiagnostics.endPass(); + } + + private static int projectatmosphere$countElements(CloudMeshGeneratorDiagnosticsAccessor accessor, boolean transparent) { + if (accessor.projectatmosphere$getChunks() == null) { + return 0; + } + + int total = 0; + for (MeshChunk chunk : accessor.projectatmosphere$getChunks()) { + if (transparent) { + total += chunk.getTransparentBuffers().map(MeshChunk.BufferSet::getElementCount).orElse(0); + } else { + total += chunk.getOpaqueBuffers().getElementCount(); + } + } + return total; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/client/SimpleCloudsRendererLightningBufferMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/client/SimpleCloudsRendererLightningBufferMixin.java new file mode 100644 index 00000000..0a37596e --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/client/SimpleCloudsRendererLightningBufferMixin.java @@ -0,0 +1,59 @@ +package net.Gabou.projectatmosphere.mixin.client; + +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import it.unimi.dsi.fastutil.ints.IntList; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL43; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyConstant; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = SimpleCloudsRenderer.class, remap = false) +public abstract class SimpleCloudsRendererLightningBufferMixin { + @Unique + private static final Logger PROJECTATMOSPHERE$LOGGER = LogManager.getLogger(ProjectAtmosphere.MODID + "/SimpleCloudsCompat"); + + @Unique + private int projectatmosphere$lightningBufferCountOverride; + @Unique + private boolean projectatmosphere$loggedLightningBufferCap; + + @Inject(method = {"onResourceManagerReload", "m_6213_"}, at = @At("HEAD"), require = 0) + private void projectatmosphere$resetLightningBufferCount(CallbackInfo ci) { + this.projectatmosphere$lightningBufferCountOverride = 0; + } + + @ModifyConstant(method = {"onResourceManagerReload", "m_6213_"}, constant = @Constant(intValue = 3), require = 0) + private int projectatmosphere$capLightningBufferCount(int original) { + if (this.projectatmosphere$lightningBufferCountOverride > 0) { + return this.projectatmosphere$lightningBufferCountOverride; + } + + IntList usedBindings = BindingManagerAccessor.projectatmosphere$getShaderStorageBindings(); + int maxBindings = GL11.glGetInteger(GL43.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS); + int usablePositiveBindings = Math.max(0, maxBindings - 1); + int availableBindings = usablePositiveBindings - usedBindings.size(); + int capped = Math.max(1, Math.min(original, availableBindings)); + this.projectatmosphere$lightningBufferCountOverride = capped; + + if (capped < original && !this.projectatmosphere$loggedLightningBufferCap) { + this.projectatmosphere$loggedLightningBufferCap = true; + PROJECTATMOSPHERE$LOGGER.warn( + "Capping Simple Clouds lightning SSBO buffers from {} to {} because this client has {} shader-storage bindings and {} are already reserved before lightning setup.", + original, + capped, + maxBindings, + usedBindings.size() + ); + } + + return capped; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/compat/auroras/AuroraRendererMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/compat/auroras/AuroraRendererMixin.java index ad862397..1a20f30e 100644 --- a/src/main/java/net/Gabou/projectatmosphere/mixin/compat/auroras/AuroraRendererMixin.java +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/compat/auroras/AuroraRendererMixin.java @@ -1,53 +1,55 @@ package net.Gabou.projectatmosphere.mixin.compat.auroras; -import net.Gabou.projectatmosphere.compat.auroras.AuroraSeasonHelper; +import auroras.util.AuroraData; +import com.mojang.blaze3d.vertex.PoseStack; import net.Gabou.projectatmosphere.client.render.SkyEffectState; -import net.Gabou.projectatmosphere.compat.temperature.ClientTemperatureResolver; +import net.Gabou.projectatmosphere.compat.auroras.AuroraCompatController; import net.minecraft.client.Minecraft; -import net.minecraft.core.BlockPos; import net.minecraft.world.level.Level; -import net.minecraft.world.level.biome.Biome; +import org.joml.Matrix4f; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -/** - * Safely scales aurora brightness using Project Atmosphere seasonal boosts. - */ @Mixin(targets = "auroras.util.AuroraRenderer", remap = false) public abstract class AuroraRendererMixin { - /** - * The first float local variable stored in render() is nlBrightness. - * We intercept it right after it’s calculated. - */ @ModifyVariable( method = "render(Lauroras/util/AuroraData;Lnet/minecraft/client/Minecraft;Lnet/minecraft/world/level/Level;Lcom/mojang/blaze3d/vertex/PoseStack;Lorg/joml/Matrix4f;F)V", at = @At(value = "STORE"), ordinal = 0, - require = 0 // don’t crash if the target changes + index = 7, + require = 0 ) private float projectatmosphere$scaleAuroraBrightness(float nlBrightness) { - Minecraft mc = Minecraft.getInstance(); - if (mc == null || mc.player == null || mc.level == null) { - SkyEffectState.setAurora(false, null); - return 0.0f; - } + return AuroraCompatController.scaleBrightness(nlBrightness); + } + + @ModifyVariable( + method = "render(Lauroras/util/AuroraData;Lnet/minecraft/client/Minecraft;Lnet/minecraft/world/level/Level;Lcom/mojang/blaze3d/vertex/PoseStack;Lorg/joml/Matrix4f;F)V", + at = @At(value = "STORE"), + ordinal = 0, + index = 20, + require = 0 + ) + private float projectatmosphere$overrideRainLevel(float rainLevel) { + return AuroraCompatController.overrideRainLevel(rainLevel); + } - Level level = mc.level; - BlockPos pos = mc.player.blockPosition(); - double time = level.getTimeOfDay(0.0f); - boolean night = time < 0.25 || time > 0.75; - Biome biome = level.getBiome(pos).value(); - float tempC = ClientTemperatureResolver.getCelsius(level, pos); - boolean cold = biome.coldEnoughToSnow(pos) || tempC <= 4.0f; - if (!night || !cold) { - SkyEffectState.setAurora(false, null); - return 0.0f; - } - float boost = AuroraSeasonHelper.combinedBoost(level, pos); - float scaled = Math.min(1.0F, nlBrightness * boost); - SkyEffectState.setAurora(scaled > 0.01f, mc.player.position()); - return scaled; + @Inject( + method = "render(Lauroras/util/AuroraData;Lnet/minecraft/client/Minecraft;Lnet/minecraft/world/level/Level;Lcom/mojang/blaze3d/vertex/PoseStack;Lorg/joml/Matrix4f;F)V", + at = @At("RETURN") + ) + private void projectatmosphere$publishAuroraState(AuroraData auroraData, + Minecraft minecraft, + Level level, + PoseStack poseStack, + Matrix4f projectionMatrix, + float partialTick, + CallbackInfo ci) { + boolean active = minecraft != null && minecraft.player != null && AuroraCompatController.isActive(); + SkyEffectState.setAurora(active, active ? minecraft.player.position() : null); } } diff --git a/src/main/java/net/Gabou/projectatmosphere/mixin/compat/rainbows/RainbowsRendererParticleMixin.java b/src/main/java/net/Gabou/projectatmosphere/mixin/compat/rainbows/RainbowsRendererParticleMixin.java index cf5ad456..8292670c 100644 --- a/src/main/java/net/Gabou/projectatmosphere/mixin/compat/rainbows/RainbowsRendererParticleMixin.java +++ b/src/main/java/net/Gabou/projectatmosphere/mixin/compat/rainbows/RainbowsRendererParticleMixin.java @@ -3,8 +3,8 @@ import com.mojang.blaze3d.vertex.VertexConsumer; import net.Gabou.projectatmosphere.client.render.SkyEffectState; import net.Gabou.projectatmosphere.compat.rainbows.RainbowWeatherTracker; +import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; -import net.minecraft.util.Mth; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -12,65 +12,68 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -/** - * Adjusts rainbow rendering to follow Project Atmosphere weather. - */ @Mixin(targets = "rainbows.util.RainbowsRendererParticle", remap = false) public abstract class RainbowsRendererParticleMixin { @Shadow public double rainbowTick; - /** - * Replaces the rain level float when the renderer calls level.getRainLevel(partialTicks). - * The target is the first stored float local in render(). - */ @ModifyVariable( method = "render(Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/client/Camera;F)V", at = @At(value = "STORE"), ordinal = 0, + index = 5, require = 0 ) private float projectatmosphere$overrideRainLevel(float rainLevel) { - Minecraft mc = Minecraft.getInstance(); - if (mc.level == null || !RainbowWeatherTracker.isEnabled()) - return rainLevel; - float tracked = RainbowWeatherTracker.getRainLevel(mc.level.dimension()); - if (tracked > 0.01f) { - SkyEffectState.setRainbow(false, null); - } - return tracked; + return RainbowWeatherTracker.getRainLevelOverride(rainLevel); + } + + @ModifyVariable( + method = "render(Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/client/Camera;F)V", + at = @At(value = "STORE"), + ordinal = 0, + index = 7, + require = 0 + ) + private double projectatmosphere$scaleRainbowBrightness(double brightness) { + return RainbowWeatherTracker.scaleBrightness(brightness); } - /** - * Hooks right before Math.sin() is called to optionally trigger rainbow appearance - * when rain stops, depending on Project Atmosphere’s tracker. - */ @Inject( - method = "render", + method = "render(Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/client/Camera;F)V", at = @At("HEAD") ) - private void projectatmosphere$triggerWhenRainStops(VertexConsumer buffer, - net.minecraft.client.Camera camera, + private void projectatmosphere$syncRainbowLifecycle(VertexConsumer buffer, + Camera camera, float partialTicks, CallbackInfo ci) { Minecraft mc = Minecraft.getInstance(); - if (mc.level == null || !RainbowWeatherTracker.isEnabled()) - return; - - float rain = RainbowWeatherTracker.getRainLevel(mc.level.dimension()); - if (rain > 0.01f) { + if (mc.level == null || !RainbowWeatherTracker.isEnabled()) { SkyEffectState.setRainbow(false, null); return; } - if (RainbowWeatherTracker.consumeRainStop(mc.level.dimension())) { - double time = mc.level.getTimeOfDay(partialTicks); - double brightness = Mth.clamp(Math.cos(Math.PI * 2 * time) * 3.0, 0.0, 1.0); - if (brightness > 0.0 && this.rainbowTick <= 0.0) - this.rainbowTick = 5000.0; + if ((RainbowWeatherTracker.shouldRender() || RainbowWeatherTracker.consumeActivationPulse()) && this.rainbowTick <= 0.0D) { + this.rainbowTick = 5000.0D; } - if (this.rainbowTick > 0.0) { - SkyEffectState.setRainbow(true, mc.player != null ? mc.player.position() : null); + if (!RainbowWeatherTracker.shouldRender() && RainbowWeatherTracker.getVisualStrength() < 0.02F) { + this.rainbowTick = 0.0D; } } + + @Inject( + method = "render(Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/client/Camera;F)V", + at = @At("RETURN") + ) + private void projectatmosphere$publishRainbowState(VertexConsumer buffer, + Camera camera, + float partialTicks, + CallbackInfo ci) { + Minecraft mc = Minecraft.getInstance(); + boolean active = mc.level != null + && RainbowWeatherTracker.shouldRender() + && this.rainbowTick > 0.0D + && RainbowWeatherTracker.getVisualStrength() > 0.02F; + SkyEffectState.setRainbow(active, active && mc.player != null ? mc.player.position() : null); + } } diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/atmosphere/AtmosphereStatusSyncManager.java b/src/main/java/net/Gabou/projectatmosphere/modules/atmosphere/AtmosphereStatusSyncManager.java new file mode 100644 index 00000000..3e75097e --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/atmosphere/AtmosphereStatusSyncManager.java @@ -0,0 +1,49 @@ +package net.Gabou.projectatmosphere.modules.atmosphere; + +import net.Gabou.projectatmosphere.api.AtmoApi; +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; +import net.Gabou.projectatmosphere.network.NetworkHandler; +import net.Gabou.projectatmosphere.network.SyncAtmosphereStatusPacket; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraftforge.network.PacketDistributor; + +public final class AtmosphereStatusSyncManager { + private AtmosphereStatusSyncManager() { + } + + public static void syncPlayers(ServerLevel level) { + int interval = Math.max(1, AtmoCommonConfig.FOG_SYNC_INTERVAL_TICKS.get()); + if (level.getGameTime() % interval != 0L) { + return; + } + + for (ServerPlayer player : level.players()) { + syncPlayer(player); + } + } + + public static void syncPlayer(ServerPlayer player) { + if (player == null) { + return; + } + + ServerLevel level = player.serverLevel(); + BlockPos pos = player.blockPosition(); + long gameTime = level.getGameTime(); + float humidity = ForecastOrchestrator.getCurrentHumidity(level, pos, gameTime); + var snapshot = AtmoApi.getInstance().getWeatherSnapshot(level, pos, gameTime); + + NetworkHandler.CHANNEL.send( + PacketDistributor.PLAYER.with(() -> player), + new SyncAtmosphereStatusPacket( + Mth.clamp(humidity, 0.0F, 100.0F), + Mth.clamp(snapshot.rainIntensity(), 0.0F, 1.0F), + Mth.clamp(snapshot.cloudCover(), 0.0F, 1.0F) + ) + ); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/atmosphere/CloudManager.java b/src/main/java/net/Gabou/projectatmosphere/modules/atmosphere/CloudManager.java index 3f30d502..f229f0fb 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/atmosphere/CloudManager.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/atmosphere/CloudManager.java @@ -225,10 +225,6 @@ private static RegionSample sampleRegion(RegionDescriptor descriptor, List radius+regionRadius) { continue; } @@ -381,10 +377,6 @@ private static void applyBiomeContributions(Map ACTIVE_CYCLONES = new CopyOnWriteArrayList<>(); + private static final Map ACTIVE_SNAPSHOTS = new ConcurrentHashMap<>(); private static final long COOLDOWN_TICKS = 24000L * 2; private static long lastSpawnTick = -COOLDOWN_TICKS; private static long lastMidnightTick = -1L; @@ -27,11 +33,19 @@ private CycloneManager() { public static void initialize(ServerLevel level) { ACTIVE_CYCLONES.clear(); + ACTIVE_SNAPSHOTS.clear(); lastSpawnTick = level.getDayTime(); lastMidnightTick = -1L; spawnInitialCyclones(level); } + public static List getActiveCycloneSnapshots() { + if (ACTIVE_SNAPSHOTS.isEmpty()) { + return List.of(); + } + return List.copyOf(ACTIVE_SNAPSHOTS.values()); + } + public static void update(ServerLevel level) { if (AtmosphericStateRegistry.isEmpty()) { return; @@ -55,6 +69,9 @@ public static void update(ServerLevel level) { applyCyclone(result); if (result.remove()) { ACTIVE_CYCLONES.remove(cyclone); + ACTIVE_SNAPSHOTS.remove(cyclone.id); + } else { + ACTIVE_SNAPSHOTS.put(cyclone.id, cyclone.snapshot()); } } ); @@ -114,13 +131,15 @@ private static void spawnCyclone(ServerLevel level, List float pressureDrop = 5f + random.nextFloat() * 10f; long lifetime = 24000L + random.nextInt(24000); - ACTIVE_CYCLONES.add(new Cyclone( + Cyclone cyclone = new Cyclone( new Vec2(state.getPosition().getX(), state.getPosition().getZ()), radius, intensity, pressureDrop, lifetime - )); + ); + ACTIVE_CYCLONES.add(cyclone); + ACTIVE_SNAPSHOTS.put(cyclone.id, cyclone.snapshot()); lastSpawnTick = level.getDayTime(); } @@ -153,6 +172,7 @@ private static List findNearbyStates(ServerLevel level) { private static final class Cyclone { + private final UUID id = UUID.randomUUID(); private Vec2 center; private float radius; private float intensity; @@ -233,6 +253,18 @@ private RegionAtmosphereState findNearest(List states, do } return nearest; } + + private CycloneSnapshot snapshot() { + return new CycloneSnapshot( + this.id, + this.center.x, + this.center.y, + this.radius, + this.intensity, + this.corePressureDrop, + this.lifetimeTicks + ); + } } private static void applyCyclone(CycloneStep step) { diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/atmosphere/CycloneSnapshot.java b/src/main/java/net/Gabou/projectatmosphere/modules/atmosphere/CycloneSnapshot.java new file mode 100644 index 00000000..4e211f41 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/atmosphere/CycloneSnapshot.java @@ -0,0 +1,14 @@ +package net.Gabou.projectatmosphere.modules.atmosphere; + +import java.util.UUID; + +public record CycloneSnapshot( + UUID id, + float centerX, + float centerZ, + float radius, + float intensity, + float corePressureDrop, + long lifetimeTicks +) { +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/core/CloudLibrary.java b/src/main/java/net/Gabou/projectatmosphere/modules/core/CloudLibrary.java index dc19577b..16f8ff87 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/core/CloudLibrary.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/core/CloudLibrary.java @@ -89,7 +89,6 @@ public class CloudLibrary { "stratocumulus", "cumulus", "altocumulus", - "stratocumulus_opacus", "altostratus_dry", "mammatus_thin" }; @@ -99,7 +98,7 @@ public class CloudLibrary { "smaller_stratocumulus", "islands", "spots", - //"pattern", + "pattern", "balls", "cumulus_noise", "dense_cumulus", @@ -114,6 +113,8 @@ public class CloudLibrary { "pathway", "spotted", "matrix", + "snow", + "tall_weirdness", "cumulus_humilis" }; @@ -173,24 +174,25 @@ public static int getSeverityFromCloudId(String id) { "thicker_stratocumulus" -> 4; case "dense_itty_bitty", "stratocumulus", - "stratocumulus_opacus", "cumulus" -> 3; case "small_cumulus", "smaller_stratocumulus", "islands", "spots", - //"pattern", + "pattern", "balls", "cumulus_noise", - "dense_cumulus"/*, - "tall_noise"*/ -> 2; + "dense_cumulus", + "tall_noise" -> 2; case "itty_bitty", "real_itty_bitty", "itty_bitty_bigger", "pathway", "spotted", "matrix", - "mammatus_thin" -> 1; + "snow", + "mammatus_thin", + "tall_weirdness" -> 1; default -> 0; }; } diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/fog/FogCommand.java b/src/main/java/net/Gabou/projectatmosphere/modules/fog/FogCommand.java new file mode 100644 index 00000000..b37753d9 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/fog/FogCommand.java @@ -0,0 +1,79 @@ +package net.Gabou.projectatmosphere.modules.fog; + +import com.mojang.brigadier.arguments.FloatArgumentType; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.Gabou.projectatmosphere.network.FogDebugOverridePacket; +import net.Gabou.projectatmosphere.network.NetworkHandler; +import net.Gabou.projectatmosphere.modules.temperature.command.TemperatureCommandHelper; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.PacketDistributor; + +public final class FogCommand { + private static final float DEFAULT_STRENGTH = 0.85F; + private static final int DEFAULT_DURATION_SECONDS = 30; + + private FogCommand() { + } + + public static LiteralArgumentBuilder build() { + return Commands.literal("fog") + .requires(source -> source.hasPermission(2)) + .then(Commands.literal("spawn") + .executes(ctx -> applyOverride(ctx, DEFAULT_STRENGTH, DEFAULT_DURATION_SECONDS)) + .then(Commands.argument("strength", FloatArgumentType.floatArg(0.05F, 1.0F)) + .executes(ctx -> applyOverride( + ctx, + FloatArgumentType.getFloat(ctx, "strength"), + DEFAULT_DURATION_SECONDS + )) + .then(Commands.argument("seconds", IntegerArgumentType.integer(1, 600)) + .executes(ctx -> applyOverride( + ctx, + FloatArgumentType.getFloat(ctx, "strength"), + IntegerArgumentType.getInteger(ctx, "seconds") + ))))) + .then(Commands.literal("clear") + .executes(FogCommand::clearOverride)); + } + + private static int applyOverride(com.mojang.brigadier.context.CommandContext ctx, float strength, int seconds) throws com.mojang.brigadier.exceptions.CommandSyntaxException { + ServerPlayer player = ctx.getSource().getPlayerOrException(); + ServerLevel level = player.serverLevel(); + if (!TemperatureCommandHelper.isInOverworld(level)) { + ctx.getSource().sendFailure(Component.literal("Fog debug override is only available in the Overworld.")); + return 0; + } + + int durationTicks = seconds * 20; + NetworkHandler.CHANNEL.send( + PacketDistributor.PLAYER.with(() -> player), + new FogDebugOverridePacket(strength, durationTicks) + ); + ctx.getSource().sendSuccess( + () -> Component.literal(String.format("Fog override applied: strength=%.2f duration=%ds", strength, seconds)), + true + ); + return 1; + } + + private static int clearOverride(com.mojang.brigadier.context.CommandContext ctx) throws com.mojang.brigadier.exceptions.CommandSyntaxException { + ServerPlayer player = ctx.getSource().getPlayerOrException(); + ServerLevel level = player.serverLevel(); + if (!TemperatureCommandHelper.isInOverworld(level)) { + ctx.getSource().sendFailure(Component.literal("Fog debug override is only available in the Overworld.")); + return 0; + } + + NetworkHandler.CHANNEL.send( + PacketDistributor.PLAYER.with(() -> player), + new FogDebugOverridePacket(0.0F, 0) + ); + ctx.getSource().sendSuccess(() -> Component.literal("Fog override cleared."), true); + return 1; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/fog/FogHeuristics.java b/src/main/java/net/Gabou/projectatmosphere/modules/fog/FogHeuristics.java new file mode 100644 index 00000000..a5c65d55 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/fog/FogHeuristics.java @@ -0,0 +1,54 @@ +package net.Gabou.projectatmosphere.modules.fog; + +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.minecraft.util.Mth; + +public final class FogHeuristics { + private FogHeuristics() { + } + + public static FogProfile sample(float humidityPercent, float wetBiomeFactor, float rainIntensity) { + float humidityStart = AtmoCommonConfig.FOG_HUMIDITY_START_PERCENT.get().floatValue(); + float humidityFull = AtmoCommonConfig.FOG_HUMIDITY_FULL_PERCENT.get().floatValue(); + float humidityFactor = remapClamped(humidityPercent, humidityStart, humidityFull); + + float strength = humidityFactor * (0.75F + wetBiomeFactor * 0.35F); + strength += wetBiomeFactor * AtmoCommonConfig.FOG_WET_BIOME_BASE_STRENGTH.get().floatValue(); + strength += rainIntensity * AtmoCommonConfig.FOG_RAIN_BOOST.get().floatValue(); + strength = Mth.clamp(strength, 0.0F, 1.0F); + strength = Mth.sqrt(strength); + + return new FogProfile(strength, humidityFactor, wetBiomeFactor, rainIntensity); + } + + public static FogProfile debugSample(float strength) { + float clamped = Mth.clamp(strength, 0.0F, 1.0F); + return new FogProfile(clamped, clamped, clamped, 0.0F); + } + + public static FogProfile max(FogProfile first, FogProfile second) { + if (first == null || first == FogProfile.NONE) { + return second == null ? FogProfile.NONE : second; + } + if (second == null || second == FogProfile.NONE) { + return first; + } + return new FogProfile( + Math.max(first.strength(), second.strength()), + Math.max(first.humidityFactor(), second.humidityFactor()), + Math.max(first.wetBiomeFactor(), second.wetBiomeFactor()), + Math.max(first.rainFactor(), second.rainFactor()) + ); + } + + public static float remapClamped(float value, float start, float end) { + if (end <= start) { + return value >= end ? 1.0F : 0.0F; + } + return Mth.clamp((value - start) / (end - start), 0.0F, 1.0F); + } + + public record FogProfile(float strength, float humidityFactor, float wetBiomeFactor, float rainFactor) { + public static final FogProfile NONE = new FogProfile(0.0F, 0.0F, 0.0F, 0.0F); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneBlockBreakRules.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneBlockBreakRules.java new file mode 100644 index 00000000..096584aa --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneBlockBreakRules.java @@ -0,0 +1,74 @@ +package net.Gabou.projectatmosphere.modules.hurricane; + +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.Gabou.projectatmosphere.modules.weather.StormShieldManager; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.BlockTags; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.PushReaction; +import net.minecraftforge.common.Tags; + +final class HurricaneBlockBreakRules { + static final net.minecraft.tags.TagKey HURRICANE_FRAGILE = BlockTags.create( + ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "hurricane_fragile") + ); + static final net.minecraft.tags.TagKey HURRICANE_TREE_DAMAGE = BlockTags.create( + ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "hurricane_tree_damage") + ); + static final net.minecraft.tags.TagKey HURRICANE_NEVER_BREAK = BlockTags.create( + ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "hurricane_never_break") + ); + + private HurricaneBlockBreakRules() { + } + + static boolean canBreak(ServerLevel level, BlockPos pos, BlockState state) { + if (!level.isLoaded(pos) || state.isAir() || StormShieldManager.isProtected(level, pos)) { + return false; + } + if (!state.getFluidState().isEmpty() || state.hasBlockEntity()) { + return false; + } + if (state.is(HURRICANE_NEVER_BREAK) || state.is(Tags.Blocks.ORES)) { + return false; + } + if (state.getDestroySpeed(level, pos) < 0.0F) { + return false; + } + PushReaction pushReaction = state.getPistonPushReaction(); + if (pushReaction == PushReaction.BLOCK || pushReaction == PushReaction.IGNORE) { + return false; + } + return isFragile(state) || isTreeDamageCandidate(state); + } + + static boolean isFragile(BlockState state) { + return state.is(HURRICANE_FRAGILE); + } + + static boolean isTreeDamageCandidate(BlockState state) { + return state.is(HURRICANE_TREE_DAMAGE); + } + + static boolean shouldBreakFragile(RandomSource random, float aggression, float radialWeight) { + float normalizedAggression = Mth.clamp(aggression / 1.5F, 0.0F, 1.0F); + float chance = 0.12F + normalizedAggression * 0.24F + radialWeight * 0.14F; + return random.nextFloat() < Mth.clamp(chance, 0.0F, 0.78F); + } + + static boolean shouldBreakTree(RandomSource random, BlockState state, float aggression, float radialWeight) { + float normalizedAggression = Mth.clamp(aggression / 1.5F, 0.0F, 1.0F); + float chance; + if (state.is(BlockTags.LOGS)) { + chance = 0.012F + normalizedAggression * 0.045F + radialWeight * 0.035F; + } else { + chance = 0.10F + normalizedAggression * 0.20F + radialWeight * 0.12F; + } + return random.nextFloat() < Mth.clamp(chance, 0.0F, 0.35F); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneCategory.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneCategory.java index 84921a24..b6817e06 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneCategory.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneCategory.java @@ -29,4 +29,10 @@ public static HurricaneCategory fromId(int id) { default -> ONE; }; } + + public static HurricaneCategory fromStrength(float normalizedStrength) { + float clamped = Math.max(0.0F, Math.min(1.0F, normalizedStrength)); + int id = 1 + Math.min(4, (int) Math.floor(clamped * 5.0F)); + return fromId(id); + } } diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneCloudVolume.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneCloudVolume.java new file mode 100644 index 00000000..07b88f86 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneCloudVolume.java @@ -0,0 +1,143 @@ +package net.Gabou.projectatmosphere.modules.hurricane; + +import net.Gabou.projectatmosphere.client.hurricane.ClientHurricaneStateCache.RenderableHurricane; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; + +import java.util.UUID; + +/** + * Represents one coherent hurricane cloud formation for the custom + * volumetric render path. This is intentionally independent from + * Simple Clouds {@code CloudRegion} so the storm remains one object + * instead of a ring of separate cloud instances. + */ +public record HurricaneCloudVolume( + UUID id, + float centerX, + float centerZ, + float baseY, + float height, + float eyeRadius, + float eyeClearRadius, + float eyeSlope, + float eyewallThickness, + float canopyRadius, + float shieldRadius, + float canopyBaseFactor, + float canopyTopFactor, + float shieldBaseFactor, + float shieldTopFactor, + float bandStartRadius, + float bandEndRadius, + float bandWidth, + float bandStrength, + float bandCount, + float fringeStrength, + float spin, + float intensity, + float seed, + Vec3 renderPosWorld, + float cloudScale +) { + public static HurricaneCloudVolume from(HurricaneInstance hurricane, float partialTick) { + float scale = SimpleCloudsConstants.CLOUD_SCALE; + Vec3 renderPos = hurricane.getRenderPosition(partialTick); + HurricaneRenderDescriptor descriptor = hurricane.getRenderDescriptor(partialTick); + + return new HurricaneCloudVolume( + hurricane.getId(), + (float) renderPos.x / scale, + (float) renderPos.z / scale, + -descriptor.baseOffsetWorld() / scale, + descriptor.volumeHeightWorld() / scale, + descriptor.eyeRadiusWorld() / scale, + descriptor.eyeClearRadiusWorld() / scale, + descriptor.eyeSlope(), + descriptor.eyewallThicknessWorld() / scale, + descriptor.canopyRadiusWorld() / scale, + descriptor.shieldRadiusWorld() / scale, + descriptor.canopyBaseFactor(), + descriptor.canopyTopFactor(), + descriptor.shieldBaseFactor(), + descriptor.shieldTopFactor(), + descriptor.bandStartRadiusWorld() / scale, + descriptor.bandEndRadiusWorld() / scale, + descriptor.bandWidthWorld() / scale, + descriptor.bandStrength(), + descriptor.bandCount(), + descriptor.fringeStrength(), + hurricane.getVisualSpin(partialTick), + Mth.clamp(hurricane.getRenderIntensity(partialTick), 0.0F, 1.0F), + hurricane.getVisualSeed(), + renderPos, + scale + ); + } + + public static HurricaneCloudVolume from(RenderableHurricane hurricane, float partialTick) { + float scale = SimpleCloudsConstants.CLOUD_SCALE; + Vec3 renderPos = new Vec3(hurricane.centerX() * scale, hurricane.anchorY(), hurricane.centerZ() * scale); + float intensity = Mth.clamp(hurricane.intensity(), 0.0F, 1.0F); + float seed = Mth.clamp(hurricane.seed(), 0.0F, 1.0F); + float growth = Mth.lerp(intensity, 0.45F, 1.0F); + float volumeHeight = Math.max(220.0F / scale, hurricane.bandWidth() * (0.42F + intensity * 0.18F)); + return new HurricaneCloudVolume( + hurricane.id(), + (float) hurricane.centerX(), + (float) hurricane.centerZ(), + 0.0F, + volumeHeight, + hurricane.eyeRadius() * growth, + hurricane.eyeRadius() * growth + hurricane.bandWidth() * (0.14F + intensity * 0.08F), + 0.90F + intensity * 0.12F, + Math.max(hurricane.bandWidth() * (0.24F + intensity * 0.10F), 0.05F), + hurricane.coreRadius() * Mth.lerp(intensity, 0.72F, 1.0F), + hurricane.stormExtentRadius() * Mth.lerp(intensity, 0.80F, 1.0F), + 0.38F, + 0.82F, + 0.28F, + 0.96F, + hurricane.transitionStart() * Mth.lerp(intensity, 0.72F, 1.0F), + hurricane.transitionEnd() * Mth.lerp(intensity, 0.80F, 1.0F), + hurricane.bandWidth() * (0.55F + intensity * 0.45F), + Mth.clamp(intensity * 0.85F, 0.0F, 1.0F), + hurricane.bandCount(), + Mth.clamp(intensity * 0.80F + seed * 0.10F, 0.0F, 1.0F), + hurricane.rotationPhase(), + intensity, + seed, + renderPos, + scale + ); + } + + public Vec3 centerWorld() { + return new Vec3(this.renderPosWorld.x, this.baseWorld() + this.heightWorld() * 0.5F, this.renderPosWorld.z); + } + + public float boundsRadiusCloud() { + return Math.max(this.shieldRadius * 1.20F, this.canopyRadius * 1.32F); + } + + public float boundsRadiusWorld() { + return this.boundsRadiusCloud() * this.cloudScale; + } + + public float baseWorld() { + return (float) this.renderPosWorld.y + this.baseY * this.cloudScale; + } + + public float heightWorld() { + return this.height * this.cloudScale; + } + + public Vec3 boundsMinCloud() { + return new Vec3(this.centerX - this.boundsRadiusCloud(), this.baseY - 2.0F, this.centerZ - this.boundsRadiusCloud()); + } + + public Vec3 boundsMaxCloud() { + return new Vec3(this.centerX + this.boundsRadiusCloud(), this.baseY + this.height + 4.0F, this.centerZ + this.boundsRadiusCloud()); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneCommand.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneCommand.java index b231b6b7..958e2f65 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneCommand.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneCommand.java @@ -2,12 +2,9 @@ import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import net.Gabou.projectatmosphere.compat.SimpleCloudsCompat; import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; import net.Gabou.projectatmosphere.seasons.SeasonStage; import net.Gabou.projectatmosphere.seasons.SeasonTimeHelper; -import net.Gabou.projectatmosphere.util.AtmosphereUtils; -import net.Gabou.projectatmosphere.util.BiomeInstanceKey; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; @@ -41,21 +38,19 @@ public static void appendTo(LiteralArgumentBuilder root) { } int catInt = IntegerArgumentType.getInteger(ctx, "category"); HurricaneCategory cat = HurricaneCategory.fromId(catInt); - BiomeInstanceKey key = new BiomeInstanceKey(AtmosphereUtils.getBiomeLocation(pos, level), pos); var wind = ForecastOrchestrator.getWind(level, pos, level.getGameTime()); - SimpleCloudsCompat.spawnCloudInBiome("custom_cumulonimbus", key, level, null, wind); - Vec3 spawnPos = new Vec3(player.getX(), level.getSeaLevel(), player.getZ()); - HurricaneManager.spawnServer(level, spawnPos, 40f, wind, cat); - ctx.getSource().sendSuccess(() -> Component.literal("🌀 Hurricane category " + catInt + " spawned."), true); - return 1; - })); + Vec3 spawnPos = new Vec3(player.getX(), level.getSeaLevel(), player.getZ()); + HurricaneManager.spawnServer(level, spawnPos, 40.0F, wind, cat); + ctx.getSource().sendSuccess(() -> Component.literal("Hurricane category " + catInt + " spawned and is forming."), true); + return 1; + })); root.then(base); root.then(Commands.literal("clearhurricanes") .requires(source -> source.hasPermission(2)) .executes(ctx -> { HurricaneManager.clearHurricanes(); - ctx.getSource().sendSuccess(() -> Component.literal("🌀 All hurricanes cleared."), true); + ctx.getSource().sendSuccess(() -> Component.literal("All hurricanes cleared."), true); return 1; })); root.then(Commands.literal("removehurricane") @@ -68,7 +63,7 @@ public static void appendTo(LiteralArgumentBuilder root) { .findFirst().orElse(null); if (hurricane != null) { HurricaneManager.removeHurricane(hurricane); - ctx.getSource().sendSuccess(() -> Component.literal("🌀 Hurricane removed."), true); + ctx.getSource().sendSuccess(() -> Component.literal("Hurricane is dissipating."), true); } else { ctx.getSource().sendFailure(Component.literal("No hurricane found near you.")); } diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneDestructionManager.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneDestructionManager.java new file mode 100644 index 00000000..467f64db --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneDestructionManager.java @@ -0,0 +1,172 @@ +package net.Gabou.projectatmosphere.modules.hurricane; + +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap; + +final class HurricaneDestructionManager { + private static final int MAX_SURFACE_SEARCH_DEPTH = 5; + + private HurricaneDestructionManager() { + } + + static void apply(HurricaneInstance hurricane, ServerLevel level, long gameTime) { + if (!AtmoCommonConfig.ENABLE_HURRICANE_DESTRUCTION.get()) { + return; + } + if (!hurricane.markDestructionTick(gameTime)) { + return; + } + + float configuredStrength = (float) AtmoCommonConfig.HURRICANE_DESTRUCTION_STRENGTH.get().doubleValue(); + if (configuredStrength <= 0.0F || hurricane.getDestructiveStrength() <= 0.18F) { + return; + } + + float aggression = Mth.clamp(hurricane.getDestructiveStrength() * configuredStrength, 0.0F, 3.0F); + if (aggression <= 0.05F) { + return; + } + + int maxBreaks = Mth.clamp(1 + Mth.floor(aggression * 1.25F), 1, 4); + int samples = Mth.clamp(3 + Mth.floor(aggression * 4.0F) + hurricane.category.ordinal(), 3, 20); + float minRadius = hurricane.getVisualEyeRadius() * 1.20F; + float maxRadius = Mth.clamp( + hurricane.getCoreRadius() * 0.92F, + minRadius + 8.0F, + 144.0F + aggression * 40.0F + hurricane.category.ordinal() * 10.0F + ); + + int broken = 0; + RandomSource random = level.random; + for (int i = 0; i < samples && broken < maxBreaks; i++) { + BlockPos samplePos = projectatmosphere$sampleSurface(level, hurricane, random, minRadius, maxRadius); + if (samplePos == null) { + continue; + } + + BlockPos candidate = projectatmosphere$findBreakCandidate(level, samplePos); + if (candidate == null) { + continue; + } + + float radialWeight = projectatmosphere$radialWeight(hurricane, candidate, minRadius, maxRadius); + BlockState state = level.getBlockState(candidate); + if (!HurricaneBlockBreakRules.canBreak(level, candidate, state)) { + continue; + } + + if (HurricaneBlockBreakRules.isFragile(state)) { + if (!HurricaneBlockBreakRules.shouldBreakFragile(random, aggression, radialWeight)) { + continue; + } + if (level.destroyBlock(candidate, AtmoCommonConfig.HURRICANE_DROP_BROKEN_BLOCKS.get())) { + broken++; + } + continue; + } + + if (!AtmoCommonConfig.HURRICANE_DAMAGE_TREES.get()) { + continue; + } + + broken += projectatmosphere$damageTree(level, candidate, random, aggression, radialWeight, maxBreaks - broken); + } + } + + private static BlockPos projectatmosphere$sampleSurface(ServerLevel level, + HurricaneInstance hurricane, + RandomSource random, + float minRadius, + float maxRadius) { + float angle = random.nextFloat() * Mth.TWO_PI; + float sampleRadius = Mth.sqrt(Mth.lerp(random.nextFloat(), minRadius * minRadius, maxRadius * maxRadius)); + int x = Mth.floor(hurricane.position.x + Math.cos(angle) * sampleRadius); + int z = Mth.floor(hurricane.position.z + Math.sin(angle) * sampleRadius); + int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z) - 1; + if (y < level.getMinBuildHeight()) { + return null; + } + return new BlockPos(x, y, z); + } + + private static BlockPos projectatmosphere$findBreakCandidate(ServerLevel level, BlockPos origin) { + BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); + int[] offsets = {0, 1, -1, -2, -3, -4, -5}; + for (int offset : offsets) { + if (Math.abs(offset) > MAX_SURFACE_SEARCH_DEPTH) { + continue; + } + cursor.set(origin.getX(), origin.getY() + offset, origin.getZ()); + if (!level.isLoaded(cursor)) { + continue; + } + BlockState state = level.getBlockState(cursor); + if (HurricaneBlockBreakRules.isFragile(state) || HurricaneBlockBreakRules.isTreeDamageCandidate(state)) { + return cursor.immutable(); + } + } + return null; + } + + private static float projectatmosphere$radialWeight(HurricaneInstance hurricane, BlockPos pos, float minRadius, float maxRadius) { + double dx = pos.getX() + 0.5D - hurricane.position.x; + double dz = pos.getZ() + 0.5D - hurricane.position.z; + float distance = (float) Math.sqrt(dx * dx + dz * dz); + float span = Math.max(1.0F, maxRadius - minRadius); + return 1.0F - Mth.clamp((distance - minRadius) / span, 0.0F, 1.0F); + } + + private static int projectatmosphere$damageTree(ServerLevel level, + BlockPos origin, + RandomSource random, + float aggression, + float radialWeight, + int remainingBudget) { + if (remainingBudget <= 0) { + return 0; + } + + int destroyed = 0; + destroyed += projectatmosphere$tryBreakTreeBlock(level, origin, random, aggression, radialWeight); + if (destroyed >= remainingBudget) { + return destroyed; + } + + int localBudget = Math.min(remainingBudget, 1 + Mth.floor(aggression * 0.85F)); + BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); + int attempts = 6 + localBudget * 4; + for (int i = 0; i < attempts && destroyed < localBudget; i++) { + cursor.set( + origin.getX() + random.nextInt(5) - 2, + origin.getY() + random.nextInt(5) - 1, + origin.getZ() + random.nextInt(5) - 2 + ); + destroyed += projectatmosphere$tryBreakTreeBlock(level, cursor, random, aggression, radialWeight); + } + return destroyed; + } + + private static int projectatmosphere$tryBreakTreeBlock(ServerLevel level, + BlockPos pos, + RandomSource random, + float aggression, + float radialWeight) { + if (!level.isLoaded(pos)) { + return 0; + } + + BlockState state = level.getBlockState(pos); + if (!HurricaneBlockBreakRules.isTreeDamageCandidate(state) || !HurricaneBlockBreakRules.canBreak(level, pos, state)) { + return 0; + } + if (!HurricaneBlockBreakRules.shouldBreakTree(random, state, aggression, radialWeight)) { + return 0; + } + return level.destroyBlock(pos, AtmoCommonConfig.HURRICANE_DROP_BROKEN_BLOCKS.get()) ? 1 : 0; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneInstance.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneInstance.java index 49fe16a8..a6b3c582 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneInstance.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneInstance.java @@ -1,63 +1,348 @@ package net.Gabou.projectatmosphere.modules.hurricane; +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import net.Gabou.projectatmosphere.modules.atmosphere.CycloneSnapshot; import net.Gabou.projectatmosphere.modules.core.WindVector; -import net.minecraft.world.entity.Entity; +import net.Gabou.projectatmosphere.modules.weather.StormLifecyclePhase; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; import net.minecraft.world.level.Level; -import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; -import net.minecraft.server.level.ServerLevel; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; -/** - * Represents a server-side hurricane event. - */ public class HurricaneInstance { + public static final ResourceLocation HURRICANE_CLOUD_TYPE_ID = + ResourceLocation.fromNamespaceAndPath("projectatmosphere", "hurricane"); + + private static final float DEFAULT_ANCHOR_Y = 384.0F; + private static final float MIN_WORLD_ANCHOR_Y = 256.0F; + private static final float CLOUD_LAYER_DESCENT = 200.0F; + private static final int WIND_FIELD_INTERVAL_TICKS = 2; + private static final int DESTRUCTION_INTERVAL_TICKS = 8; + + public final UUID id; + @Nullable + private final UUID cycloneId; + private final boolean debugSpawn; public Vec3 position; - public final long spawnTime; - public final float radius; - public final WindVector wind; - public final HurricaneCategory category; + public float radius; + public WindVector wind; + public HurricaneCategory category; - private long lastAmbientWindCheck = 0L; - private final long ambientWindIntervalMs = 2000L; + private float cycloneRadius; + private float cycloneIntensity; + private float destructiveStrength; + private float targetDestructiveStrength; + private float anchorY = DEFAULT_ANCHOR_Y; + private int ageTicks; + private int phaseTicks; + private int formationTicks; + private int activeTicks; + private int dissipationTicks; + private long lastWindFieldTick = Long.MIN_VALUE; + private long lastDestructionTick = Long.MIN_VALUE; + private StormLifecyclePhase phase; - public HurricaneInstance(Vec3 position, float radius, WindVector wind, HurricaneCategory category) { + private HurricaneInstance(UUID id, @Nullable UUID cycloneId, Vec3 position, float radius, WindVector wind, + HurricaneCategory category, boolean debugSpawn) { + this.id = id; + this.cycloneId = cycloneId; this.position = position; this.radius = radius; this.wind = wind; this.category = category; - this.spawnTime = System.currentTimeMillis(); + this.debugSpawn = debugSpawn; + this.cycloneRadius = Math.max(radius * 6.0F, 260.0F); + this.cycloneIntensity = 0.55F; + this.targetDestructiveStrength = 0.55F; + this.destructiveStrength = 0.18F; + this.phase = StormLifecyclePhase.FORMING; + this.rebuildLifecycleTimings(); + } + + public static HurricaneInstance createDebug(Vec3 position, float radius, WindVector wind, HurricaneCategory category) { + return new HurricaneInstance(UUID.randomUUID(), null, position, radius, wind, category, true); + } + + public static HurricaneInstance fromCyclone(ServerLevel level, CycloneSnapshot snapshot, WindVector wind, + HurricaneCategory category, float intensificationStrength) { + Vec3 center = new Vec3(snapshot.centerX(), level.getSeaLevel(), snapshot.centerZ()); + float localRadius = Mth.clamp(snapshot.radius() * 0.18F, 38.0F, 64.0F); + HurricaneInstance hurricane = new HurricaneInstance(snapshot.id(), snapshot.id(), center, localRadius, wind, category, false); + hurricane.updateFromCyclone(level, snapshot, wind, category, intensificationStrength); + return hurricane; + } + + public void refreshAnchorY(Level level) { + CloudManager manager = CloudManager.get(level); + if (manager == null) { + this.anchorY = Math.max(this.anchorY, MIN_WORLD_ANCHOR_Y); + return; + } + + float cloudHeight = manager.getCloudHeight(); + this.anchorY = Math.max(MIN_WORLD_ANCHOR_Y, cloudHeight - CLOUD_LAYER_DESCENT); + } + + public void updateFromCyclone(ServerLevel level, CycloneSnapshot snapshot, WindVector ambientWind, + HurricaneCategory nextCategory, float intensificationStrength) { + this.position = new Vec3(snapshot.centerX(), level.getSeaLevel(), snapshot.centerZ()); + this.cycloneRadius = snapshot.radius(); + this.cycloneIntensity = Mth.clamp(snapshot.intensity(), 0.0F, 1.0F); + this.radius = Mth.clamp(snapshot.radius() * 0.18F, 38.0F, 64.0F); + this.category = nextCategory; + this.targetDestructiveStrength = Mth.clamp( + this.cycloneIntensity * 0.60F + intensificationStrength * 0.40F, + 0.0F, + 1.0F + ); + this.rebuildLifecycleTimings(); + + float boostedBase = Math.max(ambientWind.baseSpeed(), 11.0F + this.targetDestructiveStrength * 22.0F); + float boostedGust = Math.max(ambientWind.gustSpeed(), boostedBase + 6.0F + this.category.ordinal() * 3.0F); + this.wind = new WindVector(boostedBase, ambientWind.angleRadians(), boostedGust); + this.refreshAnchorY(level); } public float getLifetimeSeconds() { - return (System.currentTimeMillis() - spawnTime) / 1000f; + return this.ageTicks / 20.0F; + } + + public UUID getId() { + return this.id; + } + + public int getAgeTicks() { + return this.ageTicks; + } + + public StormLifecyclePhase getPhase() { + return this.phase; + } + + public boolean isDebugSpawn() { + return this.debugSpawn; + } + + public boolean isLinkedToCyclone() { + return this.cycloneId != null; + } + + @Nullable + public UUID getCycloneId() { + return this.cycloneId; + } + + public float getAnchorY() { + return this.anchorY; + } + + public float getCoreRadius() { + float localCore = Math.max(this.radius * 7.8F, 320.0F + this.category.ordinal() * 52.0F); + float cycloneDriven = Math.max(260.0F, this.cycloneRadius * 1.18F); + return Math.max(localCore, cycloneDriven); + } + + public float getStormExtentRadius() { + float coreRadius = this.getCoreRadius(); + float cycloneDriven = Math.max(4200.0F, this.cycloneRadius * 18.0F); + return Math.max(coreRadius * 14.0F, cycloneDriven + this.category.ordinal() * 520.0F); + } + + public float getVisualEyeRadius() { + float coreRadius = this.getCoreRadius(); + float ratio = 0.17F + this.category.ordinal() * 0.011F; + return coreRadius * ratio; + } + + public float getVisualEdgeFade() { + return Math.max(this.getStormExtentRadius() * 0.09F, 160.0F); + } + + public int getBandCount() { + return 3 + Math.min(2, this.category.ordinal() / 2); + } + + public float getBandWidth() { + return Math.max(this.getCoreRadius() * 0.145F, 52.0F); + } + + public float getSpiralTightness() { + return 0.052F + this.category.ordinal() * 0.0060F; + } + + public float getRotationSpeed() { + int periodTicks = Math.max(12000, 14400 - this.category.ordinal() * 600); + return (float) (Math.PI * 2.0D / (double) periodTicks); + } + + public float getTransitionStart() { + return Math.max(this.getVisualEyeRadius() + this.getBandWidth() * 0.78F, this.getCoreRadius() * 0.30F); + } + + public float getTransitionEnd() { + return Math.max(this.getTransitionStart() + this.getBandWidth() * 18.0F, this.getStormExtentRadius() * 0.72F); + } + + public float getRotationPhase() { + return this.ageTicks * this.getRotationSpeed(); + } + + public Vec3 getRenderPosition(float partialTick) { + return this.position; + } + + public HurricaneRenderDescriptor getRenderDescriptor(float partialTick) { + return HurricaneRenderDescriptor.create( + Math.max(this.radius, 1.0F), + this.getRenderIntensity(partialTick), + this.category + ); + } + + public float getRenderIntensity(float partialTick) { + return Mth.clamp(this.destructiveStrength, 0.0F, 1.0F); + } + + public float getVisualSpin(float partialTick) { + return (this.ageTicks + partialTick) * this.getRotationSpeed(); + } + + public float getVisualSeed() { + return (Math.abs(this.id.hashCode()) % 10000) / 10000.0F; + } + + public HurricaneRenderSnapshot createRenderSnapshot() { + return new HurricaneRenderSnapshot( + this.id, + this.position.x, + this.position.z, + this.getAnchorY(), + this.getCoreRadius(), + this.getStormExtentRadius(), + this.getVisualEyeRadius(), + this.getVisualEdgeFade(), + this.getBandCount(), + this.getBandWidth(), + this.getSpiralTightness(), + this.getRotationPhase(), + this.getRotationSpeed(), + this.getTransitionStart(), + this.getTransitionEnd(), + this.getRenderIntensity(0.0F), + HURRICANE_CLOUD_TYPE_ID, + this.ageTicks + ); } public void tick(Level level) { if (level.isClientSide) { return; } - long now = System.currentTimeMillis(); - if (now - lastAmbientWindCheck >= ambientWindIntervalMs) { - lastAmbientWindCheck = now; - applyAmbientWind((ServerLevel) level); + + this.ageTicks++; + this.refreshAnchorY(level); + this.tickLifecycle(); + ServerLevel serverLevel = (ServerLevel) level; + if (this.phase.isTerminal()) { + return; + } + long gameTime = serverLevel.getGameTime(); + HurricaneWindField.apply(this, serverLevel, gameTime); + HurricaneDestructionManager.apply(this, serverLevel, gameTime); + } + + float getDestructiveStrength() { + return this.destructiveStrength; + } + + float getWindIntensity() { + return Mth.clamp(this.cycloneIntensity * 0.30F + this.destructiveStrength * 0.70F, 0.0F, 1.0F); + } + + float getRotationDirection() { + return (this.id.getLeastSignificantBits() & 1L) == 0L ? 1.0F : -1.0F; + } + + boolean markWindFieldTick(long gameTime) { + if (gameTime - this.lastWindFieldTick < WIND_FIELD_INTERVAL_TICKS) { + return false; } + this.lastWindFieldTick = gameTime; + return true; } - private void applyAmbientWind(ServerLevel level) { - double influence = radius; - AABB box = new AABB( - position.x - influence, position.y - 5, - position.z - influence, position.x + influence, - position.y + 50, position.z + influence - ); + boolean markDestructionTick(long gameTime) { + if (gameTime - this.lastDestructionTick < DESTRUCTION_INTERVAL_TICKS) { + return false; + } + this.lastDestructionTick = gameTime; + return true; + } + + public void markDissipating() { + if (this.phase == StormLifecyclePhase.DISSIPATING || this.phase == StormLifecyclePhase.DISSIPATED) { + return; + } + this.phase = StormLifecyclePhase.DISSIPATING; + this.phaseTicks = 0; + } - double windSpeed = wind.gustSpeed() * 0.02f; - double vx = Math.cos(wind.angleRadians()) * windSpeed; - double vz = Math.sin(wind.angleRadians()) * windSpeed; + public void activateImmediately() { + this.phase = StormLifecyclePhase.ACTIVE; + this.phaseTicks = 0; + this.destructiveStrength = this.targetDestructiveStrength; + } - for (Entity entity : level.getEntities(null, box)) { - entity.push(vx, 0, vz); + public boolean isDead() { + return this.phase.isTerminal(); + } + + private void tickLifecycle() { + switch (this.phase) { + case FORMING -> { + float rate = 1.0F / Math.max(1, this.formationTicks); + this.destructiveStrength = Math.min(this.targetDestructiveStrength, this.destructiveStrength + rate); + if (this.phaseTicks >= this.formationTicks || this.destructiveStrength >= this.targetDestructiveStrength - 0.02F) { + this.phase = StormLifecyclePhase.ACTIVE; + this.phaseTicks = 0; + } + } + case ACTIVE -> { + this.destructiveStrength = Mth.lerp(0.06F, this.destructiveStrength, this.targetDestructiveStrength); + if (this.phaseTicks >= this.activeTicks) { + this.phase = StormLifecyclePhase.DISSIPATING; + this.phaseTicks = 0; + } + } + case DISSIPATING -> { + float rate = 1.0F / Math.max(1, this.dissipationTicks); + this.destructiveStrength = Math.max(0.0F, this.destructiveStrength - rate); + if (this.phaseTicks >= this.dissipationTicks || this.destructiveStrength <= 0.02F) { + this.phase = StormLifecyclePhase.DISSIPATED; + this.destructiveStrength = 0.0F; + } + } + case DISSIPATED -> this.destructiveStrength = 0.0F; } + this.phaseTicks++; + } + + private void rebuildLifecycleTimings() { + float categoryBias = HurricaneCategory.values().length <= 1 + ? 0.0F + : this.category.ordinal() / (float) (HurricaneCategory.values().length - 1); + float persistenceFactor = Mth.clamp( + this.targetDestructiveStrength * 0.70F + this.cycloneIntensity * 0.30F + categoryBias * 0.20F, + 0.0F, + 1.0F + ); + this.formationTicks = Mth.floor(Mth.lerp(persistenceFactor, 20.0F * 10.0F, 20.0F * 26.0F)); + this.activeTicks = Mth.floor(Mth.lerp(persistenceFactor, 20.0F * 120.0F, 20.0F * 260.0F)); + this.dissipationTicks = Mth.floor(Mth.lerp(persistenceFactor, 20.0F * 16.0F, 20.0F * 52.0F)); } } diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneManager.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneManager.java index 1227ec09..516fc7b1 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneManager.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneManager.java @@ -1,42 +1,566 @@ package net.Gabou.projectatmosphere.modules.hurricane; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; +import dev.nonamecrackers2.simpleclouds.common.cloud.spawning.CloudGenerator; +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; +import net.Gabou.projectatmosphere.modules.atmosphere.AtmosphericStateRegistry; +import net.Gabou.projectatmosphere.modules.atmosphere.CycloneManager; +import net.Gabou.projectatmosphere.modules.atmosphere.CycloneSnapshot; +import net.Gabou.projectatmosphere.modules.atmosphere.RegionAtmosphereState; +import net.Gabou.projectatmosphere.modules.core.CloudLibrary; import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.Gabou.projectatmosphere.modules.weather.StormLifecyclePhase; +import net.Gabou.projectatmosphere.network.NetworkHandler; +import net.Gabou.projectatmosphere.network.SyncHurricaneStatePacket; +import net.Gabou.projectatmosphere.util.RegionInstanceKey; +import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.tags.BiomeTags; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LightningBolt; +import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.phys.Vec3; +import net.minecraftforge.network.PacketDistributor; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; public class HurricaneManager { + private static final int SYNC_INTERVAL_TICKS = 10; + private static final int ATMOSPHERE_INTERVAL_TICKS = 20; + private static final int LIGHTNING_INTERVAL_TICKS = 40; + private static final int INTENSIFICATION_REQUIRED_TICKS = 20 * 30; + private static final int DISSIPATION_GRACE_TICKS = 20 * 90; - private static final List ACTIVE_HURRICANES = new ArrayList<>(); + private static final Map LINKED_HURRICANES = new LinkedHashMap<>(); + private static final List DEBUG_HURRICANES = new ArrayList<>(); + private static final Map FORMATION_TRACKERS = new LinkedHashMap<>(); + private static final Map ENVIRONMENT_CACHE = new LinkedHashMap<>(); + private static final Map RESERVATION_REGIONS = new LinkedHashMap<>(); + private static boolean dirty = true; + + private HurricaneManager() { + } public static void spawnServer(ServerLevel level, Vec3 pos, float radius, WindVector wind, HurricaneCategory category) { - ACTIVE_HURRICANES.add(new HurricaneInstance(pos, radius, wind, category)); + HurricaneInstance hurricane = HurricaneInstance.createDebug(pos, radius, wind, category); + hurricane.refreshAnchorY(level); + DEBUG_HURRICANES.add(hurricane); + RESERVATION_REGIONS.put(hurricane.id, HurricaneSemantics.createReservationRegion(hurricane)); + dirty = true; + syncToDimension(level); } public static void tick(ServerLevel level) { - ACTIVE_HURRICANES.removeIf(h -> h.getLifetimeSeconds() > 1200); - for (HurricaneInstance hurricane : ACTIVE_HURRICANES) { - float speed = hurricane.wind.baseSpeed() * 0.01f; - hurricane.position = hurricane.position.add( - Math.cos(hurricane.wind.angleRadians()) * speed, - 0, - Math.sin(hurricane.wind.angleRadians()) * speed); - hurricane.tick(level); + long gameTime = level.getGameTime(); + + projectatmosphere$tickDebugHurricanes(level); + projectatmosphere$syncCycloneHurricanes(level, gameTime); + projectatmosphere$syncReservationRegions(); + + List active = projectatmosphere$getAllHurricanes(); + if (!active.isEmpty() && gameTime % ATMOSPHERE_INTERVAL_TICKS == 0L) { + applyAtmosphereAmplification(level, active); + reconcileReservedCloudSpace(level, active); + } + if (!active.isEmpty() && gameTime % LIGHTNING_INTERVAL_TICKS == 0L) { + spawnEyewallLightning(level, active); + } + if (dirty || gameTime % SYNC_INTERVAL_TICKS == 0L) { + syncToDimension(level); + dirty = false; } } public static List getActiveHurricanes() { - return Collections.unmodifiableList(ACTIVE_HURRICANES); + return List.copyOf(projectatmosphere$getAllHurricanes()); + } + + public static List getClientHurricanes() { + return getActiveHurricanes(); } public static void clearHurricanes() { - ACTIVE_HURRICANES.clear(); + LINKED_HURRICANES.clear(); + DEBUG_HURRICANES.clear(); + FORMATION_TRACKERS.clear(); + ENVIRONMENT_CACHE.clear(); + RESERVATION_REGIONS.clear(); + dirty = true; } public static void removeHurricane(HurricaneInstance hurricane) { - ACTIVE_HURRICANES.remove(hurricane); + if (hurricane == null) { + return; + } + + if ((DEBUG_HURRICANES.contains(hurricane) || hurricane.isLinkedToCyclone()) + && hurricane.getPhase() != StormLifecyclePhase.DISSIPATING + && hurricane.getPhase() != StormLifecyclePhase.DISSIPATED) { + hurricane.markDissipating(); + dirty = true; + } + } + + public static CloudRegion getReservationRegionAt(double worldX, double worldZ) { + for (HurricaneInstance hurricane : projectatmosphere$getAllHurricanes()) { + if (HurricaneSemantics.intersectsReservation(hurricane, worldX, worldZ, 0.0D)) { + CloudRegion region = RESERVATION_REGIONS.get(hurricane.id); + if (region != null) { + return region; + } + } + } + return null; + } + + public static void syncToPlayer(ServerPlayer player) { + NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), createSyncPacket()); + } + + private static void syncToDimension(ServerLevel level) { + NetworkHandler.CHANNEL.send(PacketDistributor.DIMENSION.with(level::dimension), createSyncPacket()); + } + + private static SyncHurricaneStatePacket createSyncPacket() { + return new SyncHurricaneStatePacket(projectatmosphere$getAllHurricanes().stream() + .map(HurricaneInstance::createRenderSnapshot) + .collect(Collectors.toList())); + } + + private static void projectatmosphere$tickDebugHurricanes(ServerLevel level) { + List dead = new ArrayList<>(); + for (HurricaneInstance hurricane : DEBUG_HURRICANES) { + float speed = hurricane.wind.baseSpeed() * 0.01F; + hurricane.position = hurricane.position.add( + Math.cos(hurricane.wind.angleRadians()) * speed, + 0.0D, + Math.sin(hurricane.wind.angleRadians()) * speed + ); + hurricane.tick(level); + if (hurricane.isDead() || hurricane.getLifetimeSeconds() > 1200.0F) { + dead.add(hurricane); + } + } + + if (!dead.isEmpty()) { + for (HurricaneInstance hurricane : dead) { + DEBUG_HURRICANES.remove(hurricane); + RESERVATION_REGIONS.remove(hurricane.id); + } + dirty = true; + } + } + + private static void projectatmosphere$syncCycloneHurricanes(ServerLevel level, long gameTime) { + List snapshots = CycloneManager.getActiveCycloneSnapshots(); + Set activeCyclones = snapshots.stream().map(CycloneSnapshot::id).collect(Collectors.toCollection(LinkedHashSet::new)); + + boolean anyDissipating = false; + for (Map.Entry entry : LINKED_HURRICANES.entrySet()) { + if (activeCyclones.contains(entry.getKey())) { + continue; + } + HurricaneInstance hurricane = entry.getValue(); + if (hurricane.getPhase() != StormLifecyclePhase.DISSIPATING + && hurricane.getPhase() != StormLifecyclePhase.DISSIPATED) { + hurricane.markDissipating(); + anyDissipating = true; + } + } + if (anyDissipating) { + dirty = true; + } + FORMATION_TRACKERS.keySet().removeIf(id -> !activeCyclones.contains(id)); + ENVIRONMENT_CACHE.keySet().removeIf(id -> !activeCyclones.contains(id)); + + boolean refreshEnvironment = gameTime % ATMOSPHERE_INTERVAL_TICKS == 0L; + for (CycloneSnapshot snapshot : snapshots) { + CycloneEnvironment environment = ENVIRONMENT_CACHE.get(snapshot.id()); + if (environment == null || refreshEnvironment) { + environment = projectatmosphere$analyzeCyclone(level, snapshot); + ENVIRONMENT_CACHE.put(snapshot.id(), environment); + } + + FormationTracker tracker = FORMATION_TRACKERS.computeIfAbsent(snapshot.id(), ignored -> new FormationTracker()); + tracker.update(environment.formationEligible(snapshot), environment.sustainEligible(snapshot)); + + HurricaneInstance hurricane = LINKED_HURRICANES.get(snapshot.id()); + if (hurricane == null) { + if (tracker.readyToIntensify()) { + HurricaneInstance created = HurricaneInstance.fromCyclone( + level, + snapshot, + environment.wind(), + environment.targetCategory(snapshot), + environment.intensificationStrength() + ); + LINKED_HURRICANES.put(snapshot.id(), created); + RESERVATION_REGIONS.put(created.id, HurricaneSemantics.createReservationRegion(created)); + dirty = true; + } + continue; + } + + hurricane.updateFromCyclone( + level, + snapshot, + environment.wind(), + environment.targetCategory(snapshot), + environment.intensificationStrength() + ); + hurricane.tick(level); + + if (tracker.shouldDissipate(snapshot)) { + if (hurricane.getPhase() != StormLifecyclePhase.DISSIPATING + && hurricane.getPhase() != StormLifecyclePhase.DISSIPATED) { + hurricane.markDissipating(); + dirty = true; + } + } + + if (hurricane.isDead()) { + LINKED_HURRICANES.remove(snapshot.id()); + RESERVATION_REGIONS.remove(hurricane.id); + dirty = true; + } + } + + Iterator> iterator = LINKED_HURRICANES.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (activeCyclones.contains(entry.getKey())) { + continue; + } + + HurricaneInstance hurricane = entry.getValue(); + hurricane.tick(level); + if (hurricane.isDead()) { + RESERVATION_REGIONS.remove(hurricane.id); + iterator.remove(); + dirty = true; + } + } + } + + private static CycloneEnvironment projectatmosphere$analyzeCyclone(ServerLevel level, CycloneSnapshot snapshot) { + List states = AtmosphericStateRegistry.snapshot(); + float sampleRadius = Math.max(snapshot.radius() * 1.45F, 480.0F); + float oceanWeight = 0.0F; + float warmOceanWeight = 0.0F; + float humidityWeighted = 0.0F; + float stormSignalWeighted = 0.0F; + float totalWeight = 0.0F; + + for (RegionAtmosphereState state : states) { + double distance = state.distanceTo(snapshot.centerX(), snapshot.centerZ()); + if (distance > sampleRadius) { + continue; + } + + float weight = 1.0F - (float) (distance / sampleRadius); + totalWeight += weight; + humidityWeighted += state.getHumidity() * weight; + float stateStormSignal = Math.max( + state.getRainIntensity(), + Math.max(state.getCloudCover(), state.getCycloneRainFloor()) + ); + stormSignalWeighted += stateStormSignal * weight; + + BlockPos pos = state.getPosition(); + if (pos != null && level.getBiome(pos).is(BiomeTags.IS_OCEAN)) { + oceanWeight += weight; + if (state.getTemperature() >= 24.0F) { + warmOceanWeight += weight; + } + } + } + + float convectiveCoverage = projectatmosphere$sampleConvectiveCoverage(level, snapshot); + WindVector wind = projectatmosphere$resolveCycloneWind(level, snapshot); + float warmOceanCoverage = totalWeight <= 0.0F ? 0.0F : warmOceanWeight / totalWeight; + float totalOceanCoverage = totalWeight <= 0.0F ? 0.0F : oceanWeight / totalWeight; + float meanHumidity = totalWeight <= 0.0F ? 0.0F : humidityWeighted / totalWeight; + float stormSignal = totalWeight <= 0.0F ? 0.0F : stormSignalWeighted / totalWeight; + float intensificationStrength = Mth.clamp( + snapshot.intensity() * 0.45F + + warmOceanCoverage * 0.20F + + totalOceanCoverage * 0.10F + + convectiveCoverage * 0.15F + + stormSignal * 0.10F, + 0.0F, + 1.0F + ); + + return new CycloneEnvironment( + totalOceanCoverage, + warmOceanCoverage, + convectiveCoverage, + meanHumidity, + stormSignal, + intensificationStrength, + wind + ); + } + + private static float projectatmosphere$sampleConvectiveCoverage(ServerLevel level, CycloneSnapshot snapshot) { + CloudManager manager = CloudManager.get(level); + if (manager == null) { + return 0.0F; + } + + float scanRadius = Math.max(snapshot.radius() * 1.6F, 520.0F); + float strongest = 0.0F; + for (CloudRegion cloud : manager.getClouds()) { + String path = cloud.getCloudTypeId().getPath(); + if (!projectatmosphere$isConvectiveStorm(path)) { + continue; + } + double edgeDistance = Math.max( + 0.0D, + Math.sqrt(projectatmosphere$distanceToSqr(snapshot.centerX(), snapshot.centerZ(), cloud.getWorldX(), cloud.getWorldZ())) + - cloud.getWorldRadius() + ); + if (edgeDistance > scanRadius) { + continue; + } + float influence = 1.0F - (float) (edgeDistance / scanRadius); + strongest = Math.max(strongest, Mth.clamp(influence, 0.0F, 1.0F)); + } + return strongest; + } + + private static WindVector projectatmosphere$resolveCycloneWind(ServerLevel level, CycloneSnapshot snapshot) { + BlockPos anchor = new BlockPos(Mth.floor(snapshot.centerX()), level.getSeaLevel(), Mth.floor(snapshot.centerZ())); + RegionInstanceKey key = RegionInstanceKey.from(anchor); + WindVector sampled = ForecastOrchestrator.getWind(key, level.getGameTime()); + if (sampled == null) { + return WindVector.fromBase(10.0F, 0.0F); + } + return sampled; + } + + private static void applyAtmosphereAmplification(ServerLevel level, List hurricanes) { + List snapshot = AtmosphericStateRegistry.snapshot(); + if (snapshot.isEmpty()) { + return; + } + + for (RegionAtmosphereState state : snapshot) { + float cloudCeil = 0.0F; + float rainCeil = 0.0F; + float cloudWater = 0.0F; + float humidityGain = 0.0F; + float pressureDrop = 0.0F; + float cooling = 0.0F; + + for (HurricaneInstance hurricane : hurricanes) { + float forcingRadius = hurricane.getStormExtentRadius(); + double distance = state.distanceTo(hurricane.position.x, hurricane.position.z); + if (distance > forcingRadius) { + continue; + } + + float influence = 1.0F - (float) (distance / forcingRadius); + influence = Mth.clamp(influence, 0.0F, 1.0F); + float eyeRadius = hurricane.getVisualEyeRadius(); + float coreRadius = hurricane.getCoreRadius(); + boolean eyewallZone = distance >= eyeRadius * 1.3F && distance <= coreRadius * 0.9F; + float stormWeight = influence * (eyewallZone ? 1.0F : 0.78F); + cloudCeil = Math.max(cloudCeil, Mth.clamp(0.72F + stormWeight * 0.28F, 0.0F, 1.0F)); + rainCeil = Math.max(rainCeil, Mth.clamp(0.45F + stormWeight * 0.55F, 0.0F, 1.0F)); + cloudWater = Math.max(cloudWater, Mth.clamp(0.35F + stormWeight * 0.80F, 0.0F, 1.2F)); + humidityGain = Math.max(humidityGain, stormWeight * 0.08F); + pressureDrop = Math.max(pressureDrop, stormWeight * 3.5F); + cooling = Math.max(cooling, stormWeight * 1.6F); + } + + if (cloudCeil <= 0.0F && rainCeil <= 0.0F) { + continue; + } + + state.adjustHumidity(humidityGain); + state.adjustPressure(-pressureDrop); + state.adjustTemperature(-cooling); + state.applyCycloneVisualFloor(cloudCeil, rainCeil); + state.setCloudCover(Math.max(state.getCloudCover(), cloudCeil)); + state.setRainIntensity(Math.max(state.getRainIntensity(), rainCeil)); + state.setCloudWater(Math.max(state.getCloudWater(), cloudWater)); + } + } + + private static void reconcileReservedCloudSpace(ServerLevel level, List hurricanes) { + CloudManager cloudManager = CloudManager.get(level); + if (cloudManager == null) { + return; + } + + CloudGenerator generator = cloudManager.getCloudGenerator(); + double padding = SimpleCloudsConstants.MIN_SPAWN_DIST_BETWEEN_REGIONS; + generator.removeClouds(region -> hurricanes.stream().anyMatch(hurricane -> + HurricaneSemantics.intersectsReservation(hurricane, region.getWorldX(), region.getWorldZ(), region.getWorldRadius() + padding) + )); + } + + private static void spawnEyewallLightning(ServerLevel level, List hurricanes) { + if (level.players().isEmpty()) { + return; + } + + for (HurricaneInstance hurricane : hurricanes) { + float activityRadius = hurricane.getStormExtentRadius() + 512.0F; + if (!projectatmosphere$isPlayerNear(level, hurricane, activityRadius)) { + continue; + } + float strikeChance = 0.18F + hurricane.category.ordinal() * 0.08F; + if (level.random.nextFloat() > strikeChance) { + continue; + } + + float minRadius = hurricane.getVisualEyeRadius() * 1.3F; + float maxRadius = hurricane.getCoreRadius() * 0.9F; + if (maxRadius <= minRadius) { + maxRadius = minRadius + 16.0F; + } + + float angle = level.random.nextFloat() * Mth.TWO_PI; + float radius = Mth.lerp(level.random.nextFloat(), minRadius, maxRadius); + double strikeX = hurricane.position.x + Math.cos(angle) * radius; + double strikeZ = hurricane.position.z + Math.sin(angle) * radius; + int blockX = Mth.floor(strikeX); + int blockZ = Mth.floor(strikeZ); + int blockY = level.getHeight(Heightmap.Types.MOTION_BLOCKING, blockX, blockZ); + + LightningBolt lightningBolt = EntityType.LIGHTNING_BOLT.create(level); + if (lightningBolt == null) { + continue; + } + lightningBolt.moveTo(strikeX, blockY, strikeZ); + lightningBolt.setVisualOnly(false); + level.addFreshEntity(lightningBolt); + } + } + + private static void projectatmosphere$syncReservationRegions() { + List hurricanes = projectatmosphere$getAllHurricanes(); + Set activeIds = hurricanes.stream().map(hurricane -> hurricane.id).collect(Collectors.toSet()); + RESERVATION_REGIONS.entrySet().removeIf(entry -> !activeIds.contains(entry.getKey())); + + for (HurricaneInstance hurricane : hurricanes) { + CloudRegion region = RESERVATION_REGIONS.computeIfAbsent(hurricane.id, ignored -> HurricaneSemantics.createReservationRegion(hurricane)); + HurricaneSemantics.updateReservationRegion(region, hurricane); + } + } + + private static boolean projectatmosphere$isPlayerNear(ServerLevel level, HurricaneInstance hurricane, float radius) { + float radiusSq = radius * radius; + for (ServerPlayer player : level.players()) { + double dx = player.getX() - hurricane.position.x; + double dz = player.getZ() - hurricane.position.z; + if (dx * dx + dz * dz <= radiusSq) { + return true; + } + } + return false; + } + + private static boolean projectatmosphere$isConvectiveStorm(String cloudId) { + return CloudLibrary.isThunderCloud(cloudId) + || cloudId.contains("cumulonimbus") + || cloudId.contains("tsegrus") + || cloudId.contains("dark_wall"); + } + + private static double projectatmosphere$distanceToSqr(float x1, float z1, double x2, double z2) { + double dx = x1 - x2; + double dz = z1 - z2; + return dx * dx + dz * dz; + } + + private static List projectatmosphere$getAllHurricanes() { + if (LINKED_HURRICANES.isEmpty()) { + return new ArrayList<>(DEBUG_HURRICANES); + } + List hurricanes = new ArrayList<>(LINKED_HURRICANES.size() + DEBUG_HURRICANES.size()); + hurricanes.addAll(LINKED_HURRICANES.values()); + hurricanes.addAll(DEBUG_HURRICANES); + return hurricanes; + } + + private static final class FormationTracker { + private int qualifyingTicks; + private int weakTicks; + + private void update(boolean formationEligible, boolean sustainEligible) { + if (formationEligible) { + this.qualifyingTicks = Math.min(INTENSIFICATION_REQUIRED_TICKS, this.qualifyingTicks + ATMOSPHERE_INTERVAL_TICKS); + this.weakTicks = 0; + return; + } + + this.qualifyingTicks = Math.max(0, this.qualifyingTicks - ATMOSPHERE_INTERVAL_TICKS); + if (sustainEligible) { + this.weakTicks = Math.max(0, this.weakTicks - ATMOSPHERE_INTERVAL_TICKS); + } else { + this.weakTicks += ATMOSPHERE_INTERVAL_TICKS; + } + } + + private boolean readyToIntensify() { + return this.qualifyingTicks >= INTENSIFICATION_REQUIRED_TICKS; + } + + private boolean shouldDissipate(CycloneSnapshot snapshot) { + return snapshot.intensity() < 0.38F || this.weakTicks >= DISSIPATION_GRACE_TICKS; + } + } + + private record CycloneEnvironment( + float oceanCoverage, + float warmOceanCoverage, + float convectiveCoverage, + float meanHumidity, + float stormSignal, + float intensificationStrength, + WindVector wind + ) { + private boolean formationEligible(CycloneSnapshot snapshot) { + return snapshot.intensity() >= 0.58F + && this.warmOceanCoverage >= 0.35F + && this.convectiveCoverage >= 0.25F + && this.meanHumidity >= 0.68F + && this.stormSignal >= 0.56F; + } + + private boolean sustainEligible(CycloneSnapshot snapshot) { + return snapshot.intensity() >= 0.42F + && this.oceanCoverage >= 0.20F + && this.warmOceanCoverage >= 0.15F + && this.stormSignal >= 0.42F; + } + + private HurricaneCategory targetCategory(CycloneSnapshot snapshot) { + float strength = Mth.clamp( + snapshot.intensity() * 0.55F + + this.warmOceanCoverage * 0.20F + + this.convectiveCoverage * 0.15F + + this.stormSignal * 0.10F, + 0.0F, + 1.0F + ); + return HurricaneCategory.fromStrength(strength); + } } } diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneRenderDescriptor.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneRenderDescriptor.java new file mode 100644 index 00000000..b163ff29 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneRenderDescriptor.java @@ -0,0 +1,151 @@ +package net.Gabou.projectatmosphere.modules.hurricane; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.Mth; + +public record HurricaneRenderDescriptor( + float baseOffsetWorld, + float volumeHeightWorld, + float eyeRadiusWorld, + float eyeClearRadiusWorld, + float eyeSlope, + float eyewallThicknessWorld, + float canopyRadiusWorld, + float shieldRadiusWorld, + float canopyBaseFactor, + float canopyTopFactor, + float shieldBaseFactor, + float shieldTopFactor, + float bandStartRadiusWorld, + float bandEndRadiusWorld, + float bandWidthWorld, + float bandStrength, + float bandCount, + float fringeStrength +) { + public static HurricaneRenderDescriptor create(float stormRadiusWorld, float intensity, HurricaneCategory category) { + float normalizedIntensity = Mth.clamp(intensity, 0.0F, 1.0F); + float categoryBias = category.ordinal() / (float) (HurricaneCategory.values().length - 1); + float torusMajorRadius = Mth.clamp( + stormRadiusWorld * (2.2F + normalizedIntensity * 0.80F + categoryBias * 0.45F), + 110.0F, + 360.0F + ); + float torusMinorRadius = Mth.clamp( + stormRadiusWorld * (0.72F + normalizedIntensity * 0.18F + categoryBias * 0.14F), + 32.0F, + 110.0F + ); + float eyeRadius = Math.max(torusMajorRadius - torusMinorRadius * 1.16F, stormRadiusWorld * 0.55F); + float eyeClearRadius = eyeRadius + torusMinorRadius * (0.32F + normalizedIntensity * 0.18F); + float canopyRadius = torusMajorRadius; + float shieldRadius = torusMajorRadius + torusMinorRadius * (2.10F + categoryBias * 0.60F); + float bandStart = shieldRadius * (0.96F + normalizedIntensity * 0.04F); + float bandEnd = shieldRadius * (1.78F + normalizedIntensity * 0.18F + categoryBias * 0.10F); + float bandWidth = Mth.clamp(torusMinorRadius * (1.15F + normalizedIntensity * 0.30F), 48.0F, 180.0F); + float bandStrength = Mth.clamp(0.22F + normalizedIntensity * 0.34F + categoryBias * 0.12F, 0.0F, 1.0F); + float bandCount = 2.0F + category.ordinal(); + float fringeStrength = Mth.clamp(0.30F + normalizedIntensity * 0.34F + categoryBias * 0.12F, 0.0F, 1.0F); + float baseOffset = 56.0F + normalizedIntensity * 92.0F + categoryBias * 20.0F; + float volumeHeight = Mth.clamp( + 220.0F + torusMinorRadius * 2.8F + normalizedIntensity * 80.0F + categoryBias * 36.0F, + 220.0F, + 430.0F + ); + float canopyBase = 0.38F - categoryBias * 0.03F; + float canopyTop = 0.82F + normalizedIntensity * 0.05F; + float shieldBase = 0.28F; + float shieldTop = 0.96F; + float eyeSlope = 0.94F + normalizedIntensity * 0.16F + categoryBias * 0.05F; + float eyewallThickness = torusMinorRadius; + + return new HurricaneRenderDescriptor( + baseOffset, + volumeHeight, + eyeRadius, + eyeClearRadius, + eyeSlope, + eyewallThickness, + canopyRadius, + shieldRadius, + canopyBase, + canopyTop, + shieldBase, + shieldTop, + bandStart, + bandEnd, + bandWidth, + bandStrength, + bandCount, + fringeStrength + ); + } + + public static HurricaneRenderDescriptor lerp(HurricaneRenderDescriptor from, HurricaneRenderDescriptor to, float partialTick) { + float t = Mth.clamp(partialTick, 0.0F, 1.0F); + return new HurricaneRenderDescriptor( + Mth.lerp(t, from.baseOffsetWorld, to.baseOffsetWorld), + Mth.lerp(t, from.volumeHeightWorld, to.volumeHeightWorld), + Mth.lerp(t, from.eyeRadiusWorld, to.eyeRadiusWorld), + Mth.lerp(t, from.eyeClearRadiusWorld, to.eyeClearRadiusWorld), + Mth.lerp(t, from.eyeSlope, to.eyeSlope), + Mth.lerp(t, from.eyewallThicknessWorld, to.eyewallThicknessWorld), + Mth.lerp(t, from.canopyRadiusWorld, to.canopyRadiusWorld), + Mth.lerp(t, from.shieldRadiusWorld, to.shieldRadiusWorld), + Mth.lerp(t, from.canopyBaseFactor, to.canopyBaseFactor), + Mth.lerp(t, from.canopyTopFactor, to.canopyTopFactor), + Mth.lerp(t, from.shieldBaseFactor, to.shieldBaseFactor), + Mth.lerp(t, from.shieldTopFactor, to.shieldTopFactor), + Mth.lerp(t, from.bandStartRadiusWorld, to.bandStartRadiusWorld), + Mth.lerp(t, from.bandEndRadiusWorld, to.bandEndRadiusWorld), + Mth.lerp(t, from.bandWidthWorld, to.bandWidthWorld), + Mth.lerp(t, from.bandStrength, to.bandStrength), + Mth.lerp(t, from.bandCount, to.bandCount), + Mth.lerp(t, from.fringeStrength, to.fringeStrength) + ); + } + + public void write(FriendlyByteBuf buf) { + buf.writeFloat(this.baseOffsetWorld); + buf.writeFloat(this.volumeHeightWorld); + buf.writeFloat(this.eyeRadiusWorld); + buf.writeFloat(this.eyeClearRadiusWorld); + buf.writeFloat(this.eyeSlope); + buf.writeFloat(this.eyewallThicknessWorld); + buf.writeFloat(this.canopyRadiusWorld); + buf.writeFloat(this.shieldRadiusWorld); + buf.writeFloat(this.canopyBaseFactor); + buf.writeFloat(this.canopyTopFactor); + buf.writeFloat(this.shieldBaseFactor); + buf.writeFloat(this.shieldTopFactor); + buf.writeFloat(this.bandStartRadiusWorld); + buf.writeFloat(this.bandEndRadiusWorld); + buf.writeFloat(this.bandWidthWorld); + buf.writeFloat(this.bandStrength); + buf.writeFloat(this.bandCount); + buf.writeFloat(this.fringeStrength); + } + + public static HurricaneRenderDescriptor read(FriendlyByteBuf buf) { + return new HurricaneRenderDescriptor( + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat() + ); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneRenderSnapshot.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneRenderSnapshot.java new file mode 100644 index 00000000..2adf0c3e --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneRenderSnapshot.java @@ -0,0 +1,71 @@ +package net.Gabou.projectatmosphere.modules.hurricane; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +import java.util.UUID; + +public record HurricaneRenderSnapshot( + UUID id, + double centerX, + double centerZ, + float anchorY, + float coreRadius, + float stormExtentRadius, + float eyeRadius, + float edgeFade, + int bandCount, + float bandWidth, + float spiralTightness, + float rotationPhase, + float rotationSpeed, + float transitionStart, + float transitionEnd, + float normalizedIntensity, + ResourceLocation cloudTypeId, + int ageTicks +) { + public void encode(FriendlyByteBuf buf) { + buf.writeUUID(this.id); + buf.writeDouble(this.centerX); + buf.writeDouble(this.centerZ); + buf.writeFloat(this.anchorY); + buf.writeFloat(this.coreRadius); + buf.writeFloat(this.stormExtentRadius); + buf.writeFloat(this.eyeRadius); + buf.writeFloat(this.edgeFade); + buf.writeVarInt(this.bandCount); + buf.writeFloat(this.bandWidth); + buf.writeFloat(this.spiralTightness); + buf.writeFloat(this.rotationPhase); + buf.writeFloat(this.rotationSpeed); + buf.writeFloat(this.transitionStart); + buf.writeFloat(this.transitionEnd); + buf.writeFloat(this.normalizedIntensity); + buf.writeResourceLocation(this.cloudTypeId); + buf.writeVarInt(this.ageTicks); + } + + public static HurricaneRenderSnapshot decode(FriendlyByteBuf buf) { + return new HurricaneRenderSnapshot( + buf.readUUID(), + buf.readDouble(), + buf.readDouble(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readVarInt(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readFloat(), + buf.readResourceLocation(), + buf.readVarInt() + ); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneSemanticSample.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneSemanticSample.java new file mode 100644 index 00000000..44b68f78 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneSemanticSample.java @@ -0,0 +1,21 @@ +package net.Gabou.projectatmosphere.modules.hurricane; + +import net.minecraft.resources.ResourceLocation; + +public record HurricaneSemanticSample( + ResourceLocation cloudTypeId, + float anchorY, + float coverage, + float rainStrength, + boolean inEye, + float coreCoverage, + float outerCoverage +) { + public static HurricaneSemanticSample none() { + return new HurricaneSemanticSample(HurricaneInstance.HURRICANE_CLOUD_TYPE_ID, 0.0F, 0.0F, 0.0F, false, 0.0F, 0.0F); + } + + public boolean isPresent() { + return this.coverage > 0.001F; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneSemantics.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneSemantics.java new file mode 100644 index 00000000..fb3f2dd7 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneSemantics.java @@ -0,0 +1,347 @@ +package net.Gabou.projectatmosphere.modules.hurricane; + +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import net.Gabou.projectatmosphere.client.hurricane.ClientHurricaneStateCache; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec2; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; + +public final class HurricaneSemantics { + private static final float MIN_COVERAGE = 0.02F; + private static final float OUTER_RAIN_FLOOR = 0.58F; + private static final Field CLOUD_MANAGER_LEVEL_FIELD = projectatmosphere$initCloudManagerLevelField(); + + private HurricaneSemantics() { + } + + public static HurricaneSemanticSample sampleBest(Level level, double worldX, double worldZ) { + HurricaneSemanticSample best = HurricaneSemanticSample.none(); + if (level.isClientSide) { + for (HurricaneRenderSnapshot hurricane : ClientHurricaneStateCache.getSemanticSnapshots()) { + HurricaneSemanticSample sample = sample(hurricane, worldX, worldZ); + if (sample.inEye() && !best.isPresent()) { + best = sample; + continue; + } + if (sample.coverage() > best.coverage()) { + best = sample; + } + } + return best; + } + + for (HurricaneInstance hurricane : HurricaneManager.getActiveHurricanes()) { + HurricaneSemanticSample sample = sample(hurricane, worldX, worldZ); + if (sample.inEye() && !best.isPresent()) { + best = sample; + continue; + } + if (sample.coverage() > best.coverage()) { + best = sample; + } + } + return best; + } + + public static boolean intersectsReservation(Level level, double worldX, double worldZ, double extraRadius) { + if (level.isClientSide) { + for (HurricaneRenderSnapshot hurricane : ClientHurricaneStateCache.getSemanticSnapshots()) { + if (intersectsReservation(hurricane, worldX, worldZ, extraRadius)) { + return true; + } + } + return false; + } + + for (HurricaneInstance hurricane : HurricaneManager.getActiveHurricanes()) { + if (intersectsReservation(hurricane, worldX, worldZ, extraRadius)) { + return true; + } + } + return false; + } + + public static boolean intersectsReservation(HurricaneInstance hurricane, double worldX, double worldZ, double extraRadius) { + return intersectsReservation(hurricane.position.x, hurricane.position.z, hurricane.getStormExtentRadius(), worldX, worldZ, extraRadius); + } + + public static boolean intersectsReservation(HurricaneRenderSnapshot hurricane, double worldX, double worldZ, double extraRadius) { + return intersectsReservation(hurricane.centerX(), hurricane.centerZ(), hurricane.stormExtentRadius(), worldX, worldZ, extraRadius); + } + + public static @Nullable CloudRegion getReservationRegionAt(Level level, double worldX, double worldZ) { + if (!level.isClientSide) { + return HurricaneManager.getReservationRegionAt(worldX, worldZ); + } + for (HurricaneRenderSnapshot snapshot : ClientHurricaneStateCache.getSemanticSnapshots()) { + if (intersectsReservation(snapshot, worldX, worldZ, 0.0D)) { + return ClientHurricaneStateCache.getReservationRegion(snapshot); + } + } + return null; + } + + public static CloudRegion createReservationRegion(HurricaneInstance hurricane) { + CloudRegion region = new CloudRegion( + hurricane.createRenderSnapshot().cloudTypeId(), + new Vec2(0.0F, 0.0F), + 0.0F, + 0.0F, + (float)(hurricane.position.x / (double)SimpleCloudsConstants.CLOUD_SCALE), + (float)(hurricane.position.z / (double)SimpleCloudsConstants.CLOUD_SCALE), + (hurricane.getStormExtentRadius() + SimpleCloudsConstants.MIN_SPAWN_DIST_BETWEEN_REGIONS) / (float)SimpleCloudsConstants.CLOUD_SCALE, + 0.0F, + 1.0F, + Integer.MAX_VALUE, + 0, + Integer.MIN_VALUE + ); + updateReservationRegion(region, hurricane); + return region; + } + + public static CloudRegion createReservationRegion(HurricaneRenderSnapshot hurricane) { + CloudRegion region = new CloudRegion( + hurricane.cloudTypeId(), + new Vec2(0.0F, 0.0F), + 0.0F, + 0.0F, + (float)(hurricane.centerX() / (double)SimpleCloudsConstants.CLOUD_SCALE), + (float)(hurricane.centerZ() / (double)SimpleCloudsConstants.CLOUD_SCALE), + (hurricane.stormExtentRadius() + SimpleCloudsConstants.MIN_SPAWN_DIST_BETWEEN_REGIONS) / (float)SimpleCloudsConstants.CLOUD_SCALE, + 0.0F, + 1.0F, + Integer.MAX_VALUE, + 0, + Integer.MIN_VALUE + ); + region.moveToWorldPos((float)hurricane.centerX(), (float)hurricane.centerZ()); + region.setWorldRadius(hurricane.stormExtentRadius() + SimpleCloudsConstants.MIN_SPAWN_DIST_BETWEEN_REGIONS); + region.setStretchFactor(1.0F); + region.setRotation(0.0F); + return region; + } + + public static void updateReservationRegion(CloudRegion region, HurricaneInstance hurricane) { + region.moveToWorldPos((float)hurricane.position.x, (float)hurricane.position.z); + region.setWorldRadius(hurricane.getStormExtentRadius() + SimpleCloudsConstants.MIN_SPAWN_DIST_BETWEEN_REGIONS); + region.setStretchFactor(1.0F); + region.setRotation(0.0F); + } + + public static void updateReservationRegion(CloudRegion region, HurricaneRenderSnapshot hurricane) { + region.moveToWorldPos((float)hurricane.centerX(), (float)hurricane.centerZ()); + region.setWorldRadius(hurricane.stormExtentRadius() + SimpleCloudsConstants.MIN_SPAWN_DIST_BETWEEN_REGIONS); + region.setStretchFactor(1.0F); + region.setRotation(0.0F); + } + + public static @Nullable Level resolveLevel(@Nullable Object owner) { + if (!(owner instanceof CloudManager cloudManager) || CLOUD_MANAGER_LEVEL_FIELD == null) { + return null; + } + try { + Object level = CLOUD_MANAGER_LEVEL_FIELD.get(cloudManager); + return level instanceof Level resolved ? resolved : null; + } catch (IllegalAccessException ignored) { + return null; + } + } + + private static boolean intersectsReservation(double centerX, double centerZ, double stormExtentRadius, + double worldX, double worldZ, double extraRadius) { + double dx = worldX - centerX; + double dz = worldZ - centerZ; + double reservedRadius = stormExtentRadius + Math.max(0.0D, extraRadius); + return dx * dx + dz * dz <= reservedRadius * reservedRadius; + } + + private static HurricaneSemanticSample sample(HurricaneInstance hurricane, double worldX, double worldZ) { + return sample( + hurricane.position.x, + hurricane.position.z, + hurricane.getAnchorY(), + hurricane.getCoreRadius(), + hurricane.getStormExtentRadius(), + hurricane.getVisualEyeRadius(), + hurricane.getVisualEdgeFade(), + hurricane.getBandCount(), + hurricane.getBandWidth(), + hurricane.getSpiralTightness(), + hurricane.getRotationPhase(), + hurricane.getTransitionStart(), + hurricane.getTransitionEnd(), + HurricaneInstance.HURRICANE_CLOUD_TYPE_ID, + worldX, + worldZ + ); + } + + private static HurricaneSemanticSample sample(HurricaneRenderSnapshot hurricane, double worldX, double worldZ) { + return sample( + hurricane.centerX(), + hurricane.centerZ(), + hurricane.anchorY(), + hurricane.coreRadius(), + hurricane.stormExtentRadius(), + hurricane.eyeRadius(), + hurricane.edgeFade(), + hurricane.bandCount(), + hurricane.bandWidth(), + hurricane.spiralTightness(), + hurricane.rotationPhase(), + hurricane.transitionStart(), + hurricane.transitionEnd(), + hurricane.cloudTypeId(), + worldX, + worldZ + ); + } + + private static HurricaneSemanticSample sample(double centerX, double centerZ, float anchorY, float coreRadius, + float stormExtentRadius, float eyeRadius, float edgeFade, int bandCount, + float bandWidth, float spiralTightness, float rotationPhase, + float transitionStart, float transitionEnd, + net.minecraft.resources.ResourceLocation cloudTypeId, + double worldX, double worldZ) { + float dx = (float)(worldX - centerX); + float dz = (float)(worldZ - centerZ); + float radius = Mth.sqrt(dx * dx + dz * dz); + if (radius > stormExtentRadius + edgeFade * 1.20F) { + return HurricaneSemanticSample.none(); + } + + boolean inEye = radius <= eyeRadius; + float angle = (float)Math.atan2(dz, dx); + float normalizedRadius = stormExtentRadius > 0.0F ? saturate(radius / stormExtentRadius) : 0.0F; + float spinPhase = angle + Math.max(radius - eyeRadius, 0.0F) * spiralTightness - rotationPhase; + + float outerMask = 1.0F - smoothstep(stormExtentRadius - edgeFade * 0.34F, stormExtentRadius + edgeFade * 0.92F, radius); + float eyeHole = inEye ? 0.0F : smoothstep(eyeRadius + edgeFade * 0.06F, eyeRadius + edgeFade * 0.82F, radius); + + float eyewallCenter = eyeRadius + bandWidth * 0.36F; + float eyewallThickness = bandWidth * 0.88F + edgeFade * 0.14F; + float eyewall = 1.0F - smoothstep(eyewallThickness * 0.26F, eyewallThickness, Math.abs(radius - eyewallCenter)); + eyewall *= eyeHole; + + float armNoiseA = cos01(spinPhase * bandCount + normalizedRadius * 7.0F); + float armNoiseB = cos01(spinPhase * (bandCount * 0.72F + 1.10F) - normalizedRadius * 9.5F); + float armNoise = smoothstep(0.60F, 0.93F, Mth.lerp(0.42F, armNoiseA, armNoiseB)); + + float spiralEnvelope = smoothstep(eyeRadius + bandWidth * 0.12F, eyeRadius + bandWidth * 1.28F, radius); + spiralEnvelope *= 1.0F - smoothstep(coreRadius * 0.78F, coreRadius * 1.18F, radius); + + float coreCoverage = Math.max(eyewall, armNoise * spiralEnvelope); + + // Start the cumulonimbus recovery close to the eyewall so the core never hands off into a dead gap. + float bridgeStart = eyeRadius + bandWidth * 0.52F; + float bridgeBuildEnd = Math.max(bridgeStart + bandWidth * 1.85F, coreRadius * 0.36F); + float bridgeFadeEnd = Math.max(coreRadius * 1.08F, bridgeBuildEnd + bandWidth * 2.40F); + + float cbStart = Math.min(transitionStart, bridgeStart); + float cbEnvelope = smoothstep(cbStart, transitionEnd, radius); + cbEnvelope *= 1.0F - smoothstep(stormExtentRadius * 1.02F, stormExtentRadius + edgeFade * 0.78F, radius); + + float cbNoiseA = cos01(angle * 1.9F - rotationPhase * 0.05F + normalizedRadius * 6.8F); + float cbNoiseB = cos01(angle * 4.4F + rotationPhase * 0.025F - normalizedRadius * 12.6F); + float cbNoiseC = cos01(angle * 2.7F - normalizedRadius * 4.4F); + float cbNoise = smoothstep(0.18F, 0.90F, Mth.lerp(0.30F, Mth.lerp(0.45F, cbNoiseA, cbNoiseB), cbNoiseC)); + + float innerCbA = cos01(spinPhase * (bandCount * 0.92F + 0.85F) - normalizedRadius * 6.4F); + float innerCbB = cos01(angle * 2.2F - rotationPhase * 0.10F + normalizedRadius * 4.8F); + float innerCbMask = smoothstep(0.20F, 0.82F, Mth.lerp(0.42F, innerCbA, innerCbB)); + + float innerBridgeEnvelope = smoothstep(bridgeStart, bridgeBuildEnd, radius); + innerBridgeEnvelope *= 1.0F - smoothstep(coreRadius * 0.94F, bridgeFadeEnd, radius); + + float outerBandEnvelope = smoothstep(coreRadius * 0.42F, stormExtentRadius * 0.90F, radius); + outerBandEnvelope *= 1.0F - smoothstep(stormExtentRadius * 0.96F, stormExtentRadius + edgeFade * 0.72F, radius); + + float outerBandA = smoothstep( + 0.58F, + 0.94F, + cos01(spinPhase * (bandCount * 0.42F + 1.05F) - normalizedRadius * 15.0F) + ); + float outerBandB = smoothstep( + 0.56F, + 0.92F, + cos01((angle - rotationPhase * 0.16F) * (bandCount * 0.30F + 1.85F) + normalizedRadius * 21.0F) + ); + float outerBandMask = smoothstep(0.34F, 0.88F, Mth.lerp(0.38F, outerBandA, outerBandB)); + + float cbMass = cbEnvelope * (0.22F + cbNoise * 0.26F + outerBandMask * 0.52F); + + float innerBridge = innerBridgeEnvelope * (0.54F + innerCbMask * 0.26F + armNoise * 0.20F); + + float continuityBand = smoothstep(bridgeStart, coreRadius * 0.96F, radius); + continuityBand *= 1.0F - smoothstep(coreRadius * 1.08F, transitionEnd * 0.92F, radius); + continuityBand *= 0.48F + Mth.lerp(0.50F, armNoise, outerBandMask) * 0.44F; + + float spiralShoulders = outerBandEnvelope * (0.28F + outerBandMask * 0.72F); + spiralShoulders *= 0.52F + cbNoise * 0.30F; + + float anvilEdge = smoothstep(stormExtentRadius * 0.70F, stormExtentRadius * 0.95F, radius); + anvilEdge *= 1.0F - smoothstep(stormExtentRadius * 1.04F, stormExtentRadius + edgeFade * 0.94F, radius); + anvilEdge *= smoothstep(0.34F, 0.88F, cbNoiseB) * (0.42F + outerBandMask * 0.58F); + + float outerCoverage = Math.max(Math.max(cbMass, spiralShoulders), Math.max(innerBridge, continuityBand + anvilEdge * 0.20F)); + float coverage = Math.max(coreCoverage, outerCoverage); + coverage *= outerMask * eyeHole; + coverage = smoothstep(0.04F, 0.88F, saturate(coverage)); + + if (inEye) { + return new HurricaneSemanticSample(cloudTypeId, anchorY, 0.0F, 0.0F, true, 0.0F, 0.0F); + } + if (coverage <= MIN_COVERAGE) { + return HurricaneSemanticSample.none(); + } + + float rainStrength = Math.max(outerCoverage * 0.78F, coreCoverage * 0.94F); + if (outerCoverage > 0.12F) { + rainStrength = Math.max(rainStrength, OUTER_RAIN_FLOOR); + } + rainStrength = Mth.clamp(rainStrength, 0.0F, 1.0F); + + return new HurricaneSemanticSample( + cloudTypeId, + anchorY, + coverage, + rainStrength, + false, + Mth.clamp(coreCoverage, 0.0F, 1.0F), + Mth.clamp(outerCoverage, 0.0F, 1.0F) + ); + } + + private static float smoothstep(float edge0, float edge1, float value) { + if (edge1 <= edge0) { + return value >= edge1 ? 1.0F : 0.0F; + } + float t = saturate((value - edge0) / (edge1 - edge0)); + return t * t * (3.0F - 2.0F * t); + } + + private static float saturate(float value) { + return Mth.clamp(value, 0.0F, 1.0F); + } + + private static float cos01(float value) { + return 0.5F + 0.5F * Mth.cos(value); + } + + private static @Nullable Field projectatmosphere$initCloudManagerLevelField() { + try { + Field field = CloudManager.class.getDeclaredField("level"); + field.setAccessible(true); + return field; + } catch (ReflectiveOperationException ignored) { + return null; + } + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneSnapshot.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneSnapshot.java new file mode 100644 index 00000000..8c1fb874 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneSnapshot.java @@ -0,0 +1,55 @@ +package net.Gabou.projectatmosphere.modules.hurricane; + +import net.Gabou.projectatmosphere.modules.weather.StormLifecyclePhase; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.phys.Vec3; + +import java.util.UUID; + +public record HurricaneSnapshot( + UUID id, + Vec3 position, + float radius, + float eyewallRadius, + int ageTicks, + float windSpeed, + float windAngle, + float windGust, + float normalizedIntensity, + HurricaneRenderDescriptor renderDescriptor, + HurricaneCategory category, + StormLifecyclePhase phase +) { + public void write(FriendlyByteBuf buf) { + buf.writeUUID(this.id); + buf.writeDouble(this.position.x); + buf.writeDouble(this.position.y); + buf.writeDouble(this.position.z); + buf.writeFloat(this.radius); + buf.writeFloat(this.eyewallRadius); + buf.writeVarInt(this.ageTicks); + buf.writeFloat(this.windSpeed); + buf.writeFloat(this.windAngle); + buf.writeFloat(this.windGust); + buf.writeFloat(this.normalizedIntensity); + this.renderDescriptor.write(buf); + buf.writeEnum(this.category); + buf.writeEnum(this.phase); + } + + public static HurricaneSnapshot read(FriendlyByteBuf buf) { + UUID id = buf.readUUID(); + Vec3 position = new Vec3(buf.readDouble(), buf.readDouble(), buf.readDouble()); + float radius = buf.readFloat(); + float eyewallRadius = buf.readFloat(); + int ageTicks = buf.readVarInt(); + float windSpeed = buf.readFloat(); + float windAngle = buf.readFloat(); + float windGust = buf.readFloat(); + float normalizedIntensity = buf.readFloat(); + HurricaneRenderDescriptor renderDescriptor = HurricaneRenderDescriptor.read(buf); + HurricaneCategory category = buf.readEnum(HurricaneCategory.class); + StormLifecyclePhase phase = buf.readEnum(StormLifecyclePhase.class); + return new HurricaneSnapshot(id, position, radius, eyewallRadius, ageTicks, windSpeed, windAngle, windGust, normalizedIntensity, renderDescriptor, category, phase); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneState.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneState.java deleted file mode 100644 index d2ce2983..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneState.java +++ /dev/null @@ -1,4 +0,0 @@ -package net.Gabou.projectatmosphere.modules.hurricane; - -public record HurricaneState(double centerX, double centerZ, double eyeRadius, double eyewallFade) {} - diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneWindField.java b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneWindField.java new file mode 100644 index 00000000..24f89fe3 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/hurricane/HurricaneWindField.java @@ -0,0 +1,122 @@ +package net.Gabou.projectatmosphere.modules.hurricane; + +import net.Gabou.projectatmosphere.modules.weather.StormShieldManager; +import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +final class HurricaneWindField { + private static final double MIN_ENTITY_HEIGHT_OFFSET = -8.0D; + private static final double MAX_ENTITY_HEIGHT = 128.0D; + + private HurricaneWindField() { + } + + static void apply(HurricaneInstance hurricane, ServerLevel level, long gameTime) { + if (!hurricane.markWindFieldTick(gameTime)) { + return; + } + + float stormIntensity = hurricane.getWindIntensity(); + if (stormIntensity <= 0.05F) { + return; + } + + double effectRadius = Mth.clamp( + hurricane.getCoreRadius() * 0.82D, + 96.0D, + 248.0D + hurricane.category.ordinal() * 20.0D + ); + double eyeRadius = hurricane.getVisualEyeRadius(); + AABB box = new AABB( + hurricane.position.x - effectRadius, hurricane.position.y + MIN_ENTITY_HEIGHT_OFFSET, hurricane.position.z - effectRadius, + hurricane.position.x + effectRadius, hurricane.position.y + MAX_ENTITY_HEIGHT, hurricane.position.z + effectRadius + ); + Vec3 stormDrift = new Vec3( + Math.cos(hurricane.wind.angleRadians()), + 0.0D, + Math.sin(hurricane.wind.angleRadians()) + ).scale(Math.min(hurricane.wind.baseSpeed(), 40.0F) * (0.00045D + stormIntensity * 0.00025D)); + + for (Entity entity : level.getEntities(null, box)) { + if (!projectatmosphere$isAffectedEntity(level, entity)) { + continue; + } + + double dx = entity.getX() - hurricane.position.x; + double dz = entity.getZ() - hurricane.position.z; + double distSq = dx * dx + dz * dz; + if (distSq < 0.25D || distSq > effectRadius * effectRadius) { + continue; + } + + double dist = Math.sqrt(distSq); + double inverseDist = 1.0D / dist; + float centerFalloff = Mth.clamp((float) (1.0D - dist / effectRadius), 0.0F, 1.0F); + float ringFactor = projectatmosphere$ringFactor((float) dist, (float) (eyeRadius * 1.10D), (float) (effectRadius * 0.74D)); + + Vec3 inward = new Vec3(-dx * inverseDist, 0.0D, -dz * inverseDist); + Vec3 tangential = new Vec3(-inward.z * hurricane.getRotationDirection(), 0.0D, inward.x * hurricane.getRotationDirection()); + + double tangentialStrength = (0.028D + stormIntensity * 0.052D) + * (0.45D + centerFalloff * 0.25D + ringFactor * 0.30D); + double inwardStrength = (0.007D + stormIntensity * 0.020D) + * (0.25D + centerFalloff * 0.75D); + double liftStrength = (0.003D + stormIntensity * 0.010D) + * (0.10D + ringFactor * 0.90D); + + if (dist < eyeRadius * 0.90D) { + tangentialStrength *= 0.35D; + inwardStrength *= 0.45D; + liftStrength *= 0.25D; + } + + double entityScale = entity instanceof Player ? 0.82D : 1.0D; + if (entity.onGround()) { + entityScale *= 0.94D; + liftStrength += 0.008D + stormIntensity * 0.006D; + } + + Vec3 push = tangential.scale(tangentialStrength * entityScale) + .add(inward.scale(inwardStrength * entityScale)) + .add(stormDrift) + .add(0.0D, liftStrength * entityScale, 0.0D); + + entity.push(push.x, push.y, push.z); + entity.hasImpulse = true; + entity.hurtMarked = true; + if (push.y > 0.0D) { + entity.fallDistance = 0.0F; + } + + if (entity instanceof ServerPlayer serverPlayer) { + serverPlayer.connection.send(new ClientboundSetEntityMotionPacket(serverPlayer)); + } + } + } + + private static boolean projectatmosphere$isAffectedEntity(ServerLevel level, Entity entity) { + if (entity == null || !entity.isAlive() || entity.isRemoved() || entity.noPhysics || entity.isSpectator()) { + return false; + } + if (entity instanceof Player player && (player.isCreative() || player.isSpectator())) { + return false; + } + return !StormShieldManager.isProtected(level, entity.position()); + } + + private static float projectatmosphere$ringFactor(float radius, float innerRadius, float outerRadius) { + if (outerRadius <= innerRadius) { + return 0.0F; + } + float mid = (innerRadius + outerRadius) * 0.5F; + float span = Math.max(1.0F, (outerRadius - innerRadius) * 0.5F); + float normalized = 1.0F - Math.abs(radius - mid) / span; + return Mth.clamp(normalized, 0.0F, 1.0F); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/storm/StormGenerator.java b/src/main/java/net/Gabou/projectatmosphere/modules/storm/StormGenerator.java index 483de3af..a149d6fd 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/storm/StormGenerator.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/storm/StormGenerator.java @@ -2,10 +2,9 @@ import net.Gabou.projectatmosphere.ProjectAtmosphere; import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.Gabou.projectatmosphere.seasons.SeasonStage; import net.Gabou.projectatmosphere.util.BiomeInstanceKey; import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; -import sereneseasons.api.season.Season; import java.util.Random; @@ -23,7 +22,7 @@ public static float[][] generateWeeklyStormProfile( float[][] humidity, float[][] pressure, WindVector[] wind, - Season season + SeasonStage season ) { float[][] stormWeek = new float[7][2]; BlockPos pos = biome.samplePos(); @@ -98,29 +97,32 @@ private static float clamp(float val, float min, float max) { return Math.max(min, Math.min(max, val)); } - private static float getSeasonalStormMultiplier(Season season) { + private static float getSeasonalStormMultiplier(SeasonStage season) { return switch (season) { case SPRING -> 1.1f; case SUMMER -> 1.4f; case AUTUMN -> 1.6f; case WINTER -> 0.8f; + case NEUTRAL -> 1.0f; }; } - private static float getSeasonalStormMin(Season season) { + private static float getSeasonalStormMin(SeasonStage season) { return switch (season) { case SPRING -> 0.18f; case SUMMER -> 0.12f; case AUTUMN -> 0.0f; case WINTER -> 0.08f; + case NEUTRAL -> 0.12f; }; } - private static float getSeasonalStormMax(Season currentSeason) { + private static float getSeasonalStormMax(SeasonStage currentSeason) { return switch (currentSeason) { case SPRING -> 0.9f; case SUMMER, AUTUMN -> 1.0f; case WINTER -> 0.7f; + case NEUTRAL -> 0.9f; }; } diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/temperature/command/TemperatureCommandHelper.java b/src/main/java/net/Gabou/projectatmosphere/modules/temperature/command/TemperatureCommandHelper.java index 3c72d890..62d1b941 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/temperature/command/TemperatureCommandHelper.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/temperature/command/TemperatureCommandHelper.java @@ -4,20 +4,15 @@ import net.Gabou.projectatmosphere.manager.ForecastGenerator; import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; import net.Gabou.projectatmosphere.modules.atmosphere.AtmosphericStateRegistry; -import net.Gabou.projectatmosphere.modules.temperature.compat.SereneTempToCelcius; +import net.Gabou.projectatmosphere.seasons.SeasonSnapshot; +import net.Gabou.projectatmosphere.seasons.SeasonTimeHelper; import net.Gabou.projectatmosphere.util.AtmosphereUtils; import net.Gabou.projectatmosphere.util.BiomeInstanceKey; import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; -import net.minecraft.world.level.biome.Biome; -import sereneseasons.init.ModConfig; -import sereneseasons.season.SeasonHandler; -import sereneseasons.season.SeasonHooks; -import sereneseasons.season.SeasonTime; import java.util.Map; @@ -64,20 +59,9 @@ public static float[] getDayProfile(BiomeInstanceKey biome) { } public static String getCurrentSubSeason(ServerLevel level) { - var data = SeasonHandler.getSeasonSavedData(level); - var time = new SeasonTime(data.seasonCycleTicks); - return time.getSubSeason().toString(); - } - - public static float getFinalBiomeTemperature(Level level, Holder biomeHolder, BlockPos pos) { - return ModConfig.seasons.isDimensionWhitelisted(level.dimension()) - && !biomeHolder.is(sereneseasons.init.ModTags.Biomes.BLACKLISTED_BIOMES) - ? SeasonHooks.getBiomeTemperature(level, biomeHolder, pos) - : biomeHolder.value().getBaseTemperature(); - } - - public static double convertToCelsius(float sereneTemp) { - return SereneTempToCelcius.SereneTempToCelcius(sereneTemp); + SeasonSnapshot snapshot = SeasonTimeHelper.snapshot(level); + return snapshot.stage() + " (" + snapshot.providerId() + ", progress " + + Math.round(snapshot.progress() * 100.0f) + "%)"; } public static float getRealTemperature(ServerLevel level, BiomeInstanceKey biome, BlockPos pos) { diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/temperature/command/TemperatureCommands.java b/src/main/java/net/Gabou/projectatmosphere/modules/temperature/command/TemperatureCommands.java index 93f568b7..5a25dad4 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/temperature/command/TemperatureCommands.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/temperature/command/TemperatureCommands.java @@ -114,13 +114,12 @@ public static LiteralArgumentBuilder build() { var biomeHolder = level.getBiome(pos); BiomeInstanceKey biomeId = new BiomeInstanceKey(biomeHolder.unwrapKey().get().location(), pos); - float serene = TemperatureCommandHelper.getFinalBiomeTemperature(level, biomeHolder, pos); - double celsius = TemperatureCommandHelper.convertToCelsius(serene); + float biomeBase = biomeHolder.value().getBaseTemperature(); float realTemp = TemperatureCommandHelper.getRealTemperature(level, biomeId, pos); - ctx.getSource().sendSuccess(() -> Component.literal("Current temperature (raw): " + serene), false); - ctx.getSource().sendSuccess(() -> Component.literal("Converted: " + net.Gabou.projectatmosphere.util.UnitFormatter.formatTemperature((float) celsius)), false); - ctx.getSource().sendSuccess(() -> Component.literal("Current (converted): " + net.Gabou.projectatmosphere.util.UnitFormatter.formatTemperature(realTemp)), false); + ctx.getSource().sendSuccess(() -> Component.literal("Biome base temperature: " + biomeBase), false); + ctx.getSource().sendSuccess(() -> Component.literal("Season provider: " + TemperatureCommandHelper.getCurrentSubSeason(level)), false); + ctx.getSource().sendSuccess(() -> Component.literal("Current forecast temperature: " + net.Gabou.projectatmosphere.util.UnitFormatter.formatTemperature(realTemp)), false); return 1; })) @@ -156,8 +155,8 @@ public static LiteralArgumentBuilder build() { "/pa temperature forecast - Show 7-day forecast for your current biome.\n" + "/pa temperature get - Display the current temperature at a specific biome and tick.\n" + "/pa temperature dayprofile - View the 240-point daily temperature curve.\n" + - "/pa temperature getseason - Show the current Serene Seasons sub-season.\n" + - "/pa temperature gettemp - Raw and converted temperatures.\n" + + "/pa temperature getseason - Show the current season provider state.\n" + + "/pa temperature gettemp - Show biome base and PA forecast temperatures.\n" + "/pa temperature regenerate - Clear forecast cache and regenerate missing data.\n" + "/pa temperature resetSpikes - Clear spike simulation state cache." ), false); diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/temperature/compat/SereneTempToCelcius.java b/src/main/java/net/Gabou/projectatmosphere/modules/temperature/compat/SereneTempToCelcius.java deleted file mode 100644 index 5a6252a2..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/modules/temperature/compat/SereneTempToCelcius.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.Gabou.projectatmosphere.modules.temperature.compat; - -public class SereneTempToCelcius { - private final static float maxTemp = 56f; - private final static float minTemp = -20f; - - public static float SereneTempToCelcius(float sereneTemp) { - - return (float) mapToTemperature(sereneTemp, -0.5f, 2.0f, minTemp, maxTemp); - } - - public static double mapToTemperature(float input, float inputMin, float inputMax, float outputMin, float outputMax) { - - input = Math.max(inputMin, Math.min(inputMax, input)); - - - return outputMin + (input - inputMin) * (outputMax - outputMin) / (inputMax - inputMin); - } -} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/GlassDamageManager.java b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/GlassDamageManager.java index 44adc95e..4002ad8f 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/GlassDamageManager.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/GlassDamageManager.java @@ -36,6 +36,7 @@ public class GlassDamageManager { * @param toProcess A list of block positions (as long values) to process for damage. */ public static void damageGlass(ServerLevel level, it.unimi.dsi.fastutil.longs.LongArrayList toProcess) { + if (!AtmoCommonConfig.ENABLE_TORNADO_DESTRUCTION.get()) return; if (toProcess.isEmpty()) return; for(int i = 0; i < toProcess.size(); i++) { BlockPos pos = BlockPos.of(toProcess.getLong(i)); @@ -48,7 +49,7 @@ public static void damageGlass(ServerLevel level, it.unimi.dsi.fastutil.longs.Lo glass.damage = Math.min(MAX_DAMAGE, glass.damage + 1); glass.lastDamageTime = System.currentTimeMillis(); - if (glass.damage >= MAX_DAMAGE && !glass.broken && doDamageGlass) { + if (glass.damage >= MAX_DAMAGE && !glass.broken && AtmoCommonConfig.DAMAGE_GLASS_ON_TORNADO.get()) { toDestroy.add((pos.asLong())); glass.broken = true; } @@ -65,6 +66,11 @@ public static void damageGlass(ServerLevel level, it.unimi.dsi.fastutil.longs.Lo private static void processGlassDestruction(ServerLevel level, it.unimi.dsi.fastutil.longs.LongArrayList list, int perTick) { + if (!AtmoCommonConfig.ENABLE_TORNADO_DESTRUCTION.get() || !AtmoCommonConfig.DAMAGE_GLASS_ON_TORNADO.get()) { + _destroyCursor = 0; + list.clear(); + return; + } if (_destroyCursor >= list.size()) { _destroyCursor = 0; return; } int end = Math.min(_destroyCursor + perTick, list.size()); diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoCommand.java b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoCommand.java index 09865dd8..a196f81b 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoCommand.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoCommand.java @@ -1,13 +1,11 @@ package net.Gabou.projectatmosphere.modules.tornado; +import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; import dev.nonamecrackers2.simpleclouds.common.world.SpawnRegion; -import net.Gabou.projectatmosphere.ProjectAtmosphere; import net.Gabou.projectatmosphere.api.WindVectorApi; -import net.Gabou.projectatmosphere.api.common.cloud.region.ITornadoRegion; -import net.Gabou.projectatmosphere.api.common.cloud.region.TornadoDescriptor; import net.Gabou.projectatmosphere.compat.SimpleCloudsCompat; import net.Gabou.projectatmosphere.util.AtmosphereUtils; import net.Gabou.projectatmosphere.util.BiomeInstanceKey; @@ -16,23 +14,56 @@ import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; +import java.util.Comparator; + public class TornadoCommand { - private static final ResourceLocation PROJECTATMOSPHERE$TORNADO_CONTROLLER = - new ResourceLocation(ProjectAtmosphere.MODID, "command_spawn"); private static final String PROJECTATMOSPHERE$CUMULONIMBUS_ID = "simpleclouds:cumulonimbus"; private static final int PROJECTATMOSPHERE$SEARCH_RADIUS = 10; private static final float PROJECTATMOSPHERE$DEFAULT_RADIUS = 10.0F; private static final int PROJECTATMOSPHERE$SPAWN_DELAY_TICKS = 500; private static final int PROJECTATMOSPHERE$AWAIT_INTERVAL = 100; private static final int PROJECTATMOSPHERE$AWAIT_POLLS = 40; + private static final double PROJECTATMOSPHERE$DEFAULT_REMOVE_RADIUS = 256.0D; public static void appendTo(LiteralArgumentBuilder root) { + LiteralArgumentBuilder noCloudsBaseCommand = Commands.literal("spawnTornadoNoClouds") + .requires(source -> source.hasPermission(2)) + .executes(ctx -> { + ServerPlayer player = ctx.getSource().getPlayerOrException(); + ServerLevel level = player.serverLevel(); + if (!level.dimension().equals(Level.OVERWORLD)) { + return 0; + } + + Vec3 playerPos = player.position(); + Vec3 tornadoPos = new Vec3(playerPos.x, level.getSeaLevel(), playerPos.z); + RegionInstanceKey regionKey = RegionInstanceKey.from(player.blockPosition()); + WindVectorApi.WindSample sample = WindVectorApi.getOrFallback(regionKey, level.getGameTime()); + net.Gabou.projectatmosphere.modules.core.WindVector wind = + net.Gabou.projectatmosphere.modules.core.WindVector.fromBase( + sample.speedMps(), + (float) Math.toRadians(sample.directionDeg()) + ); + + if (TornadoManager.spawnServerWithoutCloud(level, tornadoPos, PROJECTATMOSPHERE$DEFAULT_RADIUS, wind)) { + ctx.getSource().sendSuccess( + () -> Component.literal("Standalone tornado spawned and is forming."), + true + ); + return 1; + } + + ctx.getSource().sendFailure(Component.literal("Unable to spawn standalone tornado.")); + return 0; + }); + root.then(noCloudsBaseCommand); + LiteralArgumentBuilder baseCommand = Commands.literal("spawnTornado") .requires(source -> source.hasPermission(2)) .executes(ctx -> { @@ -41,11 +72,13 @@ public static void appendTo(LiteralArgumentBuilder root) { if (!level.dimension().equals(Level.OVERWORLD)) { return 0; } + Vec3 playerPos = player.position(); Vec3 tornadoPos = new Vec3(playerPos.x, level.getSeaLevel(), playerPos.z); BiomeInstanceKey key = new BiomeInstanceKey( AtmosphereUtils.getBiomeLocation(player.blockPosition(), level), - player.blockPosition()); + player.blockPosition() + ); RegionInstanceKey regionKey = RegionInstanceKey.from(player.blockPosition()); WindVectorApi.WindSample sample = WindVectorApi.getOrFallback(regionKey, level.getGameTime()); net.Gabou.projectatmosphere.modules.core.WindVector wind = @@ -56,33 +89,44 @@ public static void appendTo(LiteralArgumentBuilder root) { CloudRegion existing = projectatmosphere$findCumulonimbus(level, tornadoPos); if (existing != null) { - if (projectatmosphere$attachDescriptor(level, existing, tornadoPos)) { + if (TornadoManager.spawnServer(level, tornadoPos, PROJECTATMOSPHERE$DEFAULT_RADIUS, wind)) { ctx.getSource().sendSuccess( - () -> Component.literal("🌪️ Tornado engaged using SimpleClouds cumulonimbus."), true); + () -> Component.literal("Tornado engaged using SimpleClouds cumulonimbus."), + true + ); return 1; } - projectatmosphere$awaitCloud(ctx.getSource(), level, tornadoPos, PROJECTATMOSPHERE$AWAIT_POLLS); + projectatmosphere$awaitCloud(ctx.getSource(), level, tornadoPos, wind, PROJECTATMOSPHERE$AWAIT_POLLS); return 1; } CloudRegion spawnedRegion = SimpleCloudsCompat.spawnCloudInBiome( - "cumulonimbus", key, level, null, wind); + "cumulonimbus", + key, + level, + null, + wind + ); if (spawnedRegion != null) { CommandSourceStack source = ctx.getSource(); DelayedTaskScheduler.schedule(PROJECTATMOSPHERE$SPAWN_DELAY_TICKS, () -> { - if (projectatmosphere$attachDescriptor(level, spawnedRegion, tornadoPos)) { + if (TornadoManager.spawnServer(level, tornadoPos, PROJECTATMOSPHERE$DEFAULT_RADIUS, wind)) { source.sendSuccess( - () -> Component.literal("🌪️ Tornado engaged once the seeded cumulonimbus matured."), true); + () -> Component.literal("Tornado engaged once the seeded cumulonimbus matured."), + true + ); } else { - projectatmosphere$awaitCloud(source, level, tornadoPos, PROJECTATMOSPHERE$AWAIT_POLLS); + projectatmosphere$awaitCloud(source, level, tornadoPos, wind, PROJECTATMOSPHERE$AWAIT_POLLS); } }); ctx.getSource().sendSuccess( - () -> Component.literal("☁️ Seeded a cumulonimbus; waiting for SimpleClouds tornado engagement."), true); + () -> Component.literal("Seeded a cumulonimbus; waiting for SimpleClouds tornado engagement."), + true + ); return 1; } - projectatmosphere$awaitCloud(ctx.getSource(), level, tornadoPos, PROJECTATMOSPHERE$AWAIT_POLLS); + projectatmosphere$awaitCloud(ctx.getSource(), level, tornadoPos, wind, PROJECTATMOSPHERE$AWAIT_POLLS); return 1; }); @@ -93,64 +137,95 @@ public static void appendTo(LiteralArgumentBuilder root) { root.then(Commands.literal("cleartornadoes") .requires(source -> source.hasPermission(2)) - .executes(ctx -> { - ServerLevel level = ctx.getSource().getLevel(); - TornadoManager.clearTornadoes(); - ctx.getSource().sendSuccess( - () -> Component.literal("🌪️ All tornadoes cleared."), true); - return 1; - })); - root.then(Commands.literal("removetornado") + .executes(ctx -> projectatmosphere$clearAllTornadoes(ctx.getSource()))); + + LiteralArgumentBuilder removeTornado = Commands.literal("removetornado") .requires(source -> source.hasPermission(2)) - .executes(ctx -> { - ServerPlayer player = ctx.getSource().getPlayerOrException(); - ServerLevel level = player.serverLevel(); - if (!level.dimension().equals(Level.OVERWORLD)) { - return 0; - } - Vec3 playerPos = player.position(); - TornadoInstance tornado = TornadoManager.getActiveTornadoes().stream() - .filter(t -> t.position.distanceToSqr(playerPos) < 100) - .findFirst() - .orElse(null); - if (tornado != null) { - TornadoManager.removeTornado(tornado); - ctx.getSource().sendSuccess( - () -> Component.literal("🌪️ Tornado removed."), true); - } else { - ctx.getSource().sendFailure( - Component.literal("No tornado found near you.")); - } - return 1; - })); + .executes(ctx -> projectatmosphere$removeNearestTornado(ctx.getSource(), PROJECTATMOSPHERE$DEFAULT_REMOVE_RADIUS)) + .then(Commands.argument("radius", IntegerArgumentType.integer(1)) + .executes(ctx -> projectatmosphere$removeNearestTornado( + ctx.getSource(), + IntegerArgumentType.getInteger(ctx, "radius") + ))) + .then(Commands.literal("all") + .executes(ctx -> projectatmosphere$clearAllTornadoes(ctx.getSource()))); + root.then(removeTornado); + root.then(Commands.literal("remove") + .requires(source -> source.hasPermission(2)) + .then(Commands.literal("tornado") + .executes(ctx -> projectatmosphere$removeNearestTornado(ctx.getSource(), PROJECTATMOSPHERE$DEFAULT_REMOVE_RADIUS)) + .then(Commands.argument("radius", IntegerArgumentType.integer(1)) + .executes(ctx -> projectatmosphere$removeNearestTornado( + ctx.getSource(), + IntegerArgumentType.getInteger(ctx, "radius") + ))) + .then(Commands.literal("all") + .executes(ctx -> projectatmosphere$clearAllTornadoes(ctx.getSource()))))); + } + + private static int projectatmosphere$removeNearestTornado(CommandSourceStack source, double maxDistance) throws com.mojang.brigadier.exceptions.CommandSyntaxException { + ServerPlayer player = source.getPlayerOrException(); + ServerLevel level = player.serverLevel(); + if (!level.dimension().equals(Level.OVERWORLD)) { + return 0; + } + + Vec3 playerPos = player.position(); + double maxDistanceSq = maxDistance * maxDistance; + TornadoInstance tornado = TornadoManager.getActiveTornadoes().stream() + .filter(t -> t.position.distanceToSqr(playerPos) <= maxDistanceSq) + .min(Comparator.comparingDouble(t -> t.position.distanceToSqr(playerPos))) + .orElse(null); + if (tornado == null) { + source.sendFailure(Component.literal("No tornado found within " + Mth.floor(maxDistance) + " blocks.")); + return 0; + } + + int distance = Mth.floor(Math.sqrt(tornado.position.distanceToSqr(playerPos))); + TornadoManager.removeTornado(tornado); + source.sendSuccess(() -> Component.literal("Tornado " + distance + " blocks away is dissipating."), true); + return 1; + } + + private static int projectatmosphere$clearAllTornadoes(CommandSourceStack source) { + TornadoManager.clearTornadoes(); + source.sendSuccess(() -> Component.literal("All tornadoes cleared."), true); + return 1; } private static void projectatmosphere$awaitCloud(CommandSourceStack source, ServerLevel level, Vec3 tornadoPos, + net.Gabou.projectatmosphere.modules.core.WindVector wind, int remainingPolls) { if (remainingPolls <= 0) { - source.sendFailure(Component.literal("⚠️ No SimpleClouds cumulonimbus became available for this tornado.")); + source.sendFailure(Component.literal("No SimpleClouds cumulonimbus became available for this tornado.")); return; } + DelayedTaskScheduler.schedule(PROJECTATMOSPHERE$AWAIT_INTERVAL, () -> { CloudRegion region = projectatmosphere$findCumulonimbus(level, tornadoPos); - if (region != null && projectatmosphere$attachDescriptor(level, region, tornadoPos)) { + if (region != null && TornadoManager.spawnServer(level, tornadoPos, PROJECTATMOSPHERE$DEFAULT_RADIUS, wind)) { source.sendSuccess( - () -> Component.literal("🌪️ Tornado engaged once a SimpleClouds cumulonimbus entered range."), true); + () -> Component.literal("Tornado engaged once a SimpleClouds cumulonimbus entered range."), + true + ); return; } if (remainingPolls - 1 > 0) { - projectatmosphere$awaitCloud(source, level, tornadoPos, remainingPolls - 1); + projectatmosphere$awaitCloud(source, level, tornadoPos, wind, remainingPolls - 1); } else { - source.sendFailure(Component.literal("⚠️ Timed out waiting for a suitable SimpleClouds cumulonimbus.")); + source.sendFailure(Component.literal("Timed out waiting for a suitable SimpleClouds cumulonimbus.")); } }); } private static CloudRegion projectatmosphere$findCumulonimbus(ServerLevel level, Vec3 pos) { - SpawnRegion region = new SpawnRegion((int) Math.floor(pos.x), (int) Math.floor(pos.z), - PROJECTATMOSPHERE$SEARCH_RADIUS); + SpawnRegion region = new SpawnRegion( + (int) Math.floor(pos.x), + (int) Math.floor(pos.z), + PROJECTATMOSPHERE$SEARCH_RADIUS + ); for (CloudRegion cloud : CloudManager.get(level).getClouds()) { if (!PROJECTATMOSPHERE$CUMULONIMBUS_ID.equals(cloud.getCloudTypeId().toString())) { continue; @@ -161,30 +236,4 @@ public static void appendTo(LiteralArgumentBuilder root) { } return null; } - - private static boolean projectatmosphere$attachDescriptor(ServerLevel level, CloudRegion region, Vec3 tornadoPos) { - if (!(region instanceof ITornadoRegion tornadoRegion)) { - return false; - } - float offsetX = (float) (tornadoPos.x - region.getWorldX()); - float offsetZ = (float) (tornadoPos.z - region.getWorldZ()); - float cappedRadius = (float) Math.max(2.0F, - Math.min(PROJECTATMOSPHERE$DEFAULT_RADIUS, region.getWorldRadius())); - float bottom = (float) Math.min(tornadoPos.y, level.getSeaLevel()); - float height = Math.max(80.0F, cappedRadius * 12.0F); - tornadoRegion.getTornadoes().removeIf(descriptor -> - PROJECTATMOSPHERE$TORNADO_CONTROLLER.equals(descriptor.getControllerId())); - TornadoDescriptor descriptor = new TornadoDescriptor( - PROJECTATMOSPHERE$TORNADO_CONTROLLER, - offsetX, - offsetZ, - 0.0F, - 0.0F, - cappedRadius, - bottom, - height - ); - tornadoRegion.addTornado(descriptor); - return true; - } } diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoDebug.java b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoDebug.java index 565340e4..4a5935dc 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoDebug.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoDebug.java @@ -1,19 +1,23 @@ package net.Gabou.projectatmosphere.modules.tornado; +import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.FloatArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.Gabou.projectatmosphere.api.WindVectorApi; import net.Gabou.projectatmosphere.compat.SimpleCloudsCompat; +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; import net.Gabou.projectatmosphere.modules.temperature.command.TemperatureCommandHelper; import net.Gabou.projectatmosphere.util.AtmosphereUtils; import net.Gabou.projectatmosphere.util.RegionInstanceKey; import net.Gabou.projectatmosphere.data.TornadoStorageManager; +import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; public final class TornadoDebug { private TornadoDebug() {} @@ -68,6 +72,51 @@ public static void appendTo(LiteralArgumentBuilder root) { () -> Component.literal("Risk: " + risk), false); return 1; })) + .then(Commands.literal("runtime") + .executes(ctx -> { + ServerPlayer player = ctx.getSource().getPlayerOrException(); + ServerLevel level = player.serverLevel(); + TornadoInstance tornado = TornadoManager.getActiveTornadoes().stream() + .min((left, right) -> Double.compare( + left.position.distanceToSqr(player.position()), + right.position.distanceToSqr(player.position()) + )) + .orElse(null); + if (tornado == null) { + ctx.getSource().sendFailure(Component.literal("No active tornado found.")); + return 0; + } + + TornadoInstance.RuntimeDebugSnapshot debug = tornado.getRuntimeDebugSnapshot(); + ctx.getSource().sendSuccess(() -> Component.literal( + "Tornado Runtime: " + debug.id() + + "\n Phase: " + debug.phase() + + "\n Intensity: " + String.format(java.util.Locale.ROOT, "%.3f", debug.normalizedIntensity()) + + "\n Eligible entities: " + debug.eligibleEntityCount() + + "\n Captured entities: " + debug.capturedEntityCount() + + "\n Pull force avg/max: " + String.format(java.util.Locale.ROOT, "%.3f / %.3f", debug.averagePullForce(), debug.maxPullForce()) + + "\n Upward force avg/max: " + String.format(java.util.Locale.ROOT, "%.3f / %.3f", debug.averageUpwardForce(), debug.maxUpwardForce()) + + "\n Sweep radius: " + String.format(java.util.Locale.ROOT, "%.3f", debug.destructionSweepRadius()) + + "\n Candidate blocks: " + debug.destructionCandidateBlockCount() + + "\n Destroyed blocks: " + debug.destroyedBlockCount() + + "\n Destroyed detail: leaves/logs=" + debug.destroyedLeafLogCount() + + " weak=" + debug.destroyedWeakCount() + + " grass=" + debug.destroyedGrassCount() + + " glass=" + debug.destroyedGlassCount() + ).withStyle(ChatFormatting.YELLOW), false); + return 1; + })) + .then(Commands.literal("logging") + .then(Commands.argument("value", BoolArgumentType.bool()) + .executes(ctx -> { + boolean value = BoolArgumentType.getBool(ctx, "value"); + AtmoCommonConfig.TORNADO_DEBUG_LOGGING.set(value); + ctx.getSource().sendSuccess( + () -> Component.literal("Tornado runtime logging set to: " + value), + true + ); + return 1; + }))) .then(Commands.literal("force") .then(Commands.argument("intensity", FloatArgumentType.floatArg(0f, 1f)) .executes(ctx -> { diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoInstance.java b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoInstance.java index 75bae94c..325d9932 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoInstance.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoInstance.java @@ -1,285 +1,1595 @@ package net.Gabou.projectatmosphere.modules.tornado; - +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; -import net.Gabou.projectatmosphere.util.AsyncAtmosphereService; +import net.Gabou.projectatmosphere.api.common.cloud.region.ITornadoRegion; +import net.Gabou.projectatmosphere.api.common.cloud.region.TornadoDescriptor; +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; +import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.Gabou.projectatmosphere.modules.weather.StormLifecyclePhase; +import net.Gabou.projectatmosphere.modules.weather.StormMotionModel; +import net.Gabou.projectatmosphere.modules.weather.StormSeverityScale; +import net.Gabou.projectatmosphere.modules.weather.StormShieldManager; import net.Gabou.projectatmosphere.util.AtmosphereUtils; import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.tags.BlockTags; +import net.minecraft.tags.FluidTags; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.item.FallingBlockEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; -import net.Gabou.projectatmosphere.modules.core.WindVector; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; public class TornadoInstance { + public static final double AMBIENT_WIND_INFLUENCE_EXTENSION = 15.0D; + public static final double WIND_SPEED_SCALING_FACTOR = 0.05D; + public static final double WIND_EFFECT_VERTICAL_MAX_OFFSET = 50.0D; + public static final double WIND_EFFECT_VERTICAL_MIN_OFFSET = -5.0D; + private static final int MINIMUM_PERSISTENCE_TICKS = 20 * 120; + private static final int MINIMUM_ACTIVE_TICKS = 20 * 120; + private static final int MAXIMUM_ACTIVE_TICKS = 20 * 600; + private static final int MINIMUM_FORMATION_TICKS = 20 * 6; + private static final int MAXIMUM_FORMATION_TICKS = 20 * 20; + private static final int MINIMUM_DISSIPATION_TICKS = 20 * 8; + private static final int MAXIMUM_DISSIPATION_TICKS = 20 * 40; + private static final int FLOW_FIELD_INTERVAL_TICKS = 1; + private static final int DEMOLITION_INTERVAL_TICKS = 4; + private static final float MIN_EFFECTIVE_WIND = 73.0F; + private static final float MAX_EFFECTIVE_WIND = 260.0F; + private static final double OUTER_ENTITY_INFLUENCE_PADDING = 16.0D; + private static final double ENTITY_MIN_VERTICAL_RANGE = -8.0D; + private static final double ENTITY_MAX_VERTICAL_PADDING = 20.0D; + private static final double ENTITY_RELEASE_HEIGHT_PADDING = 10.0D; + private static final float CAPTURE_RADIUS_FACTOR = 0.84F; + private static final float CORE_RADIUS_FACTOR = 0.34F; + private static final float OUTER_ORBIT_RADIUS_FACTOR = 0.60F; + private static final float INNER_ORBIT_RADIUS_FACTOR = 0.30F; + private static final float CAPTURE_HYSTERESIS_FACTOR = 1.18F; + private static final int CAPTURE_FULL_TICKS = 24; + private static final int CAPTURE_ASCENT_TICKS = 90; + private static final int CAPTURE_RELEASE_TICKS = 220; + private static final float BASE_SUCTION_FORCE = 0.13F; + private static final float BASE_TANGENTIAL_FORCE = 0.11F; + private static final float BASE_LIFT_FORCE = 0.12F; + private static final float CAPTURED_SUCTION_FORCE = 0.25F; + private static final float CAPTURED_TANGENTIAL_FORCE = 0.23F; + private static final float CAPTURED_LIFT_FORCE = 0.34F; + private static final float WATER_PENALTY_THRESHOLD = 0.20F; + private static final int CLOUD_DETACH_GRACE_TICKS = 20 * 30; + private static final float CLIENT_POSITION_INTERPOLATION = 0.18F; + private static final float CLIENT_SHAPE_INTERPOLATION = 0.22F; + private static final double CLIENT_SNAPSHOT_INTERVAL_TICKS = 5.0D; + private static final double CLIENT_VELOCITY_TRACKING = 0.45D; + private static final double CLIENT_VELOCITY_DAMPING = 0.84D; + private static final double CLIENT_EXTRAPOLATION_TICKS = 1.35D; + private static final int TREE_CLUSTER_HORIZONTAL_RADIUS = 4; + private static final int TREE_CLUSTER_BELOW = 4; + private static final int TREE_CLUSTER_ABOVE = 20; + private static final int TREE_CLUSTER_VISIT_LIMIT = 512; + private static final float MAX_DEBRIS_ENTITY_SPAWN_CHANCE = 0.46F; + private static final int BASE_MAX_DEBRIS_ENTITY_SPAWNS = 8; + private static final int ENTITY_DAMAGE_INTERVAL_TICKS = 8; + private static final float MOVEMENT_ROUTE_LEASH_RADIUS = 150.0F; + private static final double MOVEMENT_ROUTE_REACHED_DISTANCE_SQR = 36.0D; + private static final float MOVEMENT_HEADING_BLEND = 0.010F; + private static final float MOVEMENT_SPEED_BLEND = 0.10F; + private static final double MOVEMENT_VECTOR_BLEND = 0.16D; + private static final double MOVEMENT_AMBIENT_SCALE = 0.0035D; + private static final int MOVEMENT_REPLAN_ON_AVOIDANCE_TICKS = 24; - private static final int DEBRIS_RANGE_EXTENSION = 5; - public static final double AMBIENT_WIND_INFLUENCE_EXTENSION= 15; - public static final double WIND_SPEED_SCALING_FACTOR= 0.05; - public static final double WIND_EFFECT_VERTICAL_MAX_OFFSET = 50; - public static final double WIND_EFFECT_VERTICAL_MIN_OFFSET= -5; + private final UUID id; public Vec3 position; - public final long spawnTime; - public final float radius; - public final WindVector wind; + public float radius; + public WindVector wind; + private final float maxRadius; + private float targetVisualHeight; + private float visualBottomY; + private float visualHeight; + private float angularSpeed; + private float normalizedIntensity; + private float targetIntensity; + private int stormLevel; + private float headingRadians; + private float targetHeadingRadians; + private Vec3 motion = Vec3.ZERO; + private float plannedMoveSpeed; + private float targetMoveSpeed; + private int routeTicksRemaining; + @Nullable + private Vec3 routeWaypoint; + private boolean descriptorMissing; + private int ageTicks; + private int phaseTicks; + private int formationTicks; + private int activeTicks; + private int dissipationTicks; + private int detachedTicks; + private long spawnGameTime; + private long lastAmbientWindTick = 0; + private long lastDemolitionTick = 0; + private float anchorX; + private float anchorZ; + private final boolean requiresCloudAttachment; + private StormLifecyclePhase phase; + private float recentDebrisScore; + private Vec3 clientPreviousRenderPosition; + private Vec3 clientRenderPosition; + private Vec3 clientTargetPosition; + private Vec3 clientTargetVelocity; + private float clientPreviousRenderBottomY; + private float clientRenderBottomY; + private float clientTargetBottomY; + private float clientPreviousRenderHeight; + private float clientRenderHeight; + private float clientTargetHeight; + private float clientPreviousRenderRadius; + private float clientRenderRadius; + private float clientTargetRadius; + private final Map capturedEntities = new HashMap<>(); + private int debugEligibleEntityCount; + private int debugCapturedEntityCount; + private int debugForceSampleCount; + private double debugPullForceSum; + private double debugUpwardForceSum; + private double debugPullForceMax; + private double debugUpwardForceMax; + private float debugDestructionSweepRadius; + private int debugDestructionCandidateBlockCount; + private int debugDestroyedBlockCount; + private int debugDestroyedLeafLogCount; + private int debugDestroyedWeakCount; + private int debugDestroyedGrassCount; + private int debugDestroyedGlassCount; - private final CloudRegion cloudRegion; + @Nullable + private CloudRegion cloudRegion; - private float angularSpeed = 0.15f; - private long lastDemolitionCheck = 0L; - private final long demolitionIntervalMs = 1000L; - private final long ambientWindIntervalMs = 2000L; - private long lastAmbientWindCheck = 0L; + public TornadoInstance(Vec3 position, float radius, WindVector wind, @Nullable CloudRegion cloudRegion) { + this(UUID.randomUUID(), position, radius, wind, 0.05F, (float) position.y, Math.max(96.0F, radius * 12.0F), cloudRegion, StormSeverityScale.fromNormalized(Mth.clamp((radius - 5.0F) / 20.0F, 0.25F, 1.0F))); + } + public TornadoInstance(UUID id, Vec3 position, float radius, WindVector wind, + float visualBottomY, float visualHeight, @Nullable CloudRegion cloudRegion) { + this(id, position, radius, wind, 0.05F, visualBottomY, visualHeight, cloudRegion, StormSeverityScale.fromNormalized(Mth.clamp((radius - 5.0F) / 20.0F, 0.25F, 1.0F))); + } - private final TornadoLevel level; + public TornadoInstance(UUID id, Vec3 position, float radius, WindVector wind, float angularSpeed, + float visualBottomY, float visualHeight, @Nullable CloudRegion cloudRegion) { + this(id, position, radius, wind, angularSpeed, visualBottomY, visualHeight, cloudRegion, StormSeverityScale.fromNormalized(Mth.clamp((radius - 5.0F) / 20.0F, 0.25F, 1.0F))); + } + public TornadoInstance(UUID id, Vec3 position, float radius, WindVector wind, float angularSpeed, + float visualBottomY, float visualHeight, @Nullable CloudRegion cloudRegion, int stormLevel) { + this(id, position, radius, wind, angularSpeed, visualBottomY, visualHeight, cloudRegion, stormLevel, true); + } - public TornadoLevel getLevel() { - return level; + public TornadoInstance(UUID id, Vec3 position, float radius, WindVector wind, float angularSpeed, + float visualBottomY, float visualHeight, @Nullable CloudRegion cloudRegion, int stormLevel, + boolean requiresCloudAttachment) { + this.id = id; + this.position = position; + this.radius = radius; + this.maxRadius = radius; + this.wind = wind; + this.angularSpeed = angularSpeed; + this.visualBottomY = visualBottomY; + this.visualHeight = visualHeight; + this.targetVisualHeight = Math.max(visualHeight, 32.0F); + this.cloudRegion = cloudRegion; + this.stormLevel = StormSeverityScale.clamp(stormLevel); + this.requiresCloudAttachment = requiresCloudAttachment; + this.anchorX = (float) position.x; + this.anchorZ = (float) position.z; + this.phase = StormLifecyclePhase.FORMING; + this.targetIntensity = defaultTargetIntensity(radius, wind, this.stormLevel); + this.normalizedIntensity = Math.max(0.18F, this.targetIntensity * 0.35F); + this.headingRadians = wind.angleRadians(); + this.targetHeadingRadians = this.headingRadians; + this.plannedMoveSpeed = 0.04F + this.targetIntensity * 0.05F; + this.targetMoveSpeed = this.plannedMoveSpeed; + this.routeTicksRemaining = 0; + this.routeWaypoint = null; + float persistenceFactor = Mth.clamp( + this.targetIntensity * 0.65F + StormSeverityScale.toNormalized(this.stormLevel) * 0.35F, + 0.0F, + 1.0F + ); + this.formationTicks = Mth.floor(Mth.lerp(persistenceFactor, MINIMUM_FORMATION_TICKS, MAXIMUM_FORMATION_TICKS)); + this.activeTicks = Mth.floor(Mth.lerp(persistenceFactor, MINIMUM_ACTIVE_TICKS, MAXIMUM_ACTIVE_TICKS)); + this.dissipationTicks = Mth.floor(Mth.lerp(persistenceFactor, MINIMUM_DISSIPATION_TICKS, MAXIMUM_DISSIPATION_TICKS)); + this.applyIntensityToVisuals(); + this.clientPreviousRenderPosition = position; + this.clientRenderPosition = position; + this.clientTargetPosition = position; + this.clientTargetVelocity = Vec3.ZERO; + this.clientPreviousRenderBottomY = this.visualBottomY; + this.clientRenderBottomY = this.visualBottomY; + this.clientTargetBottomY = this.visualBottomY; + this.clientPreviousRenderHeight = this.visualHeight; + this.clientRenderHeight = this.visualHeight; + this.clientTargetHeight = this.visualHeight; + this.clientPreviousRenderRadius = this.radius; + this.clientRenderRadius = this.radius; + this.clientTargetRadius = this.radius; + } + + public UUID getId() { + return this.id; } + @Nullable public CloudRegion getCloudRegion() { - return cloudRegion; + return this.cloudRegion; } - public double getSuctionRadius() { - return level.getBaseDamage() * 2; + public void setCloudRegion(@Nullable CloudRegion cloudRegion) { + this.cloudRegion = cloudRegion; + this.detachedTicks = cloudRegion == null ? this.detachedTicks : 0; } - public double getDamageMultiplier() { - return level.getBaseDamage(); + public float getVisualBottomY() { + return this.visualBottomY; } - public TornadoInstance(Vec3 position, float radius, WindVector wind,CloudRegion cloudRegion) { - this(position, radius, wind, 0.05f,cloudRegion); + public float getVisualHeight() { + return this.visualHeight; } - public TornadoInstance(Vec3 position, float radius, WindVector wind, float angularSpeed, CloudRegion cloudRegion) { - this.position = position; - this.radius = radius; - this.wind = wind; - this.angularSpeed = angularSpeed; - this.spawnTime = System.currentTimeMillis(); - this.level = TornadoLevel.fromWindSpeed(wind.baseSpeed()); - this.cloudRegion = cloudRegion; + public float getNormalizedIntensity() { + return this.normalizedIntensity; + } + + public StormLifecyclePhase getPhase() { + return this.phase; + } + + public int getStormLevel() { + return this.stormLevel; + } + + public float getRecentDebrisScore() { + return this.recentDebrisScore; + } + + public Vec3 getRenderPosition(float partialTick) { + return this.clientPreviousRenderPosition.lerp(this.clientRenderPosition, Mth.clamp(partialTick, 0.0F, 1.0F)); + } + + public float getRenderBottomY(float partialTick) { + return Mth.lerp(Mth.clamp(partialTick, 0.0F, 1.0F), this.clientPreviousRenderBottomY, this.clientRenderBottomY); + } + + public float getRenderHeight(float partialTick) { + return Mth.lerp(Mth.clamp(partialTick, 0.0F, 1.0F), this.clientPreviousRenderHeight, this.clientRenderHeight); + } + + public float getRenderRadius(float partialTick) { + return Mth.lerp(Mth.clamp(partialTick, 0.0F, 1.0F), this.clientPreviousRenderRadius, this.clientRenderRadius); + } + public TornadoLevel getLevel() { + return TornadoLevel.fromWindSpeed(this.getEffectiveWindSpeed()); + } + + public double getSuctionRadius() { + return this.radius + this.getLevel().getBaseDamage() * 1.2D; + } + + public double getDamageMultiplier() { + return this.getLevel().getBaseDamage() + * Math.max(0.3D, this.normalizedIntensity) + * (0.75D + StormSeverityScale.toNormalized(this.stormLevel) * 0.65D); } public float getLifetimeSeconds() { - return (System.currentTimeMillis() - spawnTime) / 1000f; + return this.ageTicks / 20.0F; } public float getTwist() { - long elapsedMs = System.currentTimeMillis() - spawnTime; - float elapsedTicks = elapsedMs / 100.0f; - return Mth.clamp(elapsedTicks * angularSpeed,0.5f,5.0f); + return this.getVisualSpin(0.0F); + } + + public float getVisualSpin(float partialTick) { + float elapsedTicks = this.ageTicks + Mth.clamp(partialTick, 0.0F, 1.0F); + return elapsedTicks * (0.004F + this.angularSpeed * 0.16F); + } + + public boolean isDescriptorMissing() { + return this.descriptorMissing; } - /** - * Called each tick from tornado manager. Server handles demolition, - * client relies on separate rendering logic. - */ - public void tick(Level level) { - if (level.isClientSide) { + public void markDissipating() { + if (this.phase == StormLifecyclePhase.DISSIPATED || this.phase == StormLifecyclePhase.DISSIPATING) { return; } + this.phase = StormLifecyclePhase.DISSIPATING; + this.phaseTicks = 0; + } - long now = System.currentTimeMillis(); + public void activateImmediately() { + this.phase = StormLifecyclePhase.ACTIVE; + this.phaseTicks = 0; + this.normalizedIntensity = this.targetIntensity; + this.applyIntensityToVisuals(); + } - boolean ambientDue = now - lastAmbientWindCheck >= ambientWindIntervalMs; - boolean demolitionDue = now - lastDemolitionCheck >= demolitionIntervalMs; + public boolean isDead() { + return this.phase.isTerminal(); + } - if (ambientDue || demolitionDue) { - AsyncAtmosphereService.runStorm(() -> { - try { - if (ambientDue) { - lastAmbientWindCheck = now; - applyAmbientWind(level); - } - if (demolitionDue) { - lastDemolitionCheck = now; - demolishBlocks((ServerLevel) level); - level.getServer().execute(()->playDemolitionSound(level)); - } - } catch (Exception e) { - e.printStackTrace(); + public void updateCloudAttachment(boolean attached) { + if (!this.requiresCloudAttachment) { + this.detachedTicks = 0; + return; + } + if (attached) { + this.detachedTicks = 0; + return; + } + + this.detachedTicks++; + if (this.detachedTicks >= CLOUD_DETACH_GRACE_TICKS + && this.ageTicks >= MINIMUM_PERSISTENCE_TICKS + && this.phase != StormLifecyclePhase.DISSIPATING) { + this.markDissipating(); + } + } + + public int getDetachedTicks() { + return this.detachedTicks; + } + + public RuntimeDebugSnapshot getRuntimeDebugSnapshot() { + double averagePullForce = this.debugForceSampleCount <= 0 ? 0.0D : this.debugPullForceSum / this.debugForceSampleCount; + double averageUpwardForce = this.debugForceSampleCount <= 0 ? 0.0D : this.debugUpwardForceSum / this.debugForceSampleCount; + return new RuntimeDebugSnapshot( + this.id, + this.phase, + this.normalizedIntensity, + this.debugEligibleEntityCount, + this.debugCapturedEntityCount, + averagePullForce, + this.debugPullForceMax, + averageUpwardForce, + this.debugUpwardForceMax, + this.debugDestructionSweepRadius, + this.debugDestructionCandidateBlockCount, + this.debugDestroyedBlockCount, + this.debugDestroyedLeafLogCount, + this.debugDestroyedWeakCount, + this.debugDestroyedGrassCount, + this.debugDestroyedGlassCount + ); + } + + public void tickServer(ServerLevel level, long gameTime) { + this.ageTicks++; + this.phaseTicks++; + if (this.spawnGameTime == 0L) { + this.spawnGameTime = gameTime; + } + + this.resetRuntimeDebugStats(); + + WindVector sampledWind = ForecastOrchestrator.getWind(level, BlockPos.containing(this.position), gameTime); + this.wind = sampledWind; + this.refreshStormLevel(level, gameTime); + float waterExposure = this.sampleWaterExposure(level); + this.applyWaterPenalty(waterExposure); + this.recentDebrisScore = Math.max(0.0F, this.recentDebrisScore - 0.015F); + this.pruneCapturedEntities(level); + this.tickLifecycle(); + this.updateMovement(level, gameTime); + this.updateGroundedVisualBase(level); + this.pushStateToDescriptor(); + + if (!this.phase.isTerminal() && this.normalizedIntensity >= 0.08F) { + if (gameTime - this.lastAmbientWindTick >= FLOW_FIELD_INTERVAL_TICKS) { + this.lastAmbientWindTick = gameTime; + this.applyAmbientWind(level); + } + if (gameTime - this.lastDemolitionTick >= DEMOLITION_INTERVAL_TICKS && this.normalizedIntensity >= 0.18F) { + this.lastDemolitionTick = gameTime; + if (this.demolishBlocks(level)) { + this.playDemolitionSound(level); + } + } + } + } + + public void tickClient() { + this.ageTicks++; + this.clientPreviousRenderPosition = this.clientRenderPosition; + this.clientPreviousRenderBottomY = this.clientRenderBottomY; + this.clientPreviousRenderHeight = this.clientRenderHeight; + this.clientPreviousRenderRadius = this.clientRenderRadius; + + Vec3 predictedTarget = this.clientTargetPosition.add(this.clientTargetVelocity.scale(CLIENT_EXTRAPOLATION_TICKS)); + this.clientRenderPosition = this.clientRenderPosition.lerp(predictedTarget, CLIENT_POSITION_INTERPOLATION); + this.clientRenderBottomY = Mth.lerp(CLIENT_SHAPE_INTERPOLATION, this.clientRenderBottomY, this.clientTargetBottomY); + this.clientRenderHeight = Mth.lerp(CLIENT_SHAPE_INTERPOLATION, this.clientRenderHeight, this.clientTargetHeight); + this.clientRenderRadius = Mth.lerp(CLIENT_SHAPE_INTERPOLATION, this.clientRenderRadius, this.clientTargetRadius); + this.clientTargetVelocity = this.clientTargetVelocity.scale(CLIENT_VELOCITY_DAMPING); + } + + public TornadoSnapshot snapshot() { + return new TornadoSnapshot( + this.id, + this.position, + this.radius, + this.visualBottomY, + this.visualHeight, + this.wind.baseSpeed(), + this.wind.angleRadians(), + this.wind.gustSpeed(), + this.normalizedIntensity, + this.stormLevel, + this.recentDebrisScore, + this.phase + ); + } + + public void applySnapshot(TornadoSnapshot snapshot, @Nullable CloudRegion region) { + boolean snapToTarget = this.ageTicks <= 1 || this.clientRenderPosition.distanceToSqr(snapshot.position()) > 1024.0D; + Vec3 previousTargetPosition = this.clientTargetPosition; + this.position = snapshot.position(); + this.radius = snapshot.radius(); + this.visualBottomY = snapshot.visualBottomY(); + this.visualHeight = snapshot.visualHeight(); + this.wind = new WindVector(snapshot.windSpeed(), snapshot.windAngle(), snapshot.windGust()); + this.normalizedIntensity = snapshot.normalizedIntensity(); + this.stormLevel = StormSeverityScale.clamp(snapshot.stormLevel()); + this.recentDebrisScore = snapshot.recentDebrisScore(); + this.phase = snapshot.phase(); + this.cloudRegion = region; + this.anchorX = (float) this.position.x; + this.anchorZ = (float) this.position.z; + if (snapToTarget) { + this.clientPreviousRenderPosition = this.position; + this.clientRenderPosition = this.position; + this.clientTargetPosition = this.position; + this.clientTargetVelocity = Vec3.ZERO; + this.clientPreviousRenderBottomY = this.visualBottomY; + this.clientRenderBottomY = this.visualBottomY; + this.clientTargetBottomY = this.visualBottomY; + this.clientPreviousRenderHeight = this.visualHeight; + this.clientRenderHeight = this.visualHeight; + this.clientTargetHeight = this.visualHeight; + this.clientPreviousRenderRadius = this.radius; + this.clientRenderRadius = this.radius; + this.clientTargetRadius = this.radius; + return; + } + + Vec3 snapshotVelocity = this.position.subtract(previousTargetPosition).scale(1.0D / CLIENT_SNAPSHOT_INTERVAL_TICKS); + this.clientTargetPosition = this.position; + this.clientTargetVelocity = this.clientTargetVelocity.lerp(snapshotVelocity, CLIENT_VELOCITY_TRACKING); + this.clientTargetBottomY = this.visualBottomY; + this.clientTargetHeight = this.visualHeight; + this.clientTargetRadius = this.radius; + } + + public boolean synchronizeWithDescriptor() { + TornadoDescriptor descriptor = this.findDescriptor(); + if (descriptor == null) { + this.descriptorMissing = this.cloudRegion instanceof ITornadoRegion; + return false; + } + + this.descriptorMissing = false; + this.visualBottomY = descriptor.getBottomY(); + this.visualHeight = descriptor.getHeight(); + this.radius = descriptor.getRadius(); + this.position = new Vec3( + this.cloudRegion.getWorldX() + descriptor.getOffsetX(), + descriptor.getBottomY(), + this.cloudRegion.getWorldZ() + descriptor.getOffsetZ() + ); + return true; + } + + public void advanceByWind() { + this.ensureFallbackMovementPlan(); + this.advanceAlongMovementPlan(null); + } + + private void tickLifecycle() { + switch (this.phase) { + case FORMING -> { + float rate = 1.0F / Math.max(1, this.formationTicks); + this.normalizedIntensity = Math.min(this.targetIntensity, this.normalizedIntensity + rate); + if (this.phaseTicks >= this.formationTicks || this.normalizedIntensity >= this.targetIntensity - 0.02F) { + this.phase = StormLifecyclePhase.ACTIVE; + this.phaseTicks = 0; + } + } + case ACTIVE -> { + this.normalizedIntensity = Mth.lerp(0.06F, this.normalizedIntensity, this.targetIntensity); + if (this.phaseTicks >= this.activeTicks) { + this.phase = StormLifecyclePhase.DISSIPATING; + this.phaseTicks = 0; + } + } + case DISSIPATING -> { + float rate = 1.0F / Math.max(1, this.dissipationTicks); + this.normalizedIntensity = Math.max(0.0F, this.normalizedIntensity - rate); + if (this.phaseTicks >= this.dissipationTicks || this.normalizedIntensity <= 0.02F) { + this.phase = StormLifecyclePhase.DISSIPATED; + this.normalizedIntensity = 0.0F; + } + } + case DISSIPATED -> this.normalizedIntensity = 0.0F; + } + this.applyIntensityToVisuals(); + } + + private void applyIntensityToVisuals() { + float growth = Mth.clamp(this.normalizedIntensity, 0.0F, 1.0F); + float stormBias = 0.18F + StormSeverityScale.toNormalized(this.stormLevel) * 0.82F; + this.radius = Mth.lerp(growth, this.maxRadius * (0.26F + stormBias * 0.12F), this.maxRadius * (0.90F + stormBias * 0.22F)); + this.visualHeight = Mth.lerp(growth, this.targetVisualHeight * (0.30F + stormBias * 0.08F), this.targetVisualHeight * (0.92F + stormBias * 0.15F)); + this.angularSpeed = 0.08F + growth * 0.16F + StormSeverityScale.toNormalized(this.stormLevel) * 0.05F; + } + + private void updateGroundedVisualBase(ServerLevel level) { + float groundedBottomY = TornadoManager.resolveGroundedBottomY(level, this.position, this.visualBottomY); + this.visualBottomY = groundedBottomY; + this.position = new Vec3(this.position.x, groundedBottomY, this.position.z); + + float cloudBase = CloudManager.get(level).getCloudHeight(); + float reachToCloudBase = Math.max(0.0F, cloudBase - groundedBottomY); + this.targetVisualHeight = Math.max(96.0F, reachToCloudBase + this.maxRadius * 6.0F + 40.0F); + this.applyIntensityToVisuals(); + } + + private void updateMovement(ServerLevel level, long gameTime) { + // Route selection runs on a slower cadence. Per-tick movement only blends toward the + // current plan so the tornado keeps committing to a path instead of re-steering every tick. + this.ensureMovementPlan(level, gameTime); + this.advanceAlongMovementPlan(level); + } + + private void ensureMovementPlan(ServerLevel level, long gameTime) { + if (!this.shouldReplanMovement(level)) { + return; + } + this.applyMovementPlan(StormMotionModel.planTornadoRoute( + level, + this.id, + this.position, + this.wind, + Math.max(this.normalizedIntensity, 0.08F), + this.stormLevel, + this.headingRadians, + gameTime, + this.anchorX, + this.anchorZ + )); + } + + private void ensureFallbackMovementPlan() { + if (!this.shouldReplanMovement(null)) { + return; + } + this.applyMovementPlan(StormMotionModel.planFallbackTornadoRoute( + this.id, + this.position, + this.wind, + Math.max(this.normalizedIntensity, 0.08F), + this.stormLevel, + this.headingRadians, + this.ageTicks, + this.anchorX, + this.anchorZ + )); + } + + private boolean shouldReplanMovement(@Nullable ServerLevel level) { + if (this.routeWaypoint == null || this.routeTicksRemaining <= 0) { + return true; + } + if (this.hasReachedRouteWaypoint()) { + return true; + } + if (level != null && StormShieldManager.isProtected(level, this.routeWaypoint)) { + return true; + } + double dx = this.routeWaypoint.x - this.anchorX; + double dz = this.routeWaypoint.z - this.anchorZ; + return dx * dx + dz * dz > MOVEMENT_ROUTE_LEASH_RADIUS * MOVEMENT_ROUTE_LEASH_RADIUS; + } + + private boolean hasReachedRouteWaypoint() { + if (this.routeWaypoint == null) { + return true; + } + double dx = this.routeWaypoint.x - this.position.x; + double dz = this.routeWaypoint.z - this.position.z; + double dynamicThreshold = Math.max(MOVEMENT_ROUTE_REACHED_DISTANCE_SQR, this.motion.lengthSqr() * 48.0D); + return dx * dx + dz * dz <= dynamicThreshold; + } + + private void applyMovementPlan(StormMotionModel.TornadoRoutePlan plan) { + this.routeWaypoint = new Vec3(plan.waypoint().x, this.visualBottomY, plan.waypoint().z); + this.targetHeadingRadians = plan.headingRadians(); + this.targetMoveSpeed = plan.speed(); + this.routeTicksRemaining = Math.max(1, plan.durationTicks()); + if (this.motion.lengthSqr() <= 1.0E-5D) { + this.headingRadians = this.targetHeadingRadians; + this.plannedMoveSpeed = this.targetMoveSpeed; + } + } + + private void advanceAlongMovementPlan(@Nullable ServerLevel level) { + Vec3 waypoint = this.routeWaypoint != null + ? this.routeWaypoint + : this.position.add(horizontalVector(this.targetHeadingRadians).scale(12.0D)); + Vec3 toWaypoint = new Vec3(waypoint.x - this.position.x, 0.0D, waypoint.z - this.position.z); + float desiredHeading = toWaypoint.lengthSqr() > 1.0E-4D + ? (float) Math.atan2(toWaypoint.z, toWaypoint.x) + : this.targetHeadingRadians; + float stormFactor = StormSeverityScale.toNormalized(this.stormLevel); + float maxTurn = MOVEMENT_HEADING_BLEND + + this.normalizedIntensity * 0.012F + + stormFactor * 0.008F; + this.headingRadians = rotateTowards(this.headingRadians, desiredHeading, maxTurn); + this.plannedMoveSpeed = Mth.lerp(MOVEMENT_SPEED_BLEND, this.plannedMoveSpeed, this.targetMoveSpeed); + + Vec3 plannedVector = horizontalVector(this.headingRadians).scale(this.plannedMoveSpeed); + Vec3 ambientVector = horizontalVector(this.wind.angleRadians()).scale( + Math.max(0.6F, this.wind.baseSpeed()) * (MOVEMENT_AMBIENT_SCALE + this.normalizedIntensity * 0.0012D) + ); + Vec3 leashCorrection = this.sampleMovementLeashCorrection(); + Vec3 shieldCorrection = Vec3.ZERO; + if (level != null) { + Vec3 avoidance = StormShieldManager.sampleAvoidance(level, this.position, 24.0D + this.stormLevel * 8.0D); + if (avoidance.lengthSqr() > 1.0E-6D) { + shieldCorrection = avoidance.scale(0.05D + stormFactor * 0.08D); + if (this.routeTicksRemaining > MOVEMENT_REPLAN_ON_AVOIDANCE_TICKS) { + this.routeTicksRemaining = MOVEMENT_REPLAN_ON_AVOIDANCE_TICKS; } - }); + } } + Vec3 targetMotion = plannedVector.add(ambientVector).add(leashCorrection).add(shieldCorrection); + this.motion = this.motion.lerp(targetMotion, MOVEMENT_VECTOR_BLEND + this.normalizedIntensity * 0.05D); + if (this.motion.lengthSqr() <= 1.0E-6D && targetMotion.lengthSqr() > 0.0D) { + this.motion = targetMotion; + } + + double maxSpeed = Math.max(0.05D, this.targetMoveSpeed * 1.75D + 0.04D); + if (this.motion.lengthSqr() > maxSpeed * maxSpeed) { + this.motion = this.motion.normalize().scale(maxSpeed); + } + + this.position = new Vec3(this.position.x + this.motion.x, this.visualBottomY, this.position.z + this.motion.z); + if (this.routeTicksRemaining > 0) { + this.routeTicksRemaining--; + } + } + + private Vec3 sampleMovementLeashCorrection() { + double dx = this.anchorX - this.position.x; + double dz = this.anchorZ - this.position.z; + double distSqr = dx * dx + dz * dz; + if (distSqr <= MOVEMENT_ROUTE_LEASH_RADIUS * MOVEMENT_ROUTE_LEASH_RADIUS) { + return Vec3.ZERO; + } + double dist = Math.sqrt(distSqr); + double strength = Mth.clamp( + (dist - MOVEMENT_ROUTE_LEASH_RADIUS) / (MOVEMENT_ROUTE_LEASH_RADIUS * 0.80D), + 0.0D, + 1.0D + ); + return new Vec3(dx / Math.max(dist, 0.001D), 0.0D, dz / Math.max(dist, 0.001D)) + .scale(0.03D + strength * 0.11D); } + private static Vec3 horizontalVector(float heading) { + return new Vec3(Math.cos(heading), 0.0D, Math.sin(heading)); + } + + private static float rotateTowards(float current, float target, float maxTurn) { + float delta = Mth.wrapDegrees((float) Math.toDegrees(target - current)); + float clamped = Mth.clamp(delta, (float) Math.toDegrees(-maxTurn), (float) Math.toDegrees(maxTurn)); + return current + (float) Math.toRadians(clamped); + } + + private void pushStateToDescriptor() { + TornadoDescriptor descriptor = this.findDescriptor(); + if (descriptor == null) { + this.descriptorMissing = this.cloudRegion instanceof ITornadoRegion; + return; + } + this.descriptorMissing = false; + descriptor.setBottomY(this.visualBottomY); + descriptor.setHeight(this.visualHeight); + descriptor.setRadius(this.radius); + descriptor.setVelocityX((float) this.motion.x); + descriptor.setVelocityZ((float) this.motion.z); + if (this.cloudRegion != null) { + descriptor.setOffsetX((float) (this.position.x - this.cloudRegion.getWorldX())); + descriptor.setOffsetZ((float) (this.position.z - this.cloudRegion.getWorldZ())); + } + } private void applyAmbientWind(Level level) { if (!(level instanceof ServerLevel serverLevel)) { return; } - double influence = radius + AMBIENT_WIND_INFLUENCE_EXTENSION; - double minY = position.y + WIND_EFFECT_VERTICAL_MIN_OFFSET; - double maxY = position.y + WIND_EFFECT_VERTICAL_MAX_OFFSET; + Vec3 anchor = this.getInteractionAnchor(serverLevel); + double influence = this.getOuterInfluenceRadius(); + double minY = anchor.y + ENTITY_MIN_VERTICAL_RANGE; + double maxY = this.getInteractionTopY(anchor) + ENTITY_MAX_VERTICAL_PADDING; AABB box = new AABB( - position.x - influence, minY, - position.z - influence, position.x + influence, - maxY, position.z + influence + anchor.x - influence, minY, + anchor.z - influence, anchor.x + influence, + maxY, anchor.z + influence ); - // Base along-wind component (ambient) - double ambientSpeed = Math.max(0.0, wind.gustSpeed()) * WIND_SPEED_SCALING_FACTOR; - double ax = Math.cos(wind.angleRadians()) * ambientSpeed; - double az = Math.sin(wind.angleRadians()) * ambientSpeed; - + int eligibleEntities = 0; for (Entity entity : serverLevel.getEntities(null, box)) { - // Vector from entity to tornado center (horizontal) - double dx = position.x - entity.getX(); - double dz = position.z - entity.getZ(); - double distSq = dx * dx + dz * dz; - double dist = Math.sqrt(distSq); - - // Avoid div by zero; normalize inward vector - double nx = dist > 1e-4 ? dx / dist : 0.0; - double nz = dist > 1e-4 ? dz / dist : 0.0; - - // Suction strength scales with proximity, capped - double suctionRadius = Math.max(4.0, this.getSuctionRadius()); - double proximity = Math.max(0.0, 1.0 - Math.min(dist, suctionRadius) / suctionRadius); - - // Tangential (swirl) component: perpendicular to inward vector - double swirlDirX = -nz; - double swirlDirZ = nx; - - // Scale factors — stronger than before; scales with tornado level - double baseMag = 0.06 + this.getLevel().getMaxWindSpeed() * 0.02; // stronger baseline - double suctionMag = baseMag * 2.0 * proximity; // inward - double swirlMag = baseMag * 1.5 * Math.sqrt(proximity); // rotational swirl - - double vx = ax + nx * suctionMag + swirlDirX * swirlMag; - double vz = az + nz * suctionMag + swirlDirZ * swirlMag; - - // Vertical lift increases near center - double vy = 0.02 * proximity * (1.0 + this.getLevel().getMaxWindSpeed() * 0.02); - - entity.push(vx, vy, vz); - if (entity instanceof Player) { - entity.hurtMarked = true; + if (!this.isAffectedEntity(serverLevel, entity)) { + continue; } + eligibleEntities++; + this.applyTornadoForces(serverLevel, entity, anchor); } + this.debugEligibleEntityCount = eligibleEntities; + this.debugCapturedEntityCount = this.capturedEntities.size(); } - // worker thread seulement - private void demolishBlocks(ServerLevel level) { - final BlockPos center = BlockPos.containing(position); - final int intRadius = Mth.ceil(radius); - final double outerSq = (radius + 5) * (radius + 5); - final double innerSq = radius * radius; - final double band = Math.max(1.0, outerSq - innerSq); - final double invBand = 1.0 / band; - final BlockPos min = center.offset(-intRadius - DEBRIS_RANGE_EXTENSION, 0, -intRadius - DEBRIS_RANGE_EXTENSION); - final BlockPos max = center.offset( intRadius + DEBRIS_RANGE_EXTENSION, 3 + intRadius, intRadius + DEBRIS_RANGE_EXTENSION); - - RandomSource random = RandomSource.create(); - it.unimi.dsi.fastutil.longs.LongArrayList toDestroy = new it.unimi.dsi.fastutil.longs.LongArrayList(2048); - it.unimi.dsi.fastutil.longs.LongArrayList toDestroyGlass = new it.unimi.dsi.fastutil.longs.LongArrayList(2048); - - // lecture off thread avec checks stricts - for (BlockPos pos : BlockPos.betweenClosed(min, max)) { - // ne charge pas de chunk ici - if (!level.isLoaded(pos)) continue; - - try { - // récupère le chunk si déjà chargé sinon skip - LevelChunk chunk = level.getChunkSource().getChunk(pos.getX() >> 4, pos.getZ() >> 4, false); - if (chunk == null) continue; - - // lecture état depuis le chunk existant - BlockState state = chunk.getBlockState(pos); - if (state.isAir()) continue; - final double distSq = pos.distSqr(center); - // ton test demandé hors main thread - if (state.is(BlockTags.LEAVES) || state.is(BlockTags.LOGS)) { - toDestroy.add(pos.asLong()); + private boolean demolishBlocks(ServerLevel level) { + if (!AtmoCommonConfig.ENABLE_TORNADO_DESTRUCTION.get()) { + return false; + } + + Vec3 anchor = this.getInteractionAnchor(level); + BlockPos center = BlockPos.containing(anchor); + float stormFactor = StormSeverityScale.toNormalized(this.stormLevel); + float windfieldWidth = this.getWindfieldWidth(); + float destructionRadius = (float) Math.max(this.getCaptureRadius() * 0.88D, this.radius * (2.35F + stormFactor * 0.60F)); + float coreRadius = (float) Math.max(this.getCoreRadius(), this.radius * (1.10F + stormFactor * 0.18F)); + this.debugDestructionSweepRadius = destructionRadius; + int intRadius = Mth.ceil(destructionRadius); + double outerSq = destructionRadius * destructionRadius; + RandomSource random = RandomSource.create(this.id.getLeastSignificantBits() ^ (this.ageTicks * 31L)); + + int scannedColumns = 0; + int eligibleColumns = 0; + int candidateBlocks = 0; + int leafLogDestroyed = 0; + int weakDestroyed = 0; + int grassScoured = 0; + int glassDestroyed = 0; + int[] spawnedDebrisEntities = new int[] {0}; + int maxLeafLogBreaks = 280 + Mth.floor(this.normalizedIntensity * 420.0F + stormFactor * 320.0F); + int maxWeakBreaks = 220 + Mth.floor(this.normalizedIntensity * 280.0F + stormFactor * 220.0F); + int maxGrassScours = 96 + Mth.floor(this.normalizedIntensity * 132.0F + stormFactor * 84.0F); + int maxGlassBreaks = 36 + Mth.floor(this.normalizedIntensity * 64.0F + stormFactor * 44.0F); + int maxDebrisEntitySpawns = BASE_MAX_DEBRIS_ENTITY_SPAWNS + + Mth.floor(this.normalizedIntensity * 10.0F + stormFactor * 8.0F); + int minBuildY = level.getMinBuildHeight(); + int maxBuildY = level.getMaxBuildHeight() - 1; + BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); + + for (int dx = -intRadius; dx <= intRadius; dx++) { + for (int dz = -intRadius; dz <= intRadius; dz++) { + double horizontalDistSq = dx * dx + dz * dz; + if (horizontalDistSq > outerSq) { + continue; } - else if (AtmosphereUtils.isGlass(state)) { - if (distSq > outerSq) continue; - final float pMax = 0.35f; - final double t = Mth.clamp((outerSq - distSq) * invBand, 0.0, 1.0); - final float p = (float) (t * pMax); + int x = center.getX() + dx; + int z = center.getZ() + dz; + cursor.set(x, Mth.floor(anchor.y), z); + if (!level.isLoaded(cursor)) { + continue; + } + scannedColumns++; + float distance = Mth.sqrt((float) horizontalDistSq); + float columnStrength = 1.0F - smoothStep(coreRadius, destructionRadius, distance); + boolean coreColumn = distance <= coreRadius; + if (columnStrength <= 0.06F && !coreColumn) { + continue; + } + eligibleColumns++; - if (random.nextFloat() < p) { - toDestroyGlass.add(pos.asLong()); + int terrainY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1; + int canopyY = level.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z) - 1; + boolean forceBreak = coreColumn || columnStrength >= 0.72F; + int sweepStartY = Math.max(minBuildY, terrainY); + int sweepEndY = Math.min(maxBuildY, Math.max(canopyY + 8, terrainY + 12 + Mth.floor(columnStrength * 14.0F))); + for (int y = sweepStartY; y <= sweepEndY; y++) { + if (weakDestroyed >= maxWeakBreaks && grassScoured >= maxGrassScours && glassDestroyed >= maxGlassBreaks) { + break; + } + + cursor.set(x, y, z); + if (!level.isLoaded(cursor) || StormShieldManager.isProtected(level, cursor)) { + continue; + } + + BlockState state = level.getBlockState(cursor); + if (state.isAir() || !state.getFluidState().isEmpty()) { + continue; + } + candidateBlocks++; + + if (state.is(BlockTags.LOGS) || state.is(BlockTags.LEAVES)) { + if (leafLogDestroyed < maxLeafLogBreaks) { + float breakChance = Mth.clamp( + 0.44F + columnStrength * 0.46F + this.normalizedIntensity * 0.24F + stormFactor * 0.16F, + 0.0F, + 1.0F + ); + leafLogDestroyed += this.destroyTreeClusterImmediate( + level, + cursor.immutable(), + random, + breakChance, + forceBreak, + maxLeafLogBreaks - leafLogDestroyed, + anchor, + spawnedDebrisEntities, + maxDebrisEntitySpawns + ); + } + continue; + } + + if (AtmosphereUtils.isGlass(state)) { + if (glassDestroyed >= maxGlassBreaks) { + continue; + } + float glassChance = Mth.clamp( + 0.24F + columnStrength * 0.56F + this.normalizedIntensity * 0.22F + stormFactor * 0.18F, + 0.0F, + 1.0F + ); + if (forceBreak || random.nextFloat() < glassChance) { + if (this.removeBlockWithDebris( + level, + cursor.immutable(), + state, + anchor, + random, + spawnedDebrisEntities, + maxDebrisEntitySpawns, + 0.10F + )) { + glassDestroyed++; + } + } + continue; } - } + if (isSurfaceSoilBlock(state)) { + if (grassScoured >= maxGrassScours) { + continue; + } + float scourChance = Mth.clamp( + 0.26F + columnStrength * 0.44F + this.normalizedIntensity * 0.14F + stormFactor * 0.10F, + 0.0F, + 1.0F + ); + boolean extremeExcavation = forceBreak + && columnStrength >= 0.96F + && this.normalizedIntensity >= 0.96F + && stormFactor >= 0.85F + && random.nextFloat() < (state.is(Blocks.DIRT) ? 0.025F : 0.055F); + if (extremeExcavation) { + if (this.removeBlockWithDebris( + level, + cursor.immutable(), + state, + anchor, + random, + spawnedDebrisEntities, + maxDebrisEntitySpawns, + 0.28F + )) { + weakDestroyed++; + } + } else if (!state.is(Blocks.DIRT) && (forceBreak || random.nextFloat() < scourChance)) { + level.setBlockAndUpdate(cursor, Blocks.DIRT.defaultBlockState()); + grassScoured++; + } + continue; + } - } catch (Throwable t) { - // au moindre souci on ignore cette position + boolean looseTerrain = isLooseTerrainBlock(state); + boolean weakBlock = isWeakBlock(state, level, cursor, stormFactor); + boolean eligibleWeak = isVegetationBlock(state) || isSimpleStructureBlock(state) || looseTerrain || weakBlock; + if (!eligibleWeak || weakDestroyed >= maxWeakBreaks) { + continue; + } + + float breakChance = Mth.clamp( + 0.20F + columnStrength * 0.54F + this.normalizedIntensity * 0.24F + stormFactor * 0.20F, + 0.0F, + 1.0F + ); + if (looseTerrain) { + breakChance *= 1.25F; + } + if (isVegetationBlock(state)) { + breakChance *= 1.18F; + } + if (forceBreak || random.nextFloat() < breakChance) { + if (this.removeBlockWithDebris( + level, + cursor.immutable(), + state, + anchor, + random, + spawnedDebrisEntities, + maxDebrisEntitySpawns, + this.getDebrisSpawnChance(state) + )) { + weakDestroyed++; + } + } + } } } - // destruction uniquement sur le thread serveur - final int perTick = 256; - this._destroyCursor = 0; - if (!toDestroy.isEmpty()) { - AsyncAtmosphereService.runOnMainThread( - ()-> processLeafLogDestruction(level, toDestroy, perTick) - ); - } - if (!toDestroyGlass.isEmpty()) { - GlassDamageManager.damageGlass(level, toDestroyGlass); - } + this.debugDestructionCandidateBlockCount = candidateBlocks; + this.debugDestroyedLeafLogCount = leafLogDestroyed; + this.debugDestroyedWeakCount = weakDestroyed; + this.debugDestroyedGrassCount = grassScoured; + this.debugDestroyedGlassCount = glassDestroyed; + this.debugDestroyedBlockCount = leafLogDestroyed + weakDestroyed + grassScoured + glassDestroyed; + float debrisGain = Math.min(1.0F, + leafLogDestroyed * 0.010F + + weakDestroyed * 0.018F + + grassScoured * 0.008F + + glassDestroyed * 0.024F); + this.recentDebrisScore = Mth.clamp(this.recentDebrisScore + debrisGain, 0.0F, 1.0F); + + return leafLogDestroyed > 0 || weakDestroyed > 0 || grassScoured > 0 || glassDestroyed > 0; } - // curseur pour le batching - private int _destroyCursor = 0; + private int destroyTreeClusterImmediate(ServerLevel level, + BlockPos origin, + RandomSource random, + float breakChance, + boolean forceBreak, + int remainingBudget, + Vec3 anchor, + int[] spawnedDebrisEntities, + int maxDebrisEntitySpawns) { + if (remainingBudget <= 0) { + return 0; + } + + int originX = origin.getX(); + int originY = origin.getY(); + int originZ = origin.getZ(); + int destroyed = 0; + ArrayDeque queue = new ArrayDeque<>(); + Set visited = new HashSet<>(); + queue.add(origin); + visited.add(origin.asLong()); + + while (!queue.isEmpty() && destroyed < remainingBudget && visited.size() <= TREE_CLUSTER_VISIT_LIMIT) { + BlockPos current = queue.removeFirst(); + if (!level.isLoaded(current) || StormShieldManager.isProtected(level, current)) { + continue; + } - // main thread seulement - private void processLeafLogDestruction(ServerLevel level, - it.unimi.dsi.fastutil.longs.LongArrayList list, - int perTick) { - if (_destroyCursor >= list.size()) { _destroyCursor = 0; return; } + BlockState state = level.getBlockState(current); + if (!(state.is(BlockTags.LOGS) || state.is(BlockTags.LEAVES) || isVegetationBlock(state))) { + continue; + } - int end = Math.min(_destroyCursor + perTick, list.size()); - for (int i = _destroyCursor; i < end; i++) { - BlockPos pos = BlockPos.of(list.getLong(i)); - if (!level.isLoaded(pos)) continue; + if (forceBreak || random.nextFloat() < breakChance) { + if (this.removeBlockWithDebris( + level, + current, + state, + anchor, + random, + spawnedDebrisEntities, + maxDebrisEntitySpawns, + this.getDebrisSpawnChance(state) + )) { + destroyed++; + } + } - BlockState state = level.getBlockState(pos); - if (!(state.is(BlockTags.LEAVES) || state.is(BlockTags.LOGS))) continue; - level.destroyBlock(pos, false); + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0 && dz == 0) { + continue; + } + BlockPos next = current.offset(dx, dy, dz); + int localX = next.getX() - originX; + int localY = next.getY() - originY; + int localZ = next.getZ() - originZ; + if (Math.abs(localX) > TREE_CLUSTER_HORIZONTAL_RADIUS + || Math.abs(localZ) > TREE_CLUSTER_HORIZONTAL_RADIUS + || localY < -TREE_CLUSTER_BELOW + || localY > TREE_CLUSTER_ABOVE) { + continue; + } + if (visited.add(next.asLong())) { + queue.addLast(next); + } + } + } + } } + return destroyed; + } - _destroyCursor = end; - if (_destroyCursor < list.size()) { - level.getServer().execute(() -> processLeafLogDestruction(level, list, perTick)); - } else { - _destroyCursor = 0; + private boolean removeBlockWithDebris(ServerLevel level, + BlockPos pos, + BlockState state, + Vec3 anchor, + RandomSource random, + int[] spawnedDebrisEntities, + int maxDebrisEntitySpawns, + float debrisChance) { + float clampedDebrisChance = Mth.clamp(debrisChance, 0.0F, MAX_DEBRIS_ENTITY_SPAWN_CHANCE); + if (spawnedDebrisEntities[0] < maxDebrisEntitySpawns + && this.isVisualDebrisBlock(state) + && random.nextFloat() < clampedDebrisChance + && this.spawnVisualDebrisEntity(level, pos, state, anchor, random)) { + spawnedDebrisEntities[0]++; + return true; } + return level.destroyBlock(pos, false); } + private boolean spawnVisualDebrisEntity(ServerLevel level, + BlockPos pos, + BlockState state, + Vec3 anchor, + RandomSource random) { + if (!level.isLoaded(pos) || StormShieldManager.isProtected(level, pos) || state.isAir()) { + return false; + } + FallingBlockEntity debris = FallingBlockEntity.fall(level, pos, state); + debris.disableDrop(); + debris.setNoGravity(true); + debris.time = 560 + random.nextInt(20); + Vec3 blockCenter = Vec3.atCenterOf(pos); + Vec3 towardAnchor = anchor.subtract(blockCenter); + double horizontalDistance = Math.max(Math.hypot(towardAnchor.x, towardAnchor.z), 0.001D); + Vec3 inward = new Vec3(towardAnchor.x / horizontalDistance, 0.0D, towardAnchor.z / horizontalDistance); + float rotationDirection = random.nextBoolean() ? 1.0F : -1.0F; + Vec3 tangential = new Vec3(-inward.z * rotationDirection, 0.0D, inward.x * rotationDirection); + double inwardStrength = 0.08D + this.normalizedIntensity * 0.12D; + double tangentialStrength = 0.12D + StormSeverityScale.toNormalized(this.stormLevel) * 0.10D; + double verticalStrength = 0.18D + this.normalizedIntensity * 0.12D + random.nextDouble() * 0.06D; + debris.setDeltaMovement( + inward.scale(inwardStrength) + .add(tangential.scale(tangentialStrength)) + .add(0.0D, verticalStrength, 0.0D) + ); + return true; + } - private void playDemolitionSound(Level level) { - BlockPos center = BlockPos.containing(position); + private float getDebrisSpawnChance(BlockState state) { + if (state.is(BlockTags.LOGS) || state.is(BlockTags.PLANKS)) { + return 0.38F; + } + if (state.is(BlockTags.LEAVES)) { + return 0.24F; + } + if (isSurfaceSoilBlock(state) || isLooseTerrainBlock(state)) { + return 0.30F; + } + if (isSimpleStructureBlock(state)) { + return 0.32F; + } + return 0.16F; + } + private void playDemolitionSound(Level level) { + BlockPos center = level instanceof ServerLevel serverLevel + ? BlockPos.containing(this.getInteractionAnchor(serverLevel)) + : BlockPos.containing(this.position); level.playLocalSound( center.getX(), center.getY(), center.getZ(), SoundEvents.GENERIC_EXPLODE, SoundSource.WEATHER, - 2.0f, - 0.5f + level.getRandom().nextFloat() * 0.4f, + 1.0F + this.normalizedIntensity, + 0.6F + level.getRandom().nextFloat() * 0.3F, false ); } + + private void refreshStormLevel(ServerLevel level, long gameTime) { + int strongest = this.cloudRegion == null ? StormSeverityScale.MIN_LEVEL : StormSeverityScale.clamp(net.Gabou.projectatmosphere.modules.core.CloudLibrary.getSeverityFromRessourceLocation(this.cloudRegion.getCloudTypeId())); + var regionKey = net.Gabou.projectatmosphere.util.RegionInstanceKey.from(BlockPos.containing(this.position)); + strongest = Math.max(strongest, StormSeverityScale.resolve(level, regionKey, gameTime)); + this.stormLevel = strongest; + this.targetIntensity = defaultTargetIntensity(this.maxRadius, this.wind, this.stormLevel); + } + + @Nullable + private TornadoDescriptor findDescriptor() { + if (!(this.cloudRegion instanceof ITornadoRegion tornadoRegion)) { + return null; + } + return tornadoRegion.findTornado(this.id); + } + + private static float defaultTargetIntensity(float radius, WindVector wind, int stormLevel) { + float radiusFactor = Mth.clamp((radius - 5.0F) / 20.0F, 0.0F, 1.0F); + float windFactor = Mth.clamp((wind.baseSpeed() - 12.0F) / 28.0F, 0.0F, 1.0F); + float stormFactor = StormSeverityScale.toNormalized(stormLevel); + return Mth.clamp(0.18F + radiusFactor * 0.28F + windFactor * 0.18F + stormFactor * 0.42F, 0.18F, 1.0F); + } + + private static boolean isVegetationBlock(BlockState state) { + return state.is(BlockTags.LEAVES) + || state.is(BlockTags.SAPLINGS) + || state.is(BlockTags.FLOWERS) + || state.is(BlockTags.CROPS) + || state.is(Blocks.VINE) + || state.is(Blocks.TALL_GRASS) + || state.is(Blocks.GRASS) + || state.is(Blocks.FERN) + || state.is(Blocks.LARGE_FERN) + || state.is(Blocks.DEAD_BUSH) + || state.is(Blocks.SUGAR_CANE) + || state.is(Blocks.BAMBOO) + || state.is(Blocks.SWEET_BERRY_BUSH) + || state.is(Blocks.KELP) + || state.is(Blocks.KELP_PLANT) + || state.is(Blocks.SEAGRASS) + || state.is(Blocks.TALL_SEAGRASS) + || state.canBeReplaced(); + } + + private static boolean isSimpleStructureBlock(BlockState state) { + return state.is(BlockTags.PLANKS) + || state.is(BlockTags.WOODEN_DOORS) + || state.is(BlockTags.WOODEN_TRAPDOORS) + || state.is(BlockTags.FENCES) + || state.is(BlockTags.FENCE_GATES) + || state.is(BlockTags.WOOL) + || state.is(BlockTags.WOODEN_STAIRS) + || state.is(BlockTags.WOODEN_SLABS) + || state.is(BlockTags.BEDS) + || state.is(BlockTags.CAMPFIRES); + } + + private static boolean isSurfaceSoilBlock(BlockState state) { + return state.is(Blocks.DIRT) + || state.is(Blocks.COARSE_DIRT) + || state.is(Blocks.ROOTED_DIRT) + || state.is(Blocks.GRASS_BLOCK) + || state.is(Blocks.PODZOL) + || state.is(Blocks.MYCELIUM) + || state.is(Blocks.DIRT_PATH) + || state.is(Blocks.FARMLAND) + || state.is(Blocks.MUD) + || state.is(Blocks.MUDDY_MANGROVE_ROOTS) + || state.is(Blocks.MOSS_BLOCK); + } + + private static boolean isLooseTerrainBlock(BlockState state) { + return state.is(Blocks.CLAY) + || state.is(Blocks.SAND) + || state.is(Blocks.RED_SAND) + || state.is(Blocks.GRAVEL) + || state.is(Blocks.SNOW) + || state.is(Blocks.SNOW_BLOCK) + || state.is(Blocks.POWDER_SNOW); + } + + private boolean isVisualDebrisBlock(BlockState state) { + return state.is(BlockTags.LOGS) + || state.is(BlockTags.LEAVES) + || state.is(BlockTags.PLANKS) + || state.is(BlockTags.WOODEN_SLABS) + || state.is(BlockTags.WOODEN_STAIRS) + || state.is(BlockTags.FENCES) + || state.is(BlockTags.FENCE_GATES) + || state.is(BlockTags.WOOL) + || isSurfaceSoilBlock(state) + || isLooseTerrainBlock(state); + } + + private static boolean isWeakBlock(BlockState state, Level level, BlockPos pos, float stormFactor) { + if (state.is(BlockTags.BASE_STONE_OVERWORLD) + || state.is(BlockTags.BASE_STONE_NETHER) + || state.is(BlockTags.LOGS) + || AtmosphereUtils.isGlass(state)) { + return false; + } + float destroySpeed = state.getDestroySpeed(level, pos); + if (destroySpeed < 0.0F) { + return false; + } + float threshold = 1.1F + stormFactor * 1.8F; + return destroySpeed <= threshold; + } + + private float getWindfieldWidth() { + return Math.max(this.radius * (1.65F + StormSeverityScale.toNormalized(this.stormLevel) * 0.45F), 28.0F); + } + + private double getCoreRadius() { + return Math.max(this.getWindfieldWidth() * CORE_RADIUS_FACTOR, this.radius * 1.05F); + } + + private double getCaptureRadius() { + return Math.max(this.getWindfieldWidth() * CAPTURE_RADIUS_FACTOR, this.getCoreRadius() + 8.0D); + } + + private double getOuterInfluenceRadius() { + return Math.max(this.getWindfieldWidth() * 1.75D, this.getCaptureRadius() + OUTER_ENTITY_INFLUENCE_PADDING); + } + + private double getInteractionTopY(Vec3 anchor) { + return anchor.y + Math.max(36.0D, this.visualHeight * 0.96D); + } + + private boolean isAffectedEntity(ServerLevel level, Entity entity) { + if (entity == null || !entity.isAlive() || entity.isRemoved() || entity.noPhysics) { + return false; + } + if (entity instanceof Player player && (player.isCreative() || player.isSpectator())) { + return false; + } + return !StormShieldManager.isProtected(level, entity.position()); + } + + private void applyTornadoForces(ServerLevel level, Entity entity, Vec3 anchor) { + CapturedEntityState captured = this.capturedEntities.get(entity.getId()); + double outerInfluenceRadius = captured != null + ? this.getOuterInfluenceRadius() * CAPTURE_HYSTERESIS_FACTOR + : this.getOuterInfluenceRadius(); + double captureRadius = this.getCaptureRadius(); + double coreRadius = this.getCoreRadius(); + double topY = this.getInteractionTopY(anchor); + int terrainY = level.getHeight( + Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, + entity.blockPosition().getX(), + entity.blockPosition().getZ() + ) - 1; + + if (entity.getY() + entity.getBbHeight() < terrainY - 1.5D + || entity.getY() > topY + ENTITY_MAX_VERTICAL_PADDING) { + this.capturedEntities.remove(entity.getId()); + return; + } + + Vec3 relativePos = entity.position().subtract(anchor); + double horizontalDistance = Math.hypot(relativePos.x, relativePos.z); + if (horizontalDistance > outerInfluenceRadius) { + this.capturedEntities.remove(entity.getId()); + return; + } + + double safeDistance = Math.max(horizontalDistance, 0.001D); + Vec3 inward = new Vec3(-relativePos.x / safeDistance, 0.0D, -relativePos.z / safeDistance); + float rotationDirection = captured != null ? captured.rotationDirection : this.getRotationDirection(entity); + Vec3 tangential = new Vec3(-inward.z * rotationDirection, 0.0D, inward.x * rotationDirection); + float stormFactor = StormSeverityScale.toNormalized(this.stormLevel); + float tornadoForce = Mth.clamp(this.normalizedIntensity * 0.72F + stormFactor * 0.28F, 0.25F, 1.0F); + float approachFactor = 1.0F - smoothStep((float) captureRadius, (float) outerInfluenceRadius, (float) horizontalDistance); + float coreFactor = 1.0F - smoothStep((float) (coreRadius * 0.72D), (float) (captureRadius * 0.94D), (float) horizontalDistance); + float heightNorm = Mth.clamp((float) ((entity.getY() - anchor.y) / Math.max(topY - anchor.y, 1.0D)), 0.0F, 1.25F); + float liftWindow = 1.0F - smoothStep(0.88F, 1.05F, heightNorm); + boolean shouldCapture = captured != null + || horizontalDistance <= captureRadius + || (approachFactor > 0.72F && heightNorm < 1.0F); + if (captured == null && shouldCapture) { + captured = this.createCaptureState(entity, captureRadius, horizontalDistance); + this.capturedEntities.put(entity.getId(), captured); + } + + Vec3 translationAssist = this.motion.scale(0.10D + tornadoForce * 0.10D); + Vec3 add; + if (captured != null) { + captured.lastSeenAge = this.ageTicks; + captured.captureTicks++; + float captureProgress = smoothStep(2.0F, CAPTURE_FULL_TICKS, captured.captureTicks); + float ascentProgress = smoothStep(10.0F, CAPTURE_ASCENT_TICKS, captured.captureTicks); + float desiredBand = Mth.lerp(ascentProgress, OUTER_ORBIT_RADIUS_FACTOR, INNER_ORBIT_RADIUS_FACTOR + coreFactor * 0.06F); + captured.orbitRadiusFactor = Mth.lerp(0.20F, captured.orbitRadiusFactor, desiredBand); + captured.orbitAngle += (0.16F + tornadoForce * 0.14F + captureProgress * 0.12F + coreFactor * 0.06F) + * captured.rotationDirection; + + double desiredRadius = Math.max(coreRadius * 0.72D, captureRadius * captured.orbitRadiusFactor); + double liftStep = (CAPTURED_LIFT_FORCE + tornadoForce * 0.26F + ascentProgress * 0.42F + coreFactor * 0.18F) + * captured.liftBias + * liftWindow; + double desiredHeight = Mth.clamp( + entity.getY() + liftStep, + anchor.y + 1.5D, + topY + ENTITY_RELEASE_HEIGHT_PADDING + ); + Vec3 orbitTarget = new Vec3( + Math.cos(captured.orbitAngle) * desiredRadius, + desiredHeight, + Math.sin(captured.orbitAngle) * desiredRadius + ).add(anchor.x, 0.0D, anchor.z); + Vec3 towardOrbit = orbitTarget.subtract(entity.position()); + Vec3 horizontalOrbitOffset = new Vec3(towardOrbit.x, 0.0D, towardOrbit.z); + Vec3 orbitCorrection = horizontalOrbitOffset.lengthSqr() > 1.0E-4 + ? horizontalOrbitOffset.normalize() + : Vec3.ZERO; + + add = orbitCorrection.scale(CAPTURED_SUCTION_FORCE + tornadoForce * 0.24F + captureProgress * 0.18F) + .add(tangential.scale(CAPTURED_TANGENTIAL_FORCE + tornadoForce * 0.20F + ascentProgress * 0.10F)) + .add(0.0D, liftStep, 0.0D) + .add(translationAssist); + if (horizontalDistance < coreRadius * 0.42D) { + add = add.add(inward.scale(-0.10D - coreFactor * 0.10D)); + } + } else { + float suctionStrength = BASE_SUCTION_FORCE + approachFactor * (0.28F + tornadoForce * 0.18F); + float tangentialStrength = BASE_TANGENTIAL_FORCE + approachFactor * 0.12F + coreFactor * 0.06F; + float liftStrength = (BASE_LIFT_FORCE + approachFactor * 0.16F + coreFactor * 0.12F) + * (0.75F + tornadoForce * 0.45F) + * liftWindow; + + add = inward.scale(suctionStrength) + .add(tangential.scale(tangentialStrength)) + .add(0.0D, liftStrength, 0.0D) + .add(translationAssist.scale(0.60D)); + } + + if (entity.onGround()) { + add = add.add(0.0D, captured != null ? 0.35D : 0.18D, 0.0D); + } + + this.recordForceSample( + Math.max(0.0D, add.x * inward.x + add.z * inward.z), + Math.max(0.0D, add.y) + ); + + Vec3 current = entity.getDeltaMovement(); + Vec3 damped = captured != null + ? new Vec3(current.x * 0.54D, Math.max(current.y * 0.55D, 0.0D), current.z * 0.54D) + : new Vec3(current.x * 0.82D, Math.max(current.y * 0.35D, -0.02D), current.z * 0.82D); + entity.setDeltaMovement(damped.add(add)); + + if (captured != null && (entity.getY() > topY + ENTITY_RELEASE_HEIGHT_PADDING || captured.captureTicks > CAPTURE_RELEASE_TICKS)) { + this.capturedEntities.remove(entity.getId()); + entity.setDeltaMovement(entity.getDeltaMovement().add(tangential.scale(0.18D + tornadoForce * 0.08D)).add(0.0D, 0.20D, 0.0D)); + } + entity.hasImpulse = true; + entity.hurtMarked = true; + entity.fallDistance = 0.0F; + if (entity instanceof ServerPlayer serverPlayer) { + serverPlayer.connection.send(new ClientboundSetEntityMotionPacket(serverPlayer)); + } + this.applyEntityDamage(entity, captured, approachFactor, coreFactor, liftWindow); + } + + private CapturedEntityState createCaptureState(Entity entity, double captureRadius, double dist) { + double angle = Math.atan2(entity.getZ() - this.position.z, entity.getX() - this.position.x); + float orbitRadiusFactor = Mth.clamp((float) (dist / Math.max(captureRadius, 0.001D)), 0.42F, 0.88F); + float liftBias = 0.88F + (float) StormMotionModel.noise01(this.id, entity.getId() * 31L, 0.07F) * 0.28F; + return new CapturedEntityState( + angle, + orbitRadiusFactor, + this.getRotationDirection(entity), + liftBias, + this.ageTicks, + 0 + ); + } + + private void applyEntityDamage(Entity entity, + @Nullable CapturedEntityState captured, + float approachFactor, + float coreFactor, + float liftWindow) { + if (!(entity instanceof LivingEntity living)) { + return; + } + if (living instanceof Player player && (player.isCreative() || player.isSpectator())) { + return; + } + if (this.ageTicks % ENTITY_DAMAGE_INTERVAL_TICKS != Math.floorMod(entity.getId(), ENTITY_DAMAGE_INTERVAL_TICKS)) { + return; + } + if (captured == null && coreFactor < 0.28F && approachFactor < 0.48F) { + return; + } + + float stormFactor = StormSeverityScale.toNormalized(this.stormLevel); + float intensityFactor = 0.48F + this.normalizedIntensity * 0.96F + stormFactor * 0.42F; + float pressureFactor = captured != null ? 1.35F : 0.82F; + float zoneFactor = 0.34F + coreFactor * 0.92F + approachFactor * 0.38F + (1.0F - liftWindow) * 0.12F; + float damage = (float) (this.getDamageMultiplier() * 0.08D) * intensityFactor * pressureFactor * zoneFactor; + damage = Mth.clamp(damage, captured != null ? 1.5F : 0.75F, 9.0F); + if (damage > 0.0F) { + living.hurt(living.damageSources().generic(), damage); + } + } + + private float getRotationDirection(Entity entity) { + return (((this.id.getLeastSignificantBits() ^ entity.getId()) & 1L) == 0L) ? 1.0F : -1.0F; + } + + private void resetRuntimeDebugStats() { + this.debugEligibleEntityCount = 0; + this.debugCapturedEntityCount = this.capturedEntities.size(); + this.debugForceSampleCount = 0; + this.debugPullForceSum = 0.0D; + this.debugUpwardForceSum = 0.0D; + this.debugPullForceMax = 0.0D; + this.debugUpwardForceMax = 0.0D; + this.debugDestructionSweepRadius = 0.0F; + this.debugDestructionCandidateBlockCount = 0; + this.debugDestroyedBlockCount = 0; + this.debugDestroyedLeafLogCount = 0; + this.debugDestroyedWeakCount = 0; + this.debugDestroyedGrassCount = 0; + this.debugDestroyedGlassCount = 0; + } + + private void recordForceSample(double pullForce, double upwardForce) { + this.debugForceSampleCount++; + this.debugPullForceSum += pullForce; + this.debugUpwardForceSum += upwardForce; + this.debugPullForceMax = Math.max(this.debugPullForceMax, pullForce); + this.debugUpwardForceMax = Math.max(this.debugUpwardForceMax, upwardForce); + } + + private void pruneCapturedEntities(ServerLevel level) { + Iterator> iterator = this.capturedEntities.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + Entity entity = level.getEntity(entry.getKey()); + if (entity == null || !entity.isAlive()) { + iterator.remove(); + continue; + } + CapturedEntityState state = entry.getValue(); + if ((this.ageTicks - state.lastSeenAge) > 22) { + iterator.remove(); + } + } + } + + private float getEffectiveWindSpeed() { + float composite = Mth.clamp(this.normalizedIntensity * 0.72F + StormSeverityScale.toNormalized(this.stormLevel) * 0.28F, 0.0F, 1.0F); + return Mth.lerp(composite, MIN_EFFECTIVE_WIND, MAX_EFFECTIVE_WIND); + } + + private void applyWaterPenalty(float waterExposure) { + if (waterExposure <= WATER_PENALTY_THRESHOLD) { + return; + } + + float penalty = Mth.clamp((waterExposure - WATER_PENALTY_THRESHOLD) / (1.0F - WATER_PENALTY_THRESHOLD), 0.0F, 1.0F); + this.targetIntensity = Math.max(0.18F, this.targetIntensity * (1.0F - penalty * 0.18F)); + } + + private Vec3 getInteractionAnchor(ServerLevel level) { + return new Vec3(this.position.x, this.sampleTerrainSurfaceY(level), this.position.z); + } + + private float sampleTerrainSurfaceY(ServerLevel level) { + int centerX = Mth.floor(this.position.x); + int centerZ = Mth.floor(this.position.z); + int sampleOffset = Math.max(2, Mth.ceil(Math.min(this.getWindfieldWidth() * 0.18F, 10.0F))); + + float highestSurface = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, centerX, centerZ) - 1.0F; + highestSurface = Math.max(highestSurface, level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, centerX + sampleOffset, centerZ) - 1.0F); + highestSurface = Math.max(highestSurface, level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, centerX - sampleOffset, centerZ) - 1.0F); + highestSurface = Math.max(highestSurface, level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, centerX, centerZ + sampleOffset) - 1.0F); + highestSurface = Math.max(highestSurface, level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, centerX, centerZ - sampleOffset) - 1.0F); + return highestSurface; + } + + private float sampleWaterExposure(ServerLevel level) { + double sampleRadius = Math.max(6.0D, this.getWindfieldWidth() * 0.45D); + double[][] offsets = { + {0.0D, 0.0D}, + {sampleRadius, 0.0D}, + {-sampleRadius, 0.0D}, + {0.0D, sampleRadius}, + {0.0D, -sampleRadius}, + {sampleRadius * 0.7D, sampleRadius * 0.7D}, + {sampleRadius * 0.7D, -sampleRadius * 0.7D}, + {-sampleRadius * 0.7D, sampleRadius * 0.7D}, + {-sampleRadius * 0.7D, -sampleRadius * 0.7D} + }; + + int waterSamples = 0; + for (double[] offset : offsets) { + int x = Mth.floor(this.position.x + offset[0]); + int z = Mth.floor(this.position.z + offset[1]); + int terrainY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z); + BlockPos surfacePos = new BlockPos(x, terrainY - 1, z); + BlockPos belowPos = surfacePos.below(); + BlockState surface = level.getBlockState(surfacePos); + BlockState below = level.getBlockState(belowPos); + if (surface.is(Blocks.WATER) + || below.is(Blocks.WATER) + || surface.getFluidState().is(FluidTags.WATER) + || below.getFluidState().is(FluidTags.WATER)) { + waterSamples++; + } + } + return waterSamples / (float) offsets.length; + } + + private static final class CapturedEntityState { + private double orbitAngle; + private float orbitRadiusFactor; + private final float rotationDirection; + private final float liftBias; + private int lastSeenAge; + private int captureTicks; + + private CapturedEntityState(double orbitAngle, + float orbitRadiusFactor, + float rotationDirection, + float liftBias, + int lastSeenAge, + int captureTicks) { + this.orbitAngle = orbitAngle; + this.orbitRadiusFactor = orbitRadiusFactor; + this.rotationDirection = rotationDirection; + this.liftBias = liftBias; + this.lastSeenAge = lastSeenAge; + this.captureTicks = captureTicks; + } + } + + public record RuntimeDebugSnapshot( + UUID id, + StormLifecyclePhase phase, + float normalizedIntensity, + int eligibleEntityCount, + int capturedEntityCount, + double averagePullForce, + double maxPullForce, + double averageUpwardForce, + double maxUpwardForce, + float destructionSweepRadius, + int destructionCandidateBlockCount, + int destroyedBlockCount, + int destroyedLeafLogCount, + int destroyedWeakCount, + int destroyedGrassCount, + int destroyedGlassCount + ) { + } + + private static float smoothStep(float edge0, float edge1, float value) { + if (edge0 == edge1) { + return value < edge0 ? 0.0F : 1.0F; + } + float t = Mth.clamp((value - edge0) / (edge1 - edge0), 0.0F, 1.0F); + return t * t * (3.0F - 2.0F * t); + } } diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoManager.java b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoManager.java index fea90d56..7d61d5bb 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoManager.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoManager.java @@ -3,66 +3,159 @@ import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; import dev.nonamecrackers2.simpleclouds.common.world.SpawnRegion; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.Gabou.projectatmosphere.api.common.cloud.region.ITornadoRegion; +import net.Gabou.projectatmosphere.api.common.cloud.region.TornadoDescriptor; import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.Gabou.projectatmosphere.modules.core.CloudLibrary; import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.Gabou.projectatmosphere.modules.weather.StormSeverityScale; +import net.Gabou.projectatmosphere.modules.weather.StormShieldManager; +import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; import net.Gabou.projectatmosphere.network.NetworkHandler; +import net.Gabou.projectatmosphere.network.RemoveTornadoPacket; import net.Gabou.projectatmosphere.network.SpawnTornadoPacket; +import net.Gabou.projectatmosphere.network.SyncTornadoesPacket; import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; import net.minecraft.world.level.Level; +import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.phys.Vec3; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.network.PacketDistributor; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.UUID; public class TornadoManager { - private static final List ACTIVE_TORNADOES = new ArrayList<>(); + private static final List SERVER_TORNADOES = new ArrayList<>(); + private static final List CLIENT_TORNADOES = new ArrayList<>(); + private static final ResourceLocation RUNTIME_TORNADO_CONTROLLER = + ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "runtime_spawn"); + private static final float MIN_VISUAL_HEIGHT = 96.0F; + private static final float HEIGHT_RADIUS_FACTOR = 6.0F; + private static final float HEIGHT_CLOUD_PADDING = 40.0F; + private static final float DEFAULT_ANGULAR_SPEED = 0.05F; + private static final int SYNC_INTERVAL_TICKS = 5; - private static float shaderTime = 0.0f; + private static float shaderTime = 0.0F; - public static void spawn(Vec3 pos, float radius, WindVector wind, Level level) { - SpawnRegion temporaryRegion = new SpawnRegion((int)pos.x,(int) pos.z,(int) radius); - for (CloudRegion cloud : CloudManager.get(level).getClouds()) { - if (cloud.intersects(temporaryRegion)) { - if (!AtmoCommonConfig.ENABLE_TORNADOES.get()) return; - ACTIVE_TORNADOES.add(new TornadoInstance(pos, radius, wind, cloud)); - break; - } - } + @OnlyIn(Dist.CLIENT) + public static void spawnClient(UUID id, Vec3 pos, float radius, WindVector wind, float bottomY, float height) { + applyClientSnapshot(new TornadoSnapshot( + id, + new Vec3(pos.x, bottomY, pos.z), + radius, + bottomY, + height, + wind.baseSpeed(), + wind.angleRadians(), + wind.gustSpeed(), + Mth.clamp((radius - 5.0F) / 20.0F, 0.25F, 1.0F), + StormSeverityScale.fromNormalized(Mth.clamp((radius - 5.0F) / 20.0F, 0.25F, 1.0F)), + 0.0F, + net.Gabou.projectatmosphere.modules.weather.StormLifecyclePhase.FORMING + )); + } + public static boolean spawnServer(ServerLevel level, Vec3 pos, float radius, WindVector wind) { + CloudRegion cloud = findIntersectingCloud(level, pos, radius); + int stormLevel = deriveStormLevel(level, cloud, BlockPos.containing(pos)); + return spawnServer(level, pos, radius, wind, stormLevel); + } + public static boolean spawnServer(ServerLevel level, Vec3 pos, float radius, WindVector wind, int stormLevel) { + CloudRegion cloud = findIntersectingCloud(level, pos, radius); + if (cloud == null) { + return false; + } + return spawnServerInternal(level, pos, radius, wind, stormLevel, cloud, true); } - @OnlyIn(Dist.CLIENT) - public static void spawnClient(Vec3 pos, float radius, WindVector wind) { - if (!AtmoCommonConfig.ENABLE_TORNADOES.get()) return; - Level level = Minecraft.getInstance().level; - if (level == null) return; - spawn(pos, radius, wind, level); + + public static boolean spawnServerWithoutCloud(ServerLevel level, Vec3 pos, float radius, WindVector wind) { + int stormLevel = deriveStormLevel(level, null, BlockPos.containing(pos)); + return spawnServerWithoutCloud(level, pos, radius, wind, stormLevel); } + public static boolean spawnServerWithoutCloud(ServerLevel level, Vec3 pos, float radius, WindVector wind, int stormLevel) { + return spawnServerInternal(level, pos, radius, wind, stormLevel, null, false); + } + + private static boolean spawnServerInternal(ServerLevel level, Vec3 pos, float radius, WindVector wind, + int stormLevel, @Nullable CloudRegion cloud, + boolean requiresCloudAttachment) { + if (!AtmoCommonConfig.ENABLE_TORNADOES.get()) { + return false; + } + if (StormShieldManager.isProtected(level, pos)) { + return false; + } -// public static void spawn(Vec3 pos, float radius) { -// spawn(pos, radius, WindVector.fromBase(0, 0)); -// } + UUID id = UUID.randomUUID(); + TornadoGeometry geometry = computeGeometry(level, pos, radius); + Vec3 spawnPos = new Vec3(pos.x, geometry.bottomY(), pos.z); + TornadoInstance tornado = new TornadoInstance( + id, + spawnPos, + radius, + wind, + DEFAULT_ANGULAR_SPEED, + geometry.bottomY(), + geometry.height(), + cloud, + stormLevel, + requiresCloudAttachment + ); + if (cloud != null) { + attachDescriptor(cloud, tornado); + } + SERVER_TORNADOES.add(tornado); - public static void spawnServer(ServerLevel level, Vec3 pos, float radius, WindVector wind) { - spawn(pos, radius, wind, level); - NetworkHandler.CHANNEL.send(PacketDistributor.ALL.noArg(), new SpawnTornadoPacket(pos, radius, wind)); + NetworkHandler.CHANNEL.send( + PacketDistributor.ALL.noArg(), + new SpawnTornadoPacket(id, spawnPos, radius, wind, geometry.bottomY(), geometry.height()) + ); + broadcastSnapshots(); + return true; } public static List getActiveTornadoes() { - return ACTIVE_TORNADOES; + return SERVER_TORNADOES; + } + + public static List getClientTornadoes() { + return CLIENT_TORNADOES; } public static void removeTornado(TornadoInstance tornado) { - ACTIVE_TORNADOES.remove(tornado); + if (tornado != null && SERVER_TORNADOES.contains(tornado)) { + tornado.markDissipating(); + broadcastSnapshots(); + } } public static void clearTornadoes() { - ACTIVE_TORNADOES.clear(); + for (TornadoInstance tornado : new ArrayList<>(SERVER_TORNADOES)) { + removeAttachedDescriptor(tornado); + broadcastRemoval(tornado.getId()); + } + SERVER_TORNADOES.clear(); + broadcastSnapshots(); + } + + public static void removeClientTornado(UUID id) { + CLIENT_TORNADOES.removeIf(tornado -> tornado.getId().equals(id)); + } + + public static void clearClientTornadoes() { + CLIENT_TORNADOES.clear(); } @OnlyIn(Dist.CLIENT) @@ -71,18 +164,206 @@ public static float getShaderTime() { } public static void tick(Level level) { - ACTIVE_TORNADOES.removeIf(tornado -> tornado.getLifetimeSeconds() > 600); - for (TornadoInstance tornado : ACTIVE_TORNADOES) { - float speed = tornado.wind.baseSpeed() * 0.2f; - tornado.position = tornado.position.add( - Math.cos(tornado.wind.angleRadians()) * speed, - 0, - Math.sin(tornado.wind.angleRadians()) * speed); - tornado.tick(level); + if (level == null) { + return; } + if (level.isClientSide) { - shaderTime += 0.05f; + shaderTime += 0.05F; + for (TornadoInstance tornado : CLIENT_TORNADOES) { + tornado.tickClient(); + } + return; + } + + ServerLevel serverLevel = (ServerLevel) level; + long gameTime = serverLevel.getGameTime(); + Iterator iterator = SERVER_TORNADOES.iterator(); + while (iterator.hasNext()) { + TornadoInstance tornado = iterator.next(); + CloudRegion currentRegion = findIntersectingCloud(serverLevel, tornado.position, tornado.radius); + if (currentRegion != tornado.getCloudRegion()) { + removeAttachedDescriptor(tornado); + tornado.setCloudRegion(currentRegion); + if (currentRegion != null) { + attachDescriptor(currentRegion, tornado); + } + } else if (currentRegion != null) { + ensureDescriptor(currentRegion, tornado); + } + + tornado.updateCloudAttachment(tornado.getCloudRegion() != null); + + tornado.tickServer(serverLevel, gameTime); + if (tornado.isDead()) { + removeAttachedDescriptor(tornado); + iterator.remove(); + broadcastRemoval(tornado.getId()); + } + } + + if (gameTime % SYNC_INTERVAL_TICKS == 0L) { + broadcastSnapshots(); + } + } + + @OnlyIn(Dist.CLIENT) + public static void applyClientSnapshots(List snapshots) { + List next = new ArrayList<>(snapshots.size()); + for (TornadoSnapshot snapshot : snapshots) { + TornadoInstance existing = findClient(snapshot.id()); + CloudRegion cloud = findClientCloud(snapshot.position(), snapshot.radius()); + if (existing == null) { + existing = new TornadoInstance( + snapshot.id(), + snapshot.position(), + snapshot.radius(), + new WindVector(snapshot.windSpeed(), snapshot.windAngle(), snapshot.windGust()), + DEFAULT_ANGULAR_SPEED, + snapshot.visualBottomY(), + snapshot.visualHeight(), + cloud, + snapshot.stormLevel() + ); + } + existing.applySnapshot(snapshot, cloud); + next.add(existing); } + CLIENT_TORNADOES.clear(); + CLIENT_TORNADOES.addAll(next); } + @OnlyIn(Dist.CLIENT) + private static void applyClientSnapshot(TornadoSnapshot snapshot) { + TornadoInstance existing = findClient(snapshot.id()); + CloudRegion cloud = findClientCloud(snapshot.position(), snapshot.radius()); + if (existing == null) { + existing = new TornadoInstance( + snapshot.id(), + snapshot.position(), + snapshot.radius(), + new WindVector(snapshot.windSpeed(), snapshot.windAngle(), snapshot.windGust()), + DEFAULT_ANGULAR_SPEED, + snapshot.visualBottomY(), + snapshot.visualHeight(), + cloud, + snapshot.stormLevel() + ); + CLIENT_TORNADOES.add(existing); + } + existing.applySnapshot(snapshot, cloud); + } + + @OnlyIn(Dist.CLIENT) + private static TornadoInstance findClient(UUID id) { + for (TornadoInstance tornado : CLIENT_TORNADOES) { + if (tornado.getId().equals(id)) { + return tornado; + } + } + return null; + } + + @OnlyIn(Dist.CLIENT) + @Nullable + private static CloudRegion findClientCloud(Vec3 pos, float radius) { + Level level = Minecraft.getInstance().level; + if (level == null) { + return null; + } + return findIntersectingCloud(level, pos, radius); + } + + @Nullable + private static CloudRegion findIntersectingCloud(Level level, Vec3 pos, float radius) { + SpawnRegion temporaryRegion = new SpawnRegion(Mth.floor(pos.x), Mth.floor(pos.z), Mth.ceil(radius)); + for (CloudRegion cloud : CloudManager.get(level).getClouds()) { + if (cloud.intersects(temporaryRegion)) { + return cloud; + } + } + return null; + } + + static float resolveGroundedBottomY(Level level, Vec3 pos, float fallbackY) { + int sampleX = Mth.floor(pos.x); + int sampleZ = Mth.floor(pos.z); + if (!level.hasChunkAt(new BlockPos(sampleX, Mth.floor(fallbackY), sampleZ))) { + return fallbackY; + } + int height = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, sampleX, sampleZ); + if (height <= level.getMinBuildHeight()) { + return fallbackY; + } + return height - 1.0F; + } + + private static TornadoGeometry computeGeometry(Level level, Vec3 pos, float radius) { + float bottomY = resolveGroundedBottomY(level, pos, (float) pos.y); + float cloudBase = CloudManager.get(level).getCloudHeight(); + float reachToCloudBase = Math.max(0.0F, cloudBase - bottomY); + float height = Math.max(MIN_VISUAL_HEIGHT, reachToCloudBase + radius * HEIGHT_RADIUS_FACTOR + HEIGHT_CLOUD_PADDING); + return new TornadoGeometry(bottomY, height); + } + + private static void attachDescriptor(CloudRegion cloud, TornadoInstance tornado) { + if (cloud instanceof ITornadoRegion tornadoRegion) { + tornadoRegion.replaceTornado(createRuntimeDescriptor(tornado, cloud)); + } + } + + private static void ensureDescriptor(CloudRegion cloud, TornadoInstance tornado) { + if (cloud instanceof ITornadoRegion tornadoRegion && tornadoRegion.findTornado(tornado.getId()) == null) { + tornadoRegion.addTornado(createRuntimeDescriptor(tornado, cloud)); + } + } + + private static TornadoDescriptor createRuntimeDescriptor(TornadoInstance tornado, CloudRegion cloud) { + float offsetX = (float) (tornado.position.x - cloud.getWorldX()); + float offsetZ = (float) (tornado.position.z - cloud.getWorldZ()); + return new TornadoDescriptor( + tornado.getId(), + RUNTIME_TORNADO_CONTROLLER, + offsetX, + offsetZ, + tornado.wind.baseSpeed() * (float) Math.cos(tornado.wind.angleRadians()) * 0.05F, + tornado.wind.baseSpeed() * (float) Math.sin(tornado.wind.angleRadians()) * 0.05F, + tornado.radius, + tornado.getVisualBottomY(), + tornado.getVisualHeight() + ); + } + + private static void removeAttachedDescriptor(TornadoInstance tornado) { + if (tornado.getCloudRegion() instanceof ITornadoRegion tornadoRegion) { + tornadoRegion.removeTornado(tornado.getId()); + } + } + + private static void broadcastRemoval(UUID id) { + NetworkHandler.CHANNEL.send(PacketDistributor.ALL.noArg(), new RemoveTornadoPacket(id)); + } + + private static void broadcastSnapshots() { + List snapshots = new ArrayList<>(SERVER_TORNADOES.size()); + for (TornadoInstance tornado : SERVER_TORNADOES) { + snapshots.add(tornado.snapshot()); + } + NetworkHandler.CHANNEL.send(PacketDistributor.ALL.noArg(), new SyncTornadoesPacket(snapshots)); + } + + private record TornadoGeometry(float bottomY, float height) { + } + + private static int deriveStormLevel(ServerLevel level, @Nullable CloudRegion cloud, net.minecraft.core.BlockPos pos) { + int cloudLevel = cloud == null ? StormSeverityScale.MIN_LEVEL : CloudLibrary.getSeverityFromRessourceLocation(cloud.getCloudTypeId()); + RegionLevelProbe probe = RegionLevelProbe.at(level, pos); + return Math.max(cloudLevel, probe.level()); + } + + private record RegionLevelProbe(int level) { + private static RegionLevelProbe at(ServerLevel level, net.minecraft.core.BlockPos pos) { + return new RegionLevelProbe(StormSeverityScale.resolve(level, net.Gabou.projectatmosphere.util.RegionInstanceKey.from(pos), level.getGameTime())); + } + } } diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoProbabilityManager.java b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoProbabilityManager.java index b23e83e3..645902c4 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoProbabilityManager.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoProbabilityManager.java @@ -10,6 +10,8 @@ import net.Gabou.projectatmosphere.data.TornadoStorageManager; import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; import net.Gabou.projectatmosphere.modules.core.CloudLibrary; +import net.Gabou.projectatmosphere.modules.weather.RegionalWeatherPhase; +import net.Gabou.projectatmosphere.modules.weather.StormSeverityScale; import net.Gabou.projectatmosphere.modules.atmosphere.AtmosphericStateRegistry; import net.Gabou.projectatmosphere.util.BiomeInstanceKey; import net.Gabou.projectatmosphere.util.RegionInstanceKey; @@ -35,15 +37,17 @@ public static void onScheduledCheck(ServerLevel level) { if (isCellOnCooldown(key, level, now)) continue; float risk = computeRisk(key, level, now); + int stormLevel = StormSeverityScale.resolve(level, key, now); float riskMin = AtmoCommonConfig.TORNADO_RISK_MIN_TO_CONSIDER.get().floatValue(); if (risk < riskMin) continue; - float chance = AtmoCommonConfig.TORNADO_BASE_TRIGGER_CHANCE.get().floatValue() * risk; + float chance = AtmoCommonConfig.TORNADO_BASE_TRIGGER_CHANCE.get().floatValue() * risk * (0.55F + StormSeverityScale.toNormalized(stormLevel) * 0.75F); if (random.nextFloat() < chance) { float intensity = map(risk, riskMin, riskMin + 4f, AtmoCommonConfig.TORNADO_INTENSITY_MIN.get().floatValue(), AtmoCommonConfig.TORNADO_INTENSITY_MAX.get().floatValue()); + intensity = Math.max(intensity, 0.18F + StormSeverityScale.toNormalized(stormLevel) * 0.52F); TornadoSpawner.spawn(key, level, clamp01(intensity)); TornadoStorageManager.setCooldown(key, now + minutesToTicks(AtmoCommonConfig.TORNADO_CELL_COOLDOWN_MINUTES.get())); @@ -109,6 +113,13 @@ public static boolean isCellOnCooldown(BiomeInstanceKey key, ServerLevel level, } private static boolean isStormy(RegionInstanceKey key, ServerLevel level) { + RegionalWeatherPhase phase = ForecastOrchestrator.getWeatherPhase(level, key, level.getGameTime()); + if (!phase.isStormCapable()) { + return false; + } + if (StormSeverityScale.resolve(level, key, level.getGameTime()) < 6) { + return false; + } ServerCloudManager manager = (ServerCloudManager) CloudManager.get(level); CloudGenerator generator = manager.getCloudGenerator(); BlockPos pos = key.center(); diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoSnapshot.java b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoSnapshot.java new file mode 100644 index 00000000..68bb558c --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoSnapshot.java @@ -0,0 +1,55 @@ +package net.Gabou.projectatmosphere.modules.tornado; + +import net.Gabou.projectatmosphere.modules.weather.StormLifecyclePhase; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.phys.Vec3; + +import java.util.UUID; + +public record TornadoSnapshot( + UUID id, + Vec3 position, + float radius, + float visualBottomY, + float visualHeight, + float windSpeed, + float windAngle, + float windGust, + float normalizedIntensity, + int stormLevel, + float recentDebrisScore, + StormLifecyclePhase phase +) { + public void write(FriendlyByteBuf buf) { + buf.writeUUID(this.id); + buf.writeDouble(this.position.x); + buf.writeDouble(this.position.y); + buf.writeDouble(this.position.z); + buf.writeFloat(this.radius); + buf.writeFloat(this.visualBottomY); + buf.writeFloat(this.visualHeight); + buf.writeFloat(this.windSpeed); + buf.writeFloat(this.windAngle); + buf.writeFloat(this.windGust); + buf.writeFloat(this.normalizedIntensity); + buf.writeVarInt(this.stormLevel); + buf.writeFloat(this.recentDebrisScore); + buf.writeEnum(this.phase); + } + + public static TornadoSnapshot read(FriendlyByteBuf buf) { + UUID id = buf.readUUID(); + Vec3 position = new Vec3(buf.readDouble(), buf.readDouble(), buf.readDouble()); + float radius = buf.readFloat(); + float bottomY = buf.readFloat(); + float height = buf.readFloat(); + float windSpeed = buf.readFloat(); + float windAngle = buf.readFloat(); + float windGust = buf.readFloat(); + float normalizedIntensity = buf.readFloat(); + int stormLevel = buf.readVarInt(); + float recentDebrisScore = buf.readFloat(); + StormLifecyclePhase phase = buf.readEnum(StormLifecyclePhase.class); + return new TornadoSnapshot(id, position, radius, bottomY, height, windSpeed, windAngle, windGust, normalizedIntensity, stormLevel, recentDebrisScore, phase); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoSpawner.java b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoSpawner.java index c8e49b81..4cc19bd7 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoSpawner.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoSpawner.java @@ -2,11 +2,16 @@ import net.Gabou.projectatmosphere.api.WindVectorApi; import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.Gabou.projectatmosphere.modules.weather.StormSeverityScale; import net.Gabou.projectatmosphere.util.BiomeInstanceKey; import net.Gabou.projectatmosphere.util.RegionInstanceKey; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.FluidTags; import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.phys.Vec3; public final class TornadoSpawner { @@ -16,22 +21,24 @@ public static void spawn(BiomeInstanceKey key, ServerLevel level, float intensit float radiusSetting = AtmoCommonConfig.TORNADO_BASE_SPAWN_RADIUS_M.get().floatValue(); BlockPos center = pickSpawnPosNear(key, level, radiusSetting); WindVectorApi.WindSample wind = WindVectorApi.getSurface(key); - float radius = 5f + 20f * intensity; + int stormLevel = StormSeverityScale.resolve(level, net.Gabou.projectatmosphere.modules.atmosphere.AtmosphericStateRegistry.resolveRegionKey(key), level.getGameTime()); + float radius = 5f + 18f * intensity + stormLevel * 1.5F; net.Gabou.projectatmosphere.modules.core.WindVector w = net.Gabou.projectatmosphere.modules.core.WindVector.fromBase(wind.speedMps(), (float) Math.toRadians(wind.directionDeg())); - TornadoManager.spawnServer(level, Vec3.atCenterOf(center), radius, w); + TornadoManager.spawnServer(level, Vec3.atCenterOf(center), radius, w, stormLevel); } public static void spawn(RegionInstanceKey key, ServerLevel level, float intensity) { float radiusSetting = AtmoCommonConfig.TORNADO_BASE_SPAWN_RADIUS_M.get().floatValue(); BlockPos center = pickSpawnPosNear(key, level, radiusSetting); WindVectorApi.WindSample wind = WindVectorApi.getSurface(key, level.getGameTime()); - float radius = 5f + 20f * intensity; + int stormLevel = StormSeverityScale.resolve(level, key, level.getGameTime()); + float radius = 5f + 18f * intensity + stormLevel * 1.5F; net.Gabou.projectatmosphere.modules.core.WindVector w = net.Gabou.projectatmosphere.modules.core.WindVector.fromBase(wind.speedMps(), (float) Math.toRadians(wind.directionDeg())); - TornadoManager.spawnServer(level, Vec3.atCenterOf(center), radius, w); + TornadoManager.spawnServer(level, Vec3.atCenterOf(center), radius, w, stormLevel); } private static BlockPos pickSpawnPosNear(BiomeInstanceKey key, ServerLevel level, float radius) { @@ -46,11 +53,70 @@ private static BlockPos pickSpawnPosNear(RegionInstanceKey key, ServerLevel leve private static BlockPos pickSpawnPosNear(BlockPos base, ServerLevel level, float radius) { int r = (int) radius; RandomSource random = RandomSource.create(); - int dx = random.nextInt(-r, r + 1); - int dz = random.nextInt(-r, r + 1); - int y = level.getHeight(net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING, - base.getX() + dx, base.getZ() + dz); - return new BlockPos(base.getX() + dx, y, base.getZ() + dz); + BlockPos best = null; + float bestScore = Float.NEGATIVE_INFINITY; + for (int attempt = 0; attempt < 10; attempt++) { + int dx = random.nextInt(-r, r + 1); + int dz = random.nextInt(-r, r + 1); + int x = base.getX() + dx; + int z = base.getZ() + dz; + int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z); + BlockPos sample = new BlockPos(x, y, z); + float score = scoreSpawnSurface(level, sample); + if (score > bestScore) { + bestScore = score; + best = sample; + } + } + return best == null ? base : best; + } + + private static float scoreSpawnSurface(ServerLevel level, BlockPos pos) { + BlockPos groundPos = pos.below(); + BlockState ground = level.getBlockState(groundPos); + BlockState above = level.getBlockState(pos); + float score = 1.0F; + if (!ground.getFluidState().isEmpty() + || ground.is(Blocks.WATER) + || above.is(Blocks.WATER) + || ground.getFluidState().is(FluidTags.WATER) + || above.getFluidState().is(FluidTags.WATER)) { + score -= 8.0F; + } + if (ground.is(Blocks.GRASS_BLOCK) || ground.is(Blocks.DIRT) || ground.is(Blocks.COARSE_DIRT) || ground.is(Blocks.PODZOL)) { + score += 0.45F; + } + if (ground.is(Blocks.SAND) || ground.is(Blocks.RED_SAND)) { + score -= 0.20F; + } + score -= sampleNearbyWaterPenalty(level, pos); + return score; + } + + private static float sampleNearbyWaterPenalty(ServerLevel level, BlockPos pos) { + int[][] offsets = { + {0, 0}, + {8, 0}, + {-8, 0}, + {0, 8}, + {0, -8}, + {8, 8}, + {8, -8}, + {-8, 8}, + {-8, -8} + }; + int waterSamples = 0; + for (int[] offset : offsets) { + int x = pos.getX() + offset[0]; + int z = pos.getZ() + offset[1]; + int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z); + BlockPos sample = new BlockPos(x, y - 1, z); + BlockState state = level.getBlockState(sample); + if (state.is(Blocks.WATER) || state.getFluidState().is(FluidTags.WATER)) { + waterSamples++; + } + } + return waterSamples / (float) offsets.length * 5.0F; } } diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/weather/RegionalWeatherPhase.java b/src/main/java/net/Gabou/projectatmosphere/modules/weather/RegionalWeatherPhase.java new file mode 100644 index 00000000..c8ebd80f --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/weather/RegionalWeatherPhase.java @@ -0,0 +1,14 @@ +package net.Gabou.projectatmosphere.modules.weather; + +public enum RegionalWeatherPhase { + CALM, + CLOUDY, + RAIN, + THUNDER, + SEVERE, + CYCLONE; + + public boolean isStormCapable() { + return this == THUNDER || this == SEVERE || this == CYCLONE; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/weather/ServerWeatherStateResolver.java b/src/main/java/net/Gabou/projectatmosphere/modules/weather/ServerWeatherStateResolver.java new file mode 100644 index 00000000..ca04f0e4 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/weather/ServerWeatherStateResolver.java @@ -0,0 +1,70 @@ +package net.Gabou.projectatmosphere.modules.weather; + +import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; +import net.Gabou.projectatmosphere.modules.atmosphere.AtmosphericStateRegistry; +import net.Gabou.projectatmosphere.modules.atmosphere.RegionAtmosphereState; +import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.Gabou.projectatmosphere.util.RegionInstanceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; + +public final class ServerWeatherStateResolver { + private ServerWeatherStateResolver() { + } + + public static RegionalWeatherPhase resolve(ServerLevel level, RegionInstanceKey key, long tick) { + if (key == null) { + return RegionalWeatherPhase.CALM; + } + RegionAtmosphereState state = AtmosphericStateRegistry.getState(key); + if (state == null) { + float stormChance = ForecastOrchestrator.getCurrentStormChance(key, tick); + if (stormChance >= 0.7F) { + return RegionalWeatherPhase.SEVERE; + } + if (stormChance >= 0.45F) { + return RegionalWeatherPhase.THUNDER; + } + if (stormChance >= 0.2F) { + return RegionalWeatherPhase.RAIN; + } + return RegionalWeatherPhase.CLOUDY; + } + + float rain = Mth.clamp(state.getRainIntensity(), 0.0F, 1.0F); + float cloud = Mth.clamp(state.getCloudCover(), 0.0F, 1.0F); + float cloudWater = Mth.clamp(state.getCloudWater(), 0.0F, 1.2F); + float wind = Math.max(0.0F, state.getWindStrength()); + float lowPressure = Mth.clamp((1013.25F - state.getPressure()) / 45.0F, 0.0F, 1.0F); + float cycloneFloor = Math.max(state.getCycloneCloudFloor(), state.getCycloneRainFloor()); + float stormChance = ForecastOrchestrator.getCurrentStormChance(key, tick); + WindVector dynamicWind = state.getWind(); + float gust = dynamicWind == null ? 0.0F : Math.max(dynamicWind.baseSpeed(), dynamicWind.gustSpeed()); + + float severity = rain * 0.30F + + cloud * 0.18F + + cloudWater * 0.12F + + Mth.clamp(wind / 18.0F, 0.0F, 1.0F) * 0.12F + + lowPressure * 0.08F + + stormChance * 0.15F + + cycloneFloor * 0.20F + + Mth.clamp((gust - 20.0F) / 40.0F, 0.0F, 1.0F) * 0.10F; + + if (cycloneFloor >= 0.45F || severity >= 0.95F) { + return RegionalWeatherPhase.CYCLONE; + } + if (severity >= 0.70F) { + return RegionalWeatherPhase.SEVERE; + } + if (severity >= 0.42F) { + return RegionalWeatherPhase.THUNDER; + } + if (severity >= 0.18F) { + return RegionalWeatherPhase.RAIN; + } + if (cloud >= 0.18F || cloudWater >= 0.10F) { + return RegionalWeatherPhase.CLOUDY; + } + return RegionalWeatherPhase.CALM; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormLifecyclePhase.java b/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormLifecyclePhase.java new file mode 100644 index 00000000..6167fc75 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormLifecyclePhase.java @@ -0,0 +1,12 @@ +package net.Gabou.projectatmosphere.modules.weather; + +public enum StormLifecyclePhase { + FORMING, + ACTIVE, + DISSIPATING, + DISSIPATED; + + public boolean isTerminal() { + return this == DISSIPATED; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormMotionModel.java b/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormMotionModel.java new file mode 100644 index 00000000..ccd424a5 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormMotionModel.java @@ -0,0 +1,272 @@ +package net.Gabou.projectatmosphere.modules.weather; + +import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.minecraft.core.BlockPos; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.FluidTags; +import net.minecraft.util.Mth; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.phys.Vec3; +import net.minecraft.server.level.ServerLevel; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public final class StormMotionModel { + private static final float TORNADO_BASE_DRIFT = 0.050F; + private static final float TORNADO_LEASH_RADIUS = 160.0F; + private static final int TORNADO_MIN_PLAN_TICKS = 100; + private static final int TORNADO_MAX_PLAN_TICKS = 200; + private static final float HURRICANE_BASE_DRIFT = 0.06F; + private static final float HURRICANE_TURN_RATE = 0.012F; + private static final float HURRICANE_WANDER_SCALE = 0.25F; + + private StormMotionModel() { + } + + public static TornadoRoutePlan planTornadoRoute(ServerLevel level, UUID id, Vec3 position, WindVector ambientWind, + float normalizedIntensity, int stormLevel, float currentHeading, + long ageTicks, float anchorX, float anchorZ) { + float ambientSpeed = Math.max(0.6F, ambientWind.baseSpeed()); + float ambientHeading = ambientWind.angleRadians(); + float stormNormalized = StormSeverityScale.toNormalized(stormLevel); + float baseHeading = rotateTowards( + currentHeading, + ambientHeading, + 0.26F + normalizedIntensity * 0.10F + stormNormalized * 0.08F + ); + int durationTicks = Mth.floor(Mth.lerp( + noise01(id, ageTicks + 73L, 0.0007F), + TORNADO_MIN_PLAN_TICKS, + TORNADO_MAX_PLAN_TICKS + )); + float baseSpeed = TORNADO_BASE_DRIFT + + normalizedIntensity * 0.05F + + stormNormalized * 0.02F + + Math.min(ambientSpeed, 30.0F) * 0.0007F; + float headingStep = 0.22F + (1.0F - normalizedIntensity) * 0.05F; + float bestScore = Float.NEGATIVE_INFINITY; + Vec3 bestWaypoint = position; + float bestHeading = baseHeading; + float bestSpeed = baseSpeed; + + for (int i = -4; i <= 4; i++) { + float candidateHeading = baseHeading + + headingStep * i + + noiseAngle(id, ageTicks + i * 17L, 0.0009F, 0.08F); + float candidateSpeed = baseSpeed * (0.92F + noise01(id, ageTicks + i * 29L + 11L, 0.0011F) * 0.22F); + double travelDistance = Math.max( + 10.0D, + candidateSpeed * durationTicks * (0.86D + normalizedIntensity * 0.22D) + ); + Vec3 candidateWaypoint = position.add(horizontalVector(candidateHeading).scale(travelDistance)); + candidateWaypoint = clampToAnchorLeash(candidateWaypoint, anchorX, anchorZ); + + float continuity = 1.0F - Math.abs(wrapAngle(candidateHeading - currentHeading)) / (float) Math.PI; + float ambientAlign = 1.0F - Math.abs(wrapAngle(candidateHeading - ambientHeading)) / (float) Math.PI; + float shieldPenalty = (float) StormShieldManager.getMaxProtection(level, candidateWaypoint, 22.0D + stormLevel * 8.0D); + SurfaceScore surface = sampleSurfaceScore(level, candidateWaypoint, 8.0D + stormLevel * 1.8D); + float waypointDx = anchorX - (float) candidateWaypoint.x; + float waypointDz = anchorZ - (float) candidateWaypoint.z; + float anchorDist = Mth.sqrt(waypointDx * waypointDx + waypointDz * waypointDz); + float anchorPenalty = Mth.clamp(anchorDist / (TORNADO_LEASH_RADIUS * 0.96F), 0.0F, 1.0F); + float score = continuity * 0.58F + + ambientAlign * 0.16F + + surface.landScore * (0.92F + stormNormalized * 0.38F) + - surface.waterPenalty * 4.80F + - surface.reliefPenalty * 0.55F + - shieldPenalty * 2.80F + - anchorPenalty * 0.35F + + noiseSigned(id, ageTicks + i * 13L, 0.0013F) * 0.05F; + if (score > bestScore) { + bestScore = score; + bestHeading = candidateHeading; + bestWaypoint = candidateWaypoint; + bestSpeed = candidateSpeed; + } + } + return new TornadoRoutePlan(bestWaypoint, bestHeading, bestSpeed, durationTicks); + } + + public static TornadoRoutePlan planFallbackTornadoRoute(UUID id, Vec3 position, WindVector ambientWind, + float normalizedIntensity, int stormLevel, float currentHeading, + long ageTicks, float anchorX, float anchorZ) { + float ambientSpeed = Math.max(0.6F, ambientWind.baseSpeed()); + float ambientHeading = ambientWind.angleRadians(); + float stormNormalized = StormSeverityScale.toNormalized(stormLevel); + float desiredHeading = ambientHeading + noiseAngle(id, ageTicks, 0.0013F, 0.16F); + float blendedHeading = rotateTowards(currentHeading, desiredHeading, 0.30F + stormNormalized * 0.08F); + int durationTicks = Mth.floor(Mth.lerp( + noise01(id, ageTicks + 29L, 0.0009F), + TORNADO_MIN_PLAN_TICKS, + TORNADO_MAX_PLAN_TICKS + )); + float speed = TORNADO_BASE_DRIFT + + normalizedIntensity * 0.05F + + stormNormalized * 0.02F + + Math.min(ambientSpeed, 30.0F) * 0.0007F; + double travelDistance = Math.max( + 10.0D, + speed * durationTicks * (0.82D + normalizedIntensity * 0.18D) + ); + Vec3 waypoint = clampToAnchorLeash( + position.add(horizontalVector(blendedHeading).scale(travelDistance)), + anchorX, + anchorZ + ); + return new TornadoRoutePlan(waypoint, blendedHeading, speed, durationTicks); + } + + public static Vec3 advanceHurricane(UUID id, Vec3 position, Vec3 currentVelocity, WindVector ambientWind, + float normalizedIntensity, long ageTicks) { + return advanceHurricane(null, id, position, currentVelocity, ambientWind, normalizedIntensity, ageTicks); + } + + public static Vec3 advanceHurricane(@Nullable ServerLevel level, UUID id, Vec3 position, Vec3 currentVelocity, WindVector ambientWind, + float normalizedIntensity, long ageTicks) { + float ambientSpeed = Math.max(1.0F, ambientWind.baseSpeed()); + float targetHeading = ambientWind.angleRadians() + noiseAngle(id, ageTicks, 0.0017F, HURRICANE_WANDER_SCALE); + float currentHeading = currentVelocity.lengthSqr() > 1.0E-4 ? (float) Math.atan2(currentVelocity.z, currentVelocity.x) : targetHeading; + float heading = rotateTowards(currentHeading, targetHeading, HURRICANE_TURN_RATE + normalizedIntensity * 0.004F); + float speed = ambientSpeed * (HURRICANE_BASE_DRIFT + normalizedIntensity * 0.08F); + + Vec3 blended = new Vec3( + Math.cos(heading) * speed, + 0.0D, + Math.sin(heading) * speed + ); + if (level != null) { + blended = blended.add(StormShieldManager.sampleAvoidance(level, position, 96.0D).scale(0.25D)); + } + Vec3 velocity = currentVelocity.lerp(blended, 0.08D); + return position.add(velocity); + } + + public static float noise01(UUID id, long tick, float rate) { + long seed = id.getMostSignificantBits() ^ id.getLeastSignificantBits(); + float a = (float) Math.sin(seed * 0.00000013D + tick * rate); + float b = (float) Math.sin(seed * 0.00000029D + tick * rate * 0.63D + 1.7D); + return Mth.clamp((a * 0.6F + b * 0.4F) * 0.5F + 0.5F, 0.0F, 1.0F); + } + + public static float noiseSigned(UUID id, long tick, float rate) { + return noise01(id, tick, rate) * 2.0F - 1.0F; + } + + private static SurfaceScore sampleSurfaceScore(ServerLevel level, Vec3 center, double spread) { + double[][] offsets = { + {0.0D, 0.0D}, + {spread, 0.0D}, + {-spread, 0.0D}, + {0.0D, spread}, + {0.0D, -spread} + }; + + int landSamples = 0; + int waterSamples = 0; + int vegetationSamples = 0; + int minHeight = Integer.MAX_VALUE; + int maxHeight = Integer.MIN_VALUE; + + for (double[] offset : offsets) { + int x = Mth.floor(center.x + offset[0]); + int z = Mth.floor(center.z + offset[1]); + int terrainY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z); + int surfaceY = level.getHeight(Heightmap.Types.WORLD_SURFACE, x, z); + minHeight = Math.min(minHeight, terrainY); + maxHeight = Math.max(maxHeight, terrainY); + + BlockPos groundPos = new BlockPos(x, terrainY - 1, z); + BlockPos surfacePos = new BlockPos(x, surfaceY - 1, z); + BlockState ground = level.getBlockState(groundPos); + BlockState surface = level.getBlockState(surfacePos); + boolean water = surface.is(Blocks.WATER) + || ground.is(Blocks.WATER) + || surface.getFluidState().is(FluidTags.WATER) + || ground.getFluidState().is(FluidTags.WATER); + if (water) { + waterSamples++; + } else { + landSamples++; + } + + if (ground.is(Blocks.GRASS_BLOCK) + || ground.is(Blocks.DIRT) + || ground.is(Blocks.COARSE_DIRT) + || ground.is(Blocks.PODZOL) + || ground.is(BlockTags.LOGS) + || ground.is(BlockTags.LEAVES) + || surface.is(BlockTags.FLOWERS) + || surface.is(BlockTags.CROPS)) { + vegetationSamples++; + } + } + + float sampleCount = offsets.length; + float landScore = (landSamples / sampleCount) + (vegetationSamples / sampleCount) * 0.30F; + float waterPenalty = waterSamples / sampleCount; + float reliefPenalty = minHeight == Integer.MAX_VALUE + ? 0.0F + : Mth.clamp((maxHeight - minHeight) / 16.0F, 0.0F, 1.0F); + return new SurfaceScore(landScore, waterPenalty, reliefPenalty); + } + + private static Vec3 clampToAnchorLeash(Vec3 candidateWaypoint, float anchorX, float anchorZ) { + double dx = candidateWaypoint.x - anchorX; + double dz = candidateWaypoint.z - anchorZ; + double distSqr = dx * dx + dz * dz; + double leashRadius = TORNADO_LEASH_RADIUS * 0.94D; + if (distSqr <= leashRadius * leashRadius) { + return candidateWaypoint; + } + double dist = Math.sqrt(distSqr); + double scale = leashRadius / Math.max(dist, 0.001D); + return new Vec3(anchorX + dx * scale, candidateWaypoint.y, anchorZ + dz * scale); + } + + private static Vec3 horizontalVector(float heading) { + return new Vec3(Math.cos(heading), 0.0D, Math.sin(heading)); + } + + private static Vec3 sampleWaterAvoidance(ServerLevel level, Vec3 center, double probeDistance) { + Vec3 avoid = Vec3.ZERO; + for (int i = 0; i < 8; i++) { + float heading = (float) (i * (Math.PI * 2.0D / 8.0D)); + Vec3 dir = horizontalVector(heading); + Vec3 probe = center.add(dir.scale(probeDistance)); + SurfaceScore score = sampleSurfaceScore(level, probe, probeDistance * 0.28D); + if (score.waterPenalty > 0.0F) { + avoid = avoid.add(dir.scale(-score.waterPenalty)); + } + } + return avoid.lengthSqr() > 1.0E-6 ? avoid.normalize() : Vec3.ZERO; + } + + private static float noiseAngle(UUID id, long tick, float rate, float scale) { + return noiseSigned(id, tick, rate) * scale; + } + + private static float rotateTowards(float current, float target, float maxTurn) { + float delta = Mth.wrapDegrees((float) Math.toDegrees(target - current)); + float deltaRad = (float) Math.toRadians(Mth.clamp(delta, (float) Math.toDegrees(-maxTurn), (float) Math.toDegrees(maxTurn))); + return current + deltaRad; + } + + private static float wrapAngle(float angle) { + while (angle > Math.PI) { + angle -= (float) (Math.PI * 2.0); + } + while (angle < -Math.PI) { + angle += (float) (Math.PI * 2.0); + } + return angle; + } + + public record TornadoRoutePlan(Vec3 waypoint, float headingRadians, float speed, int durationTicks) { + } + + private record SurfaceScore(float landScore, float waterPenalty, float reliefPenalty) { + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormSeverityScale.java b/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormSeverityScale.java new file mode 100644 index 00000000..7efc65a2 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormSeverityScale.java @@ -0,0 +1,91 @@ +package net.Gabou.projectatmosphere.modules.weather; + +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; +import net.Gabou.projectatmosphere.modules.atmosphere.AtmosphericStateRegistry; +import net.Gabou.projectatmosphere.modules.atmosphere.RegionAtmosphereState; +import net.Gabou.projectatmosphere.modules.core.CloudLibrary; +import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.Gabou.projectatmosphere.util.RegionInstanceKey; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; + +public final class StormSeverityScale { + public static final int MIN_LEVEL = 1; + public static final int MAX_LEVEL = 7; + public static final int SUPERCELL_LEVEL = 7; + + private StormSeverityScale() { + } + + public static int clamp(int level) { + return Mth.clamp(level, MIN_LEVEL, MAX_LEVEL); + } + + public static float toNormalized(int level) { + return (clamp(level) - MIN_LEVEL) / (float) (MAX_LEVEL - MIN_LEVEL); + } + + public static int fromNormalized(float normalized) { + return clamp(1 + Mth.floor(Mth.clamp(normalized, 0.0F, 1.0F) * 6.999F)); + } + + public static int fromWeatherPhase(RegionalWeatherPhase phase) { + return switch (phase) { + case CALM -> 1; + case CLOUDY -> 2; + case RAIN -> 3; + case THUNDER -> 5; + case SEVERE -> 6; + case CYCLONE -> 7; + }; + } + + public static int resolve(ServerLevel level, RegionInstanceKey key, long tick) { + if (key == null) { + return MIN_LEVEL; + } + + int phaseLevel = fromWeatherPhase(ServerWeatherStateResolver.resolve(level, key, tick)); + int cloudLevel = sampleCloudLevel(level, key.center()); + RegionAtmosphereState state = AtmosphericStateRegistry.getState(key); + if (state == null) { + return clamp(Math.max(phaseLevel, cloudLevel)); + } + + float rain = Mth.clamp(state.getRainIntensity(), 0.0F, 1.0F); + float cloud = Mth.clamp(state.getCloudCover(), 0.0F, 1.0F); + float water = Mth.clamp(state.getCloudWater(), 0.0F, 1.2F); + float wind = Mth.clamp(state.getWindStrength() / 26.0F, 0.0F, 1.0F); + float lowPressure = Mth.clamp((1013.25F - state.getPressure()) / 50.0F, 0.0F, 1.0F); + float chance = Mth.clamp(ForecastOrchestrator.getCurrentStormChance(key, tick), 0.0F, 1.0F); + WindVector dynamicWind = state.getWind(); + float gust = dynamicWind == null ? 0.0F : Mth.clamp(Math.max(dynamicWind.baseSpeed(), dynamicWind.gustSpeed()) / 34.0F, 0.0F, 1.0F); + + float severityScore = rain * 0.28F + + cloud * 0.15F + + water * 0.12F + + wind * 0.14F + + lowPressure * 0.12F + + chance * 0.12F + + gust * 0.15F; + + int derived = fromNormalized(severityScore); + return clamp(Math.max(Math.max(phaseLevel, cloudLevel), derived)); + } + + public static int sampleCloudLevel(ServerLevel level, BlockPos pos) { + int strongest = MIN_LEVEL; + for (CloudRegion region : CloudManager.get(level).getClouds()) { + double dx = region.getWorldX() - pos.getX(); + double dz = region.getWorldZ() - pos.getZ(); + double radius = region.getRadius(); + if (dx * dx + dz * dz <= radius * radius) { + strongest = Math.max(strongest, CloudLibrary.getSeverityFromRessourceLocation(region.getCloudTypeId())); + } + } + return clamp(strongest); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormShieldManager.java b/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormShieldManager.java new file mode 100644 index 00000000..8e53202e --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormShieldManager.java @@ -0,0 +1,358 @@ +package net.Gabou.projectatmosphere.modules.weather; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.event.level.BlockEvent; +import net.minecraftforge.event.level.ChunkEvent; +import net.minecraftforge.event.level.LevelEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.registries.ForgeRegistries; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.LongConsumer; + +@Mod.EventBusSubscriber(modid = ProjectAtmosphere.MODID) +public final class StormShieldManager { + public static final double PROTECTION_RADIUS = 96.0D; + + private static final ResourceLocation STORM_SHIELD_ID = + ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "storm_shield"); + + private static final Map SHIELDS_BY_LEVEL = new HashMap<>(); + + private StormShieldManager() { + } + + public static void register(Level level, BlockPos pos) { + if (level.isClientSide) { + return; + } + + ShieldIndex index = getOrCreateIndex(level); + index.add(chunkKey(pos), pos.asLong()); + } + + public static void unregister(Level level, BlockPos pos) { + if (level.isClientSide) { + return; + } + + String levelKey = level.dimension().location().toString(); + ShieldIndex index = SHIELDS_BY_LEVEL.get(levelKey); + if (index == null) { + return; + } + + index.remove(chunkKey(pos), pos.asLong()); + + if (index.isEmpty()) { + SHIELDS_BY_LEVEL.remove(levelKey); + } + } + + public static boolean isProtected(Level level, Vec3 pos) { + return getMaxProtection(level, pos, 0.0D) > 0.0D; + } + + public static boolean isProtected(Level level, BlockPos pos) { + return isProtected(level, Vec3.atCenterOf(pos)); + } + + public static Vec3 sampleAvoidance(ServerLevel level, Vec3 pos, double margin) { + ShieldIndex index = SHIELDS_BY_LEVEL.get(level.dimension().location().toString()); + if (index == null || index.isEmpty()) { + return Vec3.ZERO; + } + + double influenceRadius = PROTECTION_RADIUS + Math.max(0.0D, margin); + double influenceRadiusSq = influenceRadius * influenceRadius; + double[] accumulated = new double[3]; + + index.forEachNearby(pos, influenceRadius, packed -> { + double shieldX = BlockPos.getX(packed) + 0.5D; + double shieldY = BlockPos.getY(packed) + 0.5D; + double shieldZ = BlockPos.getZ(packed) + 0.5D; + + Vec3 away = pos.subtract(shieldX, shieldY, shieldZ); + double distSq = away.lengthSqr(); + + if (distSq > influenceRadiusSq || distSq < 1.0E-4D) { + return; + } + + double dist = Math.sqrt(distSq); + double strength = 1.0D - dist / influenceRadius; + Vec3 weighted = away.normalize().scale(strength); + + accumulated[0] += weighted.x; + accumulated[1] += weighted.y; + accumulated[2] += weighted.z; + }); + + Vec3 result = new Vec3(accumulated[0], accumulated[1], accumulated[2]); + + if (result.lengthSqr() < 1.0E-4D) { + return Vec3.ZERO; + } + + return result.normalize().scale(Math.min(result.length(), 1.0D)); + } + + public static double getMaxProtection(Level level, Vec3 pos, double margin) { + ShieldIndex index = SHIELDS_BY_LEVEL.get(level.dimension().location().toString()); + if (index == null || index.isEmpty()) { + return 0.0D; + } + + double influenceRadius = PROTECTION_RADIUS + Math.max(0.0D, margin); + double influenceRadiusSq = influenceRadius * influenceRadius; + double[] strongest = {0.0D}; + + index.forEachNearby(pos, influenceRadius, packed -> { + double shieldX = BlockPos.getX(packed) + 0.5D; + double shieldY = BlockPos.getY(packed) + 0.5D; + double shieldZ = BlockPos.getZ(packed) + 0.5D; + + double distSq = pos.distanceToSqr(shieldX, shieldY, shieldZ); + if (distSq > influenceRadiusSq) { + return; + } + + double dist = Math.sqrt(distSq); + strongest[0] = Math.max(strongest[0], 1.0D - dist / influenceRadius); + }); + + return strongest[0]; + } + + @SubscribeEvent + public static void onBlockPlaced(BlockEvent.EntityPlaceEvent event) { + if (!(event.getLevel() instanceof ServerLevel level)) { + return; + } + + if (isStormShield(event.getPlacedBlock())) { + register(level, event.getPos()); + } + } + + @SubscribeEvent + public static void onBlockBroken(BlockEvent.BreakEvent event) { + if (!(event.getLevel() instanceof ServerLevel level)) { + return; + } + + BlockState state = level.getBlockState(event.getPos()); + + if (isStormShield(state)) { + unregister(level, event.getPos()); + } + } + + @SubscribeEvent + public static void onChunkLoad(ChunkEvent.Load event) { + if (!(event.getLevel() instanceof ServerLevel level) || !(event.getChunk() instanceof LevelChunk chunk)) { + return; + } + + scanChunk(level, chunk); + } + + @SubscribeEvent + public static void onChunkUnload(ChunkEvent.Unload event) { + if (!(event.getLevel() instanceof ServerLevel level)) { + return; + } + + String levelKey = level.dimension().location().toString(); + ShieldIndex index = SHIELDS_BY_LEVEL.get(levelKey); + + if (index == null || index.isEmpty()) { + return; + } + + index.removeChunk(event.getChunk().getPos().toLong()); + + if (index.isEmpty()) { + SHIELDS_BY_LEVEL.remove(levelKey); + } + } + + @SubscribeEvent + public static void onLevelUnload(LevelEvent.Unload event) { + if (event.getLevel() instanceof Level level) { + SHIELDS_BY_LEVEL.remove(level.dimension().location().toString()); + } + } + + private static void scanChunk(ServerLevel level, LevelChunk chunk) { + Block stormShield = getStormShieldBlock(); + if (stormShield == null) { + return; + } + + ShieldIndex index = getOrCreateIndex(level); + long chunkKey = chunk.getPos().toLong(); + + LongArrayList foundPositions = new LongArrayList(2); + LevelChunkSection[] sections = chunk.getSections(); + + int minSection = level.getMinSection(); + int minX = chunk.getPos().getMinBlockX(); + int minZ = chunk.getPos().getMinBlockZ(); + + BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); + + for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) { + LevelChunkSection section = sections[sectionIndex]; + + if (section == null || section.hasOnlyAir() || !section.maybeHas(state -> state.is(stormShield))) { + continue; + } + + int sectionMinY = (minSection + sectionIndex) << 4; + + for (int localY = 0; localY < 16; localY++) { + int worldY = sectionMinY + localY; + + for (int localZ = 0; localZ < 16; localZ++) { + int worldZ = minZ + localZ; + + for (int localX = 0; localX < 16; localX++) { + if (!section.getBlockState(localX, localY, localZ).is(stormShield)) { + continue; + } + + cursor.set(minX + localX, worldY, worldZ); + foundPositions.add(cursor.asLong()); + } + } + } + } + + index.replaceChunk(chunkKey, foundPositions); + + if (index.isEmpty()) { + SHIELDS_BY_LEVEL.remove(level.dimension().location().toString()); + } + } + + private static ShieldIndex getOrCreateIndex(Level level) { + return SHIELDS_BY_LEVEL.computeIfAbsent(level.dimension().location().toString(), unused -> new ShieldIndex()); + } + + private static long chunkKey(BlockPos pos) { + return ChunkPos.asLong(pos.getX() >> 4, pos.getZ() >> 4); + } + + private static boolean isStormShield(BlockState state) { + Block stormShield = getStormShieldBlock(); + return stormShield != null && state.is(stormShield); + } + + private static Block getStormShieldBlock() { + return ForgeRegistries.BLOCKS.getValue(STORM_SHIELD_ID); + } + + private static final class ShieldIndex { + private final Long2ObjectOpenHashMap positionsByChunk = new Long2ObjectOpenHashMap<>(); + private int size; + + boolean isEmpty() { + return this.size == 0; + } + + void add(long chunkKey, long packedPos) { + LongArrayList positions = this.positionsByChunk.computeIfAbsent(chunkKey, unused -> new LongArrayList(1)); + + for (int i = 0; i < positions.size(); i++) { + if (positions.getLong(i) == packedPos) { + return; + } + } + + positions.add(packedPos); + this.size++; + } + + void remove(long chunkKey, long packedPos) { + LongArrayList positions = this.positionsByChunk.get(chunkKey); + if (positions == null) { + return; + } + + for (int i = 0; i < positions.size(); i++) { + if (positions.getLong(i) != packedPos) { + continue; + } + + positions.removeLong(i); + this.size--; + + if (positions.isEmpty()) { + this.positionsByChunk.remove(chunkKey); + } + + return; + } + } + + void replaceChunk(long chunkKey, LongArrayList newPositions) { + LongArrayList previous = this.positionsByChunk.remove(chunkKey); + + if (previous != null) { + this.size -= previous.size(); + } + + if (newPositions == null || newPositions.isEmpty()) { + return; + } + + this.positionsByChunk.put(chunkKey, newPositions); + this.size += newPositions.size(); + } + + void removeChunk(long chunkKey) { + LongArrayList removed = this.positionsByChunk.remove(chunkKey); + + if (removed != null) { + this.size -= removed.size(); + } + } + + void forEachNearby(Vec3 pos, double radius, LongConsumer consumer) { + int minChunkX = Mth.floor((pos.x - radius) / 16.0D); + int maxChunkX = Mth.floor((pos.x + radius) / 16.0D); + int minChunkZ = Mth.floor((pos.z - radius) / 16.0D); + int maxChunkZ = Mth.floor((pos.z + radius) / 16.0D); + + for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { + for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { + LongArrayList positions = this.positionsByChunk.get(ChunkPos.asLong(chunkX, chunkZ)); + + if (positions == null || positions.isEmpty()) { + continue; + } + + for (int i = 0; i < positions.size(); i++) { + consumer.accept(positions.getLong(i)); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/net/Gabou/projectatmosphere/modules/wind/WindForces.java b/src/main/java/net/Gabou/projectatmosphere/modules/wind/WindForces.java index 235082be..74ddff1a 100644 --- a/src/main/java/net/Gabou/projectatmosphere/modules/wind/WindForces.java +++ b/src/main/java/net/Gabou/projectatmosphere/modules/wind/WindForces.java @@ -29,11 +29,6 @@ public static void applyToPlayer(ServerLevel level, ServerPlayer player, float d return; } applyPlayerGusts(level, player); - - TornadoWindModel.TornadoForces tornado = WindEngine.getCurrentTornadoForce(player.position()); - if (tornado != null) { - applyTornadoForce(player, tornado, deltaTime); - } } public static void applyToEntity(ServerLevel level, LivingEntity entity, float deltaTime) { @@ -44,14 +39,6 @@ public static void applyToEntity(ServerLevel level, LivingEntity entity, float d ENTITY_MAX_DRIFT_BPT, deltaTime, true); } - private static void applyTornadoForce(LivingEntity entity, TornadoWindModel.TornadoForces forces, float deltaTime) { - double scale = deltaTime / 20f; - Vec3 combined = forces.pullVector().add(forces.rotationVector()).scale(scale); - Vec3 lift = forces.liftVector().scale(scale); - entity.push(combined.x, lift.y, combined.z); - entity.hurtMarked = true; - } - private static void applyWindSteering(ServerLevel level, LivingEntity entity, float thresholdMps, float weightDiff, float multiplier, float maxDriftBpt, float deltaTime, boolean allowGusts) { if (weightDiff <= 0f || multiplier <= 0f || maxDriftBpt <= 0f) { diff --git a/src/main/java/net/Gabou/projectatmosphere/network/AuthChallengePacket.java b/src/main/java/net/Gabou/projectatmosphere/network/AuthChallengePacket.java new file mode 100644 index 00000000..0f457a48 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/network/AuthChallengePacket.java @@ -0,0 +1,39 @@ +package net.Gabou.projectatmosphere.network; + +import net.Gabou.projectatmosphere.auth.ClientAuth; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class AuthChallengePacket { + private final long nonce; + + public AuthChallengePacket(long nonce) { + this.nonce = nonce; + } + + public AuthChallengePacket(FriendlyByteBuf buf) { + this.nonce = buf.readLong(); + } + + public long nonce() { + return nonce; + } + + public void encode(FriendlyByteBuf buf) { + buf.writeLong(nonce); + } + + public static AuthChallengePacket decode(FriendlyByteBuf buf) { + return new AuthChallengePacket(buf); + } + + public static void handle(AuthChallengePacket msg, Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ClientAuth.handleChallenge(msg))); + context.setPacketHandled(true); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/network/AuthChallengeReplyPacket.java b/src/main/java/net/Gabou/projectatmosphere/network/AuthChallengeReplyPacket.java new file mode 100644 index 00000000..75d9ce2d --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/network/AuthChallengeReplyPacket.java @@ -0,0 +1,59 @@ +package net.Gabou.projectatmosphere.network; + +import net.Gabou.projectatmosphere.auth.ServerAuth; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class AuthChallengeReplyPacket { + private final long nonce; + private final String response; + private final String launcherReason; + + public AuthChallengeReplyPacket(long nonce, String response, String launcherReason) { + this.nonce = nonce; + this.response = response == null ? "" : response; + this.launcherReason = launcherReason == null ? "" : launcherReason; + } + + public AuthChallengeReplyPacket(FriendlyByteBuf buf) { + this.nonce = buf.readLong(); + this.response = buf.readUtf(256); + this.launcherReason = buf.readUtf(256); + } + + public long nonce() { + return nonce; + } + + public String response() { + return response; + } + + public String launcherReason() { + return launcherReason; + } + + public void encode(FriendlyByteBuf buf) { + buf.writeLong(nonce); + buf.writeUtf(response, 256); + buf.writeUtf(launcherReason, 256); + } + + public static AuthChallengeReplyPacket decode(FriendlyByteBuf buf) { + return new AuthChallengeReplyPacket(buf); + } + + public static void handle(AuthChallengeReplyPacket msg, Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> { + ServerPlayer player = context.getSender(); + if (player != null) { + ServerAuth.handleChallengeReply(player, msg); + } + }); + context.setPacketHandled(true); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/network/FogDebugOverridePacket.java b/src/main/java/net/Gabou/projectatmosphere/network/FogDebugOverridePacket.java new file mode 100644 index 00000000..3d0c7570 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/network/FogDebugOverridePacket.java @@ -0,0 +1,40 @@ +package net.Gabou.projectatmosphere.network; + +import net.Gabou.projectatmosphere.client.ClientPacketHandlers; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class FogDebugOverridePacket { + private final float strength; + private final int durationTicks; + + public FogDebugOverridePacket(float strength, int durationTicks) { + this.strength = strength; + this.durationTicks = durationTicks; + } + + public FogDebugOverridePacket(FriendlyByteBuf buf) { + this.strength = buf.readFloat(); + this.durationTicks = buf.readVarInt(); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeFloat(this.strength); + buf.writeVarInt(this.durationTicks); + } + + public static FogDebugOverridePacket decode(FriendlyByteBuf buf) { + return new FogDebugOverridePacket(buf); + } + + public static void handle(FogDebugOverridePacket msg, Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> + ClientPacketHandlers.handleFogDebugOverride(msg.strength, msg.durationTicks))); + context.setPacketHandled(true); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/network/NetworkHandler.java b/src/main/java/net/Gabou/projectatmosphere/network/NetworkHandler.java index 86a69e37..a843eed4 100644 --- a/src/main/java/net/Gabou/projectatmosphere/network/NetworkHandler.java +++ b/src/main/java/net/Gabou/projectatmosphere/network/NetworkHandler.java @@ -8,7 +8,7 @@ import net.minecraftforge.network.simple.SimpleChannel; public class NetworkHandler { - private static final String PROTOCOL_VERSION = "1"; + private static final String PROTOCOL_VERSION = "6"; public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "main"), () -> PROTOCOL_VERSION, @@ -24,6 +24,16 @@ public static void init() { .encoder(SpawnTornadoPacket::encode) .consumerMainThread(SpawnTornadoPacket::handle) .add(); + CHANNEL.messageBuilder(RemoveTornadoPacket.class, id++, NetworkDirection.PLAY_TO_CLIENT) + .decoder(RemoveTornadoPacket::decode) + .encoder(RemoveTornadoPacket::encode) + .consumerMainThread(RemoveTornadoPacket::handle) + .add(); + CHANNEL.messageBuilder(SyncTornadoesPacket.class, id++, NetworkDirection.PLAY_TO_CLIENT) + .decoder(SyncTornadoesPacket::decode) + .encoder(SyncTornadoesPacket::encode) + .consumerMainThread(SyncTornadoesPacket::handle) + .add(); CHANNEL.messageBuilder(SyncWindPacket.class, id++, NetworkDirection.PLAY_TO_CLIENT) .decoder(SyncWindPacket::decode) .encoder(SyncWindPacket::encode) @@ -39,18 +49,36 @@ public static void init() { .encoder(ForecastLoadingStatusPacket::encode) .consumerMainThread(ForecastLoadingStatusPacket::handle) .add(); - CHANNEL.messageBuilder(RainfallUpdatePacket.class, id++, NetworkDirection.PLAY_TO_CLIENT) - .decoder(RainfallUpdatePacket::decode) - .encoder(RainfallUpdatePacket::encode) - .consumerMainThread(RainfallUpdatePacket::handle) + CHANNEL.messageBuilder(SyncAtmosphereStatusPacket.class, id++, NetworkDirection.PLAY_TO_CLIENT) + .decoder(SyncAtmosphereStatusPacket::decode) + .encoder(SyncAtmosphereStatusPacket::encode) + .consumerMainThread(SyncAtmosphereStatusPacket::handle) + .add(); + CHANNEL.messageBuilder(FogDebugOverridePacket.class, id++, NetworkDirection.PLAY_TO_CLIENT) + .decoder(FogDebugOverridePacket::decode) + .encoder(FogDebugOverridePacket::encode) + .consumerMainThread(FogDebugOverridePacket::handle) .add(); CHANNEL.messageBuilder(InstrumentReadoutPacket.class, id++, NetworkDirection.PLAY_TO_CLIENT) .decoder(InstrumentReadoutPacket::decode) .encoder(InstrumentReadoutPacket::encode) .consumerMainThread(InstrumentReadoutPacket::handle) .add(); - - + CHANNEL.messageBuilder(SyncHurricaneStatePacket.class, id++, NetworkDirection.PLAY_TO_CLIENT) + .decoder(SyncHurricaneStatePacket::decode) + .encoder(SyncHurricaneStatePacket::encode) + .consumerMainThread(SyncHurricaneStatePacket::handle) + .add(); + CHANNEL.messageBuilder(AuthChallengePacket.class, id++, NetworkDirection.PLAY_TO_CLIENT) + .decoder(AuthChallengePacket::decode) + .encoder(AuthChallengePacket::encode) + .consumerMainThread(AuthChallengePacket::handle) + .add(); + CHANNEL.messageBuilder(AuthChallengeReplyPacket.class, id++, NetworkDirection.PLAY_TO_SERVER) + .decoder(AuthChallengeReplyPacket::decode) + .encoder(AuthChallengeReplyPacket::encode) + .consumerMainThread(AuthChallengeReplyPacket::handle) + .add(); } } diff --git a/src/main/java/net/Gabou/projectatmosphere/network/RainfallUpdatePacket.java b/src/main/java/net/Gabou/projectatmosphere/network/RainfallUpdatePacket.java deleted file mode 100644 index 3dc2411e..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/network/RainfallUpdatePacket.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.Gabou.projectatmosphere.network; - -import net.Gabou.projectatmosphere.client.ClientPacketHandlers; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.DistExecutor; -import net.minecraftforge.network.NetworkEvent; - -import java.util.function.Supplier; - -public class RainfallUpdatePacket { - private final ResourceLocation dimensionId; - private final float rainLevel; - - public RainfallUpdatePacket(ResourceLocation dimensionId, float rainLevel) { - this.dimensionId = dimensionId; - this.rainLevel = rainLevel; - } - - public RainfallUpdatePacket(FriendlyByteBuf buf) { - this.dimensionId = buf.readResourceLocation(); - this.rainLevel = buf.readFloat(); - } - - public void encode(FriendlyByteBuf buf) { - buf.writeResourceLocation(dimensionId); - buf.writeFloat(rainLevel); - } - - public static RainfallUpdatePacket decode(FriendlyByteBuf buf) { - return new RainfallUpdatePacket(buf); - } - - public static void handle(RainfallUpdatePacket msg, Supplier ctx) { - NetworkEvent.Context context = ctx.get(); - context.enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> - ClientPacketHandlers.handleRainfallUpdate(msg.dimensionId, msg.rainLevel))); - context.setPacketHandled(true); - } -} diff --git a/src/main/java/net/Gabou/projectatmosphere/network/RemoveTornadoPacket.java b/src/main/java/net/Gabou/projectatmosphere/network/RemoveTornadoPacket.java new file mode 100644 index 00000000..df21e03b --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/network/RemoveTornadoPacket.java @@ -0,0 +1,34 @@ +package net.Gabou.projectatmosphere.network; +import net.Gabou.projectatmosphere.modules.tornado.TornadoManager; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +import java.util.UUID; +import java.util.function.Supplier; + +public class RemoveTornadoPacket { + private final UUID id; + + public RemoveTornadoPacket(UUID id) { + this.id = id; + } + + public RemoveTornadoPacket(FriendlyByteBuf buf) { + this.id = buf.readUUID(); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeUUID(this.id); + } + + public static RemoveTornadoPacket decode(FriendlyByteBuf buf) { + return new RemoveTornadoPacket(buf); + } + + public void handle(Supplier ctx) { + ctx.get().enqueueWork(() -> { + TornadoManager.removeClientTornado(this.id); + }); + ctx.get().setPacketHandled(true); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/network/SpawnTornadoPacket.java b/src/main/java/net/Gabou/projectatmosphere/network/SpawnTornadoPacket.java index 791fe2f6..d0023aa9 100644 --- a/src/main/java/net/Gabou/projectatmosphere/network/SpawnTornadoPacket.java +++ b/src/main/java/net/Gabou/projectatmosphere/network/SpawnTornadoPacket.java @@ -1,41 +1,53 @@ package net.Gabou.projectatmosphere.network; - import net.Gabou.projectatmosphere.modules.core.WindVector; import net.Gabou.projectatmosphere.modules.tornado.TornadoManager; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.phys.Vec3; import net.minecraftforge.network.NetworkEvent; +import java.util.UUID; import java.util.function.Supplier; public class SpawnTornadoPacket { + private final UUID id; private final Vec3 pos; private final float radius; + private final float bottomY; + private final float height; private final float speed; private final float angle; private final float gust; - public SpawnTornadoPacket(Vec3 pos, float radius, WindVector wind) { + public SpawnTornadoPacket(UUID id, Vec3 pos, float radius, WindVector wind, float bottomY, float height) { + this.id = id; this.pos = pos; this.radius = radius; + this.bottomY = bottomY; + this.height = height; this.speed = wind.baseSpeed(); this.angle = wind.angleRadians(); this.gust = wind.gustSpeed(); } public SpawnTornadoPacket(FriendlyByteBuf buf) { + this.id = buf.readUUID(); this.pos = new Vec3(buf.readDouble(), buf.readDouble(), buf.readDouble()); this.radius = buf.readFloat(); + this.bottomY = buf.readFloat(); + this.height = buf.readFloat(); this.speed = buf.readFloat(); this.angle = buf.readFloat(); this.gust = buf.readFloat(); } public void encode(FriendlyByteBuf buf) { + buf.writeUUID(this.id); buf.writeDouble(pos.x); buf.writeDouble(pos.y); buf.writeDouble(pos.z); buf.writeFloat(radius); + buf.writeFloat(bottomY); + buf.writeFloat(height); buf.writeFloat(speed); buf.writeFloat(angle); buf.writeFloat(gust); @@ -47,7 +59,7 @@ public static SpawnTornadoPacket decode(FriendlyByteBuf buf) { public void handle(Supplier ctx) { ctx.get().enqueueWork(() -> { - TornadoManager.spawnClient(pos, radius, new WindVector(speed, angle, gust)); + TornadoManager.spawnClient(id, pos, radius, new WindVector(speed, angle, gust), bottomY, height); }); ctx.get().setPacketHandled(true); } diff --git a/src/main/java/net/Gabou/projectatmosphere/network/SyncAtmosphereStatusPacket.java b/src/main/java/net/Gabou/projectatmosphere/network/SyncAtmosphereStatusPacket.java new file mode 100644 index 00000000..a2338e8c --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/network/SyncAtmosphereStatusPacket.java @@ -0,0 +1,48 @@ +package net.Gabou.projectatmosphere.network; + +import net.Gabou.projectatmosphere.client.ClientPacketHandlers; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class SyncAtmosphereStatusPacket { + private final float humidityPercent; + private final float rainIntensity; + private final float cloudCover; + + public SyncAtmosphereStatusPacket(float humidityPercent, float rainIntensity, float cloudCover) { + this.humidityPercent = humidityPercent; + this.rainIntensity = rainIntensity; + this.cloudCover = cloudCover; + } + + public SyncAtmosphereStatusPacket(FriendlyByteBuf buf) { + this.humidityPercent = buf.readFloat(); + this.rainIntensity = buf.readFloat(); + this.cloudCover = buf.readFloat(); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeFloat(this.humidityPercent); + buf.writeFloat(this.rainIntensity); + buf.writeFloat(this.cloudCover); + } + + public static SyncAtmosphereStatusPacket decode(FriendlyByteBuf buf) { + return new SyncAtmosphereStatusPacket(buf); + } + + public static void handle(SyncAtmosphereStatusPacket msg, Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> + ClientPacketHandlers.handleAtmosphereStatusUpdate( + msg.humidityPercent, + msg.rainIntensity, + msg.cloudCover + ))); + context.setPacketHandled(true); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/network/SyncHurricaneStatePacket.java b/src/main/java/net/Gabou/projectatmosphere/network/SyncHurricaneStatePacket.java new file mode 100644 index 00000000..6ea76127 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/network/SyncHurricaneStatePacket.java @@ -0,0 +1,47 @@ +package net.Gabou.projectatmosphere.network; + +import net.Gabou.projectatmosphere.client.hurricane.ClientHurricaneStateCache; +import net.Gabou.projectatmosphere.modules.hurricane.HurricaneRenderSnapshot; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.network.NetworkEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +public class SyncHurricaneStatePacket { + private final List snapshots; + + public SyncHurricaneStatePacket(List snapshots) { + this.snapshots = List.copyOf(snapshots); + } + + public SyncHurricaneStatePacket(FriendlyByteBuf buf) { + int count = buf.readVarInt(); + List decoded = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + decoded.add(HurricaneRenderSnapshot.decode(buf)); + } + this.snapshots = List.copyOf(decoded); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeVarInt(this.snapshots.size()); + for (HurricaneRenderSnapshot snapshot : this.snapshots) { + snapshot.encode(buf); + } + } + + public static SyncHurricaneStatePacket decode(FriendlyByteBuf buf) { + return new SyncHurricaneStatePacket(buf); + } + + public void handle(Supplier ctx) { + ctx.get().enqueueWork(() -> + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ClientHurricaneStateCache.applySnapshots(this.snapshots)) + ); + ctx.get().setPacketHandled(true); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/network/SyncTornadoesPacket.java b/src/main/java/net/Gabou/projectatmosphere/network/SyncTornadoesPacket.java new file mode 100644 index 00000000..eeeecf4e --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/network/SyncTornadoesPacket.java @@ -0,0 +1,43 @@ +package net.Gabou.projectatmosphere.network; + +import net.Gabou.projectatmosphere.modules.tornado.TornadoManager; +import net.Gabou.projectatmosphere.modules.tornado.TornadoSnapshot; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +public class SyncTornadoesPacket { + private final List snapshots; + + public SyncTornadoesPacket(List snapshots) { + this.snapshots = List.copyOf(snapshots); + } + + public SyncTornadoesPacket(FriendlyByteBuf buf) { + int count = buf.readVarInt(); + List read = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + read.add(TornadoSnapshot.read(buf)); + } + this.snapshots = read; + } + + public void encode(FriendlyByteBuf buf) { + buf.writeVarInt(this.snapshots.size()); + for (TornadoSnapshot snapshot : this.snapshots) { + snapshot.write(buf); + } + } + + public static SyncTornadoesPacket decode(FriendlyByteBuf buf) { + return new SyncTornadoesPacket(buf); + } + + public void handle(Supplier ctx) { + ctx.get().enqueueWork(() -> TornadoManager.applyClientSnapshots(this.snapshots)); + ctx.get().setPacketHandled(true); + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticle.java b/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticle.java index 61ad8c4e..0a768918 100644 --- a/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticle.java +++ b/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticle.java @@ -18,19 +18,40 @@ public class DebrisParticle extends TextureSheetParticle { private final double radius; private final double baseY; private final float angularSpeed; + private final float verticalDrift; + private final float radialJitter; + private final int band; private final float startAngle; - protected DebrisParticle(ClientLevel level, TornadoInstance tornado, double radius, double height, float angularSpeed) { - super(level, tornado.position.x, tornado.position.y + height, tornado.position.z, 0, 0, 0); + protected DebrisParticle(ClientLevel level, TornadoInstance tornado, double radius, double height, float angularSpeed, + float verticalDrift, float radialJitter, int band) { + super(level, tornado.getRenderPosition(1.0F).x, tornado.getRenderBottomY(1.0F) + height, tornado.getRenderPosition(1.0F).z, 0, 0, 0); this.tornadoRef = new WeakReference<>(tornado); this.radius = radius; this.baseY = height; this.angularSpeed = angularSpeed; + this.verticalDrift = verticalDrift; + this.radialJitter = radialJitter; + this.band = band; this.startAngle = level.random.nextFloat() * 360f; - this.lifetime = 40 + this.random.nextInt(20); + this.lifetime = switch (band) { + case 0 -> 34 + this.random.nextInt(16); + case 1 -> 42 + this.random.nextInt(18); + default -> 48 + this.random.nextInt(24); + }; this.gravity = 0; - this.friction = 0.95f; - this.setSize(0.2f, 0.2f); + this.friction = 0.94f; + float size = switch (band) { + case 0 -> 0.18f; + case 1 -> 0.14f; + default -> 0.24f; + }; + this.setSize(size, size); + this.alpha = switch (band) { + case 0 -> 0.85f; + case 1 -> 0.72f; + default -> 0.58f; + }; } @Override @@ -40,14 +61,42 @@ public void tick() { remove(); return; } - float angle = startAngle + (tornado.getLifetimeSeconds() * 20 + this.age) * angularSpeed; + var renderPos = tornado.getRenderPosition(1.0F); + float renderBottomY = tornado.getRenderBottomY(1.0F); + float lifeProgress = this.lifetime <= 0 ? 1.0F : (float) this.age / (float) this.lifetime; + float bandSpinBoost = switch (this.band) { + case 0 -> 1.25F; + case 1 -> 2.20F; + default -> 1.05F; + }; + float angle = startAngle + + tornado.getTwist() * 96.0F * bandSpinBoost + + (tornado.getLifetimeSeconds() * 20 + this.age) * angularSpeed; double rad = Math.toRadians(angle); + double rise = this.baseY + this.age * this.verticalDrift + switch (this.band) { + case 0 -> Math.sin((this.age + this.startAngle) * 0.10F) * 0.24D; + case 1 -> lifeProgress * 1.8D; + default -> lifeProgress * 2.6D; + }; + double pulse = Math.sin((this.age + this.startAngle) * 0.12F) * this.radialJitter + + Math.cos((this.age + this.startAngle) * 0.05F + this.band) * this.radialJitter * 0.45D; + double spiralOffset = switch (this.band) { + case 0 -> -lifeProgress * this.radius * 0.10D; + case 1 -> -lifeProgress * this.radius * 0.34D; + default -> lifeProgress * this.radius * 0.10D; + }; + double orbitRadius = Math.max(0.2D, this.radius + pulse + spiralOffset); + this.alpha = switch (this.band) { + case 0 -> 0.85F - lifeProgress * 0.35F; + case 1 -> 0.74F - lifeProgress * 0.28F; + default -> 0.60F - lifeProgress * 0.22F; + }; setPos( - tornado.position.x + Math.cos(rad) * radius, - tornado.position.y + baseY, - tornado.position.z + Math.sin(rad) * radius + renderPos.x + Math.cos(rad) * orbitRadius, + renderBottomY + rise, + renderPos.z + Math.sin(rad) * orbitRadius ); - this.yd += 0.02; + this.yd += this.verticalDrift * 0.4; super.tick(); } @@ -65,7 +114,16 @@ public Provider(SpriteSet sprites) { @Override public Particle createParticle(DebrisParticleData data, ClientLevel level, double x, double y, double z, double vx, double vy, double vz) { - DebrisParticle particle = new DebrisParticle(level, data.tornado(), data.radius(), data.height(), data.angularSpeed()); + DebrisParticle particle = new DebrisParticle( + level, + data.tornado(), + data.radius(), + data.height(), + data.angularSpeed(), + data.verticalDrift(), + data.radialJitter(), + data.band() + ); try { particle.pickSprite(sprites); } catch (Throwable ignored) { diff --git a/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticleData.java b/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticleData.java index e583f19e..60d76fa1 100644 --- a/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticleData.java +++ b/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticleData.java @@ -12,8 +12,9 @@ * Particle data for {@link DebrisParticle}. * This implementation is client-only and does not support network or command serialization. */ -public record DebrisParticleData(TornadoInstance tornado, double radius, double height, float angularSpeed) implements ParticleOptions { - public static final Codec CODEC = Codec.unit(new DebrisParticleData(null, 0, 0, 0)); +public record DebrisParticleData(TornadoInstance tornado, double radius, double height, float angularSpeed, + float verticalDrift, float radialJitter, int band) implements ParticleOptions { + public static final Codec CODEC = Codec.unit(new DebrisParticleData(null, 0, 0, 0, 0, 0, 0)); public static final Deserializer DESERIALIZER = new Deserializer<>() { @Override diff --git a/src/main/java/net/Gabou/projectatmosphere/registry/ClientOnlyRegistrar.java b/src/main/java/net/Gabou/projectatmosphere/registry/ClientOnlyRegistrar.java index 757f4ec5..9a47d2ff 100644 --- a/src/main/java/net/Gabou/projectatmosphere/registry/ClientOnlyRegistrar.java +++ b/src/main/java/net/Gabou/projectatmosphere/registry/ClientOnlyRegistrar.java @@ -2,6 +2,7 @@ import net.Gabou.projectatmosphere.client.ClientTickHandler; import net.Gabou.projectatmosphere.compat.CompatHandler; +import net.Gabou.projectatmosphere.compat.auroras.AuroraCompatController; import net.Gabou.projectatmosphere.compat.rainbows.RainbowWeatherTracker; import net.Gabou.projectatmosphere.config.AtmoConfigScreen; import net.minecraftforge.api.distmarker.Dist; @@ -20,5 +21,6 @@ public static void registerClient(IEventBus modEventBus, FMLJavaModLoadingContex context.registerExtensionPoint(ConfigScreenHandler.ConfigScreenFactory.class, () -> new ConfigScreenHandler.ConfigScreenFactory((mc, screen) -> new AtmoConfigScreen(screen))); RainbowWeatherTracker.setEnabled(CompatHandler.isRainbowsLoaded()); + AuroraCompatController.setEnabled(CompatHandler.isAurorasLoaded()); } } diff --git a/src/main/java/net/Gabou/projectatmosphere/registry/ModBlocks.java b/src/main/java/net/Gabou/projectatmosphere/registry/ModBlocks.java index b48bd81d..4c6be089 100644 --- a/src/main/java/net/Gabou/projectatmosphere/registry/ModBlocks.java +++ b/src/main/java/net/Gabou/projectatmosphere/registry/ModBlocks.java @@ -86,4 +86,12 @@ public class ModBlocks { .noOcclusion() )); + public static final RegistryObject STORM_SHIELD = REGISTRY.register("storm_shield", () -> + new StormShieldBlock(BlockBehaviour.Properties + .of() + .strength(3.0f) + .sound(SoundType.METAL) + .noOcclusion() + )); + } diff --git a/src/main/java/net/Gabou/projectatmosphere/registry/ModItems.java b/src/main/java/net/Gabou/projectatmosphere/registry/ModItems.java index da52741b..0df9a514 100644 --- a/src/main/java/net/Gabou/projectatmosphere/registry/ModItems.java +++ b/src/main/java/net/Gabou/projectatmosphere/registry/ModItems.java @@ -97,6 +97,7 @@ public static void register(IEventBus eventBus) { public static final RegistryObject SAND_LAYER = blockUtilities(ModBlocks.SAND_LAYER); public static final RegistryObject STORM_SIREN = blockUtilities(ModBlocks.STORM_SIREN); + public static final RegistryObject STORM_SHIELD = blockUtilities(ModBlocks.STORM_SHIELD); private static RegistryObject blockUtilities(RegistryObject block) { return ITEMS.register(block.getId().getPath(), () -> new BlockItem(block.get(), new Item.Properties())); diff --git a/src/main/java/net/Gabou/projectatmosphere/render/HurricaneMeshRenderer.java b/src/main/java/net/Gabou/projectatmosphere/render/HurricaneMeshRenderer.java deleted file mode 100644 index edffb093..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/render/HurricaneMeshRenderer.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.Gabou.projectatmosphere.render; - -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.BufferBuilder; -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.Tesselator; -import com.mojang.blaze3d.vertex.BufferUploader; -import com.mojang.blaze3d.vertex.VertexFormat; -import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.client.renderer.ShaderInstance; -import org.joml.Matrix4f; - -public final class HurricaneMeshRenderer { - private HurricaneMeshRenderer() {} - - /** Draw a flat eye-wall ring in Simple Clouds' cloud space. */ - public static void renderCloudSpace(SimpleCloudsRenderer sc, PoseStack pose, - Matrix4f proj, float partialTick, - double camX, double camZ) { - var mc = Minecraft.getInstance(); - if (mc.level == null) { - return; - } - - var state = HurricaneStateProvider.getActive(camX, camZ); - if (state == null) { - return; - } - - // Center & radii are expected in *cloud space* units - float cx = (float) state.centerXCloudSpace(); - float cz = (float) state.centerZCloudSpace(); - float inner = (float) state.eyeRadiusCloudSpace(); - float outer = inner + (float) state.eyewallFadeCloudSpace(); - - // Match fog/projection with SC - RenderSystem.setShader(GameRenderer::getPositionShader); - ShaderInstance shader = RenderSystem.getShader(); - SimpleCloudsRenderer.prepareShader(shader, pose.last().pose(), proj, sc.getFogStart(), sc.getFogEnd()); - shader.apply(); - - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.disableCull(); - - Tesselator t = Tesselator.getInstance(); - BufferBuilder buf = t.getBuilder(); - buf.begin(VertexFormat.Mode.TRIANGLE_STRIP, DefaultVertexFormat.POSITION); - - final int segs = 160; - final float y = 0.02f; // slight lift to avoid z-fighting - final float twoPi = (float)(Math.PI * 2.0); - Matrix4f mv = pose.last().pose(); - - for (int i = 0; i <= segs; i++) { - float a = twoPi * i / segs; - float cs = (float)Math.cos(a), sn = (float)Math.sin(a); - buf.vertex(mv, cx + cs * inner, y, cz + sn * inner).endVertex(); - buf.vertex(mv, cx + cs * outer, y, cz + sn * outer).endVertex(); - } - - BufferUploader.drawWithShader(buf.end()); - - RenderSystem.enableCull(); - RenderSystem.disableBlend(); - shader.clear(); - } -} - diff --git a/src/main/java/net/Gabou/projectatmosphere/render/HurricaneStateProvider.java b/src/main/java/net/Gabou/projectatmosphere/render/HurricaneStateProvider.java deleted file mode 100644 index a872e0ac..00000000 --- a/src/main/java/net/Gabou/projectatmosphere/render/HurricaneStateProvider.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.Gabou.projectatmosphere.render; - -import net.Gabou.projectatmosphere.compat.SimpleCloudsCompat; -import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; - -public final class HurricaneStateProvider { - private HurricaneStateProvider() {} - - /** - * Return active hurricane, or {@code null}. Coordinates are converted from world - * space to Simple Clouds' cloud space by subtracting the camera position and - * dividing by the cloud scale. - */ - public static HurricaneStateCloudSpace getActive(double camX, double camZ) { - var s = ForecastOrchestrator.getActiveHurricane(); - if (s == null) { - return null; - } - - double scale = SimpleCloudsCompat.getCloudScale(); - return new HurricaneStateCloudSpace( - (s.centerX() - camX) / scale, - (s.centerZ() - camZ) / scale, - s.eyeRadius() / scale, - s.eyewallFade() / scale - ); - } - - public record HurricaneStateCloudSpace( - double centerXCloudSpace, - double centerZCloudSpace, - double eyeRadiusCloudSpace, - double eyewallFadeCloudSpace - ) {} -} - diff --git a/src/main/java/net/Gabou/projectatmosphere/seasons/EclipticSeasonsSeasonDelegate.java b/src/main/java/net/Gabou/projectatmosphere/seasons/EclipticSeasonsSeasonDelegate.java index 6e57ca15..071eb810 100644 --- a/src/main/java/net/Gabou/projectatmosphere/seasons/EclipticSeasonsSeasonDelegate.java +++ b/src/main/java/net/Gabou/projectatmosphere/seasons/EclipticSeasonsSeasonDelegate.java @@ -1,23 +1,26 @@ package net.Gabou.projectatmosphere.seasons; -import com.teamtea.eclipticseasons.api.event.SolarTermChangeEvent; import com.teamtea.eclipticseasons.common.core.SolarHolders; import com.teamtea.eclipticseasons.common.core.solar.SolarDataManager; -import com.teamtea.eclipticseasons.api.constant.solar.SolarTerm; import net.Gabou.projectatmosphere.event.EclipticTracker; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; import net.minecraftforge.common.MinecraftForge; public class EclipticSeasonsSeasonDelegate implements SeasonTimeDelegate { - + private static final String PROVIDER_ID = "eclipticseasons"; + private static final long DEFAULT_DAYS_PER_TERM = 5L; public EclipticSeasonsSeasonDelegate() { MinecraftForge.EVENT_BUS.addListener(EclipticTracker::onSolarTermChange); } private static final int TERMS_PER_SEASON = 6; - private static final int TOTAL_TERMS = 24; + + @Override + public String providerId() { + return PROVIDER_ID; + } @Override public SeasonSnapshot snapshot(Level level) { @@ -30,7 +33,6 @@ public SeasonSnapshot snapshot(Level level) { return SeasonSnapshot.neutral(); } - SolarTerm term = data.getSolarTerm(); int termIndex = data.getSolarTermIndex(); // 0..23 int daysPerTerm = data.getSolarTermLastingDays(); @@ -64,27 +66,29 @@ public SeasonSnapshot snapshot(Level level) { @Override public long seasonCycleTicks(Level level) { if (level == null) { - return 24000L * 30 * 4; + return 0L; } SolarDataManager data = SolarHolders.getSaveData(level); if (data == null) { - return 24000L * 30 * 4; + return 0L; } - long daysPerSeason = (long) data.getSolarTermLastingDays() * TERMS_PER_SEASON; - return daysPerSeason * 24000L; + long dayInCycle = (long) data.getSolarTermIndex() * data.getSolarTermLastingDays() + + Math.max(0, data.getSolarTermDaysInPeriod()); + long dayTime = Math.floorMod(level.getDayTime(), 24000L); + return dayInCycle * 24000L + dayTime; } @Override public long seasonDuration(Level level) { if (level == null) { - return 24000L * 30; + return DEFAULT_DAYS_PER_TERM * TERMS_PER_SEASON * 24000L; } SolarDataManager data = SolarHolders.getSaveData(level); if (data == null) { - return 24000L * 30; + return DEFAULT_DAYS_PER_TERM * TERMS_PER_SEASON * 24000L; } return (long) data.getSolarTermLastingDays() * TERMS_PER_SEASON * 24000L; diff --git a/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonBootstrap.java b/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonBootstrap.java index 61047413..cd6630f5 100644 --- a/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonBootstrap.java +++ b/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonBootstrap.java @@ -1,7 +1,5 @@ package net.Gabou.projectatmosphere.seasons; -import com.teamtea.eclipticseasons.api.EclipticSeasonsApi; -import com.teamtea.eclipticseasons.api.util.EclipticUtil; import net.Gabou.projectatmospherefortfc.seasons.TfcSeasonDelegate; import net.minecraftforge.fml.ModList; import org.apache.logging.log4j.LogManager; @@ -22,6 +20,11 @@ private SeasonBootstrap() { public static void initOrCrash() { ModList mods = ModList.get(); + if (mods.isLoaded(ECLIPTIC_ID)) { + LOGGER.info("Detected Ecliptic Seasons; using EclipticSeasonsSeasonDelegate."); + SeasonTimeHelper.setDelegate(new EclipticSeasonsSeasonDelegate()); + return; + } if (mods.isLoaded(SERENE_ID)) { LOGGER.info("Detected Serene Seasons; using SereneSeasonsSeasonDelegate."); SeasonTimeHelper.setDelegate(new SereneSeasonsSeasonDelegate()); @@ -32,11 +35,6 @@ public static void initOrCrash() { SeasonTimeHelper.setDelegate(new TfcSeasonDelegate()); return; } - if(mods.isLoaded(ECLIPTIC_ID)) { - LOGGER.info("Detected Ecliptic Seasons; using EclipticSeasonsSeasonDelegate."); - SeasonTimeHelper.setDelegate(new EclipticSeasonsSeasonDelegate()); - return; - } throw new IllegalStateException(""" Project Atmosphere requires a season provider. Install Serene Seasons or ProjectAtmosphereForTFC or Ecliptic Seasons (or remove Project Atmosphere). """); diff --git a/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonTimeDelegate.java b/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonTimeDelegate.java index e01bb7d3..39e8707d 100644 --- a/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonTimeDelegate.java +++ b/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonTimeDelegate.java @@ -1,8 +1,14 @@ package net.Gabou.projectatmosphere.seasons; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; public interface SeasonTimeDelegate { + default String providerId() { + return getClass().getName(); + } + SeasonSnapshot snapshot(Level level); long seasonCycleTicks(Level level); @@ -10,4 +16,10 @@ public interface SeasonTimeDelegate { long seasonDuration(Level level); long dayDuration(Level level); + + default void onRainStarted(ServerLevel level, CloudRegion cloudRegion) { + } + + default void onRainEnded(ServerLevel level, CloudRegion cloudRegion) { + } } diff --git a/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonTimeHelper.java b/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonTimeHelper.java index a47e783c..84c0b77f 100644 --- a/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonTimeHelper.java +++ b/src/main/java/net/Gabou/projectatmosphere/seasons/SeasonTimeHelper.java @@ -1,6 +1,8 @@ package net.Gabou.projectatmosphere.seasons; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -15,27 +17,17 @@ public final class SeasonTimeHelper { private static final AtomicReference DELEGATE = new AtomicReference<>(new NeutralDelegate()); - private static boolean sereneSeasonsPresent = false; - private SeasonTimeHelper() { } - public static boolean isSereneSeasonsPresent() { - return sereneSeasonsPresent; - } - public static void setDelegate(SeasonTimeDelegate delegate) { if (delegate == null) return; - try { - if (delegate instanceof SereneSeasonsSeasonDelegate) { - sereneSeasonsPresent = true; - } - } - catch (Exception e) { - LOGGER.warn("Failed to detect Serene Seasons presence.", e); - } DELEGATE.set(delegate); - LOGGER.info("Season time delegate set to '{}'.", delegate.getClass().getName()); + LOGGER.info("Season time delegate set to '{}' ({}).", delegate.providerId(), delegate.getClass().getName()); + } + + public static String providerId() { + return DELEGATE.get().providerId(); } public static SeasonSnapshot snapshot(Level level) { @@ -75,12 +67,33 @@ public static long dayDuration(Level level) { } } + public static void onRainStarted(ServerLevel level, CloudRegion cloudRegion) { + try { + DELEGATE.get().onRainStarted(level, cloudRegion); + } catch (Exception e) { + LOGGER.warn("Season delegate failed while handling rain start.", e); + } + } + + public static void onRainEnded(ServerLevel level, CloudRegion cloudRegion) { + try { + DELEGATE.get().onRainEnded(level, cloudRegion); + } catch (Exception e) { + LOGGER.warn("Season delegate failed while handling rain end.", e); + } + } + private static final class NeutralDelegate implements SeasonTimeDelegate { static final long DAY_DURATION = 24000L; static final long SEASON_DURATION = 24000L; static final long SEASON_CYCLE = SEASON_DURATION * 4; private static final ResourceLocation ID = new ResourceLocation("projectatmosphere", "neutral"); + @Override + public String providerId() { + return ID.toString(); + } + @Override public SeasonSnapshot snapshot(Level level) { return SeasonSnapshot.neutral(); diff --git a/src/main/java/net/Gabou/projectatmosphere/event/SeasonTracker.java b/src/main/java/net/Gabou/projectatmosphere/seasons/SereneSeasonsEventBridge.java similarity index 77% rename from src/main/java/net/Gabou/projectatmosphere/event/SeasonTracker.java rename to src/main/java/net/Gabou/projectatmosphere/seasons/SereneSeasonsEventBridge.java index 0aa3fd73..860e44f1 100644 --- a/src/main/java/net/Gabou/projectatmosphere/event/SeasonTracker.java +++ b/src/main/java/net/Gabou/projectatmosphere/seasons/SereneSeasonsEventBridge.java @@ -1,15 +1,24 @@ -package net.Gabou.projectatmosphere.event; +package net.Gabou.projectatmosphere.seasons; import com.Gabou.sereneseasonsplus.util.EnvironmentHelper; import glitchcore.event.EventManager; +import net.Gabou.projectatmosphere.manager.AtmosphereManager; import net.minecraft.server.level.ServerLevel; import sereneseasons.api.season.Season; import sereneseasons.api.season.SeasonChangedEvent; -import net.Gabou.projectatmosphere.manager.AtmosphereManager; -public class SeasonTracker { +public final class SereneSeasonsEventBridge { + private static boolean registered; + + private SereneSeasonsEventBridge() { + } public static void register() { + if (registered) { + return; + } + registered = true; + EventManager.addListener((SeasonChangedEvent.Standard event) -> { if (event.getLevel() instanceof ServerLevel serverLevel) { if (event.getNewSeason().getSeason() != event.getPrevSeason().getSeason()) { @@ -17,7 +26,7 @@ public static void register() { Season.SubSeason oldSeason = event.getPrevSeason(); Season.SubSeason newSeason = event.getNewSeason(); if (newSeason != oldSeason) { - EnvironmentHelper.onSeasonChange(serverLevel,Math.abs(newSeason.ordinal() - oldSeason.ordinal()) != 1); + EnvironmentHelper.onSeasonChange(serverLevel, Math.abs(newSeason.ordinal() - oldSeason.ordinal()) != 1); } } } @@ -29,9 +38,9 @@ public static void register() { AtmosphereManager.onSeasonChange(serverLevel); Season.TropicalSeason oldSeason = event.getPrevSeason(); Season.TropicalSeason newSeason = event.getNewSeason(); - boolean b = Math.abs(newSeason.ordinal() - oldSeason.ordinal()) != 1; + boolean skippedAdjacentSeason = Math.abs(newSeason.ordinal() - oldSeason.ordinal()) != 1; if (newSeason != oldSeason) { - EnvironmentHelper.onSeasonChange(serverLevel, b); + EnvironmentHelper.onSeasonChange(serverLevel, skippedAdjacentSeason); } } } diff --git a/src/main/java/net/Gabou/projectatmosphere/seasons/SereneSeasonsSeasonDelegate.java b/src/main/java/net/Gabou/projectatmosphere/seasons/SereneSeasonsSeasonDelegate.java index aa645e93..9dd010bb 100644 --- a/src/main/java/net/Gabou/projectatmosphere/seasons/SereneSeasonsSeasonDelegate.java +++ b/src/main/java/net/Gabou/projectatmosphere/seasons/SereneSeasonsSeasonDelegate.java @@ -1,7 +1,6 @@ package net.Gabou.projectatmosphere.seasons; import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; -import net.Gabou.projectatmosphere.event.SeasonTracker; import net.Gabou.projectatmosphere.util.ICloudRegionId; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; @@ -12,11 +11,16 @@ * Season delegate backed by Serene Seasons. */ public class SereneSeasonsSeasonDelegate implements SeasonTimeDelegate { + private static final String PROVIDER_ID = "sereneseasons"; public SereneSeasonsSeasonDelegate() { - SeasonTracker.register(); + SereneSeasonsEventBridge.register(); } + @Override + public String providerId() { + return PROVIDER_ID; + } @Override public SeasonSnapshot snapshot(Level level) { @@ -51,11 +55,13 @@ public long dayDuration(Level level) { } - public static void handleRainStarted(ServerLevel level, CloudRegion cloudRegion) { + @Override + public void onRainStarted(ServerLevel level, CloudRegion cloudRegion) { SSPApi.getINSTANCE().onSimpleCloudsSpawned(level, ((ICloudRegionId) cloudRegion).projectatmosphere$getId()); } - public static void handleRainEnded(ServerLevel level, CloudRegion cloudRegion) { + @Override + public void onRainEnded(ServerLevel level, CloudRegion cloudRegion) { SSPApi.getINSTANCE().onCloudsDespawned(level, ((ICloudRegionId) cloudRegion).projectatmosphere$getId()); } } diff --git a/src/main/java/net/Gabou/projectatmosphere/util/CloudRegionQueue.java b/src/main/java/net/Gabou/projectatmosphere/util/CloudRegionQueue.java index 9a46ce63..51f48278 100644 --- a/src/main/java/net/Gabou/projectatmosphere/util/CloudRegionQueue.java +++ b/src/main/java/net/Gabou/projectatmosphere/util/CloudRegionQueue.java @@ -1,9 +1,6 @@ package net.Gabou.projectatmosphere.util; -import com.Gabou.sereneseasonsplus.storage.ChunkQueue; import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; -import net.minecraft.world.level.ChunkPos; - import java.util.ArrayDeque; import java.util.Queue; diff --git a/src/main/java/net/Gabou/projectatmosphere/util/HurricaneUpload.java b/src/main/java/net/Gabou/projectatmosphere/util/HurricaneUpload.java new file mode 100644 index 00000000..cef880c4 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/util/HurricaneUpload.java @@ -0,0 +1,20 @@ +package net.Gabou.projectatmosphere.util; + +public record HurricaneUpload( + float typeIndex, + float centerX, + float centerZ, + float anchorY, + float coreRadius, + float stormExtentRadius, + float eyeRadius, + float edgeFade, + float bandCount, + float bandWidth, + float spiralTightness, + float rotationPhase, + float rotationSpeed, + float transitionStart, + float transitionEnd +) { +} diff --git a/src/main/java/net/Gabou/projectatmosphere/util/RegionUpload.java b/src/main/java/net/Gabou/projectatmosphere/util/RegionUpload.java new file mode 100644 index 00000000..96a131ca --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/util/RegionUpload.java @@ -0,0 +1,13 @@ +package net.Gabou.projectatmosphere.util; + +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; + +public final class RegionUpload { + public final CloudRegion region; + public final float[] data; + + public RegionUpload(CloudRegion region, float[] data) { + this.region = region; + this.data = data; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/util/TornadoUpload.java b/src/main/java/net/Gabou/projectatmosphere/util/TornadoUpload.java new file mode 100644 index 00000000..b4925257 --- /dev/null +++ b/src/main/java/net/Gabou/projectatmosphere/util/TornadoUpload.java @@ -0,0 +1,19 @@ +package net.Gabou.projectatmosphere.util; + +public final class TornadoUpload { + public final float typeIndex; + public final float centerX; + public final float centerZ; + public final float radius; + public final float bottom; + public final float height; + + public TornadoUpload(float typeIndex, float centerX, float centerZ, float radius, float bottom, float height) { + this.typeIndex = typeIndex; + this.centerX = centerX; + this.centerZ = centerZ; + this.radius = radius; + this.bottom = bottom; + this.height = height; + } +} diff --git a/src/main/java/net/Gabou/projectatmosphere/util/WeatherType.java b/src/main/java/net/Gabou/projectatmosphere/util/WeatherType.java index d90e876a..2722b70e 100644 --- a/src/main/java/net/Gabou/projectatmosphere/util/WeatherType.java +++ b/src/main/java/net/Gabou/projectatmosphere/util/WeatherType.java @@ -36,18 +36,19 @@ public enum WeatherType { CLOUD_MAP.put(new ResourceLocation("simpleclouds", "matrix"), NONE); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "overcast"), RAIN); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "pathway"), NONE); - //CLOUD_MAP.put(new ResourceLocation("simpleclouds", "pattern"), NONE); + CLOUD_MAP.put(new ResourceLocation("simpleclouds", "pattern"), NONE); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "real_itty_bitty"), NONE); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "severe_cumulonimbus"), THUNDERSTORM); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "severe_nimbostratus"), THUNDERSTORM); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "smaller_stratocumulus"), NONE); - //CLOUD_MAP.put(new ResourceLocation("simpleclouds", "snow"), NONE); + CLOUD_MAP.put(new ResourceLocation("simpleclouds", "snow"), NONE); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "spots"), NONE); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "spotted"), NONE); // CLOUD_MAP.put(new ResourceLocation("simpleclouds", "stripe"), NONE); // CLOUD_MAP.put(new ResourceLocation("simpleclouds", "stripe_side"), NONE); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "stronger_stratus"), THUNDERSTORM); - //CLOUD_MAP.put(new ResourceLocation("simpleclouds", "tall_noise"), NONE); + CLOUD_MAP.put(new ResourceLocation("simpleclouds", "tall_noise"), NONE); + CLOUD_MAP.put(new ResourceLocation("simpleclouds", "tall_weirdness"), NONE); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "thicker_stratocumulus"), RAIN); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "tsegrus"), THUNDERSTORM); CLOUD_MAP.put(new ResourceLocation("simpleclouds", "cumulonimbus"), THUNDERSTORM); diff --git a/src/main/resources/assets/projectatmosphere/blockstates/storm_shield.json b/src/main/resources/assets/projectatmosphere/blockstates/storm_shield.json new file mode 100644 index 00000000..3196bb32 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/blockstates/storm_shield.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "projectatmosphere:block/storm_shield" + } + } +} diff --git a/src/main/resources/assets/projectatmosphere/lang/en_us.json b/src/main/resources/assets/projectatmosphere/lang/en_us.json index fb8ba2b7..2cf4c163 100644 --- a/src/main/resources/assets/projectatmosphere/lang/en_us.json +++ b/src/main/resources/assets/projectatmosphere/lang/en_us.json @@ -15,5 +15,7 @@ "block.projectatmosphere.anemometer": "Anemometer", "block.projectatmosphere.thermometer_block": "Thermometer", "block.projectatmosphere.barometer_block": "Barometer", - "block.projectatmosphere.humidimeter_block": "Hygrometer" + "block.projectatmosphere.humidimeter_block": "Hygrometer", + "block.projectatmosphere.storm_siren": "Storm Siren", + "block.projectatmosphere.storm_shield": "Storm Shield" } diff --git a/src/main/resources/assets/projectatmosphere/lang/fr_fr.json b/src/main/resources/assets/projectatmosphere/lang/fr_fr.json index 8d9bd6e9..43380e25 100644 --- a/src/main/resources/assets/projectatmosphere/lang/fr_fr.json +++ b/src/main/resources/assets/projectatmosphere/lang/fr_fr.json @@ -16,5 +16,7 @@ "block.projectatmosphere.anemometer": "Anémomètre", "block.projectatmosphere.thermometer_block": "Thermomètre", "block.projectatmosphere.barometer_block": "Baromètre", - "block.projectatmosphere.humidimeter_block": "Hygromètre" + "block.projectatmosphere.humidimeter_block": "Hygromètre", + "block.projectatmosphere.storm_siren": "Sirene de tempete", + "block.projectatmosphere.storm_shield": "Bouclier anti-tempete" } diff --git a/src/main/resources/assets/projectatmosphere/models/block/storm_shield.json b/src/main/resources/assets/projectatmosphere/models/block/storm_shield.json new file mode 100644 index 00000000..388ef593 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/models/block/storm_shield.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "projectatmosphere:block/black" + } +} diff --git a/src/main/resources/assets/projectatmosphere/models/item/storm_shield.json b/src/main/resources/assets/projectatmosphere/models/item/storm_shield.json new file mode 100644 index 00000000..07c69c05 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/models/item/storm_shield.json @@ -0,0 +1,3 @@ +{ + "parent": "projectatmosphere:block/storm_shield" +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/compute/cloud_regions.comp b/src/main/resources/assets/projectatmosphere/shaders/compute/cloud_regions.comp new file mode 100644 index 00000000..18f494e6 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/compute/cloud_regions.comp @@ -0,0 +1,266 @@ +#version 430 + +#define EFF ${EDGE_FADE_FACTOR} +#define PI 3.14159265358979323846 + +layout(local_size_x = ${LOCAL_SIZE_X}, local_size_y = ${LOCAL_SIZE_Y}, local_size_z = ${LOCAL_SIZE_Z}) in; + +struct CloudRegion { + float posX; + float posZ; + float index; + float radius; + mat2 transform; +}; + +layout(std430) readonly buffer CloudRegions { + CloudRegion data[]; +} cloudRegions; + +layout(std430) restrict readonly buffer LodScales { + float data[]; +} lodScales; + +struct CloudTornado { + vec4 shape0; + vec4 shape1; +}; + +struct CloudHurricane { + vec4 shape0; + vec4 shape1; + vec4 shape2; + vec4 shape3; +}; + +layout(std430) readonly buffer CloudStorms { + CloudTornado tornadoes[64]; + CloudHurricane hurricanes[8]; +} cloudStorms; + +layout(rg32f) restrict writeonly uniform image2DArray regionTexture; + +uniform int TotalCloudRegions; +uniform vec2 Offset; +bool projectatmosphere_insideTornado(vec2 coord, out float typeIndex); +vec2 projectatmosphere_sampleHurricane(CloudHurricane hurricane, vec2 coord); +vec2 projectatmosphere_compositeHurricane(vec2 current, vec2 hurricane); +uniform int TotalCloudTornadoes; +uniform int TotalCloudHurricanes; +uniform vec4 CloudTornadoData0[16]; + +float saturate(float value) +{ + return clamp(value, 0.0, 1.0); +} + +vec2 projectatmosphere_rotate(vec2 value, float angle) +{ + float s = sin(angle); + float c = cos(angle); + return vec2(value.x * c - value.y * s, value.x * s + value.y * c); +} + +vec3 circle(CloudRegion region, vec2 coord) +{ + vec2 p = vec2(region.posX, region.posZ); + coord = region.transform * (coord - p) + p; + float d = distance(p, coord); + float r = region.radius; + if (d > r + 1.0 / EFF) + return vec3(-1.0); + else if (d < r) + return vec3(min((r - d) * EFF, 1.0), 0.0, region.index); + else + return vec3(0.0, min((d - r) * EFF, 1.0), region.index); +} + +vec2 composite(vec2 old, vec3 data) +{ + if (data.r > 0.0) + { + if (old.r >= 0.0 && old.r == data.b) + return vec2(old.r, mix(old.g, 1.0, data.r)); + else + return data.br; + } + else if (data.g >= 0.0) + { + if (old.r >= 0.0 && old.r == data.b) + return old; + else + return vec2(old.r, old.g * data.g); + } + else + { + return old; + } +} + +bool projectatmosphere_insideTornado(vec2 coord, out float typeIndex) +{ + for (int i = 0; i < TotalCloudTornadoes; i++) + { + vec4 tornadoShape = CloudTornadoData0[i]; + if (distance(coord, tornadoShape.yz) <= tornadoShape.w) + { + typeIndex = tornadoShape.x; + return true; + } + } + return false; +} + +vec2 projectatmosphere_sampleHurricane(CloudHurricane hurricane, vec2 coord) +{ + float typeIndex = hurricane.shape0.x; + vec2 center = hurricane.shape0.yz; + float coreRadius = hurricane.shape1.x; + float stormExtentRadius = hurricane.shape1.y; + float eyeRadius = hurricane.shape1.z; + float edgeFade = max(hurricane.shape1.w, 1.0); + + float bandCount = max(hurricane.shape2.x, 1.0); + float bandWidth = max(hurricane.shape2.y, 1.0); + float spiralTightness = hurricane.shape2.z; + float rotationPhase = hurricane.shape2.w; + + float transitionStart = hurricane.shape3.y; + float transitionEnd = max(hurricane.shape3.z, transitionStart + 1.0); + + vec2 local = coord - center; + vec2 rotatedLocal = projectatmosphere_rotate(local, rotationPhase * 0.18); + float radius = length(local); + if (radius > stormExtentRadius + edgeFade * 1.20) + return vec2(-1.0); + + float angle = atan(rotatedLocal.y, rotatedLocal.x); + float normalizedRadius = stormExtentRadius > 0.0 ? saturate(radius / stormExtentRadius) : 0.0; + float spinPhase = angle + max(radius - eyeRadius, 0.0) * spiralTightness - rotationPhase * 0.35; + + float outerMask = 1.0 - smoothstep(stormExtentRadius - edgeFade * 0.34, stormExtentRadius + edgeFade * 0.92, radius); + float eyeHole = smoothstep(eyeRadius + edgeFade * 0.06, eyeRadius + edgeFade * 0.82, radius); + + float eyewallCenter = eyeRadius + bandWidth * 0.36; + float eyewallThickness = bandWidth * 0.88 + edgeFade * 0.14; + float eyewall = 1.0 - smoothstep(eyewallThickness * 0.26, eyewallThickness, abs(radius - eyewallCenter)); + eyewall *= eyeHole; + + float armNoiseA = 0.5 + 0.5 * cos(spinPhase * bandCount + normalizedRadius * 7.0); + float armNoiseB = 0.5 + 0.5 * cos(spinPhase * (bandCount * 0.72 + 1.10) - normalizedRadius * 9.5); + float armNoise = mix(armNoiseA, armNoiseB, 0.42); + armNoise = smoothstep(0.62, 0.94, armNoise); + + float spiralEnvelope = smoothstep(eyeRadius + bandWidth * 0.12, eyeRadius + bandWidth * 1.28, radius); + spiralEnvelope *= 1.0 - smoothstep(coreRadius * 0.78, coreRadius * 1.18, radius); + + float innerCore = max(eyewall, armNoise * spiralEnvelope); + + // Start the cumulonimbus recovery close to the eyewall so the core does not hand off into a hollow ring. + float bridgeStart = eyeRadius + bandWidth * 0.52; + float bridgeBuildEnd = max(bridgeStart + bandWidth * 1.85, coreRadius * 0.36); + float bridgeFadeEnd = max(coreRadius * 1.08, bridgeBuildEnd + bandWidth * 2.40); + + float cbBlendStart = min(transitionStart, bridgeStart); + float cbEnvelope = smoothstep(cbBlendStart, transitionEnd, radius); + cbEnvelope *= 1.0 - smoothstep(stormExtentRadius * 1.02, stormExtentRadius + edgeFade * 0.78, radius); + + float cbNoiseA = 0.5 + 0.5 * cos(angle * 1.9 - rotationPhase * 0.06 + normalizedRadius * 6.8); + float cbNoiseB = 0.5 + 0.5 * cos(angle * 4.4 + rotationPhase * 0.03 - normalizedRadius * 12.6); + float cbNoiseC = 0.5 + 0.5 * cos(angle * 2.7 - normalizedRadius * 4.4); + float cbNoise = mix(mix(cbNoiseA, cbNoiseB, 0.45), cbNoiseC, 0.30); + cbNoise = smoothstep(0.20, 0.90, cbNoise); + + float innerCbA = 0.5 + 0.5 * cos(spinPhase * (bandCount * 0.92 + 0.85) - normalizedRadius * 6.4); + float innerCbB = 0.5 + 0.5 * cos(angle * 2.2 - rotationPhase * 0.10 + normalizedRadius * 4.8); + float innerCbMask = smoothstep(0.20, 0.82, mix(innerCbA, innerCbB, 0.42)); + + float innerBridgeEnvelope = smoothstep(bridgeStart, bridgeBuildEnd, radius); + innerBridgeEnvelope *= 1.0 - smoothstep(coreRadius * 0.94, bridgeFadeEnd, radius); + + float outerBandEnvelope = smoothstep(coreRadius * 0.42, stormExtentRadius * 0.90, radius); + outerBandEnvelope *= 1.0 - smoothstep(stormExtentRadius * 0.96, stormExtentRadius + edgeFade * 0.72, radius); + + float outerBandA = 0.5 + 0.5 * cos(spinPhase * (bandCount * 0.42 + 1.05) - normalizedRadius * 15.0); + float outerBandB = 0.5 + 0.5 * cos((angle - rotationPhase * 0.16) * (bandCount * 0.30 + 1.85) + normalizedRadius * 21.0); + outerBandA = smoothstep(0.58, 0.94, outerBandA); + outerBandB = smoothstep(0.56, 0.92, outerBandB); + float outerBandMask = smoothstep(0.34, 0.88, mix(outerBandA, outerBandB, 0.38)); + + float cbMass = cbEnvelope * (0.22 + cbNoise * 0.26 + outerBandMask * 0.52); + + float innerBridge = innerBridgeEnvelope * (0.54 + innerCbMask * 0.26 + armNoise * 0.20); + + float continuityBand = smoothstep(bridgeStart, coreRadius * 0.96, radius); + continuityBand *= 1.0 - smoothstep(coreRadius * 1.08, transitionEnd * 0.92, radius); + continuityBand *= 0.48 + mix(armNoise, outerBandMask, 0.50) * 0.44; + + float spiralShoulders = outerBandEnvelope * (0.28 + outerBandMask * 0.72); + spiralShoulders *= 0.52 + cbNoise * 0.30; + + float anvilEdge = smoothstep(stormExtentRadius * 0.70, stormExtentRadius * 0.95, radius); + anvilEdge *= 1.0 - smoothstep(stormExtentRadius * 1.04, stormExtentRadius + edgeFade * 0.94, radius); + anvilEdge *= smoothstep(0.34, 0.88, cbNoiseB) * (0.42 + outerBandMask * 0.58); + + float outerStorm = max(max(cbMass, spiralShoulders), max(innerBridge, continuityBand + anvilEdge * 0.20)); + float coverage = max(innerCore, outerStorm); + coverage *= outerMask * eyeHole; + coverage = smoothstep(0.04, 0.88, saturate(coverage)); + if (coverage <= 0.001) + return vec2(-1.0); + + return vec2(typeIndex, coverage); +} + +vec2 projectatmosphere_compositeHurricane(vec2 current, vec2 hurricane) +{ + if (hurricane.x < 0.0) + return current; + + if (current.r < 0.0 || current.g <= 0.0) + return hurricane; + + if (current.r == hurricane.x) + return vec2(current.r, max(current.g, hurricane.y)); + + if (hurricane.y >= current.g) + return hurricane; + + return current; +} + +void main() +{ + uint lod = gl_GlobalInvocationID.z; + float coordScale = lodScales.data[lod]; + vec2 centerOffset = imageSize(regionTexture).xy / 2.0; + ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy); + vec2 coord = (gl_GlobalInvocationID.xy - centerOffset) * coordScale + Offset; + + vec2 result = vec2(0.0); + for (int i = 0; i < TotalCloudRegions; i++) + { + vec3 data = circle(cloudRegions.data[i], coord); + result = composite(result, data); + } + + if (TotalCloudTornadoes > 0) + { + float tornadoType = -1.0; + if (projectatmosphere_insideTornado(coord, tornadoType)) + { + if (result.x < 0.0 || result.x == tornadoType) + result = vec2(tornadoType, 1.0); + } + } + + if (TotalCloudHurricanes > 0) + { + for (int i = 0; i < TotalCloudHurricanes; i++) + { + result = projectatmosphere_compositeHurricane(result, projectatmosphere_sampleHurricane(cloudStorms.hurricanes[i], coord)); + } + } + + imageStore(regionTexture, ivec3(texelCoord, lod), vec4(result, 0.0, 0.0)); +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/compute/cube_mesh.comp b/src/main/resources/assets/projectatmosphere/shaders/compute/cube_mesh.comp new file mode 100644 index 00000000..48ab19d6 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/compute/cube_mesh.comp @@ -0,0 +1,501 @@ +#version 430 + +#define TYPE ${TYPE} //0 for multi-region, 1 for single cloud type +#define FADE_NEAR_ORIGIN ${FADE_NEAR_ORIGIN} //0 to disable, 1 to enable +#define STYLE ${STYLE} //0 for default, 1 for shaded +#define TRANSPARENCY ${TRANSPARENCY} //0 for no transparency, 1 for transparency +#define FIXED_SECTION_SIZE ${FIXED_SECTION_SIZE} //0 for false, 1 for true + +#define SHADE_DIRECTION normalize(vec3(0.5, 0.5, 0.5)) + +#define TILE_PERIOD vec3(32.0, 64.0, 32.0) + +#define LOCAL_SIZE vec3(${LOCAL_SIZE_X}, ${LOCAL_SIZE_Y}, ${LOCAL_SIZE_Z}) +layout(local_size_x = ${LOCAL_SIZE_X}, local_size_y = ${LOCAL_SIZE_Y}, local_size_z = ${LOCAL_SIZE_Z}) in; + +#moj_import + +bool projectatmosphere_insideTornado(vec3 pos, float typeIndex); +int projectatmosphere_findHurricane(vec2 pos, float typeIndex); +float projectatmosphere_getStormHeightSample(float y, int hurricaneIndex); + +struct LayerGroup { + int StartIndex; + int EndIndex; + float Storminess; + float StormStart; + float StormFadeDistance; + float TransparencyFade; +}; + +struct NoiseLayer { + float Height; + float ValueOffset; + float ScaleX; + float ScaleY; + float ScaleZ; + float FadeDistance; + float HeightOffset; + float ValueScale; +}; + +// ----- Opaque ----- + +struct SideInfo { + int side; + float x; + float y; + float z; + float brightness; + float radius; +}; + +// ----- Transparent ----- + +#if TRANSPARENCY == 1 + +struct TransparentCubeInfo { + float x; + float y; + float z; + float brightness; + float alpha; + float radius; +}; + +#endif + +// ----------------------- + +//Faces: +//-X = 0 +//+X = 1 +//-Y = 2 +//+Y = 3 +//-Z = 4 +//+Z = 5 + +//const uint sideIndices[6] = { +// 0, 1, 2, 0, 2, 3 +//}; +// +//#if TRANSPARENCY == 1 +// +//const uint transparentCubeIndices[36] = { +// 0, 1, 2, 0, 2, 3, //-z +// 4, 7, 6, 4, 6, 5, //+z +// 7, 0, 3, 7, 3, 6, //-x +// 1, 4, 5, 1, 5, 2, //+x +// 1, 0, 7, 1, 7, 4, //-y +// 5, 6, 3, 5, 3, 2 //+y +//}; +// +//#endif + +// ----- Opaque Buffers ----- + +#if FIXED_SECTION_SIZE == 0 +layout(std430) restrict buffer TotalSides { + uint totalSides; +}; +#endif + +layout(std430) restrict writeonly buffer SideInfoBuffer { + SideInfo data[]; +} +sides; + +layout(std430) restrict buffer SidesPerChunk { + uint data[]; +} +sidesPerChunk; + +// ----- Transparency Buffers ----- + +#if TRANSPARENCY == 1 + +#if FIXED_SECTION_SIZE == 0 +layout(std430) restrict buffer TotalTransparentCubes { + uint totalTransparentCubes; +}; +#endif + +layout(std430) restrict writeonly buffer TransparentCubeInfoBuffer { + TransparentCubeInfo data[]; +} +cubesTransparent; + +layout(std430) restrict buffer TransparentCubesPerChunk { + uint data[]; +} +transparentCubesPerChunk; + +#endif + +// ---------------------------------- + +layout(std430) readonly buffer NoiseLayers { + NoiseLayer data[]; +} +layers; + +layout(std430) readonly buffer LayerGroupings { + LayerGroup data[]; +} +layerGroupings; + +struct CloudTornado { + vec4 shape0; + vec4 shape1; +}; + +struct CloudHurricane { + vec4 shape0; + vec4 shape1; + vec4 shape2; + vec4 shape3; +}; + +layout(std430) readonly buffer CloudStorms { + CloudTornado tornadoes[64]; + CloudHurricane hurricanes[8]; +} +cloudStorms; + +#if TYPE == 0 +uniform sampler2DArray RegionsSampler; +uniform int RegionsTexSize; +#endif + +uniform int LodLevel; +uniform int TotalLodLevels; +uniform vec3 RenderOffset; +uniform float Scale = 1.0; +uniform vec3 Scroll; +uniform float Wiggle; +uniform vec3 Origin; +uniform bool TestFacesFacingAway; +uniform int DoNotOccludeSide = -1; +uniform int ChunkIndex; +uniform int TotalCloudTornadoes; +uniform int TotalCloudHurricanes; +uniform vec4 CloudTornadoData0[16]; +uniform vec4 CloudTornadoData1[16]; + +#if FIXED_SECTION_SIZE == 1 +//Offset in number of mesh elements +uniform int OpaqueMeshDataOffset; +uniform int TransparentMeshDataOffset; +#endif + +#if TRANSPARENCY == 1 +uniform int TransparencyDistance = 300; +#endif + +#if TYPE == 0 +uniform vec2 RegionSampleOffset; +#elif TYPE == 1 +uniform float FadeStart; +uniform float FadeEnd; +#endif + +#if FADE_NEAR_ORIGIN == 1 +uniform float FadeStart; +uniform float FadeEnd; +#endif + +float getNoiseForLayer(NoiseLayer layer, float x, float y, float z, out vec3 gradient) +{ + if (y < layer.HeightOffset || y > layer.HeightOffset + layer.Height - 1) + return -10000.0; + vec3 scale = vec3(layer.ScaleX, layer.ScaleY, layer.ScaleZ); + vec3 scrollScaled = Scroll / scale; + vec3 samplePos = vec3(x, y, z) / scale + scrollScaled; + float noise = psrdnoise(samplePos, TILE_PERIOD, Wiggle, gradient) * layer.ValueScale + layer.ValueOffset; + float heightDelta = y - layer.HeightOffset; + noise -= 1.0 - clamp(heightDelta / layer.FadeDistance, 0.0, 1.0); + noise -= 1.0 - clamp((layer.Height - heightDelta) / layer.FadeDistance, 0.0, 1.0); + return noise; +} + +float getNoiseForLayerGroup(LayerGroup group, float x, float y, float z, out vec3 gradient) +{ + int totalLayers = group.EndIndex - group.StartIndex; + if (totalLayers == 0) + { + return -10000.0; + } + else if (totalLayers == 1) + { + return getNoiseForLayer(layers.data[group.StartIndex], x, y, z, gradient); + } + else + { + vec3 finalGradient = vec3(0.0); + float combinedNoise = 0.0; + bool anyValid = false; + for (int i = 0; i < totalLayers; i++) + { + vec3 gradForLayer = vec3(0.0); + float valForLayer = getNoiseForLayer(layers.data[i + group.StartIndex], x, y, z, gradForLayer); + if (valForLayer > -10.0) + { + combinedNoise += valForLayer; + finalGradient += gradForLayer; + anyValid = true; + } + } + gradient = finalGradient; + if (anyValid) + return combinedNoise; + else + return -10000.0; + } + return 0.0; +} + +bool isPosValid(float x, float y, float z, LayerGroup group, float fade) +{ + vec3 gradient = vec3(0.0); + return getNoiseForLayerGroup(group, x, y, z, gradient) + fade > 0.0; +} + +bool isPosValid(float x, float y, float z, LayerGroup group, float fade, int hurricaneIndex) +{ + vec3 gradient = vec3(0.0); + float sampleY = projectatmosphere_getStormHeightSample(y, hurricaneIndex); + return getNoiseForLayerGroup(group, x, sampleY, z, gradient) + fade > 0.0; +} + +bool isPosValid(float x, float y, float z, int nx, int nz) +{ +#if TYPE == 0 + vec2 texelCoord = gl_GlobalInvocationID.xz + RegionSampleOffset + vec2(nx, nz); + vec4 info = texture(RegionsSampler, vec3(texelCoord / RegionsTexSize, float(LodLevel))); + uint regionId = uint(info.r); + LayerGroup group = layerGroupings.data[regionId]; + float fade = -5.0 * pow(1.0 - info.g, 10.0); + if (TotalCloudTornadoes > 0 && projectatmosphere_insideTornado(vec3(x, y, z), info.r)) + return false; + int hurricaneIndex = TotalCloudHurricanes > 0 ? projectatmosphere_findHurricane(vec2(x, z), info.r) : -1; +#if FADE_NEAR_ORIGIN == 1 + float len = distance(vec2(x, z), Origin.xz); + if (hurricaneIndex < 0) + fade = min(fade, -5.0 * (1.0 - min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0))); +#endif +#elif TYPE == 1 + LayerGroup group = layerGroupings.data[0]; + float len = distance(vec2(x, z), Origin.xz); + float fade = -5.0 * min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0); + int hurricaneIndex = -1; +#endif + return isPosValid(x, y, z, group, fade, hurricaneIndex); +} + +bool shouldNotOcclude(int index) +{ + if (DoNotOccludeSide != -1 && index == DoNotOccludeSide) + { + vec3 id = gl_GlobalInvocationID; + vec3 size = gl_NumWorkGroups * LOCAL_SIZE; + if (DoNotOccludeSide == 1) + return id.x == size.x - 1.0; + else if (DoNotOccludeSide == 0) + return id.x == 0.0; + else if (DoNotOccludeSide == 3) + return id.y == size.y - 1.0; + else if (DoNotOccludeSide == 2) + return id.y == 0.0; + else if (DoNotOccludeSide == 5) + return id.z == size.z - 1.0; + else if (DoNotOccludeSide == 4) + return id.z == 0.0; + else + return false; + } + else + { + return false; + } +} + +// ----- Opaque ----- + +void createFace(vec3 center, float cubeRadius, int index, float brightness) +{ +#if FIXED_SECTION_SIZE == 0 + atomicAdd(sidesPerChunk.data[ChunkIndex], 1u); + uint currentFace = atomicAdd(totalSides, 1u); +#elif FIXED_SECTION_SIZE == 1 + uint currentFace = atomicAdd(sidesPerChunk.data[ChunkIndex], 1u); +#endif + SideInfo side; + side.side = index; + side.x = center.x; + side.y = center.y; + side.z = center.z; + side.brightness = brightness; + side.radius = cubeRadius; +#if FIXED_SECTION_SIZE == 0 + sides.data[currentFace] = side; +#elif FIXED_SECTION_SIZE == 1 + sides.data[OpaqueMeshDataOffset + currentFace] = side; +#endif +} + +void createCube(float x, float y, float z, float cubeRadius, float brightness, float fade, LayerGroup group, int hurricaneIndex) +{ + vec3 pos = vec3(x, y, z); + vec3 norm = normalize(pos - Origin); + vec3 center = pos + cubeRadius; + //-Y + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(0.0, -1.0, 0.0)) <= 0.0) && (!isPosValid(x, y - Scale, z, group, fade, hurricaneIndex) || shouldNotOcclude(2))) + createFace(center, cubeRadius, 2, brightness); + //+Y + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(0.0, 1.0, 0.0)) <= 0.0) && (!isPosValid(x, y + Scale, z, group, fade, hurricaneIndex) || shouldNotOcclude(3))) + createFace(center, cubeRadius, 3, brightness); + //-X + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(-1.0, 0.0, 0.0)) <= 0.0) && (!isPosValid(x - Scale, y, z, -1, 0) || shouldNotOcclude(0))) + createFace(center, cubeRadius, 0, brightness); + //+X + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(1.0, 0.0, 0.0)) <= 0.0) && (!isPosValid(x + Scale, y, z, 1, 0) || shouldNotOcclude(1))) + createFace(center, cubeRadius, 1, brightness); + //-Z + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(0.0, 0.0, -1.0)) <= 0.0) && (!isPosValid(x, y, z - Scale, 0, -1) || shouldNotOcclude(4))) + createFace(center, cubeRadius, 4, brightness); + //+Z + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(0.0, 0.0, 1.0)) <= 0.0) && (!isPosValid(x, y, z + Scale, 0, 1) || shouldNotOcclude(5))) + createFace(center, cubeRadius, 5, brightness); +} + +// ----- Transparent ----- + +#if TRANSPARENCY == 1 + +void createTransparentCube(float x, float y, float z, float cubeRadius, float brightness, float alpha) +{ +#if FIXED_SECTION_SIZE == 0 + atomicAdd(transparentCubesPerChunk.data[ChunkIndex], 1u); + uint currentCube = atomicAdd(totalTransparentCubes, 1u); +#elif FIXED_SECTION_SIZE == 1 + uint currentCube = atomicAdd(transparentCubesPerChunk.data[ChunkIndex], 1u); +#endif + TransparentCubeInfo cube; + cube.x = x + cubeRadius; + cube.y = y + cubeRadius; + cube.z = z + cubeRadius; + cube.brightness = brightness; + cube.alpha = alpha; + cube.radius = cubeRadius; +#if FIXED_SECTION_SIZE == 0 + cubesTransparent.data[currentCube] = cube; +#elif FIXED_SECTION_SIZE == 1 + cubesTransparent.data[TransparentMeshDataOffset + currentCube] = cube; +#endif +} + +#endif + +// ----------------------- + +void main() +{ + vec3 id = gl_GlobalInvocationID; + float x = id.x * Scale + RenderOffset.x; + float y = id.y * Scale + RenderOffset.y; + float z = id.z * Scale + RenderOffset.z; + +#if TYPE == 0 + vec2 texelCoord = gl_GlobalInvocationID.xz + RegionSampleOffset; + vec4 info = texture(RegionsSampler, vec3(texelCoord / RegionsTexSize, float(LodLevel))); + uint regionId = uint(info.r); + LayerGroup group = layerGroupings.data[regionId]; + float fade = -5.0 * pow(1.0 - info.g, 10.0); + int hurricaneIndex = TotalCloudHurricanes > 0 ? projectatmosphere_findHurricane(vec2(x, z), info.r) : -1; +#if FADE_NEAR_ORIGIN == 1 + float len = distance(vec2(x, z), Origin.xz); + if (hurricaneIndex < 0) + fade = min(fade, -5.0 * (1.0 - min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0))); +#endif +#elif TYPE == 1 + LayerGroup group = layerGroupings.data[0]; + float len = distance(vec2(x, z), Origin.xz); + float fade = -5.0 * min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0); + int hurricaneIndex = -1; +#endif + vec3 gradient = vec3(0.0); + float sampleY = projectatmosphere_getStormHeightSample(y, hurricaneIndex); + float noise = getNoiseForLayerGroup(group, x, sampleY, z, gradient) + fade; + float storminess = clamp(group.Storminess + fade * 0.1, 0.0, 1.0); + float brightness = clamp(1.0 - storminess * (1.0 - clamp((sampleY - group.StormStart) / group.StormFadeDistance, 0.0, 1.0)), 0.0, 1.0); +#if STYLE == 1 + gradient = normalize(gradient); + float strength = dot(gradient, SHADE_DIRECTION) * 0.5 + 0.5; + brightness = clamp(brightness - strength * 0.1, 0.0, 1.0); +#endif + if (noise > 0.0) + { + createCube(x, y, z, Scale / 2.0, brightness, fade, group, hurricaneIndex); + } +#if TRANSPARENCY == 1 + else if (group.TransparencyFade > 0.01 && noise > -group.TransparencyFade && noise < 0.0) + { + float length = distance(vec2(x, z), Origin.xz); + if (length < TransparencyDistance) + { + float alpha = 1.0 / group.TransparencyFade * (noise + group.TransparencyFade); + createTransparentCube(x, y, z, Scale / 2.0, brightness, alpha); + } + } +#endif +} + +bool projectatmosphere_insideTornado(vec3 pos, float typeIndex) +{ + for (int i = 0; i < TotalCloudTornadoes; i++) + { + vec4 tornadoShape = CloudTornadoData0[i]; + vec4 tornadoHeight = CloudTornadoData1[i]; + if (abs(tornadoShape.x - typeIndex) > 0.5) + continue; + if (pos.y < tornadoHeight.x || pos.y > tornadoHeight.x + tornadoHeight.y) + continue; + if (distance(pos.xz, tornadoShape.yz) <= tornadoShape.w) + return true; + } + return false; +} + +int projectatmosphere_findHurricane(vec2 pos, float typeIndex) +{ + int bestIndex = -1; + float bestDistSq = 3.402823466e+38; + for (int i = 0; i < TotalCloudHurricanes; i++) + { + CloudHurricane hurricane = cloudStorms.hurricanes[i]; + if (abs(hurricane.shape0.x - typeIndex) > 0.5) + continue; + vec2 delta = pos - hurricane.shape0.yz; + float radius = hurricane.shape1.y + hurricane.shape1.w; + float distSq = dot(delta, delta); + if (distSq > radius * radius) + continue; + if (distSq < bestDistSq) + { + bestDistSq = distSq; + bestIndex = i; + } + } + return bestIndex; +} + +float projectatmosphere_getStormHeightSample(float y, int hurricaneIndex) +{ + if (hurricaneIndex < 0) + return y; + return y - cloudStorms.hurricanes[hurricaneIndex].shape0.w; +} + + + + diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/box.vsh b/src/main/resources/assets/projectatmosphere/shaders/core/box.vsh deleted file mode 100644 index b291cf39..00000000 --- a/src/main/resources/assets/projectatmosphere/shaders/core/box.vsh +++ /dev/null @@ -1,70 +0,0 @@ -#version 150 - -in vec3 vWorldPos; -out vec4 fragColor; - -uniform float DebugBox; -uniform vec3 CameraPos; -uniform vec3 BoxMin, BoxMax; - -uniform float Time, TwistSpeed, BaseRadius, TopRadius, Height, DustIntensity; - -float hash(float x){ return fract(sin(x*12.9898)*43758.5453); } -float noise(vec3 p){ return hash(p.x + p.y*57.0 + p.z*113.0); } - -// slab ray-AABB intersect (origin already on/inside the box front face) -vec2 rayBox(vec3 ro, vec3 rd, vec3 bmin, vec3 bmax){ - vec3 invD = 1.0 / rd; - vec3 t0 = (bmin - ro) * invD; - vec3 t1 = (bmax - ro) * invD; - vec3 tsm = min(t0, t1); - vec3 tbg = max(t0, t1); - float tmin = max(max(tsm.x, tsm.y), tsm.z); - float tmax = min(min(tbg.x, tbg.y), tbg.z); - return vec2(tmin, tmax); -} - -void main() { - // Front faces only (we cull back faces in JSON) - vec3 ro = vWorldPos; - vec3 rd = normalize(vWorldPos - CameraPos); - ro += rd * 0.01; // step just inside - - vec2 seg = rayBox(ro, rd, BoxMin, BoxMax); - float tExit = seg.y; - if (tExit <= 0.0) return; // no intersection - if (DebugBox > 0.5) { - fragColor = vec4(1.0, 0.0, 0.0, 0.15); - return; - } - - int STEPS = 32; - float step = tExit / float(STEPS); - - float alpha = 0.0; - vec3 col = vec3(0.0); - - vec3 center = 0.5 * (BoxMin + BoxMax); - - for (int i=0;i 0.99) break; - } - - fragColor = vec4(col, alpha); -} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/box_tornado.fsh b/src/main/resources/assets/projectatmosphere/shaders/core/box_tornado.fsh deleted file mode 100644 index fc69d895..00000000 --- a/src/main/resources/assets/projectatmosphere/shaders/core/box_tornado.fsh +++ /dev/null @@ -1,68 +0,0 @@ -#version 150 - -in vec2 uv; -out vec4 fragColor; - -uniform vec3 CameraPos; -uniform mat4 InvViewProj; -uniform float Time; -uniform float TwistSpeed; -uniform float BaseRadius; -uniform float TopRadius; -uniform float Height; -uniform float DustIntensity; - -const float PI = 3.14159265; -const int STEPS = 32; -const float MAX_DIST = 200.0; - -// Simple 3D noise (replace with your preferred FBM) -float hash(float x) { return fract(sin(x*12.9898)*43758.5453); } -float noise(vec3 p) { return hash(p.x + p.y*57.0 + p.z*113.0); } - -// Sample tornado density at world‐pos p -float sampleTornado(vec3 p) { - float h = clamp(p.y/Height, 0.0, 1.0); - float shaped = pow(h, 0.6); - float r = mix(BaseRadius, TopRadius, shaped); - if (h > 0.8) { - float ct = (h - 0.8) / 0.2; - r = mix(r, r * 1.5, ct); - } - float a = atan(p.z, p.x) + Time*TwistSpeed + h*6.0; - vec2 sp = vec2(cos(a), sin(a)) * r; - float d = length(p.xz - sp); - - // <-- corrected line: - float dens = 1.0 - smoothstep(r*0.8, r, d); - - // add some noise breakup - dens *= 1.0 - noise(vec3(p.xz*0.1, Time*0.1))*0.5; - return clamp(dens, 0.0, 1.0); -} - - -void main() { - // Reconstruct world‐space ray - vec4 clip = vec4(uv*2.0-1.0, 0.0, 1.0); - vec4 world = InvViewProj * clip; - world /= world.w; - vec3 rayDir = normalize(world.xyz - CameraPos); - - // Raymarch - float t=0.0, alpha=0.0; - vec3 col=vec3(0.0); - for(int i=0; iMAX_DIST||alpha>0.99) break; - vec3 pos = CameraPos + rayDir*t; - if(pos.y>=0.0 && pos.y<=Height){ - float d = sampleTornado(pos); - alpha += (1.0-alpha)*d*(DustIntensity*0.1); - col += (1.0-alpha)*vec3(0.8,0.7,0.6)*d; - } - t += MAX_DIST/float(STEPS); - } - - fragColor = vec4(uv, 0.0, 1.0); - -} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/box_tornado.json b/src/main/resources/assets/projectatmosphere/shaders/core/box_tornado.json deleted file mode 100644 index 9b83ea59..00000000 --- a/src/main/resources/assets/projectatmosphere/shaders/core/box_tornado.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "vertex": "projectatmosphere:box", - "fragment": "projectatmosphere:box_tornado", - "attributes": ["Position"], - "uniforms": [ - { "name":"CameraPos", "type":"float", "count":3, "values":[0,0,0] }, - { "name":"BoxMin", "type":"float", "count":3, "values":[-1,-1,-1] }, - { "name":"BoxMax", "type":"float", "count":3, "values":[ 1, 1, 1] }, - { "name": "DebugBox", "type": "float", "count": 1, "values": [0.0] }, - { "name":"Time", "type":"float","count":1,"values":[0] }, - { "name":"TwistSpeed", "type":"float","count":1,"values":[1] }, - { "name":"BaseRadius", "type":"float","count":1,"values":[8] }, - { "name":"TopRadius", "type":"float","count":1,"values":[1.5] }, - { "name":"Height", "type":"float","count":1,"values":[100] }, - { "name":"DustIntensity","type":"float","count":1,"values":[0.5] } - ], - "blend": { "func":"add","srcrgb":"srcalpha","dstrgb":"1-srcalpha" }, - "depthTest":"lequal", - "depthWrite": false, - "cull":"back" -} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds.fsh b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds.fsh new file mode 100644 index 00000000..ccf892da --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds.fsh @@ -0,0 +1,284 @@ +#version 430 + +uniform sampler2D BaseSampler; +uniform sampler2D NoiseSampler; +uniform sampler2D FlowSampler; +uniform sampler2D DepthSampler; + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; +uniform mat4 InverseProjMat; +uniform mat4 InverseModelViewMat; +uniform vec3 CameraPos; +uniform vec4 CloudColor; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; +uniform float AnimationTime; +uniform float MaxDistance; +uniform vec2 OutSize; +uniform vec3 VolumeMin; +uniform vec3 VolumeMax; +uniform int StormCount; +uniform float StormPositions[12]; +uniform vec4 StormHeights; +uniform vec4 EyeRadii; +uniform vec4 EyeClearRadii; +uniform vec4 EyeSlopes; +uniform vec4 EyewallThicknesses; +uniform vec4 CanopyRadii; +uniform vec4 ShieldRadii; +uniform vec4 CanopyBaseFactors; +uniform vec4 CanopyTopFactors; +uniform vec4 ShieldBaseFactors; +uniform vec4 ShieldTopFactors; +uniform vec4 BandStartRadii; +uniform vec4 BandEndRadii; +uniform vec4 BandWidths; +uniform vec4 BandStrengths; +uniform vec4 BandCounts; +uniform vec4 FringeStrengths; +uniform vec4 StormSpins; +uniform vec4 StormIntensities; +uniform vec4 StormSeeds; + +in vec2 texCoord; +in vec3 fragPos; +out vec4 fragColor; + +const float TAU = 6.28318530718; +const int MAX_STORMS_COUNT = 4; + +struct HurricaneSample { + float density; + float brightness; +}; + +float saturate(float value) { + return clamp(value, 0.0, 1.0); +} + +float hash1(float p) { + p = fract(p * 0.1031); + p *= p + 33.33; + p *= p + p; + return fract(p); +} + +float paHurricaneNoise3(vec3 p) { + vec3 i = floor(p); + vec3 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + + float n000 = hash1(dot(i + vec3(0.0, 0.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n100 = hash1(dot(i + vec3(1.0, 0.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n010 = hash1(dot(i + vec3(0.0, 1.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n110 = hash1(dot(i + vec3(1.0, 1.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n001 = hash1(dot(i + vec3(0.0, 0.0, 1.0), vec3(1.0, 57.0, 113.0))); + float n101 = hash1(dot(i + vec3(1.0, 0.0, 1.0), vec3(1.0, 57.0, 113.0))); + float n011 = hash1(dot(i + vec3(0.0, 1.0, 1.0), vec3(1.0, 57.0, 113.0))); + float n111 = hash1(dot(i + vec3(1.0, 1.0, 1.0), vec3(1.0, 57.0, 113.0))); + + float x00 = mix(n000, n100, f.x); + float x10 = mix(n010, n110, f.x); + float x01 = mix(n001, n101, f.x); + float x11 = mix(n011, n111, f.x); + float y0 = mix(x00, x10, f.y); + float y1 = mix(x01, x11, f.y); + return mix(y0, y1, f.z) * 2.0 - 1.0; +} + +float fbm(vec3 x, int octaves, float lacunarity, float gain, float amplitude) { + float y = 0.0; + for (int i = 0; i < octaves; i++) { + y += amplitude * paHurricaneNoise3(x); + x *= lacunarity; + amplitude *= gain; + } + return y; +} + +vec3 reconstructPosition(vec2 uv, float depth) { + vec4 ndc = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); + vec4 clip = InverseProjMat * ndc; + clip /= clip.w; + vec4 result = InverseModelViewMat * clip; + return result.xyz / result.w; +} + +float cloudSpaceToDepth(vec3 pos) { + vec4 clip = ProjMat * ModelViewMat * vec4(pos, 1.0); + float ndcZ = clip.z / clip.w; + return ndcZ * 0.5 + 0.5; +} + +bool intersectAabb(vec3 ro, vec3 rd, vec3 bmin, vec3 bmax, out float tNear, out float tFar) { + vec3 inv = 1.0 / rd; + vec3 t0 = (bmin - ro) * inv; + vec3 t1 = (bmax - ro) * inv; + vec3 tsmaller = min(t0, t1); + vec3 tbigger = max(t0, t1); + tNear = max(max(tsmaller.x, tsmaller.y), tsmaller.z); + tFar = min(min(tbigger.x, tbigger.y), tbigger.z); + return tFar > max(tNear, 0.0); +} + +vec3 getStormPos(int index) { + return vec3(StormPositions[index * 3], StormPositions[index * 3 + 1], StormPositions[index * 3 + 2]); +} + +float verticalWindow(float localY, float startFactor, float endFactor, float edge) { + float enter = smoothstep(startFactor - edge, startFactor + edge, localY); + float leave = 1.0 - smoothstep(endFactor - edge, endFactor + edge, localY); + return enter * leave; +} + +float band(float r, float inner, float outer, float soft) { + float enter = smoothstep(inner - soft, inner + soft, r); + float leave = 1.0 - smoothstep(outer - soft, outer + soft, r); + return enter * leave; +} + +float eyeRadiusAtHeight(int index, float localY, float baseRadius) { + float slope = max(EyeSlopes[index], 0.01); + return mix(baseRadius, baseRadius * slope, saturate(localY)); +} + +float sdTorus(vec3 p, float majorRadius, float minorRadius, float verticalScale) { + vec2 q = vec2(length(p.xz) - majorRadius, p.y / max(verticalScale, 0.01)); + return length(q) - minorRadius; +} + +HurricaneSample sampleStorm(int index, vec3 position) { + vec3 pos = getStormPos(index); + float height = max(StormHeights[index], 0.001); + float localY = (position.y - pos.y) / height; + if (localY <= 0.0 || localY >= 1.06) { + return HurricaneSample(0.0, 0.0); + } + + vec2 rel = position.xz - pos.xz; + float r = length(rel); + float theta = atan(rel.y, rel.x); + float intensity = saturate(StormIntensities[index]); + float spin = StormSpins[index]; + float seed = StormSeeds[index]; + + float eyeRadius = max(EyeRadii[index], 0.001); + float eyeClear = eyeRadiusAtHeight(index, localY, max(EyeClearRadii[index], eyeRadius * 1.04)); + float torusMajor = max(CanopyRadii[index], eyeClear + EyewallThicknesses[index] * 0.65); + float torusMinor = max(EyewallThicknesses[index], 0.001); + float torusCenterY = mix(CanopyBaseFactors[index], CanopyTopFactors[index], 0.56); + vec3 torusLocal = vec3(rel.x, (localY - torusCenterY) * height, rel.y); + float torusSdf = sdTorus(torusLocal, torusMajor, torusMinor, mix(0.78, 1.08, intensity)); + + vec2 flowUv = fract(rel * 0.010 + vec2(spin * 0.010, -AnimationTime * 0.012) + vec2(seed, localY * 0.23)); + vec2 flow = texture(FlowSampler, flowUv).rg * 2.0 - 1.0; + float baseTex = texture(BaseSampler, fract(rel * 0.018 + flow * 0.060 + vec2(spin * 0.004, localY * 0.17))).r; + float detailTex = texture(NoiseSampler, fract(rel * 0.055 - flow.yx * 0.025 + vec2(localY * 0.21, seed))).r; + float volumeNoise = fbm(vec3(rel * 0.055 + flow * 1.8, localY * 3.3 + seed * 7.0), 3, 2.0, 0.5, 1.0); + float noiseField = saturate(baseTex * 0.46 + detailTex * 0.26 + volumeNoise * 0.28 + 0.46); + + float torusBody = 1.0 - smoothstep(0.0, torusMinor * 0.72 + 0.35, torusSdf); + torusBody *= verticalWindow(localY, max(CanopyBaseFactors[index] - 0.12, 0.0), min(CanopyTopFactors[index] + 0.12, 1.02), 0.12); + torusBody *= smoothstep(eyeRadius * 0.98, eyeClear * 1.04, r); + torusBody *= mix(0.74, 1.32, noiseField); + + float torusRim = 1.0 - smoothstep(torusMinor * 0.08, torusMinor * 0.75, abs(torusSdf)); + float shieldHeight = verticalWindow(localY, ShieldBaseFactors[index], ShieldTopFactors[index], 0.10); + float shieldRadius = max(ShieldRadii[index], torusMajor + torusMinor * 1.8); + float veil = (1.0 - smoothstep(torusMajor * 0.92, shieldRadius, r)) * shieldHeight; + veil *= smoothstep(eyeClear * 1.08, eyeClear * 1.48, r); + veil *= mix(0.12, 0.30, noiseField) * FringeStrengths[index]; + + float bandWindow = band(r, BandStartRadii[index], BandEndRadii[index], max(BandWidths[index] * 0.22, 0.4)); + float bandAngle = theta * max(BandCounts[index], 1.0) - r * 0.038 - AnimationTime * (0.22 + intensity * 0.24) - spin * 0.068 + seed * TAU; + float spiral = smoothstep(0.38, 0.90, sin(bandAngle) * 0.5 + 0.5); + spiral *= bandWindow * BandStrengths[index]; + spiral *= verticalWindow(localY, ShieldBaseFactors[index], min(ShieldTopFactors[index] + 0.08, 1.02), 0.10); + spiral *= mix(0.30, 1.0, noiseField); + + float density = max(torusBody * mix(1.18, 1.92, intensity), spiral); + density = max(density, veil); + + float brightness = 0.24; + brightness += torusRim * 0.34; + brightness += smoothstep(CanopyBaseFactors[index], CanopyTopFactors[index], localY) * 0.30; + brightness += spiral * 0.08; + brightness = saturate(brightness); + + return HurricaneSample(density, brightness); +} + +void main() { + vec2 screenUv = gl_FragCoord.xy / OutSize; + float sceneDepth = texture(DepthSampler, screenUv).r; + float cappedDepth = sceneDepth < 1.0 ? sceneDepth : 1.0; + vec3 rayEnd = reconstructPosition(screenUv, cappedDepth); + vec3 ro = CameraPos; + vec3 rd = normalize(fragPos - ro); + + float tNear; + float tFar; + if (!intersectAabb(ro, rd, VolumeMin, VolumeMax, tNear, tFar)) { + discard; + } + tNear = max(tNear, 0.0); + float maxRay = min(tFar, MaxDistance); + if (sceneDepth < 1.0) { + maxRay = min(maxRay, length(rayEnd - ro)); + } + if (maxRay <= tNear + 0.001) { + discard; + } + + vec3 accum = vec3(0.0); + float transmittance = 1.0; + float nearestT = tNear; + float firstHitDepth = 1.0; + bool wroteDepth = false; + + float interval = maxRay - tNear; + int steps = int(clamp(interval / 0.92, 18.0, 56.0)); + float stepSize = interval / float(max(steps, 1)); + float jitter = hash1(screenUv.x * OutSize.x + screenUv.y * OutSize.y + 19.17); + float t = tNear + stepSize * (0.18 + jitter * 0.82); + + for (int step = 0; step < 56; step++) { + if (step >= steps || transmittance < 0.03) { + break; + } + + vec3 samplePos = ro + rd * t; + HurricaneSample storm = sampleStorm(0, samplePos); + float sigma = max(storm.density, 0.0) * 0.095; + if (sigma > 0.0005) { + if (!wroteDepth) { + firstHitDepth = clamp(cloudSpaceToDepth(samplePos), 0.0, 1.0); + wroteDepth = true; + } + float alpha = 1.0 - exp(-sigma * stepSize * 5.6); + vec3 lowColor = CloudColor.rgb * mix(0.16, 0.56, storm.brightness); + vec3 highColor = CloudColor.rgb * mix(0.66, 0.98, storm.brightness); + vec3 localColor = mix(lowColor, highColor, smoothstep(0.40, 1.0, storm.brightness)); + accum += localColor * alpha * transmittance; + transmittance *= (1.0 - alpha); + } + + t += stepSize; + } + + float alpha = 1.0 - transmittance; + if (alpha < 0.01) { + discard; + } + + vec3 color = accum / max(alpha, 0.0001); + float fogFactor = smoothstep(FogStart, FogEnd, nearestT); + color = mix(color, FogColor.rgb, fogFactor * 0.50); + + if (wroteDepth) { + gl_FragDepth = firstHitDepth; + } + fragColor = vec4(color, saturate(alpha)); +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds.json b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds.json new file mode 100644 index 00000000..7fa1a4da --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds.json @@ -0,0 +1,57 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "projectatmosphere:hurricane_volume_box", + "fragment": "projectatmosphere:hurricane_clouds", + "attributes": [ + "Position", + "UV0" + ], + "samplers": [ + { "name": "BaseSampler" }, + { "name": "NoiseSampler" }, + { "name": "FlowSampler" }, + { "name": "DepthSampler" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "InverseProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "InverseModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "VolumeMin", "type": "float", "count": 3, "values": [ 0.0, 0.0, 0.0 ] }, + { "name": "VolumeMax", "type": "float", "count": 3, "values": [ 1.0, 1.0, 1.0 ] }, + { "name": "CameraPos", "type": "float", "count": 3, "values": [ 0.0, 0.0, 0.0 ] }, + { "name": "CloudColor", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] }, + { "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "AnimationTime", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "MaxDistance", "type": "float", "count": 1, "values": [ 1100.0 ] }, + { "name": "OutSize", "type": "float", "count": 2, "values": [ 1.0, 1.0 ] }, + { "name": "StormCount", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "StormPositions", "type": "float", "count": 12, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormHeights", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeClearRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeSlopes", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "EyewallThicknesses", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "CanopyRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "ShieldRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "CanopyBaseFactors", "type": "float", "count": 4, "values": [ 0.2, 0.2, 0.2, 0.2 ] }, + { "name": "CanopyTopFactors", "type": "float", "count": 4, "values": [ 0.95, 0.95, 0.95, 0.95 ] }, + { "name": "ShieldBaseFactors", "type": "float", "count": 4, "values": [ 0.14, 0.14, 0.14, 0.14 ] }, + { "name": "ShieldTopFactors", "type": "float", "count": 4, "values": [ 0.86, 0.86, 0.86, 0.86 ] }, + { "name": "BandStartRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandEndRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandWidths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandStrengths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandCounts", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "FringeStrengths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormSpins", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormIntensities", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormSeeds", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] } + ] +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds.vsh b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds.vsh new file mode 100644 index 00000000..2c143541 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds.vsh @@ -0,0 +1,11 @@ +#version 430 + +in vec3 Position; +in vec2 UV0; + +out vec2 texCoord; + +void main() { + gl_Position = vec4(Position.xy, 0.0, 1.0); + texCoord = UV0; +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds_transparency.fsh b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds_transparency.fsh new file mode 100644 index 00000000..436ef3c1 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds_transparency.fsh @@ -0,0 +1,273 @@ +#version 430 + +uniform sampler2D BaseSampler; +uniform sampler2D NoiseSampler; +uniform sampler2D FlowSampler; +uniform sampler2D DepthSampler; + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; +uniform mat4 InverseProjMat; +uniform mat4 InverseModelViewMat; +uniform vec3 CameraPos; +uniform vec4 CloudColor; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; +uniform float AnimationTime; +uniform float MaxDistance; +uniform vec2 OutSize; +uniform vec3 VolumeMin; +uniform vec3 VolumeMax; +uniform int StormCount; +uniform float StormPositions[12]; +uniform vec4 StormHeights; +uniform vec4 EyeRadii; +uniform vec4 EyeClearRadii; +uniform vec4 EyeSlopes; +uniform vec4 EyewallThicknesses; +uniform vec4 CanopyRadii; +uniform vec4 ShieldRadii; +uniform vec4 CanopyBaseFactors; +uniform vec4 CanopyTopFactors; +uniform vec4 ShieldBaseFactors; +uniform vec4 ShieldTopFactors; +uniform vec4 BandStartRadii; +uniform vec4 BandEndRadii; +uniform vec4 BandWidths; +uniform vec4 BandStrengths; +uniform vec4 BandCounts; +uniform vec4 FringeStrengths; +uniform vec4 StormSpins; +uniform vec4 StormIntensities; +uniform vec4 StormSeeds; + +in vec2 texCoord; +in vec3 fragPos; + +layout(location = 0) out vec4 accumColor; +layout(location = 1) out float revealage; + +const float TAU = 6.28318530718; +const int MAX_STORMS_COUNT = 4; + +struct HurricaneSample { + float density; + float brightness; +}; + +float saturate(float value) { + return clamp(value, 0.0, 1.0); +} + +float hash1(float p) { + p = fract(p * 0.1031); + p *= p + 33.33; + p *= p + p; + return fract(p); +} + +float paHurricaneNoise3(vec3 p) { + vec3 i = floor(p); + vec3 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + + float n000 = hash1(dot(i + vec3(0.0, 0.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n100 = hash1(dot(i + vec3(1.0, 0.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n010 = hash1(dot(i + vec3(0.0, 1.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n110 = hash1(dot(i + vec3(1.0, 1.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n001 = hash1(dot(i + vec3(0.0, 0.0, 1.0), vec3(1.0, 57.0, 113.0))); + float n101 = hash1(dot(i + vec3(1.0, 0.0, 1.0), vec3(1.0, 57.0, 113.0))); + float n011 = hash1(dot(i + vec3(0.0, 1.0, 1.0), vec3(1.0, 57.0, 113.0))); + float n111 = hash1(dot(i + vec3(1.0, 1.0, 1.0), vec3(1.0, 57.0, 113.0))); + + float x00 = mix(n000, n100, f.x); + float x10 = mix(n010, n110, f.x); + float x01 = mix(n001, n101, f.x); + float x11 = mix(n011, n111, f.x); + float y0 = mix(x00, x10, f.y); + float y1 = mix(x01, x11, f.y); + return mix(y0, y1, f.z) * 2.0 - 1.0; +} + +float fbm(vec3 x, int octaves, float lacunarity, float gain, float amplitude) { + float y = 0.0; + for (int i = 0; i < octaves; i++) { + y += amplitude * paHurricaneNoise3(x); + x *= lacunarity; + amplitude *= gain; + } + return y; +} + +vec3 reconstructPosition(vec2 uv, float depth) { + vec4 ndc = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); + vec4 clip = InverseProjMat * ndc; + clip /= clip.w; + vec4 result = InverseModelViewMat * clip; + return result.xyz / result.w; +} + +bool intersectAabb(vec3 ro, vec3 rd, vec3 bmin, vec3 bmax, out float tNear, out float tFar) { + vec3 inv = 1.0 / rd; + vec3 t0 = (bmin - ro) * inv; + vec3 t1 = (bmax - ro) * inv; + vec3 tsmaller = min(t0, t1); + vec3 tbigger = max(t0, t1); + tNear = max(max(tsmaller.x, tsmaller.y), tsmaller.z); + tFar = min(min(tbigger.x, tbigger.y), tbigger.z); + return tFar > max(tNear, 0.0); +} + +vec3 getStormPos(int index) { + return vec3(StormPositions[index * 3], StormPositions[index * 3 + 1], StormPositions[index * 3 + 2]); +} + +float verticalWindow(float localY, float startFactor, float endFactor, float edge) { + float enter = smoothstep(startFactor - edge, startFactor + edge, localY); + float leave = 1.0 - smoothstep(endFactor - edge, endFactor + edge, localY); + return enter * leave; +} + +float band(float r, float inner, float outer, float soft) { + float enter = smoothstep(inner - soft, inner + soft, r); + float leave = 1.0 - smoothstep(outer - soft, outer + soft, r); + return enter * leave; +} + +float eyeRadiusAtHeight(int index, float localY, float baseRadius) { + float slope = max(EyeSlopes[index], 0.01); + return mix(baseRadius, baseRadius * slope, saturate(localY)); +} + +float sdTorus(vec3 p, float majorRadius, float minorRadius, float verticalScale) { + vec2 q = vec2(length(p.xz) - majorRadius, p.y / max(verticalScale, 0.01)); + return length(q) - minorRadius; +} + +HurricaneSample sampleStormFringe(int index, vec3 position) { + vec3 pos = getStormPos(index); + float height = max(StormHeights[index], 0.001); + float localY = (position.y - pos.y) / height; + if (localY <= 0.0 || localY >= 1.08) { + return HurricaneSample(0.0, 0.0); + } + + vec2 rel = position.xz - pos.xz; + float r = length(rel); + float theta = atan(rel.y, rel.x); + float intensity = saturate(StormIntensities[index]); + float spin = StormSpins[index]; + float seed = StormSeeds[index]; + float clearEye = eyeRadiusAtHeight(index, localY, max(EyeClearRadii[index], EyeRadii[index] * 1.05)); + + float torusMajor = max(CanopyRadii[index], clearEye + EyewallThicknesses[index] * 0.65); + float torusMinor = max(EyewallThicknesses[index], 0.001); + float torusCenterY = mix(CanopyBaseFactors[index], CanopyTopFactors[index], 0.56); + vec3 torusLocal = vec3(rel.x, (localY - torusCenterY) * height, rel.y); + float torusSdf = sdTorus(torusLocal, torusMajor, torusMinor, mix(0.82, 1.14, intensity)); + + vec2 flowUv = fract(rel * 0.009 + vec2(spin * 0.007, -AnimationTime * 0.010) + vec2(seed * 0.7, localY * 0.18)); + vec2 flow = texture(FlowSampler, flowUv).rg * 2.0 - 1.0; + float baseTex = texture(BaseSampler, fract(rel * 0.013 + flow * 0.052 + vec2(localY * 0.17, seed))).r; + float detailTex = texture(NoiseSampler, fract(rel * 0.049 - flow.yx * 0.018 + vec2(seed, localY * 0.29))).r; + float volumeNoise = fbm(vec3(rel * 0.045 + flow * 1.2, localY * 2.8 + seed * 5.0), 3, 2.0, 0.5, 1.0); + float noiseField = saturate(baseTex * 0.42 + detailTex * 0.30 + volumeNoise * 0.28 + 0.44); + + float torusShell = 1.0 - smoothstep(torusMinor * 0.10, torusMinor * 0.95, abs(torusSdf)); + torusShell *= verticalWindow(localY, max(CanopyBaseFactors[index] - 0.10, 0.0), min(CanopyTopFactors[index] + 0.16, 1.04), 0.12); + torusShell *= smoothstep(clearEye * 1.02, clearEye * 1.12, r); + torusShell *= mix(0.18, 0.52, noiseField); + + float shieldRadius = max(ShieldRadii[index], torusMajor + torusMinor * 1.8); + float shieldHeight = verticalWindow(localY, ShieldBaseFactors[index], min(ShieldTopFactors[index] + 0.10, 1.05), 0.10); + float shieldShell = band(r, torusMajor * 1.02, shieldRadius * 1.02, max(BandWidths[index] * 0.22, 0.55)); + shieldShell *= shieldHeight * mix(0.16, 0.40, noiseField) * FringeStrengths[index]; + + float spiralWindow = band(r, BandStartRadii[index], BandEndRadii[index], max(BandWidths[index] * 0.25, 0.55)); + float spiralPhase = theta * max(BandCounts[index], 1.0) - r * 0.040 - AnimationTime * (0.18 + intensity * 0.24) - spin * 0.060 + seed * TAU; + float spiral = smoothstep(0.50, 0.94, sin(spiralPhase) * 0.5 + 0.5); + spiral *= spiralWindow * BandStrengths[index] * FringeStrengths[index]; + spiral *= verticalWindow(localY, ShieldBaseFactors[index], min(ShieldTopFactors[index] + 0.14, 1.08), 0.10); + spiral *= mix(0.30, 1.0, noiseField); + + float veil = (1.0 - smoothstep(shieldRadius * 0.90, shieldRadius * 1.20, r)); + veil *= verticalWindow(localY, max(CanopyBaseFactors[index] - 0.06, 0.0), min(ShieldTopFactors[index] + 0.18, 1.08), 0.12); + veil *= smoothstep(clearEye * 1.24, clearEye * 1.72, r); + veil *= mix(0.08, 0.20, noiseField) * FringeStrengths[index]; + + float density = max(max(shieldShell, torusShell), spiral); + density = max(density, veil); + + float brightness = 0.28 + smoothstep(0.44, 1.0, localY) * 0.30 + spiral * 0.16 + torusShell * 0.12; + return HurricaneSample(density, saturate(brightness)); +} + +void main() { + vec2 screenUv = gl_FragCoord.xy / OutSize; + float sceneDepth = texture(DepthSampler, screenUv).r; + float cappedDepth = sceneDepth < 1.0 ? sceneDepth : 1.0; + vec3 rayEnd = reconstructPosition(screenUv, cappedDepth); + vec3 ro = CameraPos; + vec3 rd = normalize(fragPos - ro); + + float tNear; + float tFar; + if (!intersectAabb(ro, rd, VolumeMin, VolumeMax, tNear, tFar)) { + discard; + } + tNear = max(tNear, 0.0); + float maxRay = min(tFar, MaxDistance); + if (sceneDepth < 1.0) { + maxRay = min(maxRay, length(rayEnd - ro)); + } + if (maxRay <= tNear + 0.001) { + discard; + } + + vec3 accum = vec3(0.0); + float transmittance = 1.0; + float nearestT = tNear; + + float interval = maxRay - tNear; + int steps = int(clamp(interval / 1.15, 12.0, 34.0)); + float stepSize = interval / float(max(steps, 1)); + float jitter = hash1(screenUv.x * OutSize.x + screenUv.y * OutSize.y + 13.0); + float t = tNear + stepSize * (0.12 + jitter * 0.88); + + for (int step = 0; step < 34; step++) { + if (step >= steps || transmittance < 0.04) { + break; + } + + vec3 samplePos = ro + rd * t; + HurricaneSample storm = sampleStormFringe(0, samplePos); + float sigma = max(storm.density, 0.0) * 0.060; + if (sigma > 0.0004) { + float alpha = 1.0 - exp(-sigma * stepSize * 4.4); + vec3 lowColor = CloudColor.rgb * mix(0.26, 0.58, storm.brightness); + vec3 highColor = CloudColor.rgb * mix(0.70, 1.02, storm.brightness); + vec3 localColor = mix(lowColor, highColor, smoothstep(0.40, 1.0, storm.brightness)); + accum += localColor * alpha * transmittance; + transmittance *= (1.0 - alpha); + } + + t += stepSize; + } + + float alpha = 1.0 - transmittance; + if (alpha < 0.003) { + discard; + } + + vec3 color = accum / max(alpha, 0.0001); + float fogFactor = smoothstep(FogStart, FogEnd, nearestT); + color = mix(color, FogColor.rgb, fogFactor * 0.42); + + vec4 premul = vec4(color * alpha, alpha); + float z = min(nearestT / 1000.0, 1.0); + float weight = max(premul.a * 3000.0 * pow(1.0 - z, 3.0), 0.01); + + accumColor = premul * weight; + revealage = premul.a; +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds_transparency.json b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds_transparency.json new file mode 100644 index 00000000..dd8ef919 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_clouds_transparency.json @@ -0,0 +1,52 @@ +{ + "vertex": "projectatmosphere:hurricane_volume_box", + "fragment": "projectatmosphere:hurricane_clouds_transparency", + "attributes": [ + "Position", + "UV0" + ], + "samplers": [ + { "name": "BaseSampler" }, + { "name": "NoiseSampler" }, + { "name": "FlowSampler" }, + { "name": "DepthSampler" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "InverseProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "InverseModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "VolumeMin", "type": "float", "count": 3, "values": [ 0.0, 0.0, 0.0 ] }, + { "name": "VolumeMax", "type": "float", "count": 3, "values": [ 1.0, 1.0, 1.0 ] }, + { "name": "CameraPos", "type": "float", "count": 3, "values": [ 0.0, 0.0, 0.0 ] }, + { "name": "CloudColor", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] }, + { "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "AnimationTime", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "MaxDistance", "type": "float", "count": 1, "values": [ 1100.0 ] }, + { "name": "OutSize", "type": "float", "count": 2, "values": [ 1.0, 1.0 ] }, + { "name": "StormCount", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "StormPositions", "type": "float", "count": 12, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormHeights", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeClearRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeSlopes", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "EyewallThicknesses", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "CanopyRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "ShieldRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "CanopyBaseFactors", "type": "float", "count": 4, "values": [ 0.2, 0.2, 0.2, 0.2 ] }, + { "name": "CanopyTopFactors", "type": "float", "count": 4, "values": [ 0.95, 0.95, 0.95, 0.95 ] }, + { "name": "ShieldBaseFactors", "type": "float", "count": 4, "values": [ 0.14, 0.14, 0.14, 0.14 ] }, + { "name": "ShieldTopFactors", "type": "float", "count": 4, "values": [ 0.86, 0.86, 0.86, 0.86 ] }, + { "name": "BandStartRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandEndRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandWidths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandStrengths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandCounts", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "FringeStrengths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormSpins", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormIntensities", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormSeeds", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] } + ] +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask.fsh b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask.fsh new file mode 100644 index 00000000..3e16c638 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask.fsh @@ -0,0 +1,149 @@ +#version 430 + +uniform sampler2D SourceColorSampler; +uniform sampler2D SourceDepthSampler; + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; +uniform mat4 InverseProjMat; +uniform mat4 InverseModelViewMat; +uniform vec3 CameraPos; +uniform float AnimationTime; +uniform float MaxDistance; +uniform vec2 OutSize; +uniform int ProtectionEnabled; +uniform int StormCount; +uniform float StormPositions[12]; +uniform vec4 StormHeights; +uniform vec4 EyeRadii; +uniform vec4 EyeClearRadii; +uniform vec4 EyeSlopes; +uniform vec4 EyewallThicknesses; +uniform vec4 CanopyRadii; +uniform vec4 ShieldRadii; +uniform vec4 CanopyBaseFactors; +uniform vec4 CanopyTopFactors; +uniform vec4 ShieldBaseFactors; +uniform vec4 ShieldTopFactors; +uniform vec4 BandStartRadii; +uniform vec4 BandEndRadii; +uniform vec4 BandWidths; +uniform vec4 BandStrengths; +uniform vec4 BandCounts; +uniform vec4 FringeStrengths; +uniform vec4 StormSpins; +uniform vec4 StormIntensities; +uniform vec4 StormSeeds; + +in vec2 texCoord; +out vec4 fragColor; + +const int MAX_STORMS_COUNT = 4; + +vec3 reconstructPosition(vec2 uv, float depth) { + vec4 ndc = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); + vec4 clip = InverseProjMat * ndc; + clip /= clip.w; + vec4 result = InverseModelViewMat * clip; + return result.xyz / result.w; +} + +bool intersectAabb(vec3 ro, vec3 rd, vec3 bmin, vec3 bmax, out float tNear, out float tFar) { + vec3 inv = 1.0 / rd; + vec3 t0 = (bmin - ro) * inv; + vec3 t1 = (bmax - ro) * inv; + vec3 tsmaller = min(t0, t1); + vec3 tbigger = max(t0, t1); + tNear = max(max(tsmaller.x, tsmaller.y), tsmaller.z); + tFar = min(min(tbigger.x, tbigger.y), tbigger.z); + return tFar > max(tNear, 0.0); +} + +vec3 getStormPos(int index) { + return vec3(StormPositions[index * 3], StormPositions[index * 3 + 1], StormPositions[index * 3 + 2]); +} + +float eyeRadiusAtHeight(int index, float localY, float baseRadius) { + float slope = max(EyeSlopes[index], 0.01); + return mix(baseRadius, baseRadius * slope, clamp(localY, 0.0, 1.0)); +} + +bool rayHitsProtectedEye(int index, vec3 ro, vec3 rd, float maxT) { + vec3 stormPos = getStormPos(index); + float height = max(StormHeights[index], 0.001); + float maxEyeRadius = max(EyeClearRadii[index], EyeClearRadii[index] * EyeSlopes[index]) * 1.08; + vec3 bmin = vec3(stormPos.x - maxEyeRadius, stormPos.y - 2.0, stormPos.z - maxEyeRadius); + vec3 bmax = vec3(stormPos.x + maxEyeRadius, stormPos.y + height + 2.0, stormPos.z + maxEyeRadius); + + float tNear; + float tFar; + if (!intersectAabb(ro, rd, bmin, bmax, tNear, tFar)) { + return false; + } + + tNear = max(tNear, 0.0); + tFar = min(tFar, maxT); + if (tFar <= tNear) { + return false; + } + + float interval = tFar - tNear; + int steps = int(clamp(interval / 0.95, 10.0, 18.0)); + float stepSize = interval / float(max(steps, 1)); + float t = tNear + stepSize * 0.35; + + for (int step = 0; step < 18; step++) { + if (step >= steps) { + break; + } + + vec3 samplePos = ro + rd * t; + float localY = (samplePos.y - stormPos.y) / height; + if (localY >= 0.0 && localY <= 1.04) { + vec2 rel = samplePos.xz - stormPos.xz; + float clearRadius = eyeRadiusAtHeight(index, localY, max(EyeClearRadii[index], EyeRadii[index] * 1.04)); + if (length(rel) < clearRadius) { + return true; + } + } + t += stepSize; + } + + return false; +} + +void main() { + vec4 sourceColor = texture(SourceColorSampler, texCoord); + float sourceDepth = texture(SourceDepthSampler, texCoord).r; + + if (ProtectionEnabled == 0 || StormCount <= 0) { + fragColor = sourceColor; + gl_FragDepth = sourceDepth; + return; + } + + float rayDepth = sourceDepth < 1.0 ? sourceDepth : 1.0; + vec3 rayEnd = reconstructPosition(texCoord, rayDepth); + vec3 ro = CameraPos; + vec3 rd = normalize(rayEnd - ro); + float maxT = sourceDepth < 1.0 ? length(rayEnd - ro) : MaxDistance; + if (maxT <= 0.001) { + fragColor = sourceColor; + gl_FragDepth = sourceDepth; + return; + } + + for (int i = 0; i < MAX_STORMS_COUNT; i++) { + if (i >= StormCount) { + break; + } + if (rayHitsProtectedEye(i, ro, rd, maxT)) { + fragColor = vec4(0.0); + gl_FragDepth = 1.0; + return; + } + } + + fragColor = sourceColor; + gl_FragDepth = sourceDepth; +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask.json b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask.json new file mode 100644 index 00000000..a793f555 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask.json @@ -0,0 +1,48 @@ +{ + "vertex": "projectatmosphere:hurricane_clouds", + "fragment": "projectatmosphere:hurricane_eye_mask", + "attributes": [ + "Position", + "UV0" + ], + "samplers": [ + { "name": "SourceColorSampler" }, + { "name": "SourceDepthSampler" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "InverseProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "InverseModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "CameraPos", "type": "float", "count": 3, "values": [ 0.0, 0.0, 0.0 ] }, + { "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] }, + { "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "AnimationTime", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "MaxDistance", "type": "float", "count": 1, "values": [ 1100.0 ] }, + { "name": "OutSize", "type": "float", "count": 2, "values": [ 1.0, 1.0 ] }, + { "name": "ProtectionEnabled", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "StormCount", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "StormPositions", "type": "float", "count": 12, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormHeights", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeClearRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeSlopes", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "EyewallThicknesses", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "CanopyRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "ShieldRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "CanopyBaseFactors", "type": "float", "count": 4, "values": [ 0.2, 0.2, 0.2, 0.2 ] }, + { "name": "CanopyTopFactors", "type": "float", "count": 4, "values": [ 0.95, 0.95, 0.95, 0.95 ] }, + { "name": "ShieldBaseFactors", "type": "float", "count": 4, "values": [ 0.14, 0.14, 0.14, 0.14 ] }, + { "name": "ShieldTopFactors", "type": "float", "count": 4, "values": [ 0.86, 0.86, 0.86, 0.86 ] }, + { "name": "BandStartRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandEndRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandWidths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandStrengths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandCounts", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "FringeStrengths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormSpins", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormIntensities", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormSeeds", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] } + ] +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask_transparency.fsh b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask_transparency.fsh new file mode 100644 index 00000000..ef6777b8 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask_transparency.fsh @@ -0,0 +1,157 @@ +#version 430 + +uniform sampler2D SourceAccumSampler; +uniform sampler2D SourceRevealageSampler; +uniform sampler2D SourceDepthSampler; + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; +uniform mat4 InverseProjMat; +uniform mat4 InverseModelViewMat; +uniform vec3 CameraPos; +uniform float AnimationTime; +uniform float MaxDistance; +uniform vec2 OutSize; +uniform int ProtectionEnabled; +uniform int StormCount; +uniform float StormPositions[12]; +uniform vec4 StormHeights; +uniform vec4 EyeRadii; +uniform vec4 EyeClearRadii; +uniform vec4 EyeSlopes; +uniform vec4 EyewallThicknesses; +uniform vec4 CanopyRadii; +uniform vec4 ShieldRadii; +uniform vec4 CanopyBaseFactors; +uniform vec4 CanopyTopFactors; +uniform vec4 ShieldBaseFactors; +uniform vec4 ShieldTopFactors; +uniform vec4 BandStartRadii; +uniform vec4 BandEndRadii; +uniform vec4 BandWidths; +uniform vec4 BandStrengths; +uniform vec4 BandCounts; +uniform vec4 FringeStrengths; +uniform vec4 StormSpins; +uniform vec4 StormIntensities; +uniform vec4 StormSeeds; + +in vec2 texCoord; + +layout(location = 0) out vec4 accumColor; +layout(location = 1) out float revealage; + +const int MAX_STORMS_COUNT = 4; + +vec3 reconstructPosition(vec2 uv, float depth) { + vec4 ndc = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); + vec4 clip = InverseProjMat * ndc; + clip /= clip.w; + vec4 result = InverseModelViewMat * clip; + return result.xyz / result.w; +} + +bool intersectAabb(vec3 ro, vec3 rd, vec3 bmin, vec3 bmax, out float tNear, out float tFar) { + vec3 inv = 1.0 / rd; + vec3 t0 = (bmin - ro) * inv; + vec3 t1 = (bmax - ro) * inv; + vec3 tsmaller = min(t0, t1); + vec3 tbigger = max(t0, t1); + tNear = max(max(tsmaller.x, tsmaller.y), tsmaller.z); + tFar = min(min(tbigger.x, tbigger.y), tbigger.z); + return tFar > max(tNear, 0.0); +} + +vec3 getStormPos(int index) { + return vec3(StormPositions[index * 3], StormPositions[index * 3 + 1], StormPositions[index * 3 + 2]); +} + +float eyeRadiusAtHeight(int index, float localY, float baseRadius) { + float slope = max(EyeSlopes[index], 0.01); + return mix(baseRadius, baseRadius * slope, clamp(localY, 0.0, 1.0)); +} + +bool rayHitsProtectedEye(int index, vec3 ro, vec3 rd, float maxT) { + vec3 stormPos = getStormPos(index); + float height = max(StormHeights[index], 0.001); + float maxEyeRadius = max(EyeClearRadii[index], EyeClearRadii[index] * EyeSlopes[index]) * 1.08; + vec3 bmin = vec3(stormPos.x - maxEyeRadius, stormPos.y - 2.0, stormPos.z - maxEyeRadius); + vec3 bmax = vec3(stormPos.x + maxEyeRadius, stormPos.y + height + 2.0, stormPos.z + maxEyeRadius); + + float tNear; + float tFar; + if (!intersectAabb(ro, rd, bmin, bmax, tNear, tFar)) { + return false; + } + + tNear = max(tNear, 0.0); + tFar = min(tFar, maxT); + if (tFar <= tNear) { + return false; + } + + float interval = tFar - tNear; + int steps = int(clamp(interval / 0.95, 10.0, 18.0)); + float stepSize = interval / float(max(steps, 1)); + float t = tNear + stepSize * 0.35; + + for (int step = 0; step < 18; step++) { + if (step >= steps) { + break; + } + + vec3 samplePos = ro + rd * t; + float localY = (samplePos.y - stormPos.y) / height; + if (localY >= 0.0 && localY <= 1.04) { + vec2 rel = samplePos.xz - stormPos.xz; + float clearRadius = eyeRadiusAtHeight(index, localY, max(EyeClearRadii[index], EyeRadii[index] * 1.04)); + if (length(rel) < clearRadius) { + return true; + } + } + t += stepSize; + } + + return false; +} + +void main() { + vec4 sourceAccum = texture(SourceAccumSampler, texCoord); + float sourceRevealage = texture(SourceRevealageSampler, texCoord).r; + float sourceDepth = texture(SourceDepthSampler, texCoord).r; + + if (ProtectionEnabled == 0 || StormCount <= 0) { + accumColor = sourceAccum; + revealage = sourceRevealage; + gl_FragDepth = sourceDepth; + return; + } + + float rayDepth = sourceDepth < 1.0 ? sourceDepth : 1.0; + vec3 rayEnd = reconstructPosition(texCoord, rayDepth); + vec3 ro = CameraPos; + vec3 rd = normalize(rayEnd - ro); + float maxT = sourceDepth < 1.0 ? length(rayEnd - ro) : MaxDistance; + if (maxT <= 0.001) { + accumColor = sourceAccum; + revealage = sourceRevealage; + gl_FragDepth = sourceDepth; + return; + } + + for (int i = 0; i < MAX_STORMS_COUNT; i++) { + if (i >= StormCount) { + break; + } + if (rayHitsProtectedEye(i, ro, rd, maxT)) { + accumColor = vec4(0.0); + revealage = 1.0; + gl_FragDepth = 1.0; + return; + } + } + + accumColor = sourceAccum; + revealage = sourceRevealage; + gl_FragDepth = sourceDepth; +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask_transparency.json b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask_transparency.json new file mode 100644 index 00000000..8245ef34 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_eye_mask_transparency.json @@ -0,0 +1,49 @@ +{ + "vertex": "projectatmosphere:hurricane_clouds", + "fragment": "projectatmosphere:hurricane_eye_mask_transparency", + "attributes": [ + "Position", + "UV0" + ], + "samplers": [ + { "name": "SourceAccumSampler" }, + { "name": "SourceRevealageSampler" }, + { "name": "SourceDepthSampler" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "InverseProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "InverseModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "CameraPos", "type": "float", "count": 3, "values": [ 0.0, 0.0, 0.0 ] }, + { "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] }, + { "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "AnimationTime", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "MaxDistance", "type": "float", "count": 1, "values": [ 1100.0 ] }, + { "name": "OutSize", "type": "float", "count": 2, "values": [ 1.0, 1.0 ] }, + { "name": "ProtectionEnabled", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "StormCount", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "StormPositions", "type": "float", "count": 12, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormHeights", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeClearRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "EyeSlopes", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "EyewallThicknesses", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "CanopyRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "ShieldRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "CanopyBaseFactors", "type": "float", "count": 4, "values": [ 0.2, 0.2, 0.2, 0.2 ] }, + { "name": "CanopyTopFactors", "type": "float", "count": 4, "values": [ 0.95, 0.95, 0.95, 0.95 ] }, + { "name": "ShieldBaseFactors", "type": "float", "count": 4, "values": [ 0.14, 0.14, 0.14, 0.14 ] }, + { "name": "ShieldTopFactors", "type": "float", "count": 4, "values": [ 0.86, 0.86, 0.86, 0.86 ] }, + { "name": "BandStartRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandEndRadii", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandWidths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandStrengths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "BandCounts", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "FringeStrengths", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormSpins", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormIntensities", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormSeeds", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] } + ] +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_volume_box.vsh b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_volume_box.vsh new file mode 100644 index 00000000..8bf57530 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/hurricane_volume_box.vsh @@ -0,0 +1,19 @@ +#version 430 + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; +uniform vec3 VolumeMin; +uniform vec3 VolumeMax; + +in vec3 Position; +in vec2 UV0; + +out vec2 texCoord; +out vec3 fragPos; + +void main() { + vec3 worldPos = mix(VolumeMin, VolumeMax, Position); + gl_Position = ProjMat * ModelViewMat * vec4(worldPos, 1.0); + texCoord = UV0; + fragPos = worldPos; +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/tornado.fsh b/src/main/resources/assets/projectatmosphere/shaders/core/tornado.fsh deleted file mode 100644 index cb86a650..00000000 --- a/src/main/resources/assets/projectatmosphere/shaders/core/tornado.fsh +++ /dev/null @@ -1,148 +0,0 @@ -#version 150 - -// ──────── Uniforms ──────── -uniform float Time; -uniform float TwistSpeed; -uniform float BaseRadius; -uniform float TopRadius; -uniform float Height; -uniform float DustIntensity; -uniform float CoreTightness; -uniform float FlowIntensity; -uniform float Scale; - -uniform float LightDirX; -uniform float LightDirY; -uniform float LightDirZ; - -uniform sampler2D Sampler0; -uniform sampler2D FlowMap; -uniform sampler2D NormalMap; -uniform sampler2D NoiseMap; -uniform sampler2D CloudScene; -uniform float ScreenSizeX; -uniform float ScreenSizeY; - -// ──────── Varyings ──────── -in vec2 texCoord; -out vec4 fragColor; - -// ──────── Constants ──────── -const float PI = 3.14159265; - -// ──────── Noise Functions ──────── -float noise(in vec3 x) { - vec3 p = floor(x); - vec3 f = fract(x); - f = f * f * (3.0 - 2.0 * f); - vec2 uv = (p.xy + vec2(37.0, 17.0) * p.z) + f.xy; - vec2 rg = textureLod(NoiseMap, (uv + 0.5) / 256.0, 0.0).yx; - return -1.0 + 2.4 * mix(rg.x, rg.y, f.z); -} - -mat2 Spin(float angle) { - return mat2(cos(angle), -sin(angle), sin(angle), cos(angle)); -} - -float ridged(float f) { - return 1.0 - 2.0 * abs(f); -} - -float Shape(vec3 q) { - q.y += 45.0; - float h = 90.0; - float t = Time; - vec3 spin_pos = vec3(Spin(t - sqrt(q.y)) * q.xz, q.y - t * 5.0); - float zcurve = pow(q.y, 1.5) * 0.03; - float v = abs(length(q.xz) - zcurve) - 5.5 - clamp(zcurve * 0.2, 0.1, 1.0) * noise(spin_pos * vec3(0.1)); - v = v - ridged(noise(vec3(Spin(t * 1.5 + 0.1 * q.y) * q.xz, q.y - t * 4.0) * 0.3)) * 1.2; - v = max(v, q.y - h); - return min(max(v, -q.y), 0.0) + max(v, -q.y); -} - -// ──────── Helpers ──────── -float verticalFade(float y) { - float top = smoothstep(1.0, 0.7, y); - float bottom = smoothstep(0.0, 0.1, y); - return top * bottom; -} - -vec2 buildSwirlUV(float angle, float y, vec2 offset) { - vec2 uv; - uv.x = mod(angle / (2.0 * PI), 1.0); - uv.y = y; - uv += offset * 0.01; - return uv; -} - -// ──────── Main ──────── -void main() { - // v = normalized vertical coordinate (0..1) from mesh UVs - float v = clamp(texCoord.y, 0.0, 1.0); - - // Apply overall scale - float baseR = BaseRadius * Scale; - float topR = TopRadius * Scale; - float height = Height * Scale; - - // yWorld if/when you need actual units (blocks/meters) - float yWorld = v * height; - - float angle = texCoord.x * 2.0 * PI; - - // Rotation based on time + vertical factor - float rotation = Time * TwistSpeed + v * 12.0; - - // Core falloff uses normalized v (keep this as 0..1) - float coreFalloff = exp(-pow((baseR - (baseR - topR) * v) * CoreTightness, 2.0)); - - // Large-scale swirls - vec2 polar = vec2(texCoord.x, v); - float n1 = noise(vec3(polar * 3.0, Time * 0.05)); - float n2 = noise(vec3(polar * 6.0, -Time * 0.04)); - angle += rotation + smoothstep(0.0, 1.0, n1) * 4.0; - - // Fine turbulence - vec2 offset; - offset.x = noise(vec3(texCoord * 5.0, Time * 0.02)); - offset.y = noise(vec3(texCoord.yx * 5.0, -Time * 0.02)); - angle += (offset.x - 0.5) * 3.0; - - // Final UVs - vec2 swirlUV = buildSwirlUV(angle, v, offset); - - // World position for volumetric shaping - float radius = mix(baseR, topR, v); - vec2 pos = vec2(cos(angle), sin(angle)) * radius; - vec3 shapePos = vec3(pos.x, yWorld, pos.y); - float density = max(-Shape(shapePos), 0.0); - float filledDensity = clamp(density * 1.35, 0.6, 1.5); - - // Flow warp - vec2 flow = texture(FlowMap, texCoord).rg - 0.5; - swirlUV += flow * FlowIntensity; - - // Base + normal/lighting - vec4 base = texture(Sampler0, swirlUV); - vec3 normal = normalize(texture(NormalMap, swirlUV).rgb * 2.0 - 1.0); - vec3 lightDir = normalize(vec3(LightDirX, LightDirY, LightDirZ)); - float lighting = max(dot(normal, lightDir), 0.0); - - // Screen-space cloud sampling (fix: don't normalize screen size) - vec2 scrUV = gl_FragCoord.xy / vec2(ScreenSizeX, ScreenSizeY); - vec3 cloudTint = texture(CloudScene, scrUV).rgb; - - float k = clamp(DustIntensity, 0.0, 1.0); - vec3 color = mix(base.rgb, vec3(DustIntensity), k); - color = mix(color, cloudTint, 0.35); - color *= 0.5 + 0.5 * lighting; - color *= 0.7 + 0.3 * filledDensity; - - // Alpha uses normalized v (0..1) - float alphaBase = max(base.a, 0.95); - float alpha = alphaBase * verticalFade(v); - alpha *= clamp(filledDensity, 0.8, 1.2); - alpha = clamp(alpha * coreFalloff, 0.8, 1.0); - - fragColor = vec4(color, alpha); -} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/tornado.json b/src/main/resources/assets/projectatmosphere/shaders/core/tornado.json deleted file mode 100644 index 19f3611f..00000000 --- a/src/main/resources/assets/projectatmosphere/shaders/core/tornado.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "vertex": "projectatmosphere:tornado", - "fragment": "projectatmosphere:tornado", - "attributes": ["Position", "UV0"], - "samplers": [ - { "name": "Sampler0" }, - { "name": "FlowMap" }, - { "name": "NormalMap" }, - { "name": "NoiseMap" }, - { "name": "CloudScene" } - ], - "uniforms": [ - { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] }, - { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] }, - - { "name": "Time", "type": "float", "count": 1, "values": [0.0] }, - { "name": "TwistSpeed", "type": "float", "count": 1, "values": [1.0] }, - { "name": "BaseRadius", "type": "float", "count": 1, "values": [0.2] }, - { "name": "TopRadius", "type": "float", "count": 1, "values": [3.0] }, - { "name": "Height", "type": "float", "count": 1, "values": [300.0] }, - { "name": "DustIntensity","type": "float", "count": 1, "values": [0.5] }, - { "name": "CoreTightness","type": "float", "count": 1, "values": [0.5] }, - { "name": "FlowIntensity","type": "float", "count": 1, "values": [0.2] }, - { "name": "Scale", "type": "float", "count": 1, "values": [1.0] }, - { "name": "LightDirX", "type": "float", "count": 1, "values": [0.5] }, - { "name": "LightDirY", "type": "float", "count": 1, "values": [1.0] }, - { "name": "LightDirZ", "type": "float", "count": 1, "values": [0.2] }, - { "name": "ScreenSizeX", "type": "float", "count": 1, "values": [0.1] }, - { "name": "ScreenSizeY", "type": "float", "count": 1, "values": [0.2] } - ], - - "blend": { - "func": "add", - "srcrgb": "srcalpha", - "dstrgb": "1-srcalpha", - "srcalpha": "1", - "dstalpha": "0" - }, - - "depthTest": "lequal", - "depthWrite": true, - "cull": "none" -} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/tornado.vsh b/src/main/resources/assets/projectatmosphere/shaders/core/tornado.vsh deleted file mode 100644 index 69825467..00000000 --- a/src/main/resources/assets/projectatmosphere/shaders/core/tornado.vsh +++ /dev/null @@ -1,13 +0,0 @@ -#version 150 -in vec3 Position; -in vec2 UV0; - -out vec2 texCoord; - -uniform mat4 ModelViewMat; -uniform mat4 ProjMat; - -void main() { - texCoord = UV0; - gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0); -} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/tornado_composite.fsh b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_composite.fsh new file mode 100644 index 00000000..9eaae65d --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_composite.fsh @@ -0,0 +1,44 @@ +#version 150 + +uniform sampler2D TornadoColorSampler; +uniform sampler2D TornadoDepthSampler; +uniform sampler2D SceneDepthSampler; +uniform int WriteCompositeDepth; + +in vec2 texCoord; +out vec4 fragColor; + +void main() { + vec2 texel = 1.0 / vec2(textureSize(TornadoColorSampler, 0)); + vec3 weightedColor = vec3(0.0); + float weightedAlpha = 0.0; + float totalWeight = 0.0; + + for (int y = -1; y <= 1; y++) { + for (int x = -1; x <= 1; x++) { + vec2 offset = vec2(float(x), float(y)); + float distanceWeight = x == 0 && y == 0 ? 4.0 : (x == 0 || y == 0 ? 2.0 : 1.0); + vec4 sampleColor = texture(TornadoColorSampler, texCoord + offset * texel); + float alphaWeight = sampleColor.a * distanceWeight; + weightedColor += sampleColor.rgb * alphaWeight; + weightedAlpha += sampleColor.a * distanceWeight; + totalWeight += distanceWeight; + } + } + + float alpha = weightedAlpha / max(totalWeight, 0.0001); + if (alpha <= 0.006) { + discard; + } + + if (WriteCompositeDepth != 0) { + float tornadoDepth = texture(TornadoDepthSampler, texCoord).r; + if (tornadoDepth >= 1.0) { + discard; + } + gl_FragDepth = tornadoDepth; + } + + vec3 color = weightedColor / max(weightedAlpha, 0.0001); + fragColor = vec4(color, alpha); +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/tornado_composite.json b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_composite.json new file mode 100644 index 00000000..52323981 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_composite.json @@ -0,0 +1,21 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "projectatmosphere:tornado_round", + "fragment": "projectatmosphere:tornado_composite", + "attributes": [ + "Position", + "UV0" + ], + "samplers": [ + { "name": "TornadoColorSampler" }, + { "name": "TornadoDepthSampler" }, + { "name": "SceneDepthSampler" } + ], + "uniforms": [ + { "name": "WriteCompositeDepth", "type": "int", "count": 1, "values": [ 0 ] } + ] +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/tornado_round.fsh b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_round.fsh new file mode 100644 index 00000000..cc046213 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_round.fsh @@ -0,0 +1,864 @@ +#version 150 + +uniform sampler2D TornadoSampler; +uniform sampler2D NoiseSampler; +uniform sampler2D FlowSampler; +uniform sampler2D DepthSampler; +uniform sampler2D SecondaryDepthSampler; + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; +uniform mat4 InverseProjMat; +uniform mat4 InverseModelViewMat; +uniform vec3 CameraPos; +uniform vec4 CloudColor; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; +uniform float AnimationTime; +uniform float MaxDistance; +uniform vec2 OutSize; +uniform vec3 VolumeMin; +uniform vec3 VolumeMax; +uniform float CloudScale; +uniform float RenderQuality; +uniform int UseSecondaryDepthSampler; +uniform int StormCount; +uniform int DebugMode; +uniform int DebugSelectedStorm; +uniform int DebugFreeze; +uniform int DistantHorizonsDepthMode; +uniform int DeferSceneDepthReject; +uniform float StormPositions[24]; +uniform float StormHeights[8]; +uniform float StormWidths[8]; +uniform float StormSizes[8]; +uniform float StormSpins[8]; +uniform float StormIntensities[8]; +uniform float StormShapes[8]; +uniform float StormProgress[8]; + +in vec2 texCoord; +in vec3 fragPos; +out vec4 fragColor; + +const float PI = 3.1415926535897932384626433832795; +const float TAU = 6.2831853071795864769252867665590; +const int MAX_STORMS_COUNT = 8; +const int DEBUG_OFF = 0; +const int DEBUG_BOX = 1; +const int DEBUG_HIT = 2; +const int DEBUG_FILL = 3; +const int DEBUG_FUNNEL = 4; +const int DEBUG_HEIGHT = 5; +const int DEBUG_RADIAL = 6; +const int DEBUG_RADIUS = 7; +const int DEBUG_DENSITY = 8; +const int DEBUG_ALPHA = 9; +const int DEBUG_WALLCLOUD = 10; +const int DEBUG_CONNECTION = 11; +const int DEBUG_GROUNDSKIRT = 12; +const int DEBUG_FULL = 13; +const int DEBUG_DEPTH = 14; +const int DEBUG_DEPTH_NO_FRAMEBUFFER = 15; +const int DEBUG_OCCLUSION = 16; +const int DEBUG_COVERAGE = 17; +const float TORNADO_ROTATION_SPEED_MULTIPLIER = 3.0; +const float TORNADO_TOP_DARKEN_FACTOR = 0.45; +const float SOFT_TERRAIN_OCCLUSION_BIAS = 0.50; + +struct StormSample { + float cloud; + float dust; + float upper; + float material; +}; + +struct DebugMasks { + float heightMask; + float radialMask; + float radiusMask; + float density; + float alpha; + float wallcloud; + float connection; + float groundSkirt; +}; + +float selectDebugMask(DebugMasks masks); +DebugMasks sampleDebugMasks(int index, vec3 position); +StormSample sampleFrozenStorm(int index, vec3 position); +StormSample sampleStorm(int index, vec3 position); + +float saturate(float value) { + return clamp(value, 0.0, 1.0); +} + +float hash1(float p) { + p = fract(p * 0.1031); + p *= p + 33.33; + p *= p + p; + return fract(p); +} + +float onoise(vec3 pos) { + vec3 x = pos * 2.0; + vec3 p = floor(x); + vec3 f = fract(x); + f = f * f * (3.0 - 2.0 * f); + float n = p.x + p.y * 57.0 + 113.0 * p.z; + return mix( + mix( + mix(hash1(n + 0.0), hash1(n + 1.0), f.x), + mix(hash1(n + 57.0), hash1(n + 58.0), f.x), + f.y + ), + mix( + mix(hash1(n + 113.0), hash1(n + 114.0), f.x), + mix(hash1(n + 170.0), hash1(n + 171.0), f.x), + f.y + ), + f.z + ); +} + +float paTornadoNoise3(vec3 p) { + vec3 i = floor(p); + vec3 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + + float n000 = hash1(dot(i + vec3(0.0, 0.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n100 = hash1(dot(i + vec3(1.0, 0.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n010 = hash1(dot(i + vec3(0.0, 1.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n110 = hash1(dot(i + vec3(1.0, 1.0, 0.0), vec3(1.0, 57.0, 113.0))); + float n001 = hash1(dot(i + vec3(0.0, 0.0, 1.0), vec3(1.0, 57.0, 113.0))); + float n101 = hash1(dot(i + vec3(1.0, 0.0, 1.0), vec3(1.0, 57.0, 113.0))); + float n011 = hash1(dot(i + vec3(0.0, 1.0, 1.0), vec3(1.0, 57.0, 113.0))); + float n111 = hash1(dot(i + vec3(1.0, 1.0, 1.0), vec3(1.0, 57.0, 113.0))); + + float x00 = mix(n000, n100, f.x); + float x10 = mix(n010, n110, f.x); + float x01 = mix(n001, n101, f.x); + float x11 = mix(n011, n111, f.x); + float y0 = mix(x00, x10, f.y); + float y1 = mix(x01, x11, f.y); + return mix(y0, y1, f.z) * 2.0 - 1.0; +} + +float fbm(vec3 x, int octaves, float lacunarity, float gain, float amplitude) { + float y = 0.0; + for (int i = 0; i < octaves; i++) { + y += amplitude * paTornadoNoise3(x); + x *= lacunarity; + amplitude *= gain; + } + return y; +} + +mat2 spin(float angle) { + return mat2(cos(angle), -sin(angle), sin(angle), cos(angle)); +} + +float cloudToWorld(float value) { + return value * CloudScale; +} + +vec3 cloudToWorld(vec3 value) { + return value * CloudScale; +} + +vec3 reconstructPosition(vec2 uv, float depth) { + vec4 ndc = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); + vec4 clip = InverseProjMat * ndc; + clip /= clip.w; + vec4 result = InverseModelViewMat * clip; + return result.xyz / result.w; +} + +float cloudSpaceToDepth(vec3 pos) { + vec4 clip = ProjMat * ModelViewMat * vec4(pos, 1.0); + float ndcZ = clip.z / clip.w; + return ndcZ * 0.5 + 0.5; +} + +float sampleSceneDepth(vec2 uv) { + float primaryDepth = texture(DepthSampler, uv).r; + if (UseSecondaryDepthSampler == 0) { + return primaryDepth; + } + + float secondaryDepth = texture(SecondaryDepthSampler, uv).r; + if (primaryDepth >= 1.0) { + return secondaryDepth; + } + if (secondaryDepth >= 1.0) { + return primaryDepth; + } + return min(primaryDepth, secondaryDepth); +} + +float samplePrimarySceneDepth(vec2 uv) { + return texture(DepthSampler, uv).r; +} + +float sampleSecondarySceneDepth(vec2 uv) { + return texture(SecondaryDepthSampler, uv).r; +} + +float sampleFirstHitField(vec3 position, bool debugActive, bool debugMaskMode) { + if (debugMaskMode) { + return selectDebugMask(sampleDebugMasks(0, position)); + } + + StormSample storm = debugActive && DebugFreeze != 0 + ? sampleFrozenStorm(0, position) + : sampleStorm(0, position); + return max(storm.cloud, 0.0) * 0.285; +} + +float refineFirstHitT(vec3 ro, vec3 rd, float startT, float endT, bool debugActive, bool debugMaskMode) { + float low = startT; + float high = endT; + for (int iteration = 0; iteration < 3; iteration++) { + float mid = mix(low, high, 0.5); + float value = sampleFirstHitField(ro + rd * mid, debugActive, debugMaskMode); + if (value > 0.0005) { + high = mid; + } else { + low = mid; + } + } + return high; +} + +bool intersectAabb(vec3 ro, vec3 rd, vec3 bmin, vec3 bmax, out float tNear, out float tFar) { + vec3 inv = 1.0 / rd; + vec3 t0 = (bmin - ro) * inv; + vec3 t1 = (bmax - ro) * inv; + vec3 tsmaller = min(t0, t1); + vec3 tbigger = max(t0, t1); + tNear = max(max(tsmaller.x, tsmaller.y), tsmaller.z); + tFar = min(min(tbigger.x, tbigger.y), tbigger.z); + return tFar > max(tNear, 0.0); +} + +vec3 getStormPos(int index) { + return vec3(StormPositions[index * 3], StormPositions[index * 3 + 1], StormPositions[index * 3 + 2]); +} + +float computeStormDetailQuality(int index) { + vec3 cameraPosWorld = cloudToWorld(CameraPos); + vec3 stormPosWorld = cloudToWorld(getStormPos(index)); + float horizontalDistanceWorld = distance(cameraPosWorld.xz, stormPosWorld.xz); + float distanceQuality = 1.0 - smoothstep(96.0, 320.0, horizontalDistanceWorld); + float screenProxyQuality = saturate(cloudToWorld(StormWidths[index]) / 42.0); + float retainedQuality = max(distanceQuality, screenProxyQuality * 0.45); + return clamp(RenderQuality * mix(0.42, 1.0, retainedQuality), 0.25, 1.0); +} + +float sampleFunnelRadiusWorld(float widthWorld, float stormSizeWorld, float tornadoShape, float torPerc, float percFnlHeight) { + float torShape = mix(tornadoShape, 20.0, saturate(widthWorld / 62.5)); + float widWorld = (widthWorld / 2.5) + + ((widthWorld / 2.5) * percFnlHeight * torPerc) + + ((stormSizeWorld / mix(torShape + 2.0, torShape, torPerc)) * pow(percFnlHeight, 4.0)); + return mix(widWorld, 0.0, (1.0 - percFnlHeight) * (1.0 - torPerc)); +} + +float sampleMaterialField(vec3 localTorPosWorld, float percFnlHeight, float widPerc, float widWorld, + float spinPhaseA, float spinPhaseB, float animTime, float detailQuality, bool freezeDebug) { + if (freezeDebug) { + return 1.0; + } + + if (detailQuality < 0.45) { + float coarse = paTornadoNoise3(vec3(localTorPosWorld.xz * 0.055, percFnlHeight * 2.2 + animTime * 0.018)) * 0.5 + 0.5; + float band = paTornadoNoise3(vec3(localTorPosWorld.xz * 0.028 + vec2(animTime * 0.038, -animTime * 0.032), percFnlHeight * 1.35)) * 0.5 + 0.5; + float turbulence = saturate(coarse * 0.68 + band * 0.32); + return mix(0.90, 1.12, turbulence) * mix(0.90, 1.08, widPerc); + } + + if (detailQuality < 0.72) { + vec2 swirl = spin(spinPhaseA) * localTorPosWorld.xz; + vec2 flow = texture( + FlowSampler, + fract(swirl * 0.024 + vec2(animTime * 0.014, -animTime * 0.012)) + ).rg * 2.0 - 1.0; + vec2 uv = fract(swirl * 0.064 + flow * 0.052 + vec2(percFnlHeight * 0.24, animTime * 0.034)); + vec4 tex = texture(TornadoSampler, uv); + float lum = max(tex.a, dot(tex.rgb, vec3(0.299, 0.587, 0.114))); + float noise = texture(NoiseSampler, fract(swirl * 0.030 + vec2(0.17, 0.63))).r; + float turbulence = saturate(lum * 0.74 + noise * 0.26); + return mix(0.86, 1.14, turbulence) * mix(0.90, 1.12, widPerc); + } + + vec2 swirlA = spin(spinPhaseA) * localTorPosWorld.xz; + vec2 swirlB = spin(spinPhaseB) * localTorPosWorld.xz; + + vec2 flowA = texture( + FlowSampler, + fract(swirlA * 0.020 + vec2(animTime * 0.012, -animTime * 0.014)) + ).rg * 2.0 - 1.0; + vec2 flowB = texture( + FlowSampler, + fract(swirlB * 0.033 + vec2(-animTime * 0.018, animTime * 0.016)) + ).rg * 2.0 - 1.0; + + vec2 uvA = fract(swirlA * 0.055 + flowA * 0.060 + vec2(percFnlHeight * 0.30, animTime * 0.030)); + vec2 uvB = fract(swirlB * 0.095 + flowB * 0.040 + vec2(-animTime * 0.042, percFnlHeight * 0.88)); + vec4 texA = texture(TornadoSampler, uvA); + vec4 texB = texture(TornadoSampler, uvB); + float lumA = max(texA.a, dot(texA.rgb, vec3(0.299, 0.587, 0.114))); + float lumB = max(texB.a, dot(texB.rgb, vec3(0.299, 0.587, 0.114))); + + float noiseA = texture(NoiseSampler, fract(swirlA * 0.020 + vec2(0.17, 0.63))).r; + float noiseB = texture(NoiseSampler, fract(swirlB * 0.037 + vec2(0.48, 0.22))).r; + float planarField = mix(lumA, lumB, 0.58); + + float radial = length(localTorPosWorld.xz); + float angular = atan(localTorPosWorld.z, localTorPosWorld.x) / TAU + 0.5; + vec2 cylFlow = texture( + FlowSampler, + fract(vec2(angular * 1.35 + animTime * 0.022, percFnlHeight * 1.20 - animTime * 0.016) + vec2(0.19, 0.43)) + ).rg * 2.0 - 1.0; + vec2 cylUvA = fract(vec2( + angular * 2.60 + animTime * 0.085 + cylFlow.x * 0.08, + percFnlHeight * 1.55 + radial * 0.050 + cylFlow.y * 0.06 + )); + vec2 cylUvB = fract(vec2( + angular * 4.10 - animTime * 0.132 - cylFlow.y * 0.05, + percFnlHeight * 2.05 - radial * 0.032 + cylFlow.x * 0.04 + )); + vec4 cylTexA = texture(TornadoSampler, cylUvA); + vec4 cylTexB = texture(TornadoSampler, cylUvB); + float cylLumA = max(cylTexA.a, dot(cylTexA.rgb, vec3(0.299, 0.587, 0.114))); + float cylLumB = max(cylTexB.a, dot(cylTexB.rgb, vec3(0.299, 0.587, 0.114))); + float cylindricalField = mix(cylLumA, cylLumB, 0.52); + + float lowerBlend = 1.0 - smoothstep(0.18, 0.46, percFnlHeight); + lowerBlend *= mix(0.55, 1.0, widPerc); + lowerBlend *= 1.0 - smoothstep(1.2, 3.0, widWorld); + + float texField = mix(planarField, cylindricalField, lowerBlend); + float turbulence = saturate(texField * 0.72 + noiseA * 0.18 + noiseB * 0.10); + return mix(0.84, 1.18, turbulence) * mix(0.88, 1.14, widPerc); +} + +DebugMasks sampleDebugMasks(int index, vec3 position) { + vec3 pos = getStormPos(index); + vec3 posWorld = cloudToWorld(pos); + vec3 positionWorld = cloudToWorld(position); + float widthWorld = max(cloudToWorld(StormWidths[index]), 0.001); + float stormSizeWorld = max(cloudToWorld(StormSizes[index]), widthWorld * 2.0); + float baseHeightWorld = cloudToWorld(pos.y + StormHeights[index]); + float intensity = saturate(StormIntensities[index]); + float torPerc = saturate(StormProgress[index]); + float tornadoShape = StormShapes[index]; + + float funnelTopWorld = max(baseHeightWorld - 13.125, posWorld.y + 3.75); + float heightMask = 0.0; + if (positionWorld.y >= posWorld.y && positionWorld.y <= funnelTopWorld) { + heightMask = saturate((positionWorld.y - posWorld.y) / max(funnelTopWorld - posWorld.y, 0.001)); + } + + float funnelRadiusWorld = sampleFunnelRadiusWorld(widthWorld, stormSizeWorld, tornadoShape, torPerc, heightMask); + float radialDistanceWorld = distance(positionWorld.xz, posWorld.xz); + float radialMask = 1.0 - saturate(radialDistanceWorld / max(funnelRadiusWorld, 0.001)); + float verticalGate = step(posWorld.y, positionWorld.y) * (1.0 - step(funnelTopWorld, positionWorld.y)); + float density = radialMask * verticalGate; + float alpha = saturate(density * 0.85); + + float wallcloudRadiusWorld = stormSizeWorld * 0.35; + float wallcloudLowerWorld = 15.0 * pow(max(1.0 - saturate(radialDistanceWorld / max(wallcloudRadiusWorld, 0.001)), 0.0), 0.25) * saturate((intensity - 0.45) * 2.2); + float wallcloud = 0.0; + if (positionWorld.y <= baseHeightWorld && positionWorld.y >= baseHeightWorld - wallcloudLowerWorld) { + float wallPerc = 1.0 - saturate(radialDistanceWorld / max(wallcloudRadiusWorld, 0.001)); + wallcloud = pow(max(wallPerc, 0.0), 0.55) * saturate((intensity - 0.40) * 2.6); + } + + float connectionRadiusWorld = max(funnelRadiusWorld * mix(1.8, 2.5, intensity), stormSizeWorld * 0.28); + float connectionPerc = 1.0 - saturate(radialDistanceWorld / max(connectionRadiusWorld, 0.001)); + float connection = pow(max(connectionPerc, 0.0), 0.55); + connection *= smoothstep(funnelTopWorld - 1.8, baseHeightWorld + 1.8, positionWorld.y); + connection *= saturate((intensity - 0.25) * 1.7); + + float groundSkirtRadiusWorld = max(funnelRadiusWorld * mix(0.85, 1.15, intensity), stormSizeWorld * 0.08); + float groundSkirtPerc = 1.0 - saturate(radialDistanceWorld / max(groundSkirtRadiusWorld, 0.001)); + float groundSkirtHeight = 1.0 - smoothstep(0.0, 6.0, max(positionWorld.y - posWorld.y, 0.0)); + float groundSkirt = pow(max(groundSkirtPerc, 0.0), 0.9) * groundSkirtHeight * mix(0.06, 0.18, intensity); + + DebugMasks masks; + masks.heightMask = heightMask * verticalGate; + masks.radialMask = radialMask; + masks.radiusMask = saturate(funnelRadiusWorld / max(stormSizeWorld, 0.001)); + masks.density = density; + masks.alpha = alpha; + masks.wallcloud = wallcloud; + masks.connection = connection; + masks.groundSkirt = groundSkirt; + return masks; +} + +float selectDebugMask(DebugMasks masks) { + if (DebugMode == DEBUG_FUNNEL) { + return max(masks.density, max(masks.wallcloud, max(masks.connection, masks.groundSkirt))); + } + if (DebugMode == DEBUG_HEIGHT) { + return masks.heightMask; + } + if (DebugMode == DEBUG_RADIAL) { + return masks.radialMask; + } + if (DebugMode == DEBUG_RADIUS) { + return masks.radiusMask; + } + if (DebugMode == DEBUG_DENSITY) { + return masks.density; + } + if (DebugMode == DEBUG_ALPHA) { + return masks.alpha; + } + if (DebugMode == DEBUG_WALLCLOUD) { + return masks.wallcloud; + } + if (DebugMode == DEBUG_CONNECTION) { + return masks.connection; + } + if (DebugMode == DEBUG_GROUNDSKIRT) { + return masks.groundSkirt; + } + return 0.0; +} + +StormSample sampleFrozenStorm(int index, vec3 position) { + DebugMasks masks = sampleDebugMasks(index, position); + StormSample outSample; + outSample.cloud = masks.density; + outSample.dust = 0.0; + outSample.upper = max(masks.wallcloud, masks.connection * 0.25); + outSample.material = masks.alpha; + return outSample; +} + +StormSample sampleStorm(int index, vec3 position) { + vec3 pos = getStormPos(index); + vec3 posWorld = cloudToWorld(pos); + vec3 positionWorld = cloudToWorld(position); + float baseHeightWorld = cloudToWorld(pos.y + StormHeights[index]); + float widthWorld = max(cloudToWorld(StormWidths[index]), 0.001); + float stormSizeWorld = max(cloudToWorld(StormSizes[index]), widthWorld * 2.0); + float stormSpin = StormSpins[index]; + float intensity = saturate(StormIntensities[index]); + float torPerc = saturate(StormProgress[index]); + float tornadoShape = StormShapes[index]; + float detailQuality = computeStormDetailQuality(index); + bool reducedDetail = detailQuality < 0.72; + bool lowDetail = detailQuality < 0.45; + float distWorld = distance(positionWorld.xz, posWorld.xz); + float wallcloudRadiusWorld = stormSizeWorld * 0.35; + float wallcloudLowerWorld = 15.0 * pow(max(1.0 - saturate(distWorld / max(wallcloudRadiusWorld, 0.001)), 0.0), 0.25) * saturate((intensity - 0.45) * 2.2); + + float wallcloud = 0.0; + if (positionWorld.y <= baseHeightWorld && positionWorld.y >= baseHeightWorld - wallcloudLowerWorld) { + float wallPerc = 1.0 - saturate(distWorld / max(wallcloudRadiusWorld, 0.001)); + wallcloud = pow(max(wallPerc, 0.0), 0.55) * saturate((intensity - 0.40) * 2.6); + wallcloud *= 0.7 + onoise(vec3(positionWorld.xz / 20.0, AnimationTime / 150.0)) * 0.3; + } + + StormSample outSample; + outSample.cloud = wallcloud * 0.08; + outSample.dust = 0.0; + outSample.upper = wallcloud; + outSample.material = wallcloud * 0.45; + + if (!(positionWorld.y < baseHeightWorld - wallcloudLowerWorld && positionWorld.y > posWorld.y - 14.0 && distWorld < max(widthWorld * 4.0, stormSizeWorld / 3.0))) { + return outSample; + } + + float fnlTopWorld = max(baseHeightWorld - 13.125, posWorld.y + 3.75); + float percFnlHeight = saturate((positionWorld.y - posWorld.y) / max(fnlTopWorld - posWorld.y, 0.001)); + float groundBlendWorld = 16.0; + float contactHeight = saturate((positionWorld.y - posWorld.y) / groundBlendWorld); + float percCos = (-cos(percFnlHeight * PI) + 1.0) * 0.5; + float torShape = mix(tornadoShape, 20.0, pow(saturate(widthWorld / 62.5), 1.75)); + float widWorld = (widthWorld / 2.5) + + ((widthWorld / 2.5) * percFnlHeight * torPerc) + + ((stormSizeWorld / mix(torShape + 2.0, torShape, torPerc)) * pow(percFnlHeight, 4.0)); + widWorld = mix(widWorld, 0.0, (1.0 - percFnlHeight) * (1.0 - torPerc)); + float lowerBodyHeightWorld = mix(9.0, 15.0, torPerc); + float lowerBodyBlend = saturate((positionWorld.y - posWorld.y) / max(lowerBodyHeightWorld, 0.001)); + lowerBodyBlend = lowerBodyBlend * lowerBodyBlend * (3.0 - 2.0 * lowerBodyBlend); + float baseRadiusWorld = mix(max(widthWorld * 0.26, 2.25), max(widthWorld * 0.42, 4.0), intensity); + widWorld = mix(baseRadiusWorld, widWorld, lowerBodyBlend); + float maxWidWorld = (widthWorld / 4.0) + ((widthWorld / 4.0) * torPerc) + ((stormSizeWorld / 8.0) * torPerc); + + float ropeMod = mix(3.0, 1.0, saturate(widthWorld / 3.75)); + ropeMod = mix(ropeMod, 1.0, saturate((intensity - 0.55) * 2.4)); + ropeMod = mix(0.1, ropeMod, saturate(torPerc * 1.35)); + + float swayTime = AnimationTime / 220.0; + float nx = mix( + onoise(vec3(posWorld.xz / 62.5, swayTime)), + paTornadoNoise3(vec3(posWorld.xz / 35.0, swayTime * 0.6)), + 0.35 + ) * 5.0 * ropeMod; + float nz = mix( + onoise(vec3(swayTime, posWorld.zx / 62.5)), + paTornadoNoise3(vec3(swayTime * 0.6, posWorld.zx / 35.0)), + 0.35 + ) * 5.0 * ropeMod; + vec3 attachmentPointWorld = vec3(nx, 0.0, nz); + + float xAdd = mix( + onoise(vec3(posWorld.xz / 31.25, swayTime + ((positionWorld.y * ropeMod) / 6.25))), + paTornadoNoise3(vec3(posWorld.xz / 18.0, (swayTime * 0.8) + ((positionWorld.y * ropeMod) / 9.5))), + 0.30 + ) * 2.5 * ropeMod; + float zAdd = mix( + onoise(vec3(swayTime + ((positionWorld.y * ropeMod) / 6.25), posWorld.zx / 31.25)), + paTornadoNoise3(vec3((swayTime * 0.8) + ((positionWorld.y * ropeMod) / 9.5), posWorld.zx / 18.0)), + 0.30 + ) * 2.5 * ropeMod; + float a = pow(percFnlHeight, 0.75); + xAdd *= a; + zAdd *= a; + + vec3 torPosWorld = posWorld + mix(vec3(0.0), vec3(attachmentPointWorld.x, 0.0, attachmentPointWorld.z), percCos) + vec3(xAdd, 0.0, zAdd); + float torDistWorld = distance(torPosWorld.xz, positionWorld.xz); + vec3 localTorPosWorld = positionWorld - torPosWorld; + + float influenceRadiusWorld = max( + max(widWorld * mix(1.7, 2.25, intensity), stormSizeWorld * 0.24), + max((widthWorld / 1.55) + 5.0, widthWorld * 1.65) + ); + if (torDistWorld > influenceRadiusWorld) { + return outSample; + } + + float widPerc = 1.0 - saturate(torDistWorld / max(widWorld, 0.001)); + float widMaxPerc = saturate(widWorld / max(maxWidWorld, 0.001)); + float rotation = -stormSpin * 3.0 * TORNADO_ROTATION_SPEED_MULTIPLIER; + float rotation2 = (-stormSpin / 1.5) * TORNADO_ROTATION_SPEED_MULTIPLIER; + + mat2 torSpin = spin(rotation + (torDistWorld / 6.25)); + mat2 torSpin2 = spin(rotation2 + (torDistWorld / 18.75)); + mat2 torSpin3 = spin(rotation2 + (torDistWorld / 7.5)); + vec3 torSpinPos = vec3(torSpin * localTorPosWorld.xz, positionWorld.y - (AnimationTime / 2.0)); + vec3 torSpinPos2 = vec3(torSpin2 * localTorPosWorld.xz, positionWorld.y - (AnimationTime / 2.0)); + vec3 torSpinPos3 = vec3(torSpin3 * localTorPosWorld.xz, positionWorld.y - (AnimationTime / 2.0)); + + int primaryOctaves = lowDetail ? 1 : (reducedDetail ? 2 : 3); + int secondaryOctaves = lowDetail ? 1 : 2; + int contactOctaves = lowDetail ? 1 : (reducedDetail ? 2 : 3); + float nComp1 = fbm(torSpinPos / 2.5, primaryOctaves, 2.0, 0.5, 1.0); + float nComp2 = fbm(torSpinPos2 / 5.0, primaryOctaves, 2.0, 0.5, 1.0); + float torNoise1 = mix(nComp1, nComp2, sqrt(widMaxPerc)); + float torNoise2 = lowDetail + ? paTornadoNoise3((torSpinPos + vec3(9.2, -5.7, 3.1)) / 1.6) + : fbm((torSpinPos + vec3(9.2, -5.7, 3.1)) / 1.6, secondaryOctaves, 2.0, 0.55, 1.0); + + widWorld *= mix(0.8 + (torNoise1 * 0.2), 0.9, saturate(widthWorld / 125.0) * 0.9); + widWorld *= 1.0 + torNoise2 * 0.035; + widPerc = 1.0 - saturate(torDistWorld / max(widWorld, 0.001)); + + float materialField = sampleMaterialField( + localTorPosWorld, + percFnlHeight, + widPerc, + widWorld, + rotation + (torDistWorld / 6.25), + rotation2 + (torDistWorld / 18.75), + AnimationTime, + detailQuality, + false + ); + float innerDensity = pow(max(widPerc, 0.0), mix(1.15, 1.55, 1.0 - intensity)) * 4.0; + float shearBand = saturate(widPerc * (1.0 - widPerc) * 4.0); + float shellDensity = shearBand * (0.92 + materialField * 0.34) * mix(0.95, 1.35, intensity); + float coreFill = pow(max(widPerc, 0.0), mix(2.8, 1.45, intensity)) * (0.22 + materialField * 0.20); + coreFill *= smoothstep(posWorld.y - 0.8, fnlTopWorld, positionWorld.y); + float innerVeil = smoothstep(0.18, 0.74, widPerc) * (1.0 - smoothstep(0.78, 0.98, widPerc)); + innerVeil *= 0.18 + intensity * 0.16; + float turbulence = 0.82 + (torNoise1 * 0.14) + (torNoise2 * 0.08); + float tornado = innerDensity * (0.52 + smoothstep(0.0, 0.22, contactHeight) * 0.48) * turbulence; + tornado += shearBand * 0.55 * (0.85 + materialField * 0.25); + tornado += shellDensity * 0.78; + tornado += coreFill * 0.65; + tornado += innerVeil * (0.80 + materialField * 0.20); + tornado *= materialField; + tornado *= mix(0.78, 1.06, intensity); + tornado *= mix(1.18, 1.0, lowerBodyBlend); + + float dcNoise1 = lowDetail + ? paTornadoNoise3(torSpinPos3 / 2.5) + : fbm(torSpinPos3 / 2.5, contactOctaves, 2.0, 0.5, 1.0); + + float dcPerc = saturate((intensity - 0.35) * 1.9); + float h = 5.0 + (dcNoise1 * 1.875); + float dcTopWorld = posWorld.y + (max(dcPerc, 0.35) * h); + float percDCHeight = saturate((positionWorld.y - (posWorld.y - 1.25)) / max(dcTopWorld - posWorld.y, 0.001)); + + float dustWidWorld = ((widthWorld / 1.5) + ((widthWorld / 1.5) * percFnlHeight * torPerc) + 3.125) + (3.125 * pow(percDCHeight, 1.5) * pow(dcPerc, 0.75)); + dustWidWorld *= mix(0.6 + (dcNoise1 * 0.5), 0.85, saturate(widthWorld / 62.5) * 0.9); + float dustWidPerc = 1.0 - saturate(torDistWorld / max(dustWidWorld, 0.001)); + dustWidPerc = pow(max(dustWidPerc, 0.0), 0.25); + float edge = saturate(torDistWorld / max(dustWidWorld * 0.9, 0.001)); + dustWidPerc *= edge * edge * edge; + float dust = 0.0; + if (!lowDetail) { + dust = pow(max(dustWidPerc, 0.0), 1.35) * mix(0.10, 0.18, intensity); + dust *= saturate((dcTopWorld - positionWorld.y) / 2.5); + dust *= saturate((positionWorld.y - posWorld.y) / 3.0); + dust *= 0.8 + (dcNoise1 * 0.2); + dust *= dcPerc; + dust *= 1.0 - saturate((widthWorld - 6.25) / 25.0); + } + + float groundSkirtRadiusWorld = max(widthWorld * mix(0.78, 1.10, intensity), stormSizeWorld * 0.10); + float groundSkirtPerc = 1.0 - saturate(torDistWorld / max(groundSkirtRadiusWorld, 0.001)); + float groundSkirtHeight = 1.0 - smoothstep(0.0, 5.0, max(positionWorld.y - posWorld.y, 0.0)); + float groundSkirt = pow(max(groundSkirtPerc, 0.0), 1.2) * groundSkirtHeight * mix(0.03, 0.10, intensity); + if (!reducedDetail) { + groundSkirt *= 0.80 + onoise(vec3(positionWorld.xz / 7.5, AnimationTime / 45.0)) * 0.20; + } + + float connectionRadiusWorld = max(widWorld * mix(1.8, 2.5, intensity), stormSizeWorld * 0.28); + float connectionPerc = 1.0 - saturate(torDistWorld / max(connectionRadiusWorld, 0.001)); + float connection = pow(max(connectionPerc, 0.0), 0.55); + connection *= smoothstep(fnlTopWorld - 1.8, baseHeightWorld + 1.8, positionWorld.y); + connection *= saturate((intensity - 0.25) * 1.7); + if (!reducedDetail) { + connection *= 0.82 + onoise(vec3((positionWorld.xz + posWorld.xz) / 20.0, AnimationTime / 140.0)) * 0.18; + } + + outSample.cloud = tornado; + outSample.dust = max(dust, groundSkirt); + outSample.dust = max(outSample.dust, dustWidPerc * mix(0.04, 0.10, intensity)); + outSample.upper = max(wallcloud, connection * 0.25); + outSample.material = saturate(materialField * mix(0.55, 0.95, saturate(tornado))); + return outSample; +} + +void main() { + if (DebugMode == DEBUG_BOX) { + fragColor = vec4(0.95, 0.28, 0.08, 1.0); + return; + } + + vec3 ro = CameraPos; + vec2 screenUv = gl_FragCoord.xy / OutSize; + float sceneDepth = sampleSceneDepth(screenUv); + vec3 farPlanePos = reconstructPosition(screenUv, 1.0); + vec3 rd = normalize(farPlanePos - ro); + float tNear; + float tFar; + if (!intersectAabb(ro, rd, VolumeMin, VolumeMax, tNear, tFar)) { + if (DebugMode == DEBUG_HIT) { + fragColor = vec4(1.0, 0.0, 1.0, 1.0); + return; + } + discard; + } + tNear = max(tNear, 0.0); + + if (DebugMode == DEBUG_HIT) { + fragColor = vec4(0.10, 0.95, 0.15, 1.0); + return; + } + + if (DebugMode == DEBUG_FILL) { + float intervalFill = max(tFar - tNear, 0.0); + float alphaFill = saturate(1.0 - exp(-intervalFill * 0.55)); + fragColor = vec4(vec3(0.86), max(alphaFill, 0.65)); + return; + } + + float sceneDistance = MaxDistance; + if (sceneDepth < 1.0) { + vec3 scenePos = reconstructPosition(screenUv, sceneDepth); + sceneDistance = max(length(scenePos - ro), 0.0); + } + + float depthLimitedMaxRay = MaxDistance; + float maxRay = min(min(tFar, MaxDistance), depthLimitedMaxRay); + if (maxRay <= tNear + 0.001) { + discard; + } + + bool debugActive = DebugMode != DEBUG_OFF; + bool debugDepthMode = DebugMode == DEBUG_DEPTH || DebugMode == DEBUG_DEPTH_NO_FRAMEBUFFER; + bool debugOcclusionMode = DebugMode == DEBUG_OCCLUSION; + bool debugCoverageMode = DebugMode == DEBUG_COVERAGE; + bool debugMaskMode = debugActive + && DebugMode != DEBUG_BOX + && DebugMode != DEBUG_HIT + && DebugMode != DEBUG_FILL + && !debugDepthMode + && !debugOcclusionMode + && !debugCoverageMode + && DebugMode != DEBUG_FULL; + float detailQuality = debugActive ? 1.0 : computeStormDetailQuality(0); + + vec3 accum = vec3(0.0); + float transmittance = 1.0; + float nearestT = tNear; + float firstHitDepth = 1.0; + float debugValue = 0.0; + bool wroteDepth = false; + + float interval = maxRay - tNear; + float stepSpacing = mix(1.80, 0.82, detailQuality); + float minSteps = mix(6.0, 10.0, detailQuality); + float maxSteps = mix(14.0, 28.0, detailQuality); + int steps = int(clamp(interval / stepSpacing, minSteps, maxSteps)); + float stepSize = interval / float(max(steps, 1)); + float jitter = hash1(screenUv.x * OutSize.x + screenUv.y * OutSize.y + 17.13); + float t = tNear + stepSize * (0.20 + jitter * 0.80); + float previousT = tNear; + + for (int step = 0; step < 40; step++) { + if (step >= steps) { + break; + } + if (!debugMaskMode && transmittance < mix(0.08, 0.03, detailQuality)) { + break; + } + + vec3 samplePos = ro + rd * t; + if (debugMaskMode) { + DebugMasks masks = sampleDebugMasks(0, samplePos); + float value = selectDebugMask(masks); + if (value > 0.0005) { + debugValue = max(debugValue, value); + if (!wroteDepth) { + float firstHitT = refineFirstHitT(ro, rd, previousT, t, debugActive, true); + nearestT = firstHitT; + firstHitDepth = clamp(cloudSpaceToDepth(ro + rd * firstHitT), 0.0, 1.0); + wroteDepth = true; + } + } + } else { + StormSample storm = debugActive && DebugFreeze != 0 + ? sampleFrozenStorm(0, samplePos) + : sampleStorm(0, samplePos); + float sigma = max(storm.cloud, 0.0) * 0.285; + if (sigma > 0.0005) { + if (!wroteDepth) { + float firstHitT = refineFirstHitT(ro, rd, previousT, t, debugActive, false); + nearestT = firstHitT; + firstHitDepth = clamp(cloudSpaceToDepth(ro + rd * firstHitT), 0.0, 1.0); + wroteDepth = true; + } + float nearField = 1.0 - saturate(t / 12.0); + float alpha = 1.0 - exp(-sigma * stepSize * 10.8); + alpha = saturate(alpha * (1.22 + nearField * 0.40)); + float upperStrength = saturate(storm.upper); + alpha = saturate(alpha * (1.0 + upperStrength * 0.32)); + float bodyDark = mix(0.055, 0.21, saturate(storm.material)); + vec3 cloudBase = vec3(bodyDark); + vec3 dustCol = vec3(0.20, 0.125, 0.071); + float dustTint = saturate(pow(storm.dust, 0.55)) * (1.0 - saturate(storm.material * 0.45)); + vec3 localColor = mix(cloudBase, dustCol, dustTint); + accum += localColor * alpha * transmittance; + transmittance *= (1.0 - alpha); + } + } + + previousT = t; + t += stepSize; + } + + if (debugMaskMode) { + if (debugValue < 0.01) { + discard; + } + fragColor = vec4(vec3(debugValue), 1.0); + return; + } + + float rawAlpha = 1.0 - transmittance; + float minBodyAlpha = DistantHorizonsDepthMode != 0 ? 0.045 : 0.03; + bool deferSceneDepthReject = DeferSceneDepthReject != 0; + bool sceneReject = !deferSceneDepthReject && wroteDepth && sceneDepth < 1.0 && nearestT > sceneDistance + SOFT_TERRAIN_OCCLUSION_BIAS; + if (debugCoverageMode) { + if (!wroteDepth) { + fragColor = vec4(0.05, 0.20, 1.0, 1.0); + return; + } + if (sceneReject) { + fragColor = vec4(1.0, 0.05, 0.05, 1.0); + return; + } + if (rawAlpha < minBodyAlpha) { + fragColor = vec4(1.0, 0.90, 0.05, 1.0); + return; + } + if (DistantHorizonsDepthMode != 0 && rawAlpha < 0.105) { + fragColor = vec4(0.05, 0.95, 1.0, 1.0); + return; + } + fragColor = vec4(0.05, 1.0, 0.18, 1.0); + return; + } + + if (debugDepthMode || debugOcclusionMode) { + if (!wroteDepth) { + fragColor = vec4(0.05, 0.20, 1.0, 1.0); + return; + } + if (rawAlpha < 0.03) { + fragColor = vec4(1.0, 0.90, 0.05, 1.0); + return; + } + if (debugOcclusionMode) { + float primaryDepth = samplePrimarySceneDepth(screenUv); + float secondaryDepth = UseSecondaryDepthSampler != 0 ? sampleSecondarySceneDepth(screenUv) : 1.0; + bool primaryReject = primaryDepth < 1.0 && firstHitDepth > primaryDepth + 0.0005; + bool secondaryReject = secondaryDepth < 1.0 && firstHitDepth > secondaryDepth + 0.0005; + if (primaryReject && secondaryReject) { + fragColor = vec4(1.0, 1.0, 1.0, 1.0); + return; + } + if (primaryReject) { + fragColor = vec4(1.0, 0.05, 0.05, 1.0); + return; + } + if (secondaryReject) { + fragColor = vec4(1.0, 0.05, 1.0, 1.0); + return; + } + fragColor = vec4(0.05, 1.0, 0.18, 1.0); + return; + } + if (sceneReject) { + fragColor = vec4(1.0, 0.05, 0.05, 1.0); + return; + } + fragColor = vec4(0.05, 1.0, 0.18, 1.0); + return; + } + + if (!wroteDepth || rawAlpha < minBodyAlpha) { + discard; + } + float alpha = 1.0 - pow(1.0 - rawAlpha, 1.80); + alpha = saturate(alpha * 1.10); + if (sceneReject) { + discard; + } + if (DistantHorizonsDepthMode != 0) { + float dhBodyOcclusion = smoothstep(0.045, 0.105, rawAlpha); + alpha = max(alpha, dhBodyOcclusion * 0.998); + } + + vec3 color = accum / max(rawAlpha, 0.0001); + float fogFactor = smoothstep(FogStart, FogEnd, nearestT); + color = mix(color, FogColor.rgb, fogFactor * 0.45); + if (wroteDepth) { + float outputDepth = firstHitDepth; + if (!deferSceneDepthReject && sceneDepth < 1.0 && nearestT <= sceneDistance + SOFT_TERRAIN_OCCLUSION_BIAS && firstHitDepth > sceneDepth) { + outputDepth = max(0.0, sceneDepth - 0.0005); + } + gl_FragDepth = outputDepth; + } + fragColor = vec4(color, saturate(alpha * 1.08)); +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/tornado_round.json b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_round.json new file mode 100644 index 00000000..79df5a7f --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_round.json @@ -0,0 +1,53 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "projectatmosphere:tornado_volume_box", + "fragment": "projectatmosphere:tornado_round", + "attributes": [ + "Position", + "UV0" + ], + "samplers": [ + { "name": "TornadoSampler" }, + { "name": "NoiseSampler" }, + { "name": "FlowSampler" }, + { "name": "DepthSampler" }, + { "name": "SecondaryDepthSampler" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "InverseProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "InverseModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "VolumeMin", "type": "float", "count": 3, "values": [ 0.0, 0.0, 0.0 ] }, + { "name": "VolumeMax", "type": "float", "count": 3, "values": [ 1.0, 1.0, 1.0 ] }, + { "name": "CameraPos", "type": "float", "count": 3, "values": [ 0.0, 0.0, 0.0 ] }, + { "name": "CloudColor", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] }, + { "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "AnimationTime", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "MaxDistance", "type": "float", "count": 1, "values": [ 420.0 ] }, + { "name": "OutSize", "type": "float", "count": 2, "values": [ 1.0, 1.0 ] }, + { "name": "CloudScale", "type": "float", "count": 1, "values": [ 8.0 ] }, + { "name": "RenderQuality", "type": "float", "count": 1, "values": [ 0.72 ] }, + { "name": "UseSecondaryDepthSampler", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "StormCount", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "DebugMode", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "DebugSelectedStorm", "type": "int", "count": 1, "values": [ -1 ] }, + { "name": "DebugFreeze", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "DistantHorizonsDepthMode", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "DeferSceneDepthReject", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "StormPositions", "type": "float", "count": 24, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormHeights", "type": "float", "count": 8, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormWidths", "type": "float", "count": 8, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormSizes", "type": "float", "count": 8, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormSpins", "type": "float", "count": 8, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormIntensities", "type": "float", "count": 8, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormShapes", "type": "float", "count": 8, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "StormProgress", "type": "float", "count": 8, "values": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] } + ] +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/tornado_round.vsh b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_round.vsh new file mode 100644 index 00000000..3501fd78 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_round.vsh @@ -0,0 +1,11 @@ +#version 150 + +in vec3 Position; +in vec2 UV0; + +out vec2 texCoord; + +void main() { + gl_Position = vec4(Position.xy, 0.0, 1.0); + texCoord = UV0; +} diff --git a/src/main/resources/assets/projectatmosphere/shaders/core/tornado_volume_box.vsh b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_volume_box.vsh new file mode 100644 index 00000000..414f3d17 --- /dev/null +++ b/src/main/resources/assets/projectatmosphere/shaders/core/tornado_volume_box.vsh @@ -0,0 +1,19 @@ +#version 150 + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; +uniform vec3 VolumeMin; +uniform vec3 VolumeMax; + +in vec3 Position; +in vec2 UV0; + +out vec2 texCoord; +out vec3 fragPos; + +void main() { + vec3 worldPos = mix(VolumeMin, VolumeMax, Position); + gl_Position = ProjMat * ModelViewMat * vec4(worldPos, 1.0); + texCoord = UV0; + fragPos = worldPos; +} diff --git a/src/main/resources/assets/simpleclouds/shaders/compute/cloud_regions.comp b/src/main/resources/assets/simpleclouds/shaders/compute/cloud_regions.comp index 26c08e96..18f494e6 100644 --- a/src/main/resources/assets/simpleclouds/shaders/compute/cloud_regions.comp +++ b/src/main/resources/assets/simpleclouds/shaders/compute/cloud_regions.comp @@ -1,51 +1,70 @@ #version 430 -#define EFF ${EDGE_FADE_FACTOR} // Edge Fade Factor +#define EFF ${EDGE_FADE_FACTOR} +#define PI 3.14159265358979323846 layout(local_size_x = ${LOCAL_SIZE_X}, local_size_y = ${LOCAL_SIZE_Y}, local_size_z = ${LOCAL_SIZE_Z}) in; struct CloudRegion { - float posX; - float posZ; - float index; + float posX; + float posZ; + float index; float radius; mat2 transform; }; layout(std430) readonly buffer CloudRegions { - CloudRegion data[]; -} -cloudRegions; + CloudRegion data[]; +} cloudRegions; layout(std430) restrict readonly buffer LodScales { - float data[]; -} -lodScales; + float data[]; +} lodScales; struct CloudTornado { - float typeIndex; - vec2 center; - float radius; - float bottom; - float height; - vec2 padding; + vec4 shape0; + vec4 shape1; }; -layout(std430) readonly buffer CloudTornadoes { - CloudTornado data[]; -} -cloudTornadoes; +struct CloudHurricane { + vec4 shape0; + vec4 shape1; + vec4 shape2; + vec4 shape3; +}; + +layout(std430) readonly buffer CloudStorms { + CloudTornado tornadoes[64]; + CloudHurricane hurricanes[8]; +} cloudStorms; layout(rg32f) restrict writeonly uniform image2DArray regionTexture; uniform int TotalCloudRegions; uniform vec2 Offset; +bool projectatmosphere_insideTornado(vec2 coord, out float typeIndex); +vec2 projectatmosphere_sampleHurricane(CloudHurricane hurricane, vec2 coord); +vec2 projectatmosphere_compositeHurricane(vec2 current, vec2 hurricane); uniform int TotalCloudTornadoes; +uniform int TotalCloudHurricanes; +uniform vec4 CloudTornadoData0[16]; + +float saturate(float value) +{ + return clamp(value, 0.0, 1.0); +} + +vec2 projectatmosphere_rotate(vec2 value, float angle) +{ + float s = sin(angle); + float c = cos(angle); + return vec2(value.x * c - value.y * s, value.x * s + value.y * c); +} vec3 circle(CloudRegion region, vec2 coord) { - vec2 p = vec2(region.posX, region.posZ); - coord = region.transform * (coord - p) + p; + vec2 p = vec2(region.posX, region.posZ); + coord = region.transform * (coord - p) + p; float d = distance(p, coord); float r = region.radius; if (d > r + 1.0 / EFF) @@ -78,44 +97,170 @@ vec2 composite(vec2 old, vec3 data) } } -bool projectatmosphere$insideTornado(vec2 coord, out float typeIndex) +bool projectatmosphere_insideTornado(vec2 coord, out float typeIndex) { - for (int i = 0; i < TotalCloudTornadoes; i++) + for (int i = 0; i < TotalCloudTornadoes; i++) + { + vec4 tornadoShape = CloudTornadoData0[i]; + if (distance(coord, tornadoShape.yz) <= tornadoShape.w) { - CloudTornado tornado = cloudTornadoes.data[i]; - if (distance(coord, tornado.center) <= tornado.radius) - { - typeIndex = tornado.typeIndex; - return true; - } + typeIndex = tornadoShape.x; + return true; } - return false; + } + return false; +} + +vec2 projectatmosphere_sampleHurricane(CloudHurricane hurricane, vec2 coord) +{ + float typeIndex = hurricane.shape0.x; + vec2 center = hurricane.shape0.yz; + float coreRadius = hurricane.shape1.x; + float stormExtentRadius = hurricane.shape1.y; + float eyeRadius = hurricane.shape1.z; + float edgeFade = max(hurricane.shape1.w, 1.0); + + float bandCount = max(hurricane.shape2.x, 1.0); + float bandWidth = max(hurricane.shape2.y, 1.0); + float spiralTightness = hurricane.shape2.z; + float rotationPhase = hurricane.shape2.w; + + float transitionStart = hurricane.shape3.y; + float transitionEnd = max(hurricane.shape3.z, transitionStart + 1.0); + + vec2 local = coord - center; + vec2 rotatedLocal = projectatmosphere_rotate(local, rotationPhase * 0.18); + float radius = length(local); + if (radius > stormExtentRadius + edgeFade * 1.20) + return vec2(-1.0); + + float angle = atan(rotatedLocal.y, rotatedLocal.x); + float normalizedRadius = stormExtentRadius > 0.0 ? saturate(radius / stormExtentRadius) : 0.0; + float spinPhase = angle + max(radius - eyeRadius, 0.0) * spiralTightness - rotationPhase * 0.35; + + float outerMask = 1.0 - smoothstep(stormExtentRadius - edgeFade * 0.34, stormExtentRadius + edgeFade * 0.92, radius); + float eyeHole = smoothstep(eyeRadius + edgeFade * 0.06, eyeRadius + edgeFade * 0.82, radius); + + float eyewallCenter = eyeRadius + bandWidth * 0.36; + float eyewallThickness = bandWidth * 0.88 + edgeFade * 0.14; + float eyewall = 1.0 - smoothstep(eyewallThickness * 0.26, eyewallThickness, abs(radius - eyewallCenter)); + eyewall *= eyeHole; + + float armNoiseA = 0.5 + 0.5 * cos(spinPhase * bandCount + normalizedRadius * 7.0); + float armNoiseB = 0.5 + 0.5 * cos(spinPhase * (bandCount * 0.72 + 1.10) - normalizedRadius * 9.5); + float armNoise = mix(armNoiseA, armNoiseB, 0.42); + armNoise = smoothstep(0.62, 0.94, armNoise); + + float spiralEnvelope = smoothstep(eyeRadius + bandWidth * 0.12, eyeRadius + bandWidth * 1.28, radius); + spiralEnvelope *= 1.0 - smoothstep(coreRadius * 0.78, coreRadius * 1.18, radius); + + float innerCore = max(eyewall, armNoise * spiralEnvelope); + + // Start the cumulonimbus recovery close to the eyewall so the core does not hand off into a hollow ring. + float bridgeStart = eyeRadius + bandWidth * 0.52; + float bridgeBuildEnd = max(bridgeStart + bandWidth * 1.85, coreRadius * 0.36); + float bridgeFadeEnd = max(coreRadius * 1.08, bridgeBuildEnd + bandWidth * 2.40); + + float cbBlendStart = min(transitionStart, bridgeStart); + float cbEnvelope = smoothstep(cbBlendStart, transitionEnd, radius); + cbEnvelope *= 1.0 - smoothstep(stormExtentRadius * 1.02, stormExtentRadius + edgeFade * 0.78, radius); + + float cbNoiseA = 0.5 + 0.5 * cos(angle * 1.9 - rotationPhase * 0.06 + normalizedRadius * 6.8); + float cbNoiseB = 0.5 + 0.5 * cos(angle * 4.4 + rotationPhase * 0.03 - normalizedRadius * 12.6); + float cbNoiseC = 0.5 + 0.5 * cos(angle * 2.7 - normalizedRadius * 4.4); + float cbNoise = mix(mix(cbNoiseA, cbNoiseB, 0.45), cbNoiseC, 0.30); + cbNoise = smoothstep(0.20, 0.90, cbNoise); + + float innerCbA = 0.5 + 0.5 * cos(spinPhase * (bandCount * 0.92 + 0.85) - normalizedRadius * 6.4); + float innerCbB = 0.5 + 0.5 * cos(angle * 2.2 - rotationPhase * 0.10 + normalizedRadius * 4.8); + float innerCbMask = smoothstep(0.20, 0.82, mix(innerCbA, innerCbB, 0.42)); + + float innerBridgeEnvelope = smoothstep(bridgeStart, bridgeBuildEnd, radius); + innerBridgeEnvelope *= 1.0 - smoothstep(coreRadius * 0.94, bridgeFadeEnd, radius); + + float outerBandEnvelope = smoothstep(coreRadius * 0.42, stormExtentRadius * 0.90, radius); + outerBandEnvelope *= 1.0 - smoothstep(stormExtentRadius * 0.96, stormExtentRadius + edgeFade * 0.72, radius); + + float outerBandA = 0.5 + 0.5 * cos(spinPhase * (bandCount * 0.42 + 1.05) - normalizedRadius * 15.0); + float outerBandB = 0.5 + 0.5 * cos((angle - rotationPhase * 0.16) * (bandCount * 0.30 + 1.85) + normalizedRadius * 21.0); + outerBandA = smoothstep(0.58, 0.94, outerBandA); + outerBandB = smoothstep(0.56, 0.92, outerBandB); + float outerBandMask = smoothstep(0.34, 0.88, mix(outerBandA, outerBandB, 0.38)); + + float cbMass = cbEnvelope * (0.22 + cbNoise * 0.26 + outerBandMask * 0.52); + + float innerBridge = innerBridgeEnvelope * (0.54 + innerCbMask * 0.26 + armNoise * 0.20); + + float continuityBand = smoothstep(bridgeStart, coreRadius * 0.96, radius); + continuityBand *= 1.0 - smoothstep(coreRadius * 1.08, transitionEnd * 0.92, radius); + continuityBand *= 0.48 + mix(armNoise, outerBandMask, 0.50) * 0.44; + + float spiralShoulders = outerBandEnvelope * (0.28 + outerBandMask * 0.72); + spiralShoulders *= 0.52 + cbNoise * 0.30; + + float anvilEdge = smoothstep(stormExtentRadius * 0.70, stormExtentRadius * 0.95, radius); + anvilEdge *= 1.0 - smoothstep(stormExtentRadius * 1.04, stormExtentRadius + edgeFade * 0.94, radius); + anvilEdge *= smoothstep(0.34, 0.88, cbNoiseB) * (0.42 + outerBandMask * 0.58); + + float outerStorm = max(max(cbMass, spiralShoulders), max(innerBridge, continuityBand + anvilEdge * 0.20)); + float coverage = max(innerCore, outerStorm); + coverage *= outerMask * eyeHole; + coverage = smoothstep(0.04, 0.88, saturate(coverage)); + if (coverage <= 0.001) + return vec2(-1.0); + + return vec2(typeIndex, coverage); +} + +vec2 projectatmosphere_compositeHurricane(vec2 current, vec2 hurricane) +{ + if (hurricane.x < 0.0) + return current; + + if (current.r < 0.0 || current.g <= 0.0) + return hurricane; + + if (current.r == hurricane.x) + return vec2(current.r, max(current.g, hurricane.y)); + + if (hurricane.y >= current.g) + return hurricane; + + return current; } void main() { - uint lod = gl_GlobalInvocationID.z; - float coordScale = lodScales.data[lod]; - vec2 centerOffset = imageSize(regionTexture).xy / 2.0; - ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy); - vec2 coord = (gl_GlobalInvocationID.xy - centerOffset) * coordScale + Offset; - - vec2 result = vec2(0.0); - for (int i = 0; i < TotalCloudRegions; i++) - { - vec3 data = circle(cloudRegions.data[i], coord); - result = composite(result, data); - } - - if (TotalCloudTornadoes > 0) + uint lod = gl_GlobalInvocationID.z; + float coordScale = lodScales.data[lod]; + vec2 centerOffset = imageSize(regionTexture).xy / 2.0; + ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy); + vec2 coord = (gl_GlobalInvocationID.xy - centerOffset) * coordScale + Offset; + + vec2 result = vec2(0.0); + for (int i = 0; i < TotalCloudRegions; i++) + { + vec3 data = circle(cloudRegions.data[i], coord); + result = composite(result, data); + } + + if (TotalCloudTornadoes > 0) + { + float tornadoType = -1.0; + if (projectatmosphere_insideTornado(coord, tornadoType)) { - float tornadoType = -1.0; - if (projectatmosphere$insideTornado(coord, tornadoType)) - { - if (result.x < 0.0 || result.x == tornadoType) - result = vec2(tornadoType, 1.0); - } + if (result.x < 0.0 || result.x == tornadoType) + result = vec2(tornadoType, 1.0); } + } - imageStore(regionTexture, ivec3(texelCoord, lod), vec4(result, 0.0, 0.0)); -} \ No newline at end of file + if (TotalCloudHurricanes > 0) + { + for (int i = 0; i < TotalCloudHurricanes; i++) + { + result = projectatmosphere_compositeHurricane(result, projectatmosphere_sampleHurricane(cloudStorms.hurricanes[i], coord)); + } + } + + imageStore(regionTexture, ivec3(texelCoord, lod), vec4(result, 0.0, 0.0)); +} diff --git a/src/main/resources/assets/simpleclouds/shaders/compute/cube_mesh.comp b/src/main/resources/assets/simpleclouds/shaders/compute/cube_mesh.comp index ff27a67e..48ab19d6 100644 --- a/src/main/resources/assets/simpleclouds/shaders/compute/cube_mesh.comp +++ b/src/main/resources/assets/simpleclouds/shaders/compute/cube_mesh.comp @@ -1,168 +1,176 @@ -#version 430 - -#define TYPE ${TYPE} //0 for multi-region, 1 for single cloud type -#define FADE_NEAR_ORIGIN ${FADE_NEAR_ORIGIN} //0 to disable, 1 to enable -#define STYLE ${STYLE} //0 for default, 1 for shaded -#define TRANSPARENCY ${TRANSPARENCY} //0 for no transparency, 1 for transparency -#define FIXED_SECTION_SIZE ${FIXED_SECTION_SIZE} //0 for false, 1 for true - -#define SHADE_DIRECTION normalize(vec3(0.5, 0.5, 0.5)) - -#define TILE_PERIOD vec3(32.0, 64.0, 32.0) - -#define LOCAL_SIZE vec3(${LOCAL_SIZE_X}, ${LOCAL_SIZE_Y}, ${LOCAL_SIZE_Z}) -layout(local_size_x = ${LOCAL_SIZE_X}, local_size_y = ${LOCAL_SIZE_Y}, local_size_z = ${LOCAL_SIZE_Z}) in; - -#moj_import - -struct LayerGroup { - int StartIndex; - int EndIndex; - float Storminess; - float StormStart; - float StormFadeDistance; - float TransparencyFade; -}; - -struct NoiseLayer { - float Height; - float ValueOffset; - float ScaleX; - float ScaleY; - float ScaleZ; - float FadeDistance; - float HeightOffset; - float ValueScale; -}; - -// ----- Opaque ----- - -struct SideInfo { - int side; - float x; - float y; - float z; - float brightness; - float radius; -}; - -// ----- Transparent ----- - -#if TRANSPARENCY == 1 - -struct TransparentCubeInfo { - float x; - float y; - float z; - float brightness; - float alpha; - float radius; -}; - -#endif - -// ----------------------- - -//Faces: -//-X = 0 -//+X = 1 -//-Y = 2 -//+Y = 3 -//-Z = 4 -//+Z = 5 - -//const uint sideIndices[6] = { -// 0, 1, 2, 0, 2, 3 -//}; -// -//#if TRANSPARENCY == 1 -// -//const uint transparentCubeIndices[36] = { -// 0, 1, 2, 0, 2, 3, //-z -// 4, 7, 6, 4, 6, 5, //+z -// 7, 0, 3, 7, 3, 6, //-x -// 1, 4, 5, 1, 5, 2, //+x -// 1, 0, 7, 1, 7, 4, //-y -// 5, 6, 3, 5, 3, 2 //+y -//}; -// -//#endif - -// ----- Opaque Buffers ----- - -#if FIXED_SECTION_SIZE == 0 -layout(std430) restrict buffer TotalSides { - uint totalSides; -}; -#endif - -layout(std430) restrict writeonly buffer SideInfoBuffer { - SideInfo data[]; -} -sides; - -layout(std430) restrict buffer SidesPerChunk { - uint data[]; -} -sidesPerChunk; - -// ----- Transparency Buffers ----- - -#if TRANSPARENCY == 1 - -#if FIXED_SECTION_SIZE == 0 -layout(std430) restrict buffer TotalTransparentCubes { - uint totalTransparentCubes; -}; -#endif - -layout(std430) restrict writeonly buffer TransparentCubeInfoBuffer { - TransparentCubeInfo data[]; -} -cubesTransparent; - -layout(std430) restrict buffer TransparentCubesPerChunk { - uint data[]; -} -transparentCubesPerChunk; - -#endif - -// ---------------------------------- - -layout(std430) readonly buffer NoiseLayers { - NoiseLayer data[]; -} -layers; - +#version 430 + +#define TYPE ${TYPE} //0 for multi-region, 1 for single cloud type +#define FADE_NEAR_ORIGIN ${FADE_NEAR_ORIGIN} //0 to disable, 1 to enable +#define STYLE ${STYLE} //0 for default, 1 for shaded +#define TRANSPARENCY ${TRANSPARENCY} //0 for no transparency, 1 for transparency +#define FIXED_SECTION_SIZE ${FIXED_SECTION_SIZE} //0 for false, 1 for true + +#define SHADE_DIRECTION normalize(vec3(0.5, 0.5, 0.5)) + +#define TILE_PERIOD vec3(32.0, 64.0, 32.0) + +#define LOCAL_SIZE vec3(${LOCAL_SIZE_X}, ${LOCAL_SIZE_Y}, ${LOCAL_SIZE_Z}) +layout(local_size_x = ${LOCAL_SIZE_X}, local_size_y = ${LOCAL_SIZE_Y}, local_size_z = ${LOCAL_SIZE_Z}) in; + +#moj_import + +bool projectatmosphere_insideTornado(vec3 pos, float typeIndex); +int projectatmosphere_findHurricane(vec2 pos, float typeIndex); +float projectatmosphere_getStormHeightSample(float y, int hurricaneIndex); + +struct LayerGroup { + int StartIndex; + int EndIndex; + float Storminess; + float StormStart; + float StormFadeDistance; + float TransparencyFade; +}; + +struct NoiseLayer { + float Height; + float ValueOffset; + float ScaleX; + float ScaleY; + float ScaleZ; + float FadeDistance; + float HeightOffset; + float ValueScale; +}; + +// ----- Opaque ----- + +struct SideInfo { + int side; + float x; + float y; + float z; + float brightness; + float radius; +}; + +// ----- Transparent ----- + +#if TRANSPARENCY == 1 + +struct TransparentCubeInfo { + float x; + float y; + float z; + float brightness; + float alpha; + float radius; +}; + +#endif + +// ----------------------- + +//Faces: +//-X = 0 +//+X = 1 +//-Y = 2 +//+Y = 3 +//-Z = 4 +//+Z = 5 + +//const uint sideIndices[6] = { +// 0, 1, 2, 0, 2, 3 +//}; +// +//#if TRANSPARENCY == 1 +// +//const uint transparentCubeIndices[36] = { +// 0, 1, 2, 0, 2, 3, //-z +// 4, 7, 6, 4, 6, 5, //+z +// 7, 0, 3, 7, 3, 6, //-x +// 1, 4, 5, 1, 5, 2, //+x +// 1, 0, 7, 1, 7, 4, //-y +// 5, 6, 3, 5, 3, 2 //+y +//}; +// +//#endif + +// ----- Opaque Buffers ----- + +#if FIXED_SECTION_SIZE == 0 +layout(std430) restrict buffer TotalSides { + uint totalSides; +}; +#endif + +layout(std430) restrict writeonly buffer SideInfoBuffer { + SideInfo data[]; +} +sides; + +layout(std430) restrict buffer SidesPerChunk { + uint data[]; +} +sidesPerChunk; + +// ----- Transparency Buffers ----- + +#if TRANSPARENCY == 1 + +#if FIXED_SECTION_SIZE == 0 +layout(std430) restrict buffer TotalTransparentCubes { + uint totalTransparentCubes; +}; +#endif + +layout(std430) restrict writeonly buffer TransparentCubeInfoBuffer { + TransparentCubeInfo data[]; +} +cubesTransparent; + +layout(std430) restrict buffer TransparentCubesPerChunk { + uint data[]; +} +transparentCubesPerChunk; + +#endif + +// ---------------------------------- + +layout(std430) readonly buffer NoiseLayers { + NoiseLayer data[]; +} +layers; + layout(std430) readonly buffer LayerGroupings { LayerGroup data[]; } layerGroupings; struct CloudTornado { - float typeIndex; - vec2 center; - float radius; - float bottom; - float height; - vec2 padding; + vec4 shape0; + vec4 shape1; +}; + +struct CloudHurricane { + vec4 shape0; + vec4 shape1; + vec4 shape2; + vec4 shape3; }; -layout(std430) readonly buffer CloudTornadoes { - CloudTornado data[]; +layout(std430) readonly buffer CloudStorms { + CloudTornado tornadoes[64]; + CloudHurricane hurricanes[8]; } -cloudTornadoes; - -#if TYPE == 0 -uniform sampler2DArray RegionsSampler; -uniform int RegionsTexSize; -#endif - -uniform int LodLevel; -uniform int TotalLodLevels; -uniform vec3 RenderOffset; -uniform float Scale = 1.0; +cloudStorms; + +#if TYPE == 0 +uniform sampler2DArray RegionsSampler; +uniform int RegionsTexSize; +#endif + +uniform int LodLevel; +uniform int TotalLodLevels; +uniform vec3 RenderOffset; +uniform float Scale = 1.0; uniform vec3 Scroll; uniform float Wiggle; uniform vec3 Origin; @@ -170,85 +178,95 @@ uniform bool TestFacesFacingAway; uniform int DoNotOccludeSide = -1; uniform int ChunkIndex; uniform int TotalCloudTornadoes; - -#if FIXED_SECTION_SIZE == 1 -//Offset in number of mesh elements -uniform int OpaqueMeshDataOffset; -uniform int TransparentMeshDataOffset; -#endif - -#if TRANSPARENCY == 1 -uniform int TransparencyDistance = 300; -#endif - -#if TYPE == 0 -uniform vec2 RegionSampleOffset; -#elif TYPE == 1 -uniform float FadeStart; -uniform float FadeEnd; -#endif - -#if FADE_NEAR_ORIGIN == 1 -uniform float FadeStart; -uniform float FadeEnd; -#endif - -float getNoiseForLayer(NoiseLayer layer, float x, float y, float z, out vec3 gradient) -{ - if (y < layer.HeightOffset || y > layer.HeightOffset + layer.Height - 1) - return -10000.0; - vec3 scale = vec3(layer.ScaleX, layer.ScaleY, layer.ScaleZ); - vec3 scrollScaled = Scroll / scale; - vec3 samplePos = vec3(x, y, z) / scale + scrollScaled; - float noise = psrdnoise(samplePos, TILE_PERIOD, Wiggle, gradient) * layer.ValueScale + layer.ValueOffset; - float heightDelta = y - layer.HeightOffset; - noise -= 1.0 - clamp(heightDelta / layer.FadeDistance, 0.0, 1.0); - noise -= 1.0 - clamp((layer.Height - heightDelta) / layer.FadeDistance, 0.0, 1.0); - return noise; -} - -float getNoiseForLayerGroup(LayerGroup group, float x, float y, float z, out vec3 gradient) -{ - int totalLayers = group.EndIndex - group.StartIndex; - if (totalLayers == 0) - { - return -10000.0; - } - else if (totalLayers == 1) - { - return getNoiseForLayer(layers.data[group.StartIndex], x, y, z, gradient); - } - else - { - vec3 finalGradient = vec3(0.0); - float combinedNoise = 0.0; - bool anyValid = false; - for (int i = 0; i < totalLayers; i++) - { - vec3 gradForLayer = vec3(0.0); - float valForLayer = getNoiseForLayer(layers.data[i + group.StartIndex], x, y, z, gradForLayer); - if (valForLayer > -10.0) - { - combinedNoise += valForLayer; - finalGradient += gradForLayer; - anyValid = true; - } - } - gradient = finalGradient; - if (anyValid) - return combinedNoise; - else - return -10000.0; - } - return 0.0; -} - -bool isPosValid(float x, float y, float z, LayerGroup group, float fade) -{ - vec3 gradient = vec3(0.0); - return getNoiseForLayerGroup(group, x, y, z, gradient) + fade > 0.0; -} - +uniform int TotalCloudHurricanes; +uniform vec4 CloudTornadoData0[16]; +uniform vec4 CloudTornadoData1[16]; + +#if FIXED_SECTION_SIZE == 1 +//Offset in number of mesh elements +uniform int OpaqueMeshDataOffset; +uniform int TransparentMeshDataOffset; +#endif + +#if TRANSPARENCY == 1 +uniform int TransparencyDistance = 300; +#endif + +#if TYPE == 0 +uniform vec2 RegionSampleOffset; +#elif TYPE == 1 +uniform float FadeStart; +uniform float FadeEnd; +#endif + +#if FADE_NEAR_ORIGIN == 1 +uniform float FadeStart; +uniform float FadeEnd; +#endif + +float getNoiseForLayer(NoiseLayer layer, float x, float y, float z, out vec3 gradient) +{ + if (y < layer.HeightOffset || y > layer.HeightOffset + layer.Height - 1) + return -10000.0; + vec3 scale = vec3(layer.ScaleX, layer.ScaleY, layer.ScaleZ); + vec3 scrollScaled = Scroll / scale; + vec3 samplePos = vec3(x, y, z) / scale + scrollScaled; + float noise = psrdnoise(samplePos, TILE_PERIOD, Wiggle, gradient) * layer.ValueScale + layer.ValueOffset; + float heightDelta = y - layer.HeightOffset; + noise -= 1.0 - clamp(heightDelta / layer.FadeDistance, 0.0, 1.0); + noise -= 1.0 - clamp((layer.Height - heightDelta) / layer.FadeDistance, 0.0, 1.0); + return noise; +} + +float getNoiseForLayerGroup(LayerGroup group, float x, float y, float z, out vec3 gradient) +{ + int totalLayers = group.EndIndex - group.StartIndex; + if (totalLayers == 0) + { + return -10000.0; + } + else if (totalLayers == 1) + { + return getNoiseForLayer(layers.data[group.StartIndex], x, y, z, gradient); + } + else + { + vec3 finalGradient = vec3(0.0); + float combinedNoise = 0.0; + bool anyValid = false; + for (int i = 0; i < totalLayers; i++) + { + vec3 gradForLayer = vec3(0.0); + float valForLayer = getNoiseForLayer(layers.data[i + group.StartIndex], x, y, z, gradForLayer); + if (valForLayer > -10.0) + { + combinedNoise += valForLayer; + finalGradient += gradForLayer; + anyValid = true; + } + } + gradient = finalGradient; + if (anyValid) + return combinedNoise; + else + return -10000.0; + } + return 0.0; +} + +bool isPosValid(float x, float y, float z, LayerGroup group, float fade) +{ + vec3 gradient = vec3(0.0); + return getNoiseForLayerGroup(group, x, y, z, gradient) + fade > 0.0; +} + +bool isPosValid(float x, float y, float z, LayerGroup group, float fade, int hurricaneIndex) +{ + vec3 gradient = vec3(0.0); + float sampleY = projectatmosphere_getStormHeightSample(y, hurricaneIndex); + return getNoiseForLayerGroup(group, x, sampleY, z, gradient) + fade > 0.0; +} + bool isPosValid(float x, float y, float z, int nx, int nz) { #if TYPE == 0 @@ -257,185 +275,227 @@ bool isPosValid(float x, float y, float z, int nx, int nz) uint regionId = uint(info.r); LayerGroup group = layerGroupings.data[regionId]; float fade = -5.0 * pow(1.0 - info.g, 10.0); - if (TotalCloudTornadoes > 0 && projectatmosphere$insideTornado(vec3(x, y, z), info.r)) + if (TotalCloudTornadoes > 0 && projectatmosphere_insideTornado(vec3(x, y, z), info.r)) return false; + int hurricaneIndex = TotalCloudHurricanes > 0 ? projectatmosphere_findHurricane(vec2(x, z), info.r) : -1; #if FADE_NEAR_ORIGIN == 1 float len = distance(vec2(x, z), Origin.xz); + if (hurricaneIndex < 0) fade = min(fade, -5.0 * (1.0 - min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0))); #endif #elif TYPE == 1 - LayerGroup group = layerGroupings.data[0]; - float len = distance(vec2(x, z), Origin.xz); - float fade = -5.0 * min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0); -#endif - return isPosValid(x, y, z, group, fade); -} - -bool shouldNotOcclude(int index) -{ - if (DoNotOccludeSide != -1 && index == DoNotOccludeSide) - { - vec3 id = gl_GlobalInvocationID; - vec3 size = gl_NumWorkGroups * LOCAL_SIZE; - if (DoNotOccludeSide == 1) - return id.x == size.x - 1.0; - else if (DoNotOccludeSide == 0) - return id.x == 0.0; - else if (DoNotOccludeSide == 3) - return id.y == size.y - 1.0; - else if (DoNotOccludeSide == 2) - return id.y == 0.0; - else if (DoNotOccludeSide == 5) - return id.z == size.z - 1.0; - else if (DoNotOccludeSide == 4) - return id.z == 0.0; - else - return false; - } - else - { - return false; - } -} - -// ----- Opaque ----- - -void createFace(vec3 center, float cubeRadius, int index, float brightness) -{ -#if FIXED_SECTION_SIZE == 0 - atomicAdd(sidesPerChunk.data[ChunkIndex], 1u); - uint currentFace = atomicAdd(totalSides, 1u); -#elif FIXED_SECTION_SIZE == 1 - uint currentFace = atomicAdd(sidesPerChunk.data[ChunkIndex], 1u); -#endif - SideInfo side; - side.side = index; - side.x = center.x; - side.y = center.y; - side.z = center.z; - side.brightness = brightness; - side.radius = cubeRadius; -#if FIXED_SECTION_SIZE == 0 - sides.data[currentFace] = side; -#elif FIXED_SECTION_SIZE == 1 - sides.data[OpaqueMeshDataOffset + currentFace] = side; -#endif -} - -void createCube(float x, float y, float z, float cubeRadius, float brightness, float fade, LayerGroup group) -{ - vec3 pos = vec3(x, y, z); - vec3 norm = normalize(pos - Origin); - vec3 center = pos + cubeRadius; - //-Y - if ((TestFacesFacingAway || dot(norm, vec3(0.0, -1.0, 0.0)) <= 0.0) && (!isPosValid(x, y - Scale, z, group, fade) || shouldNotOcclude(2))) - createFace(center, cubeRadius, 2, brightness); - //+Y - if ((TestFacesFacingAway || dot(norm, vec3(0.0, 1.0, 0.0)) <= 0.0) && (!isPosValid(x, y + Scale, z, group, fade) || shouldNotOcclude(3))) - createFace(center, cubeRadius, 3, brightness); - //-X - if ((TestFacesFacingAway || dot(norm, vec3(-1.0, 0.0, 0.0)) <= 0.0) && (!isPosValid(x - Scale, y, z, -1, 0) || shouldNotOcclude(0))) - createFace(center, cubeRadius, 0, brightness); - //+X - if ((TestFacesFacingAway || dot(norm, vec3(1.0, 0.0, 0.0)) <= 0.0) && (!isPosValid(x + Scale, y, z, 1, 0) || shouldNotOcclude(1))) - createFace(center, cubeRadius, 1, brightness); - //-Z - if ((TestFacesFacingAway || dot(norm, vec3(0.0, 0.0, -1.0)) <= 0.0) && (!isPosValid(x, y, z - Scale, 0, -1) || shouldNotOcclude(4))) - createFace(center, cubeRadius, 4, brightness); - //+Z - if ((TestFacesFacingAway || dot(norm, vec3(0.0, 0.0, 1.0)) <= 0.0) && (!isPosValid(x, y, z + Scale, 0, 1) || shouldNotOcclude(5))) - createFace(center, cubeRadius, 5, brightness); -} - -// ----- Transparent ----- - -#if TRANSPARENCY == 1 - -void createTransparentCube(float x, float y, float z, float cubeRadius, float brightness, float alpha) -{ -#if FIXED_SECTION_SIZE == 0 - atomicAdd(transparentCubesPerChunk.data[ChunkIndex], 1u); - uint currentCube = atomicAdd(totalTransparentCubes, 1u); -#elif FIXED_SECTION_SIZE == 1 - uint currentCube = atomicAdd(transparentCubesPerChunk.data[ChunkIndex], 1u); -#endif - TransparentCubeInfo cube; - cube.x = x + cubeRadius; - cube.y = y + cubeRadius; - cube.z = z + cubeRadius; - cube.brightness = brightness; - cube.alpha = alpha; - cube.radius = cubeRadius; -#if FIXED_SECTION_SIZE == 0 - cubesTransparent.data[currentCube] = cube; -#elif FIXED_SECTION_SIZE == 1 - cubesTransparent.data[TransparentMeshDataOffset + currentCube] = cube; -#endif -} - -#endif - -// ----------------------- - -void main() -{ - vec3 id = gl_GlobalInvocationID; - float x = id.x * Scale + RenderOffset.x; - float y = id.y * Scale + RenderOffset.y; - float z = id.z * Scale + RenderOffset.z; - -#if TYPE == 0 - vec2 texelCoord = gl_GlobalInvocationID.xz + RegionSampleOffset; - vec4 info = texture(RegionsSampler, vec3(texelCoord / RegionsTexSize, float(LodLevel))); - uint regionId = uint(info.r); - LayerGroup group = layerGroupings.data[regionId]; - float fade = -5.0 * pow(1.0 - info.g, 10.0); -#if FADE_NEAR_ORIGIN == 1 - float len = distance(vec2(x, z), Origin.xz); - fade = min(fade, -5.0 * (1.0 - min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0))); -#endif -#elif TYPE == 1 - LayerGroup group = layerGroupings.data[0]; - float len = distance(vec2(x, z), Origin.xz); - float fade = -5.0 * min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0); -#endif - vec3 gradient = vec3(0.0); - float noise = getNoiseForLayerGroup(group, x, y, z, gradient) + fade; - float storminess = clamp(group.Storminess + fade * 0.1, 0.0, 1.0); - float brightness = clamp(1.0 - storminess * (1.0 - clamp((y - group.StormStart) / group.StormFadeDistance, 0.0, 1.0)), 0.0, 1.0); -#if STYLE == 1 - gradient = normalize(gradient); - float strength = dot(gradient, SHADE_DIRECTION) * 0.5 + 0.5; - brightness = clamp(brightness - strength * 0.1, 0.0, 1.0); -#endif - if (noise > 0.0) - { - createCube(x, y, z, Scale / 2.0, brightness, fade, group); - } -#if TRANSPARENCY == 1 - else if (group.TransparencyFade > 0.01 && noise > -group.TransparencyFade && noise < 0.0) - { - float length = distance(vec2(x, z), Origin.xz); - if (length < TransparencyDistance) - { - float alpha = 1.0 / group.TransparencyFade * (noise + group.TransparencyFade); - createTransparentCube(x, y, z, Scale / 2.0, brightness, alpha); - } - } -#endif + LayerGroup group = layerGroupings.data[0]; + float len = distance(vec2(x, z), Origin.xz); + float fade = -5.0 * min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0); + int hurricaneIndex = -1; +#endif + return isPosValid(x, y, z, group, fade, hurricaneIndex); +} + +bool shouldNotOcclude(int index) +{ + if (DoNotOccludeSide != -1 && index == DoNotOccludeSide) + { + vec3 id = gl_GlobalInvocationID; + vec3 size = gl_NumWorkGroups * LOCAL_SIZE; + if (DoNotOccludeSide == 1) + return id.x == size.x - 1.0; + else if (DoNotOccludeSide == 0) + return id.x == 0.0; + else if (DoNotOccludeSide == 3) + return id.y == size.y - 1.0; + else if (DoNotOccludeSide == 2) + return id.y == 0.0; + else if (DoNotOccludeSide == 5) + return id.z == size.z - 1.0; + else if (DoNotOccludeSide == 4) + return id.z == 0.0; + else + return false; + } + else + { + return false; + } +} + +// ----- Opaque ----- + +void createFace(vec3 center, float cubeRadius, int index, float brightness) +{ +#if FIXED_SECTION_SIZE == 0 + atomicAdd(sidesPerChunk.data[ChunkIndex], 1u); + uint currentFace = atomicAdd(totalSides, 1u); +#elif FIXED_SECTION_SIZE == 1 + uint currentFace = atomicAdd(sidesPerChunk.data[ChunkIndex], 1u); +#endif + SideInfo side; + side.side = index; + side.x = center.x; + side.y = center.y; + side.z = center.z; + side.brightness = brightness; + side.radius = cubeRadius; +#if FIXED_SECTION_SIZE == 0 + sides.data[currentFace] = side; +#elif FIXED_SECTION_SIZE == 1 + sides.data[OpaqueMeshDataOffset + currentFace] = side; +#endif +} + +void createCube(float x, float y, float z, float cubeRadius, float brightness, float fade, LayerGroup group, int hurricaneIndex) +{ + vec3 pos = vec3(x, y, z); + vec3 norm = normalize(pos - Origin); + vec3 center = pos + cubeRadius; + //-Y + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(0.0, -1.0, 0.0)) <= 0.0) && (!isPosValid(x, y - Scale, z, group, fade, hurricaneIndex) || shouldNotOcclude(2))) + createFace(center, cubeRadius, 2, brightness); + //+Y + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(0.0, 1.0, 0.0)) <= 0.0) && (!isPosValid(x, y + Scale, z, group, fade, hurricaneIndex) || shouldNotOcclude(3))) + createFace(center, cubeRadius, 3, brightness); + //-X + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(-1.0, 0.0, 0.0)) <= 0.0) && (!isPosValid(x - Scale, y, z, -1, 0) || shouldNotOcclude(0))) + createFace(center, cubeRadius, 0, brightness); + //+X + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(1.0, 0.0, 0.0)) <= 0.0) && (!isPosValid(x + Scale, y, z, 1, 0) || shouldNotOcclude(1))) + createFace(center, cubeRadius, 1, brightness); + //-Z + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(0.0, 0.0, -1.0)) <= 0.0) && (!isPosValid(x, y, z - Scale, 0, -1) || shouldNotOcclude(4))) + createFace(center, cubeRadius, 4, brightness); + //+Z + if ((TestFacesFacingAway || hurricaneIndex >= 0 || dot(norm, vec3(0.0, 0.0, 1.0)) <= 0.0) && (!isPosValid(x, y, z + Scale, 0, 1) || shouldNotOcclude(5))) + createFace(center, cubeRadius, 5, brightness); +} + +// ----- Transparent ----- + +#if TRANSPARENCY == 1 + +void createTransparentCube(float x, float y, float z, float cubeRadius, float brightness, float alpha) +{ +#if FIXED_SECTION_SIZE == 0 + atomicAdd(transparentCubesPerChunk.data[ChunkIndex], 1u); + uint currentCube = atomicAdd(totalTransparentCubes, 1u); +#elif FIXED_SECTION_SIZE == 1 + uint currentCube = atomicAdd(transparentCubesPerChunk.data[ChunkIndex], 1u); +#endif + TransparentCubeInfo cube; + cube.x = x + cubeRadius; + cube.y = y + cubeRadius; + cube.z = z + cubeRadius; + cube.brightness = brightness; + cube.alpha = alpha; + cube.radius = cubeRadius; +#if FIXED_SECTION_SIZE == 0 + cubesTransparent.data[currentCube] = cube; +#elif FIXED_SECTION_SIZE == 1 + cubesTransparent.data[TransparentMeshDataOffset + currentCube] = cube; +#endif +} + +#endif + +// ----------------------- + +void main() +{ + vec3 id = gl_GlobalInvocationID; + float x = id.x * Scale + RenderOffset.x; + float y = id.y * Scale + RenderOffset.y; + float z = id.z * Scale + RenderOffset.z; + +#if TYPE == 0 + vec2 texelCoord = gl_GlobalInvocationID.xz + RegionSampleOffset; + vec4 info = texture(RegionsSampler, vec3(texelCoord / RegionsTexSize, float(LodLevel))); + uint regionId = uint(info.r); + LayerGroup group = layerGroupings.data[regionId]; + float fade = -5.0 * pow(1.0 - info.g, 10.0); + int hurricaneIndex = TotalCloudHurricanes > 0 ? projectatmosphere_findHurricane(vec2(x, z), info.r) : -1; +#if FADE_NEAR_ORIGIN == 1 + float len = distance(vec2(x, z), Origin.xz); + if (hurricaneIndex < 0) + fade = min(fade, -5.0 * (1.0 - min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0))); +#endif +#elif TYPE == 1 + LayerGroup group = layerGroupings.data[0]; + float len = distance(vec2(x, z), Origin.xz); + float fade = -5.0 * min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0); + int hurricaneIndex = -1; +#endif + vec3 gradient = vec3(0.0); + float sampleY = projectatmosphere_getStormHeightSample(y, hurricaneIndex); + float noise = getNoiseForLayerGroup(group, x, sampleY, z, gradient) + fade; + float storminess = clamp(group.Storminess + fade * 0.1, 0.0, 1.0); + float brightness = clamp(1.0 - storminess * (1.0 - clamp((sampleY - group.StormStart) / group.StormFadeDistance, 0.0, 1.0)), 0.0, 1.0); +#if STYLE == 1 + gradient = normalize(gradient); + float strength = dot(gradient, SHADE_DIRECTION) * 0.5 + 0.5; + brightness = clamp(brightness - strength * 0.1, 0.0, 1.0); +#endif + if (noise > 0.0) + { + createCube(x, y, z, Scale / 2.0, brightness, fade, group, hurricaneIndex); + } +#if TRANSPARENCY == 1 + else if (group.TransparencyFade > 0.01 && noise > -group.TransparencyFade && noise < 0.0) + { + float length = distance(vec2(x, z), Origin.xz); + if (length < TransparencyDistance) + { + float alpha = 1.0 / group.TransparencyFade * (noise + group.TransparencyFade); + createTransparentCube(x, y, z, Scale / 2.0, brightness, alpha); + } + } +#endif } -bool projectatmosphere$insideTornado(vec3 pos, float typeIndex) +bool projectatmosphere_insideTornado(vec3 pos, float typeIndex) { for (int i = 0; i < TotalCloudTornadoes; i++) { - CloudTornado tornado = cloudTornadoes.data[i]; - if (abs(tornado.typeIndex - typeIndex) > 0.5) + vec4 tornadoShape = CloudTornadoData0[i]; + vec4 tornadoHeight = CloudTornadoData1[i]; + if (abs(tornadoShape.x - typeIndex) > 0.5) continue; - if (pos.y < tornado.bottom || pos.y > tornado.bottom + tornado.height) + if (pos.y < tornadoHeight.x || pos.y > tornadoHeight.x + tornadoHeight.y) continue; - if (distance(pos.xz, tornado.center) <= tornado.radius) + if (distance(pos.xz, tornadoShape.yz) <= tornadoShape.w) return true; } return false; -} \ No newline at end of file +} + +int projectatmosphere_findHurricane(vec2 pos, float typeIndex) +{ + int bestIndex = -1; + float bestDistSq = 3.402823466e+38; + for (int i = 0; i < TotalCloudHurricanes; i++) + { + CloudHurricane hurricane = cloudStorms.hurricanes[i]; + if (abs(hurricane.shape0.x - typeIndex) > 0.5) + continue; + vec2 delta = pos - hurricane.shape0.yz; + float radius = hurricane.shape1.y + hurricane.shape1.w; + float distSq = dot(delta, delta); + if (distSq > radius * radius) + continue; + if (distSq < bestDistSq) + { + bestDistSq = distSq; + bestIndex = i; + } + } + return bestIndex; +} + +float projectatmosphere_getStormHeightSample(float y, int hurricaneIndex) +{ + if (hurricaneIndex < 0) + return y; + return y - cloudStorms.hurricanes[hurricaneIndex].shape0.w; +} + + + + diff --git a/src/main/resources/data/projectatmosphere/cloud_types/hurricane.json b/src/main/resources/data/projectatmosphere/cloud_types/hurricane.json new file mode 100644 index 00000000..815c99f3 --- /dev/null +++ b/src/main/resources/data/projectatmosphere/cloud_types/hurricane.json @@ -0,0 +1,49 @@ +{ + "noise_settings": [ + { + "scale_z": 2200.0, + "fade_distance": 184.0, + "height_offset": -48.0, + "value_scale": 1.0, + "height": 380.0, + "value_offset": 0.10, + "scale_x": 2480.0, + "scale_y": 1400.0 + }, + { + "scale_z": 96.0, + "fade_distance": 72.0, + "height_offset": -8.0, + "value_scale": 1.12, + "height": 208.0, + "value_offset": 1.12, + "scale_x": 96.0, + "scale_y": 84.0 + }, + { + "scale_z": 420.0, + "fade_distance": 138.0, + "height_offset": 8.0, + "value_scale": 0.80, + "height": 308.0, + "value_offset": 0.44, + "scale_x": 460.0, + "scale_y": 260.0 + }, + { + "scale_z": 640.0, + "fade_distance": 172.0, + "height_offset": 52.0, + "value_scale": 0.62, + "height": 320.0, + "value_offset": 0.30, + "scale_x": 680.0, + "scale_y": 340.0 + } + ], + "weather_type": "thunderstorm", + "storminess": 1.0, + "storm_start": 36.0, + "storm_fade_distance": 224.0, + "transparency_fade": 0.07 +} diff --git a/src/main/resources/data/projectatmosphere/recipes/storm_shield.json b/src/main/resources/data/projectatmosphere/recipes/storm_shield.json new file mode 100644 index 00000000..ce6fe7cb --- /dev/null +++ b/src/main/resources/data/projectatmosphere/recipes/storm_shield.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "IGI", + "RLR", + "IGI" + ], + "key": { + "I": { "item": "minecraft:iron_ingot" }, + "G": { "item": "minecraft:glass_pane" }, + "R": { "item": "minecraft:redstone" }, + "L": { "item": "minecraft:lightning_rod" } + }, + "result": { "item": "projectatmosphere:storm_shield" } +} diff --git a/src/main/resources/data/projectatmosphere/tags/blocks/hurricane_fragile.json b/src/main/resources/data/projectatmosphere/tags/blocks/hurricane_fragile.json new file mode 100644 index 00000000..54505678 --- /dev/null +++ b/src/main/resources/data/projectatmosphere/tags/blocks/hurricane_fragile.json @@ -0,0 +1,20 @@ +{ + "replace": false, + "values": [ + "#minecraft:flowers", + "#minecraft:saplings", + "#minecraft:crops", + "minecraft:grass", + "minecraft:tall_grass", + "minecraft:fern", + "minecraft:large_fern", + "minecraft:dead_bush", + "minecraft:vine", + "minecraft:weeping_vines", + "minecraft:weeping_vines_plant", + "minecraft:twisting_vines", + "minecraft:twisting_vines_plant", + "minecraft:brown_mushroom", + "minecraft:red_mushroom" + ] +} diff --git a/src/main/resources/data/projectatmosphere/tags/blocks/hurricane_never_break.json b/src/main/resources/data/projectatmosphere/tags/blocks/hurricane_never_break.json new file mode 100644 index 00000000..8f11408e --- /dev/null +++ b/src/main/resources/data/projectatmosphere/tags/blocks/hurricane_never_break.json @@ -0,0 +1,23 @@ +{ + "replace": false, + "values": [ + "#minecraft:base_stone_overworld", + "#minecraft:base_stone_nether", + "#minecraft:sand", + "#minecraft:dirt", + "#forge:ores", + "minecraft:bedrock", + "minecraft:barrier", + "minecraft:command_block", + "minecraft:chain_command_block", + "minecraft:repeating_command_block", + "minecraft:end_portal", + "minecraft:end_portal_frame", + "minecraft:nether_portal", + "minecraft:end_gateway", + "minecraft:structure_block", + "minecraft:jigsaw", + "minecraft:chest", + "minecraft:trapped_chest" + ] +} diff --git a/src/main/resources/data/projectatmosphere/tags/blocks/hurricane_tree_damage.json b/src/main/resources/data/projectatmosphere/tags/blocks/hurricane_tree_damage.json new file mode 100644 index 00000000..c392a43c --- /dev/null +++ b/src/main/resources/data/projectatmosphere/tags/blocks/hurricane_tree_damage.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "#minecraft:leaves", + "#minecraft:logs" + ] +} diff --git a/src/main/resources/data/simpleclouds/cloud_spawning/pattern.json b/src/main/resources/data/simpleclouds/cloud_spawning/pattern.json new file mode 100644 index 00000000..3fce46e1 --- /dev/null +++ b/src/main/resources/data/simpleclouds/cloud_spawning/pattern.json @@ -0,0 +1,41 @@ +{ + "type": "simpleclouds:pattern", + "exist_ticks": { + "type": "minecraft:biased_to_bottom", + "value": { + "max_inclusive": 48000, + "min_inclusive": 24000 + } + }, + "grow_ticks": { + "type": "minecraft:biased_to_bottom", + "value": { + "max_inclusive": 6000, + "min_inclusive": 3000 + } + }, + "moves_to_player": false, + "order_weight": 600, + "radius": { + "type": "minecraft:biased_to_bottom", + "value": { + "max_inclusive": 6000, + "min_inclusive": 3000 + } + }, + "speed": { + "type": "minecraft:uniform", + "value": { + "max_exclusive": 0.4, + "min_inclusive": 0.2 + } + }, + "stretch_factor": { + "type": "minecraft:uniform", + "value": { + "max_exclusive": 0.4, + "min_inclusive": 0.2 + } + }, + "weight": 14 +} diff --git a/src/main/resources/data/simpleclouds/cloud_types/altostratus.json b/src/main/resources/data/simpleclouds/cloud_types/altostratus.json index 1d641a39..2b2cbcdf 100644 --- a/src/main/resources/data/simpleclouds/cloud_types/altostratus.json +++ b/src/main/resources/data/simpleclouds/cloud_types/altostratus.json @@ -2,7 +2,7 @@ "weather_type": "rain", "storminess": 0.35, "transparency_fade": 0.85, - "storm_start": 14.0, + "storm_start": 56.0, "storm_fade_distance": 12.0, "noise_settings": [ { diff --git a/src/main/resources/data/simpleclouds/cloud_types/cumulus_congestus.json b/src/main/resources/data/simpleclouds/cloud_types/cumulus_congestus.json index 3077bd72..a779eb6c 100644 --- a/src/main/resources/data/simpleclouds/cloud_types/cumulus_congestus.json +++ b/src/main/resources/data/simpleclouds/cloud_types/cumulus_congestus.json @@ -1,9 +1,8 @@ { - "weather_type": "rain", - "storminess": 0.4, - "transparency_fade": 0.75, - "storm_start": 64.0, - "storm_fade_distance": 64.0, + "weather_type": "thunderstorm", + "storminess": 1.0, + "storm_start": 48.0, + "storm_fade_distance": 160.0, "noise_settings": [ { "height": 160.0, diff --git a/src/main/resources/data/simpleclouds/cloud_types/cumulus_humilis.json b/src/main/resources/data/simpleclouds/cloud_types/cumulus_humilis.json index 1a22f4ee..70093036 100644 --- a/src/main/resources/data/simpleclouds/cloud_types/cumulus_humilis.json +++ b/src/main/resources/data/simpleclouds/cloud_types/cumulus_humilis.json @@ -1,29 +1,19 @@ { "weather_type": "none", "storminess": 0.1, - "transparency_fade": 0.9, - "storm_start": 32.0, - "storm_fade_distance": 32.0, + "storm_start": 10.0, + "transparency_fade": 0.2, + "storm_fade_distance": 16.0, "noise_settings": [ { - "height": 64.0, + "scale_z": 3.0, + "fade_distance": 10.0, "height_offset": 0.0, - "fade_distance": 12.0, - "value_offset": 1.1, - "value_scale": 0.9, - "scale_x": 100.0, - "scale_y": 100.0, - "scale_z": 100.0 - }, - { + "value_scale": 1.0, "height": 32.0, - "height_offset": 24.0, - "fade_distance": 6.0, - "value_offset": 0.4, - "value_scale": 0.6, - "scale_x": 60.0, - "scale_y": 60.0, - "scale_z": 60.0 + "value_offset": -0.5, + "scale_x": 3.0, + "scale_y": 1.0 } ] } diff --git a/src/main/resources/data/simpleclouds/cloud_types/cumulus_mediocris.json b/src/main/resources/data/simpleclouds/cloud_types/cumulus_mediocris.json index f73583ea..6b56d5dd 100644 --- a/src/main/resources/data/simpleclouds/cloud_types/cumulus_mediocris.json +++ b/src/main/resources/data/simpleclouds/cloud_types/cumulus_mediocris.json @@ -1,29 +1,29 @@ { - "weather_type": "rain", - "storminess": 0.25, - "transparency_fade": 0.85, - "storm_start": 48.0, - "storm_fade_distance": 48.0, + "weather_type": "none", + "storminess": 0.45, + "transparency_fade": 0.8, + "storm_start": 64.0, + "storm_fade_distance": 64.0, "noise_settings": [ { - "height": 96.0, + "height": 120.0, "height_offset": 0.0, - "fade_distance": 14.0, - "value_offset": 1.2, + "fade_distance": 10.0, + "value_offset": 1.3, "value_scale": 1.0, - "scale_x": 120.0, - "scale_y": 120.0, - "scale_z": 120.0 + "scale_x": 300.0, + "scale_y": 300.0, + "scale_z": 300.0 }, { - "height": 64.0, + "height": 80.0, "height_offset": 32.0, - "fade_distance": 10.0, - "value_offset": 0.6, - "value_scale": 0.8, - "scale_x": 90.0, - "scale_y": 90.0, - "scale_z": 90.0 + "fade_distance": 8.0, + "value_offset": 0.5, + "value_scale": 0.7, + "scale_x": 200.0, + "scale_y": 200.0, + "scale_z": 200.0 } ] } diff --git a/src/main/resources/data/simpleclouds/cloud_types/pattern.json b/src/main/resources/data/simpleclouds/cloud_types/pattern.json new file mode 100644 index 00000000..9385bfd4 --- /dev/null +++ b/src/main/resources/data/simpleclouds/cloud_types/pattern.json @@ -0,0 +1,18 @@ +{ + "noise_settings": { + "scale_z": 3.0, + "fade_distance": 10.0, + "height_offset": 0.0, + "value_scale": 1.0, + "height": 32.0, + "value_offset": -0.5, + "scale_x": 3.0, + "scale_y": 1.0 + }, + "weather_type": "none", + "storminess": 0.1, + "storm_start": 10.0, + "transparency_fade": 0.2, + "storm_fade_distance": 16.0, + "transparency_fade": 0.0 +} diff --git a/src/main/resources/data/simpleclouds/cloud_types/stratocumulus_opacus.json b/src/main/resources/data/simpleclouds/cloud_types/stratocumulus_opacus.json index dee62933..df908c02 100644 --- a/src/main/resources/data/simpleclouds/cloud_types/stratocumulus_opacus.json +++ b/src/main/resources/data/simpleclouds/cloud_types/stratocumulus_opacus.json @@ -1,39 +1,39 @@ { + "weather_type": "none", + "storminess": 0.55, + "transparency_fade": 0.7, + "storm_start": 16.0, + "storm_fade_distance": 64.0, "noise_settings": [ { - "scale_z": 220.0, - "fade_distance": 24.0, - "height_offset": 0.62, - "value_scale": 0.65, - "height": 60.0, - "value_offset": 1.02, - "scale_x": 380.0, - "scale_y": 380.0 + "height": 96.0, + "height_offset": 0.0, + "fade_distance": 12.0, + "value_offset": 1.4, + "value_scale": 1.0, + "scale_x": 250.0, + "scale_y": 250.0, + "scale_z": 250.0 }, { - "scale_z": 320.0, - "fade_distance": 8.0, - "height_offset": 70.0, - "value_scale": 1.0, "height": 128.0, - "value_offset": 0.82, - "scale_x": 210.0, - "scale_y": 230.0 + "height_offset": 32.0, + "fade_distance": 8.0, + "value_offset": 0.6, + "value_scale": 0.8, + "scale_x": 180.0, + "scale_y": 180.0, + "scale_z": 180.0 }, { - "scale_z": 420.0, + "height": 192.0, + "height_offset": 64.0, "fade_distance": 8.0, - "height_offset": 95.0, - "value_scale": 0.55, - "height": 45.0, - "value_offset": 0.9, - "scale_x": 340.0, - "scale_y": 340.0 + "value_offset": 0.2, + "value_scale": 0.6, + "scale_x": 120.0, + "scale_y": 120.0, + "scale_z": 120.0 } - ], - "weather_type": "none", - "storminess": 0.40, - "storm_start": 16.0, - "storm_fade_distance": 64.0, - "transparency_fade": 0.7 + ] } diff --git a/src/main/resources/projectatmosphere.mixins.json b/src/main/resources/projectatmosphere.mixins.json index 6edffbf8..b5463079 100644 --- a/src/main/resources/projectatmosphere.mixins.json +++ b/src/main/resources/projectatmosphere.mixins.json @@ -6,19 +6,32 @@ "refmap": "projectatmosphere.refmap.json", "mixins": [ "BiomeFreezingMixin", + "CloudGeneratorHurricaneReservationMixin", "CloudRegionMixin", "CloudRegionTickEventMixin", "InfoMixin", "SeasonHooksMixin", + "SimpleCloudsCloudManagerMixin", "ServerLevelSnowStormMixin" ], "client": [ + "CloudMeshGeneratorDiagnosticsAccessor", + "CloudMeshGeneratorDiagnosticsMixin", "CloudMeshGeneratorAccessor", + "CloudMeshGeneratorShaderMixin", "MixinSandstormDebugBlocker", "MultiRegionCloudMeshGeneratorMixin", + "client.DefaultPipelineTornadoMixin", + "client.InstanceableMeshDiagnosticsMixin", + "client.DhSupportPipelineDiagnosticsMixin", + "client.SimpleCloudsRendererDiagnosticsMixin", + "client.SimpleCloudsRendererDhFallbackMixin", + "client.BindingManagerAccessor", + "client.SimpleCloudsRendererLightningBufferMixin", + "client.ShaderSupportPipelineTornadoMixin", + "client.MinecraftCrashHandlerMixin", "client.LoadingScreenMixin", "OverwriteDesertSound", - "SimpleCloudsRendererMixin", "client.LoadingOverlayMixin", "client.particle.ParticleMotionAccessor", "client.particle.WindBentParticleEngineMixin", diff --git a/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/cloud/ClientSideCloudTypeManager.java b/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/cloud/ClientSideCloudTypeManager.java new file mode 100644 index 00000000..0ebe4711 --- /dev/null +++ b/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/cloud/ClientSideCloudTypeManager.java @@ -0,0 +1,76 @@ +package dev.nonamecrackers2.simpleclouds.client.cloud; + +import java.util.Map; + +import javax.annotation.Nullable; + +import com.google.common.collect.ImmutableMap; + +import dev.nonamecrackers2.simpleclouds.api.common.cloud.weather.WeatherType; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudType; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudTypeDataManager; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudTypeSource; +import net.minecraft.resources.ResourceLocation; + +public class ClientSideCloudTypeManager implements CloudTypeSource +{ + private static final ClientSideCloudTypeManager INSTANCE = new ClientSideCloudTypeManager(); + private final CloudTypeDataManager dataManager; + private Map synced = ImmutableMap.of(); + private CloudType[] indexed = new CloudType[0]; + + private ClientSideCloudTypeManager() + { + this.dataManager = new CloudTypeDataManager(); + } + + public CloudTypeDataManager getClientSideDataManager() + { + return this.dataManager; + } + + @Override + public CloudType getCloudTypeForId(ResourceLocation id) + { + return this.getCloudTypes().get(id); + } + + @Override + public CloudType[] getIndexedCloudTypes() + { + if (this.indexed.length > 0) + return this.indexed; + else + return this.dataManager.getIndexedCloudTypes(); + } + + public Map getCloudTypes() + { + if (!this.synced.isEmpty()) + return this.synced; + else + return this.dataManager.getCloudTypes(); + } + + public void receiveSynced(Map synced, CloudType[] indexed) + { + this.synced = ImmutableMap.copyOf(synced); + this.indexed = indexed; + } + + public void clearSynced() + { + this.synced = ImmutableMap.of(); + this.indexed = new CloudType[0]; + } + + public static ClientSideCloudTypeManager getInstance() + { + return INSTANCE; + } + + public static boolean isValidClientSideSingleModeCloudType(@Nullable CloudType type) + { + return type != null && type.weatherType() == WeatherType.NONE; + } +} diff --git a/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/mesh/generator/CloudMeshGenerator.java b/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/mesh/generator/CloudMeshGenerator.java new file mode 100644 index 00000000..f2ae91fd --- /dev/null +++ b/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/mesh/generator/CloudMeshGenerator.java @@ -0,0 +1,1149 @@ +package dev.nonamecrackers2.simpleclouds.client.mesh.generator; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Queue; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL31; +import org.lwjgl.opengl.GL41; +import org.lwjgl.opengl.GL42; +import org.lwjgl.opengl.GL43; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Queues; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; + +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import dev.nonamecrackers2.simpleclouds.client.mesh.LevelOfDetailOptions; +import dev.nonamecrackers2.simpleclouds.client.mesh.RendererInitializeResult; +import dev.nonamecrackers2.simpleclouds.client.mesh.chunk.MeshChunk; +import dev.nonamecrackers2.simpleclouds.client.mesh.instancing.InstanceableMesh; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.LevelOfDetailConfig; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.PreparedChunk; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.BindingManager; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.ShaderStorageBufferObject; +import dev.nonamecrackers2.simpleclouds.client.shader.compute.ComputeShader; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudInfo; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.mixin.MixinFrustumAccessor; +import net.minecraft.CrashReportCategory; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.AABB; + +/** + * Abstract mesh generator class that generates a cloud vertex mesh using computer shaders. Implementations are only available on the render thread. + *

+ * Use {@link CloudMeshGenerator#init} to initialize the mesh generator. This will initialize all needed buffers. Note + * that this is an expensive class and having multiple instances in one environment can cause GPU memory to run out quick (including + * available SSBO bindings). + *

+ * Use {@link CloudMeshGenerator#tick} each frame to generate the mesh at a fixed interval of frames + * (defined by {@link CloudMeshGenerator#setMeshGenInterval}) or use {@link CloudMeshGenerator#generateMesh} to generate + * it in a single call. + *

+ * Use {@link CloudMeshGenerator#render} to render the currently generated cloud mesh. + * + * @author nonamecrackers2 + */ +public abstract class CloudMeshGenerator +{ + private static final Logger LOGGER = LogManager.getLogger("simpleclouds/CloudMeshGenerator"); + + public static final ResourceLocation MAIN_CUBE_MESH_GENERATOR = SimpleCloudsMod.id("cube_mesh"); + public static final int MAX_NOISE_LAYERS = 4; + public static final int VERTICAL_CHUNK_SPAN = 8; + public static final int LOCAL_SIZE = 8; + public static final int WORK_SIZE = SimpleCloudsConstants.CHUNK_SIZE / LOCAL_SIZE; + public static final int TICKS_UNTIL_FADE_RESET = 120; + + //Opaque + public static final int BYTES_PER_SIDE_INFO = 24; + public static final int MAX_SIDE_INFO_BUFFER_SIZE = 50331648; + public static final String SIDE_INFO_BUFFER_NAME = "SideInfoBuffer"; + public static final String TOTAL_SIDES_NAME = "TotalSides"; + public static final String SIDES_PER_CHUNK_NAME = "SidesPerChunk"; + //Transparent + public static final int BYTES_PER_CUBE_INFO = 24; + public static final int MAX_TRANSPARENT_CUBE_INFO_BUFFER_SIZE = 50331648; + public static final String TRANSPARENT_CUBE_INFO_BUFFER_NAME = "TransparentCubeInfoBuffer"; + public static final String TRANSPARENT_TOTAL_CUBES_NAME = "TotalTransparentCubes"; + public static final String TRANSPARENT_CUBES_PER_CHUNK_NAME = "TransparentCubesPerChunk"; + + public static final String NOISE_LAYERS_NAME = "NoiseLayers"; + public static final String LAYER_GROUPINGS_NAME = "LayerGroupings"; + + protected final ResourceLocation meshShaderLoc; + protected final int shaderType; + protected final boolean fadeNearOrigin; + protected final boolean shadedClouds; + protected final boolean useTransparency; + protected final LevelOfDetailConfig lodConfig; + protected final boolean useFixedMeshDataSectionSize; + protected @Nullable List chunks; + protected final List completedGenTasks = Lists.newArrayList(); + protected final Queue chunkGenTasks = Queues.newArrayDeque(); + protected final Supplier meshGenIntervalCalculator; + protected int meshGenInterval = 1; + protected int tasksPerTick; + protected @Nullable ComputeShader shader; + + protected @Nullable InstanceableMesh sideMesh; + protected @Nullable InstanceableMesh cubeMesh; + + // Left is for opaque geometry, right is for transparent + protected Pair meshGenStatus = Pair.of(CloudMeshGenerator.MeshGenStatus.NOT_INITIALIZED, CloudMeshGenerator.MeshGenStatus.NOT_INITIALIZED); + protected float scrollX; + protected float scrollY; + protected float scrollZ; + protected boolean testFacesFacingAway; + private float fadeStart; + private float fadeEnd; + private float cullDistance; + private int transparencyDistance; + + private int opaqueBufferSize; + private int opaqueBufferBytesUsed; + private int transparentBufferSize; + private int transparentBufferBytesUsed; + private int opaqueBytesPerChunk; + private int transparentBytesPerChunk; + + /** + * Creates a cloud mesh generator, but does not initialize it for generating (use {@link CloudMeshGenerator#init}) + * + * @param meshShaderLoc + * The location of the cloud mesh generator compute shader + * @param lodConfig + * A level of detail configuration + * @param meshGenInterval + * The frame interval at which the generate the cloud mesh + */ + public CloudMeshGenerator(ResourceLocation meshShaderLoc, int shaderType, boolean fadeNearOrigin, boolean shadedClouds, LevelOfDetailConfig lodConfig, Supplier meshGenIntervalCalculator, boolean useTransparency, boolean fixedMeshDataSectionSize) + { + this.meshShaderLoc = meshShaderLoc; + this.shaderType = shaderType; + this.fadeNearOrigin = fadeNearOrigin; + this.shadedClouds = shadedClouds; + this.useFixedMeshDataSectionSize = fixedMeshDataSectionSize; + + this.lodConfig = lodConfig; + this.meshGenIntervalCalculator = meshGenIntervalCalculator; + this.useTransparency = useTransparency; + + float maxRadius = this.getCloudAreaMaxRadius(); + this.fadeStart = 0.9F * maxRadius; + this.fadeEnd = maxRadius; + this.transparencyDistance = (int)maxRadius / 2; + } + + public boolean fadeNearOriginEnabled() + { + return this.fadeNearOrigin; + } + + public boolean shadedCloudsEnabled() + { + return this.shadedClouds; + } + + public boolean transparencyEnabled() + { + return this.useTransparency; + } + + public boolean usesFixedMeshDataSectionSize() + { + return this.useFixedMeshDataSectionSize; + } + + public LevelOfDetailConfig getLodConfig() + { + return this.lodConfig; + } + + /** + * Specifies if faces not facing the camera should be tested during + * mesh generation on the GPU for whether they should be generated or not. + *

+ * Enabling can improve performance at the cost of some visual artifacts + * or an incomplete cloud mesh + * + * @param flag + * @return + */ + public CloudMeshGenerator setTestFacesFacingAway(boolean flag) + { + this.testFacesFacingAway = flag; + return this; + } + + /** + * Sets the fade start and end distances as decimal percentages + * + * @param fadeStart + * @param fadeEnd + */ + public CloudMeshGenerator setFadeDistances(float fadeStart, float fadeEnd) + { + float fs = fadeStart; + float fe = fadeEnd; + if (fs > fe) + { + fs = fadeEnd; + fe = fadeStart; + } + this.fadeStart = fs * (float)this.getCloudAreaMaxRadius(); + this.fadeEnd = fe * (float)this.getCloudAreaMaxRadius(); + return this; + } + + public CloudMeshGenerator setTransparencyRenderDistance(float percentage) + { + this.transparencyDistance = Mth.floor(percentage * (float)this.getCloudAreaMaxRadius()); + return this; + } + + public float getFadeStart() + { + return this.fadeStart; + } + + public float getFadeEnd() + { + return this.fadeEnd; + } + + public int getCloudAreaMaxRadius() + { + return this.lodConfig.getEffectiveChunkSpan() * WORK_SIZE * LOCAL_SIZE / 2; + } + + public void setCullDistance(float dist) + { + if (dist <= 0.0F) + throw new IllegalArgumentException("Cull distance must be greater than zero"); + this.cullDistance = dist; + } + + public void disableCullDistance() + { + this.cullDistance = 0.0F; + } + + public void setScroll(float x, float y, float z) + { + this.scrollX = x; + this.scrollY = y; + this.scrollZ = z; + } + + public Pair getMeshGenStatus() + { + return this.meshGenStatus; + } + + public @Nullable InstanceableMesh getSideMesh() + { + return this.sideMesh; + } + + public @Nullable InstanceableMesh getCubeMesh() + { + return this.cubeMesh; + } + + public int getOpaqueBufferSize() + { + return this.opaqueBufferSize; + } + + public int getOpaqueBufferBytesUsed() + { + return this.opaqueBufferBytesUsed; + } + + public int getTransparentBufferSize() + { + return this.transparentBufferSize; + } + + public int getTransparentBufferBytesUsed() + { + return this.transparentBufferBytesUsed; + } + + public int getOpaqueBytesPerChunk() + { + return this.opaqueBytesPerChunk; + } + + public int getTransparentBytesPerChunk() + { + return this.transparentBytesPerChunk; + } + + public int getTotalMeshChunks() + { + if (this.chunks == null) + return 0; + return this.chunks.size(); + } + + public int getMeshGenInterval() + { + return this.meshGenInterval; + } + + public void close() + { + RenderSystem.assertOnRenderThreadOrInit(); + + this.opaqueBufferBytesUsed = 0; + this.opaqueBufferSize = 0; + this.opaqueBytesPerChunk = 0; + this.transparentBufferBytesUsed = 0; + this.transparentBufferSize = 0; + this.transparentBytesPerChunk = 0; + + GL42.glMemoryBarrier(GL42.GL_ALL_BARRIER_BITS); + this.chunkGenTasks.clear(); + this.completedGenTasks.clear(); + + if (this.shader != null) + this.shader.close(); + this.shader = null; + + if (this.chunks != null) + { + for (MeshChunk chunk : this.chunks) + chunk.destroy(); + this.chunks = null; + } + + if (this.sideMesh != null) + { + this.sideMesh.destroy(); + this.sideMesh = null; + } + + if (this.cubeMesh != null) + { + this.cubeMesh.destroy(); + this.cubeMesh = null; + } + } + + public boolean canRender() + { + return this.chunks != null; + } + + public final RendererInitializeResult init(ResourceManager manager) + { + RendererInitializeResult.Builder builder = RendererInitializeResult.builder(); + + if (!RenderSystem.isOnRenderThreadOrInit()) + return builder.errorUnknown(new IllegalStateException("Init not called on render thread"), "Mesh Generator; Head").build(); + + this.opaqueBufferBytesUsed = 0; + this.opaqueBufferSize = 0; + this.opaqueBytesPerChunk = 0; + this.transparentBufferBytesUsed = 0; + this.transparentBufferSize = 0; + this.transparentBytesPerChunk = 0; + + GL42.glMemoryBarrier(GL42.GL_ALL_BARRIER_BITS); + this.chunkGenTasks.clear(); + this.completedGenTasks.clear(); + + LOGGER.debug("Beginning mesh generator initialization"); + + if (this.shader != null) + { + LOGGER.debug("Freeing mesh compute shader"); + this.shader.close(); + this.shader = null; + } + + if (this.chunks != null) + { + for (MeshChunk chunk : this.chunks) + chunk.destroy(); + this.chunks = null; + } + + try + { + LOGGER.debug("Creating mesh compute shader..."); + this.shader = this.createShader(manager); + this.setupShader(); + } + catch (IOException e) + { + //LOGGER.warn("Failed to load compute shader", e); + builder.errorCouldNotLoadMeshScript(e, "Mesh Generator; Compute Shader"); + } + catch (Exception e) + { + builder.errorRecommendations(e, "Mesh Generator; Compute Shader"); + } + + try + { + this.initExtra(manager); + } + catch (Exception e) + { + builder.errorUnknown(e, "Init Extra"); + } + + List preparedChunks = this.getLodConfig().getPreparedChunks(); + ImmutableList.Builder meshChunks = ImmutableList.builder(); + int totalPreparedChunks = preparedChunks.size(); + this.opaqueBytesPerChunk = Mth.ceil(this.opaqueBufferSize / totalPreparedChunks); + this.transparentBytesPerChunk = Mth.ceil(this.transparentBufferSize / totalPreparedChunks); + if (!this.useFixedMeshDataSectionSize) + { + this.opaqueBytesPerChunk *= 4; + this.transparentBytesPerChunk *= 4; + } + int maxOpaqueElements = Mth.floor((float)this.opaqueBytesPerChunk / (float)BYTES_PER_SIDE_INFO); + int maxTransparentElements = Mth.floor((float)this.transparentBytesPerChunk / (float)BYTES_PER_CUBE_INFO); + int opaqueElementOffset = 0; + int transparentElementOffset = 0; + for (PreparedChunk chunk : preparedChunks) + { + meshChunks.add(new MeshChunk(chunk, maxOpaqueElements, opaqueElementOffset, BYTES_PER_SIDE_INFO, maxTransparentElements, transparentElementOffset, BYTES_PER_CUBE_INFO, this.useTransparency)); + opaqueElementOffset += maxOpaqueElements; + transparentElementOffset += maxTransparentElements; + } + this.chunks = meshChunks.build(); + + LOGGER.debug("Opaque buffer size: {} bytes, transparent buffer size: {} bytes", this.opaqueBufferSize, this.transparentBufferSize); + + if (this.sideMesh != null) + this.sideMesh.destroy(); + this.sideMesh = InstanceableMesh.defaultSide(); + + if (this.cubeMesh != null) + this.cubeMesh.destroy(); + this.cubeMesh = InstanceableMesh.defaultCube(); + + BindingManager.printDebug(); + + LOGGER.debug("Finished initializing mesh generator"); + + return builder.build(); + } + + protected ComputeShader createShader(ResourceManager manager) throws IOException + { + ImmutableMap parameters = ImmutableMap.of( + "TYPE", String.valueOf(this.shaderType), + "FADE_NEAR_ORIGIN", this.fadeNearOrigin ? "1" : "0", + "STYLE", this.shadedClouds ? "1" : "0", + "TRANSPARENCY", this.useTransparency ? "1" : "0", + "FIXED_SECTION_SIZE", this.useFixedMeshDataSectionSize ? "1" : "0" + ); + return ComputeShader.loadShader(this.meshShaderLoc, manager, LOCAL_SIZE, LOCAL_SIZE, LOCAL_SIZE, parameters); + } + + protected void setupShader() + { + this.opaqueBufferSize = this.createBuffers( + TOTAL_SIDES_NAME, + SIDES_PER_CHUNK_NAME, + SIDE_INFO_BUFFER_NAME, + MAX_SIDE_INFO_BUFFER_SIZE * (this.useFixedMeshDataSectionSize ? 4 : 1) + ); + + if (this.useTransparency) + { + this.transparentBufferSize = this.createBuffers( + TRANSPARENT_TOTAL_CUBES_NAME, + TRANSPARENT_CUBES_PER_CHUNK_NAME, + TRANSPARENT_CUBE_INFO_BUFFER_NAME, + MAX_TRANSPARENT_CUBE_INFO_BUFFER_SIZE * (this.useFixedMeshDataSectionSize ? 4 : 1) + ); + } + + this.shader.forUniform("TotalLodLevels", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, this.lodConfig.getLods().length); + }); + + this.uploadFadeData(); + } + + private void uploadFadeData() + { + if (this.shader == null || !this.shader.isValid()) + return; + + this.shader.forUniform("TransparencyDistance", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, this.transparencyDistance); + }); + this.shader.forUniform("FadeStart", (id, loc) -> { + GL41.glProgramUniform1f(id, loc, this.fadeStart); + }); + this.shader.forUniform("FadeEnd", (id, loc) -> { + GL41.glProgramUniform1f(id, loc, this.fadeEnd); + }); + } + + private int createBuffers(String totalCounterName, String countPerChunkName, String elementInfoBufferName, int maxSize) + { + if (!this.useFixedMeshDataSectionSize) + { + ShaderStorageBufferObject totalCountBuffer = this.shader.createAndBindSSBO(totalCounterName, GL15.GL_DYNAMIC_COPY); + totalCountBuffer.allocateBuffer(4); + totalCountBuffer.writeData(b -> { + b.putInt(0, 0); + }, 4, false); + } + + int bufferSize = this.shader.createAndBindSSBO(elementInfoBufferName, GL15.GL_DYNAMIC_COPY).allocateBuffer(maxSize); + + int totalChunks = this.getLodConfig().getPreparedChunks().size(); + int countPerChunkBufferSize = totalChunks * 4; + ShaderStorageBufferObject countPerChunkBuffer = this.shader.createAndBindSSBO(countPerChunkName, GL15.GL_DYNAMIC_COPY); + countPerChunkBuffer.allocateBuffer(countPerChunkBufferSize); + countPerChunkBuffer.writeData(b -> + { + for (int i = 0; i < totalChunks; i++) + b.putInt(0); + b.rewind(); + }, countPerChunkBufferSize, false); + + return bufferSize; + } + + protected void initExtra(ResourceManager manager) throws IOException {} + + /** + * Generates the entire cloud mesh at the origin at once + */ + public void generateMesh() + { + RenderSystem.assertOnRenderThread(); + + if (this.shader == null || !this.shader.isValid()) + return; + + this.prepareMeshGen(0.0D, 0.0D, 0.0D, 0.0F, 0.0F, null, 1, 1.0F); + + if (!this.chunkGenTasks.isEmpty()) + this.doMeshGenning(this.chunkGenTasks.size()); + + this.meshGenStatus = this.finalizeMeshGen(); + this.completedGenTasks.clear(); + } + + public void worldTick() + { + if (this.chunks != null) + this.chunks.forEach(MeshChunk::tick); + } + + /** + * Generates the cloud mesh on a per-frame basis + * + * @param originX + * @param originY + * @param originZ + * @param frustum + */ + public void genTick(double originX, double originY, double originZ, @Nullable Frustum frustum, float partialTick) + { + RenderSystem.assertOnRenderThread(); + + if (this.shader == null || !this.shader.isValid()) + return; + + float chunkSize = (float)SimpleCloudsConstants.CHUNK_SIZE; + float meshGenOffsetX = (float)Mth.floor(originX / chunkSize) * chunkSize; + float meshGenOffsetZ = (float)Mth.floor(originZ / chunkSize) * chunkSize; + + if (this.chunkGenTasks.isEmpty()) //If we have no chunk gen tasks + { + this.meshGenStatus = this.finalizeMeshGen(); //Split the combined mesh data from the GPU, and store them in the VBOs for each chunk that was generated + this.completedGenTasks.clear(); //Clear the chunk gen tasks + + //Prepare the next batch of chunks to generate meshes for + this.meshGenInterval = this.meshGenIntervalCalculator.get(); + if (this.meshGenInterval <= 0) + throw new RuntimeException("Mesh gen interval is <= 0"); + this.tasksPerTick = this.prepareMeshGen(originX, originY, originZ, meshGenOffsetX, meshGenOffsetZ, frustum, this.meshGenInterval, partialTick); + } + else + { + this.onOffGen(); + } + + //If there are mesh gen tasks, we do mesh genning + if (!this.chunkGenTasks.isEmpty()) + this.doMeshGenning(this.tasksPerTick); + } + + private static CloudMeshGenerator.MeshGenStatus fixedIterateAndCopyToChunkBuffer(int copyBufferId, int copyBufferSizeBytes, Collection chunks, Function byteOffsetPerChunk, Function chunkBufferId, Function bytesToCopyPerChunk, Function bufferSizeBytesPerChunk) + { + CloudMeshGenerator.MeshGenStatus result = CloudMeshGenerator.MeshGenStatus.NORMAL; + + GlStateManager._glBindBuffer(GL31.GL_COPY_READ_BUFFER, copyBufferId); + + for (MeshChunk chunk : chunks) + { + int bytesToCopy = bytesToCopyPerChunk.apply(chunk); + if (bytesToCopy > 0) + { + int maxSize = bufferSizeBytesPerChunk.apply(chunk); + if (bytesToCopy > maxSize) // Too many bytes to go in to the chunk mesh buffer + { + bytesToCopy = maxSize; + result = CloudMeshGenerator.MeshGenStatus.CHUNK_OVERFLOW; + } + + int byteOffset = byteOffsetPerChunk.apply(chunk); + if (byteOffset + bytesToCopy > copyBufferSizeBytes) // TODO: Account for this overflow using mesh gen status + { + //TODO: Make sure this uses multiples of the size of a single element to avoid cutting off a single element + bytesToCopy = copyBufferSizeBytes - byteOffset; + if (bytesToCopy <= 0) + continue; + } + + GlStateManager._glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, chunkBufferId.apply(chunk)); + GL31.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, byteOffset, 0, bytesToCopy); + } + } + + return result; + } + + private static CloudMeshGenerator.MeshGenStatus packedIterateAndCopyToChunkBuffer(int copyBufferId, int copyBufferSizeBytes, Collection chunks, Function chunkBufferId, Function bytesToCopyPerChunk, Function bufferSizeBytesPerChunk) + { + CloudMeshGenerator.MeshGenStatus result = CloudMeshGenerator.MeshGenStatus.NORMAL; + + GlStateManager._glBindBuffer(GL31.GL_COPY_READ_BUFFER, copyBufferId); + + int currentBytes = 0; + for (MeshChunk chunk : chunks) + { + int totalBytes = bytesToCopyPerChunk.apply(chunk); + if (totalBytes > 0) //If the chunk has data that needs copying over + { + int lastBytesOffset = totalBytes; + int maxSize = bufferSizeBytesPerChunk.apply(chunk); + if (lastBytesOffset > maxSize) //Make sure we don't go over the maximum the chunk buffer can hold + { + lastBytesOffset = maxSize; + result = CloudMeshGenerator.MeshGenStatus.CHUNK_OVERFLOW; + } + boolean stop = false; + if (currentBytes + lastBytesOffset > copyBufferSizeBytes) //If the the byte offset will go over the size of the copy buffer, clamp + { + lastBytesOffset = copyBufferSizeBytes - currentBytes; + if (lastBytesOffset <= 0) // If it becomes negative however, we don't want to attempt to copy data over + return CloudMeshGenerator.MeshGenStatus.MESH_POOL_OVERFLOW; + stop = true; // After copying this data over we will stop, since there is no more space in the copy buffer to read data from + } + + GlStateManager._glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, chunkBufferId.apply(chunk)); + GL31.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, currentBytes, 0, lastBytesOffset); + + currentBytes += totalBytes; + + if (stop) + return CloudMeshGenerator.MeshGenStatus.MESH_POOL_OVERFLOW; + } + } + + return result; + } + + protected Pair finalizeMeshGen() + { + if (this.shader == null || !this.shader.isValid() || this.chunks == null) + return Pair.of(CloudMeshGenerator.MeshGenStatus.NOT_INITIALIZED, CloudMeshGenerator.MeshGenStatus.NOT_INITIALIZED); + + if (this.completedGenTasks.isEmpty()) + return Pair.of(CloudMeshGenerator.MeshGenStatus.NO_TASKS, CloudMeshGenerator.MeshGenStatus.NO_TASKS); + + RenderSystem.assertOnRenderThread(); + + GL42.glMemoryBarrier(GL43.GL_SHADER_STORAGE_BARRIER_BIT); + + CloudMeshGenerator.MeshGenStatus opaqueResult = CloudMeshGenerator.MeshGenStatus.NORMAL; + CloudMeshGenerator.MeshGenStatus transparentResult = CloudMeshGenerator.MeshGenStatus.NORMAL; + + opaqueResult = this.copyMeshData( + TOTAL_SIDES_NAME, + SIDES_PER_CHUNK_NAME, + SIDE_INFO_BUFFER_NAME, + MeshChunk::getOpaqueBuffers, + BYTES_PER_SIDE_INFO, + this.opaqueBufferSize + ); + + if (this.useTransparency) + { + transparentResult = this.copyMeshData( + TRANSPARENT_TOTAL_CUBES_NAME, + TRANSPARENT_CUBES_PER_CHUNK_NAME, + TRANSPARENT_CUBE_INFO_BUFFER_NAME, + c -> c.getTransparentBuffers().get(), + BYTES_PER_CUBE_INFO, + this.transparentBufferSize + ); + } + + this.opaqueBufferBytesUsed = 0; + this.transparentBufferBytesUsed = 0; + for (MeshChunk chunk : this.chunks) + { + this.opaqueBufferBytesUsed += chunk.getOpaqueBuffers().getElementCount() * BYTES_PER_SIDE_INFO; + chunk.getTransparentBuffers().ifPresent(bufferSet -> { + this.transparentBufferBytesUsed += bufferSet.getElementCount() * BYTES_PER_CUBE_INFO; + }); + } + + return Pair.of(opaqueResult, transparentResult); + } + + private CloudMeshGenerator.MeshGenStatus copyMeshData(String totalCountBufferName, String countPerChunkBufferName, String elementBufferName, Function bufferSetFunction, int bytesPerElement, int elementBufferSize) + { + CloudMeshGenerator.MeshGenStatus status = CloudMeshGenerator.MeshGenStatus.NORMAL; + + if (!this.useFixedMeshDataSectionSize) + { + //Get the total amount of sides and indices across all chunks and reset + this.shader.getShaderStorageBuffer(totalCountBufferName).writeData(b -> { + b.putInt(0, 0); + }, 4, true); + } + + //Get the amount of total sides each chunk has and reset each counter + this.shader.getShaderStorageBuffer(countPerChunkBufferName).readWriteData(buffer -> + { + for (CloudMeshGenerator.ChunkGenTask gennedChunk : this.completedGenTasks) + { + MeshChunk.BufferSet bufferSet = bufferSetFunction.apply(gennedChunk.chunk()); + int index = gennedChunk.index() * 4; + int count = buffer.getInt(index); + bufferSet.setTotalElementCount(count); + buffer.putInt(index, 0); + } + }, this.chunks.size() * 4); + + List completedChunks = this.completedGenTasks.stream().map(CloudMeshGenerator.ChunkGenTask::chunk).toList(); + + int elementBufferId = this.shader.getShaderStorageBuffer(elementBufferName).getId(); + if (this.useFixedMeshDataSectionSize) + status = fixedIterateAndCopyToChunkBuffer(elementBufferId, elementBufferSize, completedChunks, bufferSetFunction.andThen(b -> b.getElementOffset() * bytesPerElement), bufferSetFunction.andThen(MeshChunk.BufferSet::getBufferId), bufferSetFunction.andThen(c -> c.getElementCount() * bytesPerElement), bufferSetFunction.andThen(MeshChunk.BufferSet::getBufferSize)); + else + status = packedIterateAndCopyToChunkBuffer(elementBufferId, elementBufferSize, completedChunks, bufferSetFunction.andThen(MeshChunk.BufferSet::getBufferId), bufferSetFunction.andThen(c -> c.getElementCount() * bytesPerElement), bufferSetFunction.andThen(MeshChunk.BufferSet::getBufferSize)); + + GlStateManager._glBindBuffer(GL31.GL_COPY_READ_BUFFER, 0); + GlStateManager._glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, 0); + + return status; + } + + /** + * Queues a list of chunk gen tasks for each chunk in this mesh generator + * + * @param meshGenOffsetX + * @param meshGenOffsetZ + * @param frustum + * Culling frustum, null for no culling + * @param genInterval + * How many frames mesh genning should take + * @return + */ + protected int prepareMeshGen(double originX, double originY, double originZ, float meshGenOffsetX, float meshGenOffsetZ, @Nullable Frustum frustum, int genInterval, float partialTick) + { + this.shader.forUniform("Scroll", (id, loc) -> { + GL41.glProgramUniform3f(id, loc, this.scrollX, this.scrollY, this.scrollZ); + }); + this.shader.forUniform("Wiggle", (id, loc) -> { + GL41.glProgramUniform1f(id, loc, (this.scrollX + this.scrollY + this.scrollZ) / 5.0F); + }); + this.shader.forUniform("Origin", (id, loc) -> { + GL41.glProgramUniform3f(id, loc, (float)originX, (float)originY, (float)originZ); + }); + this.shader.forUniform("TestFacesFacingAway", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, this.testFacesFacingAway ? 1 : 0); + }); + this.uploadFadeData(); + + int chunkCount = 0; + for (int i = 0; i < this.chunks.size(); i++) + { + if (this.queueChunkMeshGenTaskOrClear(this.chunks.get(i), i, meshGenOffsetX, meshGenOffsetZ, frustum)) + chunkCount++; + } + return Mth.ceil((float)chunkCount / (float)genInterval); + } + + protected void onOffGen() + { + //We read these SSBOs here to avoid weird frame spikes when in fullscreen V-Sync, not sure why it happens + if (!this.useFixedMeshDataSectionSize) + this.shader.getShaderStorageBuffer(TOTAL_SIDES_NAME).readWriteData(b -> {}, 4); + this.shader.getShaderStorageBuffer(SIDES_PER_CHUNK_NAME).readWriteData(buffer -> {}, this.chunks.size() * 4); + if (this.useTransparency) + { + if (!this.useFixedMeshDataSectionSize) + this.shader.getShaderStorageBuffer(TRANSPARENT_TOTAL_CUBES_NAME).readWriteData(b -> {}, 4); + this.shader.getShaderStorageBuffer(TRANSPARENT_CUBES_PER_CHUNK_NAME).readWriteData(buffer -> {}, this.chunks.size() * 4); + } + } + + /** + * Queues a given chunk for mesh genning or clears it if empty + * + * @param chunk + * The given {@link MeshChunk} to generate a mesh for + * @param chunkIndex + * The index of the mesh chunk in {@code this.chunks} + * @param meshGenOffsetX + * @param meshGenOffsetZ + * @param frustum + * For frustum culling, null for no culling + * @return + */ + protected boolean queueChunkMeshGenTaskOrClear(MeshChunk chunk, int chunkIndex, float meshGenOffsetX, float meshGenOffsetZ, @Nullable Frustum frustum) + { + PreparedChunk chunkInfo = chunk.getChunkInfo(); + AABB bounds = chunkInfo.bounds(); + float minX = (float)bounds.minX + meshGenOffsetX; + float minZ = (float)bounds.minZ + meshGenOffsetZ; + float maxX = (float)bounds.maxX + meshGenOffsetX; + float maxZ = (float)bounds.maxZ + meshGenOffsetZ; + + if (frustum == null || ((MixinFrustumAccessor)frustum).simpleclouds$cubeInFrustum(minX, bounds.minY, minZ, maxX, bounds.maxY, maxZ)) + { + double nearestCornerX = Math.max(Math.max(bounds.minX, -bounds.maxX), 0.0D); + double nearestCornerZ = Math.max(Math.max(bounds.minZ, -bounds.maxZ), 0.0D); + double dist = Math.sqrt(nearestCornerX * nearestCornerX + nearestCornerZ * nearestCornerZ); + + if (this.cullDistance <= 0.0F || dist < this.cullDistance) + { + CloudMeshGenerator.ChunkGenSettings settings = this.determineChunkGenSettings(minX, minZ, maxX, maxZ); + if (settings.skipChunk()) + { + chunk.clearChunk(); + return false; + } + this.chunkGenTasks.add(new CloudMeshGenerator.ChunkGenTask(chunk, minX, (float)bounds.minY, minZ, maxX, (float)bounds.maxY, maxZ, chunkIndex, minX, 0.0F, minZ, settings.minimumHeight(), settings.maximumHeight())); + return true; + } + } + return false; + } + + protected abstract CloudMeshGenerator.ChunkGenSettings determineChunkGenSettings(float minX, float minZ, float maxX, float maxZ); + + /** + * Does mesh generating for a given amount of chunks defined by tasksPerTick + * + * @param tasksPerTick + */ + protected void doMeshGenning(int tasksPerTick) + { + for (int i = 0; i < tasksPerTick; i++) + { + CloudMeshGenerator.ChunkGenTask task = this.chunkGenTasks.poll(); + if (task != null) + { + this.generateChunk(task); + this.updateMeshChunkAfterGeneration(task.chunk(), task); + this.completedGenTasks.add(task); + } + else + { + break; + } + } + } + + protected void updateMeshChunkAfterGeneration(MeshChunk chunk, CloudMeshGenerator.ChunkGenTask task) + { + chunk.setBounds(task.minX(), task.minY(), task.minZ(), task.maxX(), task.maxY(), task.maxZ()); + chunk.setHeights(task.startY(), task.endY()); + chunk.resetLastGenTime(); + } + + /** + * Generates a given chunk, or completes a chunk gen task + * + * @param task + * @param scale + * @param globalOffsetX + * @param globalOffsetZ + */ + protected void generateChunk(CloudMeshGenerator.ChunkGenTask task) + { + PreparedChunk chunkInfo = task.chunk().getChunkInfo(); + + int lodScale = chunkInfo.lodScale(); + int lowestY = task.startY(); + int height = Mth.ceil((float)(task.endY() - lowestY) / (float)lodScale); + int localHeightInvocations = Mth.ceil((float)height / (float)LOCAL_SIZE); + + if (localHeightInvocations > 0) + { + this.shader.forUniform("ChunkIndex", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, task.index()); + }); + this.shader.forUniform("LodLevel", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, chunkInfo.lodLevel()); + }); + this.shader.forUniform("RenderOffset", (id, loc) -> { + GL41.glProgramUniform3f(id, loc, task.x(), task.y() + lowestY, task.z()); + }); + this.shader.forUniform("Scale", (id, loc) -> { + GL41.glProgramUniform1f(id, loc, lodScale); + }); + this.shader.forUniform("DoNotOccludeSide", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, chunkInfo.noOcclusionDirectionIndex()); + }); + if (this.useFixedMeshDataSectionSize) + { + this.shader.forUniform("OpaqueMeshDataOffset", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, task.chunk().getOpaqueBuffers().getElementOffset()); + }); + + task.chunk().getTransparentBuffers().ifPresent(bufferSet -> + { + this.shader.forUniform("TransparentMeshDataOffset", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, bufferSet.getElementOffset()); + }); + }); + } + + this.shader.dispatch(WORK_SIZE, localHeightInvocations, WORK_SIZE, false); + if (!this.useFixedMeshDataSectionSize) + GL42.glMemoryBarrier(GL43.GL_SHADER_STORAGE_BARRIER_BIT); + } + } + + public void forRenderableMeshChunks(@Nullable Frustum frustum, Function bufferSetFunction, BiConsumer function) + { + this.forRenderableMeshChunks(frustum, bufferSetFunction, function, false); + } + + public void forRenderableMeshChunks(@Nullable Frustum frustum, Function bufferSetFunction, BiConsumer function, boolean updateFade) + { + for (MeshChunk chunk : this.chunks) + { + MeshChunk.BufferSet bufferSet = bufferSetFunction.apply(chunk); + if (bufferSet.getElementCount() > 0) + { + if (updateFade && chunk.getTicksSinceLastGen() > TICKS_UNTIL_FADE_RESET) + { + chunk.resetAlpha(); + chunk.setFadeEnabled(false); + } + + boolean render = true; + if (frustum != null) + render = ((MixinFrustumAccessor)frustum).simpleclouds$cubeInFrustum(chunk.getBoundsMinX(), chunk.getBoundsMinY(), chunk.getBoundsMinZ(), chunk.getBoundsMaxX(), chunk.getBoundsMaxY(), chunk.getBoundsMaxZ()); + + if (render) + { + PreparedChunk chunkInfo = chunk.getChunkInfo(); + AABB bounds = chunkInfo.bounds(); + double nearestCornerX = Math.max(Math.max(bounds.minX, -bounds.maxX), 0.0D); + double nearestCornerZ = Math.max(Math.max(bounds.minZ, -bounds.maxZ), 0.0D); + double dist = Math.sqrt(nearestCornerX * nearestCornerX + nearestCornerZ * nearestCornerZ); + if (this.cullDistance <= 0.0F || this.cullDistance > dist) + { + if (updateFade) + chunk.setFadeEnabled(true); + function.accept(chunk, bufferSet); + } + } + } + } + } + + public void fillReport(CrashReportCategory category) + { + category.setDetail("Shader Type", this.shaderType); + category.setDetail("Shaded Clouds", this.shadedClouds); + category.setDetail("Transparency Enabled", this.useTransparency); + category.setDetail("Fade Near Origin", this.fadeNearOrigin); + category.setDetail("Compute Shader", this.shader); + category.setDetail("Level Of Details", 1 + this.lodConfig.getLods().length); + category.setDetail("Generation Frame Interval", this.meshGenInterval); + category.setDetail("Total Prepared Chunks", this.lodConfig.getPreparedChunks().size()); + category.setDetail("Tasks Per Frame", this.tasksPerTick); + category.setDetail("Scroll", String.format("X: %s, Y: %s, Z: %s", this.scrollX, this.scrollY, this.scrollZ)); + category.setDetail("Total Mesh Chunks", this.chunks != null ? this.chunks.size() : "null"); + category.setDetail("Mesh Gen Status", this.meshGenStatus); + category.setDetail("Test Occluded Faces", this.testFacesFacingAway); + } + + @Override + public String toString() + { + return String.format("%s[shader_name=%s]", this.getClass().getSimpleName(), this.meshShaderLoc); + } + + protected static CloudMeshGenerator.ChunkGenSettings skip() + { + return new CloudMeshGenerator.ChunkGenSettings(true, 0, 0); + } + + protected static CloudMeshGenerator.ChunkGenSettings heights(int min, int max) + { + return new CloudMeshGenerator.ChunkGenSettings(false, min, max); + } + + public static CloudMeshGenerator.Builder builder() + { + return new CloudMeshGenerator.Builder(); + } + + protected static record ChunkGenSettings(boolean skipChunk, int minimumHeight, int maximumHeight) {} + + protected static record ChunkGenTask(MeshChunk chunk, float minX, float minY, float minZ, float maxX, float maxY, float maxZ, int index, float x, float y, float z, int startY, int endY) {} + + public static enum MeshGenStatus + { + NOT_INITIALIZED("Not initialized", true), + NO_TASKS("No tasks", false), + NORMAL("Normal", false), + MESH_POOL_OVERFLOW("Mesh pool overflow", true), + CHUNK_OVERFLOW("Chunk overflow", true); + + private String name; + private boolean isErroneous; + + private MeshGenStatus(String name, boolean isErroneous) + { + this.name = name; + this.isErroneous = isErroneous; + } + + public String getName() + { + return this.name; + } + + public boolean isErroneous() + { + return this.isErroneous; + } + } + + public static class Builder + { + private boolean fadeNearOrigin; + private boolean shadedClouds = true; + private LevelOfDetailConfig lodConfig = LevelOfDetailOptions.HIGH.getConfig(); + private Supplier meshGenIntervalCalculator = () -> 5; + private boolean useTransparency = true; + private boolean fixedMeshDataSectionSize; + private float fadeStart = 0.5F; + private float fadeEnd = 1.0F; + private boolean testFacesFacingAway = false; + + private Builder() {} + + public Builder fadeNearOrigin(boolean flag) + { + this.fadeNearOrigin = flag; + return this; + } + + public Builder shadedClouds(boolean flag) + { + this.shadedClouds = flag; + return this; + } + + public Builder meshGenInterval(int interval) + { + if (interval <= 0) + throw new IllegalArgumentException("Mesh gen interval must be greater than 0"); + this.meshGenIntervalCalculator = () -> interval; + return this; + } + + public Builder meshGenInterval(Supplier calculator) + { + this.meshGenIntervalCalculator = calculator; + return this; + } + + public Builder lodConfig(LevelOfDetailConfig config) + { + this.lodConfig = config; + return this; + } + + public Builder useTransparency(boolean flag) + { + this.useTransparency = flag; + return this; + } + + public Builder fixedMeshDataSectionSize(boolean flag) + { + this.fixedMeshDataSectionSize = flag; + return this; + } + + public Builder fadeStart(float fadeStart) + { + this.fadeStart = fadeStart; + return this; + } + + public Builder fadeEnd(float fadeEnd) + { + this.fadeEnd = fadeEnd; + return this; + } + + public Builder testFacesFacingAway(boolean flag) + { + this.testFacesFacingAway = flag; + return this; + } + + private T applyExtraSettings(T generator) + { + generator.setFadeDistances(this.fadeStart, this.fadeEnd); + generator.setTestFacesFacingAway(this.testFacesFacingAway); + return generator; + } + + public MultiRegionCloudMeshGenerator createMultiRegion() + { + return this.applyExtraSettings(new MultiRegionCloudMeshGenerator(this.fadeNearOrigin, this.shadedClouds, this.lodConfig, this.meshGenIntervalCalculator, this.useTransparency, this.fixedMeshDataSectionSize)); + } + + public SingleRegionCloudMeshGenerator createSingleRegion(CloudInfo type) + { + return this.applyExtraSettings(new SingleRegionCloudMeshGenerator(this.shadedClouds, this.lodConfig, this.meshGenIntervalCalculator, this.useTransparency, this.fixedMeshDataSectionSize, type)); + } + } +} diff --git a/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/mesh/generator/MultiRegionCloudMeshGenerator.java b/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/mesh/generator/MultiRegionCloudMeshGenerator.java new file mode 100644 index 00000000..8226742f --- /dev/null +++ b/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/mesh/generator/MultiRegionCloudMeshGenerator.java @@ -0,0 +1,404 @@ +package dev.nonamecrackers2.simpleclouds.client.mesh.generator; + +import java.io.IOException; +import java.nio.IntBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.joml.Matrix2f; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL41; +import org.lwjgl.opengl.GL42; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.mojang.blaze3d.platform.TextureUtil; +import com.mojang.blaze3d.systems.RenderSystem; + +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.LevelOfDetail; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.LevelOfDetailConfig; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.PreparedChunk; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.BindingManager; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.ShaderStorageBufferObject; +import dev.nonamecrackers2.simpleclouds.client.shader.compute.ComputeShader; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudInfo; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudType; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudGetter; +import dev.nonamecrackers2.simpleclouds.common.noise.AbstractNoiseSettings; +import dev.nonamecrackers2.simpleclouds.common.noise.NoiseSettings; +import net.minecraft.CrashReportCategory; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; + +public final class MultiRegionCloudMeshGenerator extends CloudMeshGenerator +{ + private static final Logger LOGGER = LogManager.getLogger("simpleclouds/MultiRegionCloudMeshGenerator"); + + private static final ResourceLocation REGION_GENERATOR_LOC = SimpleCloudsMod.id("cloud_regions"); + private static final String LOD_SCALES_NAME = "LodScales"; + private static final String CLOUD_REGIONS_NAME = "CloudRegions"; + public static final int MAX_CLOUD_TYPES = 64; + public static final int MAX_CLOUD_FORMATIONS = 10; + private static final int BYTES_PER_REGION = 32; + private int requiredRegionTexSize; + private CloudGetter cloudGetter = CloudGetter.EMPTY; + private CloudInfo[] cachedTypes = new CloudInfo[0]; + private @Nullable ComputeShader regionTextureGenerator; + private int cloudRegionTextureId = -1; + private int cloudRegionImageBinding = -1; + private boolean updateCloudTypes; + private int currentCloudFormationCount; + + protected MultiRegionCloudMeshGenerator(boolean fadeNearOrigin, boolean shadedClouds, LevelOfDetailConfig lodConfig, Supplier meshGenIntervalCalculator, boolean useTransparency, boolean fixedMeshDataSectionSize) + { + super(CloudMeshGenerator.MAIN_CUBE_MESH_GENERATOR, 0, fadeNearOrigin, shadedClouds, lodConfig, meshGenIntervalCalculator, useTransparency, fixedMeshDataSectionSize); + } + + public void setCloudGetter(CloudGetter getter) + { + this.cloudGetter = Objects.requireNonNull(getter, "Cloud getter cannot be null"); + this.updateCloudTypes(); + } + + public int getCloudRegionTextureId() + { + return this.cloudRegionTextureId; + } + + public void updateCloudTypes() + { + this.updateCloudTypes = true; + } + + public int getTotalCloudTypes() + { + return this.cachedTypes.length; + } + + public int getCloudFormationCount() + { + return this.currentCloudFormationCount; + } + + @Override + protected void setupShader() + { + super.setupShader(); + + this.cachedTypes = new CloudInfo[0]; + this.updateCloudTypes = false; + + this.shader.createAndBindSSBO(NOISE_LAYERS_NAME, GL15.GL_STATIC_DRAW).allocateBuffer(AbstractNoiseSettings.Param.values().length * 4 * MAX_NOISE_LAYERS * MAX_CLOUD_TYPES); + this.shader.createAndBindSSBO(LAYER_GROUPINGS_NAME, GL15.GL_STATIC_DRAW).allocateBuffer(CloudInfo.BYTES_PER_TYPE * MAX_CLOUD_TYPES); + + this.uploadCloudTypeData(); + } + + @Override + protected void initExtra(ResourceManager manager) throws IOException + { + // Cloud region texture generator compute shader + // This texture is a 2D array texture, with a texture for each level of detail. + // The red channel contains the index for a cloud type in the main mesh compute shader, and + // the green channel contains an "edge fade" value for smooth cloud region boundaries. + // When generating the cloud mesh, the main mesh compute shader samples this array texture + // depending on what LOD it is generating for to determine what cloud type to construct + + // Create the compute shader + + this.currentCloudFormationCount = 0; + this.requiredRegionTexSize = 0; + + if (this.regionTextureGenerator != null) + this.regionTextureGenerator.close(); + + var params = ImmutableMap.of("EDGE_FADE_FACTOR", String.valueOf(SimpleCloudsConstants.REGION_EDGE_FADE_FACTOR)); + this.regionTextureGenerator = ComputeShader.loadShader(REGION_GENERATOR_LOC, manager, 16, 16, this.lodConfig.getLods().length + 1, params); + + ShaderStorageBufferObject lodScales = this.regionTextureGenerator.createAndBindSSBO(LOD_SCALES_NAME, GL15.GL_STATIC_READ); + int lodScalesSize = this.lodConfig.getLods().length * 4 + 4; + lodScales.allocateBuffer(lodScalesSize); + lodScales.writeData(b -> { + b.putFloat(1.0F); // Primary chunk scale + for (LevelOfDetail l : this.lodConfig.getLods()) + b.putFloat((float)l.chunkScale()); + b.rewind(); + }, lodScalesSize, false); + + // Data for the cloud regions in world + this.regionTextureGenerator.createAndBindSSBO(CLOUD_REGIONS_NAME, GL15.GL_STATIC_READ).allocateBuffer(MAX_CLOUD_FORMATIONS * BYTES_PER_REGION); + + // Create the cloud region 2D array texture + + // Here we calculate the maximum size we need for this array texture, + // ensuring each block in the mesh will have a value to read in this texture + // when doing mesh generation + int prevSpan = this.lodConfig.getPrimaryChunkSpan(); + int prevScale = 1; + int largestSpan = prevSpan; + for (LevelOfDetail config : this.lodConfig.getLods()) + { + int scale = config.chunkScale(); + int div = scale / prevScale; + prevScale = scale; + prevSpan = prevSpan / div + config.spread() * 2; + if (prevSpan > largestSpan) + largestSpan = prevSpan; + } + this.requiredRegionTexSize = largestSpan * SimpleCloudsConstants.CHUNK_SIZE; + + if (this.cloudRegionTextureId != -1) + { + TextureUtil.releaseTextureId(this.cloudRegionTextureId); + this.cloudRegionTextureId = -1; + } + + this.cloudRegionTextureId = TextureUtil.generateTextureId(); + GL11.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cloudRegionTextureId); + GL11.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE); + GL11.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE); + GL11.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); + GL11.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + GL12.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RG32F, this.requiredRegionTexSize, this.requiredRegionTexSize, this.lodConfig.getLods().length + 1, 0, GL30.GL_RG, GL11.GL_FLOAT, (IntBuffer)null); + GL11.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, 0); + + // Assign an image unit to it so any shader can access it + if (this.cloudRegionImageBinding != -1) + BindingManager.freeImageUnit(this.cloudRegionImageBinding); + this.cloudRegionImageBinding = BindingManager.getAvailableImageUnit(); + BindingManager.useImageUnit(this.cloudRegionImageBinding); + GL42.glBindImageTexture(this.cloudRegionImageBinding, this.cloudRegionTextureId, 0, true, 0, GL15.GL_WRITE_ONLY, GL30.GL_RG32F); + this.regionTextureGenerator.setImageUnit("regionTexture", this.cloudRegionImageBinding); + + this.runRegionGenerator(0.0F, 0.0F, 1.0F); + + // Update the main mesh shader to use this texture + this.shader.setSampler2DArray("RegionsSampler", this.cloudRegionTextureId, 0); + this.shader.forUniform("RegionsTexSize", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, this.requiredRegionTexSize); + }); + + LOGGER.debug("Created cloud region texture generator with size {}x{}x{}", this.requiredRegionTexSize, this.requiredRegionTexSize, this.lodConfig.getLods().length + 1); + } + + @Override + protected CloudMeshGenerator.ChunkGenSettings determineChunkGenSettings(float minX, float minZ, float maxX, float maxZ) + { + float[][] positions = new float[][] { {minX, minZ}, {minX, maxZ}, {maxX, minZ}, {maxX, maxZ} }; + int smallestStartHeight = 0; + int largestEndHeight = 0; + boolean empty = true; + for (int i = 0; i < positions.length; i++) + { + float[] pos = positions[i]; + Pair typeAt = this.cloudGetter.getCloudTypeAtPosition(pos[0], pos[1]); + if (typeAt.getRight() < 1.0F) + empty = false; + NoiseSettings config = typeAt.getLeft().noiseConfig(); + int startHeight = config.getStartHeight(); + int endHeight = config.getEndHeight(); + if (i == 0 || smallestStartHeight > startHeight) + smallestStartHeight = startHeight; + if (i == 0 || largestEndHeight < endHeight) + largestEndHeight = endHeight; + } + if (empty || smallestStartHeight == largestEndHeight) + return skip(); + else + return heights(smallestStartHeight, largestEndHeight); + } + + @Override + protected void generateChunk(CloudMeshGenerator.ChunkGenTask task) + { + this.shader.forUniform("RegionSampleOffset", (id, loc) -> + { + PreparedChunk chunk = task.chunk().getChunkInfo(); + GL41.glProgramUniform2f(id, loc, chunk.x() * (float)SimpleCloudsConstants.CHUNK_SIZE + (float)this.requiredRegionTexSize / 2.0F, chunk.z() * (float)SimpleCloudsConstants.CHUNK_SIZE + (float)this.requiredRegionTexSize / 2.0F); + }); + this.shader.setSampler2DArray("RegionsSampler", this.cloudRegionTextureId, 0); + + super.generateChunk(task); + } + + private void runRegionGenerator(float meshOffsetX, float meshOffsetZ, float partialTick) + { + if (this.regionTextureGenerator == null || !this.regionTextureGenerator.isValid()) + return; + + this.uploadCloudRegionData(partialTick); + this.regionTextureGenerator.forUniform("Offset", (id, loc) -> { + GL41.glProgramUniform2f(id, loc, meshOffsetX, meshOffsetZ); + }); + this.regionTextureGenerator.dispatchAndWait(this.requiredRegionTexSize / 16, this.requiredRegionTexSize / 16, 1); + } + + private void uploadCloudRegionData(float partialTick) + { + if (this.regionTextureGenerator == null || !this.regionTextureGenerator.isValid()) + return; + + // Converts the cloud regions into data we can then easily pack into + // the SSBO. This method also checks and excludes cloud regions that + // reference cloud types that are not set up in this mesh generator + // to avoid errors. + List regionData = this.cloudGetter.getClouds().stream().map(region -> + { + Matrix2f transform = region.createTransform(partialTick); + float[] data = new float[] { + region.getPosX(partialTick), + region.getPosZ(partialTick), + (float)ArrayUtils.indexOf(this.cachedTypes, this.cloudGetter.getCloudTypeForId(region.getCloudTypeId())), + region.getRadius(partialTick), + transform.m00, + transform.m01, + transform.m10, + transform.m11 + }; + return data; + }).filter(data -> data[2] >= 0.0F).toList(); + + int regionDataSize = regionData.size(); + int count = Math.min(MAX_CLOUD_FORMATIONS, regionDataSize); + if (regionDataSize != this.currentCloudFormationCount) + { + if (regionDataSize > MAX_CLOUD_FORMATIONS && regionDataSize > this.currentCloudFormationCount) + LOGGER.warn("Cloud formations {}/{}. Maximum count has been exceeded; some cloud formations will be ignored. Please ensure cloud formation count does not exceed the maximum of {}.", regionData.size(), MAX_CLOUD_FORMATIONS, MAX_CLOUD_FORMATIONS); + this.currentCloudFormationCount = regionDataSize; + } + + if (count > 0) + { + ShaderStorageBufferObject regionsBuffer = this.regionTextureGenerator.getShaderStorageBuffer("CloudRegions"); + regionsBuffer.writeData(b -> + { + for (int i = 0; i < count; i++) + { + float[] data = regionData.get(i); + for (float f : data) + b.putFloat(f); + } + b.rewind(); + }, count * BYTES_PER_REGION, false); + } + + this.regionTextureGenerator.forUniform("TotalCloudRegions", (id, loc) -> { + GL41.glProgramUniform1i(id, loc, count); + }); + } + + private void uploadCloudTypeData() + { + RenderSystem.assertOnRenderThreadOrInit(); + + if (this.shader != null && this.shader.isValid()) + { + var toCopy = this.cloudGetter.getIndexedCloudTypes(); + if (toCopy.length > MAX_CLOUD_TYPES) + LOGGER.warn("Cloud type count exceeds the maximum. Not all cloud types will render."); + int copySize = Math.min(MAX_CLOUD_TYPES, toCopy.length); + this.cachedTypes = Arrays.copyOf(toCopy, copySize); + + LOGGER.debug("Uploading cloud type noise data..."); + + this.shader.getShaderStorageBuffer(LAYER_GROUPINGS_NAME).writeData(b -> + { + int previousLayerIndex = 0; + for (int i = 0; i < this.cachedTypes.length; i++) + { + CloudInfo type = this.cachedTypes[i]; + previousLayerIndex = type.packToBuffer(b, previousLayerIndex); + } + b.rewind(); + }, CloudInfo.BYTES_PER_TYPE * this.cachedTypes.length, false); + + this.shader.getShaderStorageBuffer(NOISE_LAYERS_NAME).writeData(b -> + { + for (int i = 0; i < this.cachedTypes.length; i++) + { + NoiseSettings settings = this.cachedTypes[i].noiseConfig(); + float[] packed = settings.packForShader(); + for (int j = 0; j < packed.length && j < AbstractNoiseSettings.Param.values().length * MAX_NOISE_LAYERS; j++) + b.putFloat(packed[j]); + } + b.rewind(); + }, AbstractNoiseSettings.Param.values().length * 4 * MAX_NOISE_LAYERS * this.cachedTypes.length, false); + } + } + + @Override + protected int prepareMeshGen(double originX, double originY, double originZ, float meshGenOffsetX, float meshGenOffsetZ, @Nullable Frustum frustum, int interval, float partialTick) + { + if (this.updateCloudTypes) + { + this.uploadCloudTypeData(); + this.updateCloudTypes = false; + } + + this.runRegionGenerator(meshGenOffsetX, meshGenOffsetZ, partialTick); + + return super.prepareMeshGen(originX, originY, originZ, meshGenOffsetX, meshGenOffsetZ, frustum, interval, partialTick); + } + + @Override + protected void onOffGen() + { + super.onOffGen(); + + if (this.regionTextureGenerator != null) + this.regionTextureGenerator.getShaderStorageBuffer("CloudRegions").readData(buf -> {}, BYTES_PER_REGION * MAX_CLOUD_FORMATIONS); + } + + @Override + public void close() + { + super.close(); + + this.currentCloudFormationCount = 0; + this.requiredRegionTexSize = 0; + this.updateCloudTypes = false; + this.cloudGetter = CloudGetter.EMPTY; + this.cachedTypes = new CloudInfo[0]; + + if (this.regionTextureGenerator != null) + { + this.regionTextureGenerator.close(); + this.regionTextureGenerator = null; + } + + if (this.cloudRegionTextureId != -1) + { + TextureUtil.releaseTextureId(this.cloudRegionTextureId); + this.cloudRegionTextureId = -1; + } + + if (this.cloudRegionImageBinding != -1) + { + BindingManager.freeImageUnit(this.cloudRegionImageBinding); + this.cloudRegionImageBinding = -1; + } + } + + @Override + public void fillReport(CrashReportCategory category) + { + category.setDetail("Cloud Types", "(" + this.cachedTypes.length + ") " + Joiner.on(", ").join(this.cachedTypes)); + category.setDetail("Cloud Regions", this.cloudGetter.getClouds().size()); + category.setDetail("Cloud Formations", this.currentCloudFormationCount); + super.fillReport(category); + } +} diff --git a/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/mesh/instancing/InstanceableMesh.java b/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/mesh/instancing/InstanceableMesh.java new file mode 100644 index 00000000..49a38c40 --- /dev/null +++ b/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/mesh/instancing/InstanceableMesh.java @@ -0,0 +1,219 @@ +package dev.nonamecrackers2.simpleclouds.client.mesh.instancing; + +import java.nio.ByteBuffer; +import java.util.function.Consumer; +import java.util.function.Function; + +import javax.annotation.Nullable; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL31; +import org.lwjgl.system.MemoryUtil; + +import com.mojang.blaze3d.platform.MemoryTracker; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.VertexFormat; + +public class InstanceableMesh +{ + private int arrayObjectId = -1; + private int vertexBufferId = -1; + private int indexBufferId = -1; + private @Nullable ByteBuffer vertexBuffer; + private @Nullable ByteBuffer indexBuffer; + private int totalIndices; + + public InstanceableMesh(int vertexBufferSize, int indexBufferSize, VertexFormat format, Consumer vertexBufferGenerator, Function indexBufferGenerator) + { + RenderSystem.assertOnRenderThread(); + + this.arrayObjectId = GL30.glGenVertexArrays(); + this.vertexBufferId = GL15.glGenBuffers(); + this.indexBufferId = GL15.glGenBuffers(); + + GL30.glBindVertexArray(this.arrayObjectId); + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.vertexBufferId); + this.vertexBuffer = MemoryTracker.create(vertexBufferSize); + vertexBufferGenerator.accept(this.vertexBuffer); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, this.vertexBuffer, GL15.GL_STATIC_DRAW); + format.setupBufferState(); + GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, this.indexBufferId); + this.indexBuffer = MemoryTracker.create(indexBufferSize); + this.totalIndices = indexBufferGenerator.apply(this.indexBuffer); + GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer, GL15.GL_STATIC_DRAW); + + GL30.glBindVertexArray(0); + } + + public static InstanceableMesh defaultSide() + { + return new InstanceableMesh(48, 24, DefaultVertexFormat.POSITION, buffer -> + { + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); + buffer.rewind(); + }, buffer -> + { + buffer.putInt(0); + buffer.putInt(1); + buffer.putInt(2); + buffer.putInt(0); + buffer.putInt(2); + buffer.putInt(3); + buffer.rewind(); + return 6; + }); + } + + public static InstanceableMesh defaultNonCulledSide() + { + return new InstanceableMesh(48, 48, DefaultVertexFormat.POSITION, buffer -> + { + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); + buffer.rewind(); + }, buffer -> + { + buffer.putInt(0); + buffer.putInt(1); + buffer.putInt(2); + buffer.putInt(0); + buffer.putInt(2); + buffer.putInt(3); + + buffer.putInt(2); + buffer.putInt(1); + buffer.putInt(0); + buffer.putInt(3); + buffer.putInt(2); + buffer.putInt(0); + + buffer.rewind(); + return 12; + }); + } + +// public static PreparedMesh defaultCube() +// { +// return new PreparedMesh(576, 144, SimpleCloudsShaders.POSITION_NORMAL, buffer -> { +// //-x +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// //+x +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)0); +// //-y +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); buffer.put((byte)0); +// //+y +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); buffer.put((byte)0); +// //-z +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)-1); buffer.put((byte)0); +// //-z +// buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); +// buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); +// buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.put((byte)0); buffer.put((byte)0); buffer.put((byte)1); buffer.put((byte)0); +// +// buffer.rewind(); +// }, buffer -> { +// buffer.putInt(0); buffer.putInt(1); buffer.putInt(2); buffer.putInt(0); buffer.putInt(2); buffer.putInt(3); // -x +// buffer.putInt(4); buffer.putInt(5); buffer.putInt(6); buffer.putInt(4); buffer.putInt(6); buffer.putInt(7); // +x +// buffer.putInt(8); buffer.putInt(9); buffer.putInt(10); buffer.putInt(8); buffer.putInt(10); buffer.putInt(11); // -y +// buffer.putInt(12); buffer.putInt(13); buffer.putInt(14); buffer.putInt(12); buffer.putInt(14); buffer.putInt(15); // +y +// buffer.putInt(16); buffer.putInt(17); buffer.putInt(18); buffer.putInt(16); buffer.putInt(18); buffer.putInt(19); // -z +// buffer.putInt(20); buffer.putInt(21); buffer.putInt(22); buffer.putInt(20); buffer.putInt(22); buffer.putInt(23); // +z +// buffer.rewind(); +// return 36; +// }); +// } + + public static InstanceableMesh defaultCube() + { + return new InstanceableMesh(96, 144, DefaultVertexFormat.POSITION, buffer -> + { + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); + buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); + buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); + buffer.putFloat( 1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); + buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); + buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); buffer.putFloat( 1.0F); + buffer.putFloat(-1.0F); buffer.putFloat(-1.0F); buffer.putFloat( 1.0F); + buffer.rewind(); + }, buffer -> + { + buffer.putInt(0); buffer.putInt(1); buffer.putInt(2); buffer.putInt(0); buffer.putInt(2); buffer.putInt(3); // -z + buffer.putInt(4); buffer.putInt(7); buffer.putInt(6); buffer.putInt(4); buffer.putInt(6); buffer.putInt(5); // +z + buffer.putInt(7); buffer.putInt(0); buffer.putInt(3); buffer.putInt(7); buffer.putInt(3); buffer.putInt(6); // -x + buffer.putInt(1); buffer.putInt(4); buffer.putInt(5); buffer.putInt(1); buffer.putInt(5); buffer.putInt(2); // +x + buffer.putInt(1); buffer.putInt(0); buffer.putInt(7); buffer.putInt(1); buffer.putInt(7); buffer.putInt(4); // -y + buffer.putInt(5); buffer.putInt(6); buffer.putInt(3); buffer.putInt(5); buffer.putInt(3); buffer.putInt(2); // +y + buffer.rewind(); + return 36; + }); + } + + public void drawInstanced(int count) + { + RenderSystem.assertOnRenderThread(); + + GL30.glBindVertexArray(this.arrayObjectId); + GL31.glDrawElementsInstanced(GL11.GL_TRIANGLES, this.totalIndices, GL11.GL_UNSIGNED_INT, 0L, count); + } + + public void destroy() + { + this.totalIndices = 0; + + if (this.arrayObjectId >= 0) + { + RenderSystem.glDeleteVertexArrays(this.arrayObjectId); + this.arrayObjectId = -1; + } + + if (this.vertexBufferId >= 0) + { + RenderSystem.glDeleteBuffers(this.vertexBufferId); + this.vertexBufferId = -1; + } + + if (this.vertexBuffer != null) + { + MemoryUtil.memFree(this.vertexBuffer); + this.vertexBuffer = null; + } + + if (this.indexBufferId >= 0) + { + RenderSystem.glDeleteBuffers(this.indexBufferId); + this.indexBufferId = -1; + } + + if (this.indexBuffer != null) + { + MemoryUtil.memFree(this.indexBuffer); + this.indexBuffer = null; + } + } +} diff --git a/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer.java b/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer.java new file mode 100644 index 00000000..7bd37397 --- /dev/null +++ b/tmp_simpleclouds_src/dev/nonamecrackers2/simpleclouds/client/renderer/SimpleCloudsRenderer.java @@ -0,0 +1,1497 @@ +package dev.nonamecrackers2.simpleclouds.client.renderer; + +import java.awt.Color; +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.joml.Matrix4f; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL14; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL40; +import org.lwjgl.opengl.GL43; + +import com.google.common.collect.Lists; +import com.google.gson.JsonSyntaxException; +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.pipeline.TextureTarget; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.BufferUploader; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.mojang.math.Axis; + +import dev.nonamecrackers2.simpleclouds.SimpleCloudsMod; +import dev.nonamecrackers2.simpleclouds.api.client.event.ModifyCloudRenderDistanceEvent; +import dev.nonamecrackers2.simpleclouds.api.common.cloud.CloudMode; +import dev.nonamecrackers2.simpleclouds.client.cloud.ClientSideCloudTypeManager; +import dev.nonamecrackers2.simpleclouds.client.compat.SimpleCloudsCompatHelper; +import dev.nonamecrackers2.simpleclouds.client.event.impl.DetermineCloudRenderPipelineEvent; +import dev.nonamecrackers2.simpleclouds.client.framebuffer.CloudRenderTarget; +import dev.nonamecrackers2.simpleclouds.client.framebuffer.ShadowMapBuffer; +import dev.nonamecrackers2.simpleclouds.client.framebuffer.WeightedBlendingTarget; +import dev.nonamecrackers2.simpleclouds.client.mesh.RendererInitializeResult; +import dev.nonamecrackers2.simpleclouds.client.mesh.chunk.MeshChunk; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.CloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.MultiRegionCloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.mesh.generator.SingleRegionCloudMeshGenerator; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.LevelOfDetailConfig; +import dev.nonamecrackers2.simpleclouds.client.mesh.lod.PreparedChunk; +import dev.nonamecrackers2.simpleclouds.client.renderer.lightning.LightningBolt; +import dev.nonamecrackers2.simpleclouds.client.renderer.pipeline.CloudsRenderPipeline; +import dev.nonamecrackers2.simpleclouds.client.renderer.settings.CloudsRendererSettings; +import dev.nonamecrackers2.simpleclouds.client.shader.SimpleCloudsShaders; +import dev.nonamecrackers2.simpleclouds.client.shader.SingleSSBOShaderInstance; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.BindingManager; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.ShaderStorageBufferObject; +import dev.nonamecrackers2.simpleclouds.client.world.ClientCloudManager; +import dev.nonamecrackers2.simpleclouds.common.cloud.CloudType; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudGetter; +import dev.nonamecrackers2.simpleclouds.common.config.SimpleCloudsConfig; +import dev.nonamecrackers2.simpleclouds.mixin.MixinPostChain; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.EffectInstance; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.PostChain; +import net.minecraft.client.renderer.PostPass; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.ResourceManagerReloadListener; +import net.minecraft.util.FastColor; +import net.minecraft.util.Mth; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.StartupMessageManager; +import net.minecraftforge.fml.loading.ImmediateWindowHandler; +import nonamecrackers2.crackerslib.common.compat.CompatHelper; + +public class SimpleCloudsRenderer implements ResourceManagerReloadListener +{ + private static final Logger LOGGER = LogManager.getLogger("simpleclouds/SimpleCloudsRenderer"); + private static final Vector3f DIFFUSE_LIGHT_0 = (new Vector3f(0.2F, 1.0F, -0.7F)).normalize(); + private static final Vector3f DIFFUSE_LIGHT_1 = (new Vector3f(-0.2F, 1.0F, 0.7F)).normalize(); + private static final ResourceLocation STORM_POST_PROCESSING_LOC = SimpleCloudsMod.id("shaders/post/storm_post.json"); + private static final ResourceLocation BLUR_POST_PROCESSING_LOC = SimpleCloudsMod.id("shaders/post/blur_post.json"); + private static final ResourceLocation SCREEN_SPACE_WORLD_FOG_LOC = SimpleCloudsMod.id("shaders/post/screen_space_world_fog.json"); + private static final ResourceLocation CLOUD_SHADOWS_LOC = SimpleCloudsMod.id("shaders/post/cloud_shadows.json"); + public static final ResourceLocation FINAL_COMPOSITE_LOC = SimpleCloudsMod.id("shaders/post/final_composite.json"); + public static final ResourceLocation FINAL_COMPOSITE_NO_TRANSPARENCY_LOC = SimpleCloudsMod.id("shaders/post/final_composite_no_transparency.json"); + private static final ResourceLocation DITHER_TEXTURE = SimpleCloudsMod.id("textures/shader/bayer_matrix.png"); + private static final ArtifactVersion REQUIRED_OPENGL_VERSION = new DefaultArtifactVersion("4.3"); + public static final int SHADOW_MAP_SIZE = 1024; + public static final int SHADOW_MAP_SPAN = 10000; + public static final int MAX_LIGHTNING_BOLTS = 16; + public static final int BYTES_PER_LIGHTNING_BOLT = 16; + public static final float CHUNK_FADE_IN_ALPHA_PER_TICK = 0.2F; + public static final float DITHER_SCALE = 0.05F; + private static @Nullable SimpleCloudsRenderer instance; + private final CloudsRendererSettings settings; + private final Minecraft mc; + private final WorldEffects worldEffectsManager; + private final AtmosphericCloudsRenderHandler atmoshpericClouds; + private @Nullable ClientCloudManager cloudManager; + private ArtifactVersion openGlVersion; + private CloudMeshGenerator meshGenerator; + private @Nullable CloudsRenderPipeline renderPipelineThisPass; + private @Nullable RenderTarget cloudTarget; + private @Nullable WeightedBlendingTarget cloudTransparencyTarget; + private @Nullable RenderTarget stormFogTarget; + private int stormFogResolutionDivisor = 4; + private @Nullable RenderTarget blurTarget; + private final List postChains = Lists.newArrayList(); + private @Nullable PostChain finalComposite; + private @Nullable PostChain stormPostProcessing; + private @Nullable PostChain blurPostProcessing; + private @Nullable PostChain screenSpaceWorldFog; + private @Nullable PostChain cloudShadows; + private @Nullable ShaderStorageBufferObject lightningBoltPositions; + private @Nullable ShadowMapBuffer stormFogShadowMap; + private Optional shadowMap = Optional.empty(); + private @Nullable Frustum cullFrustum; + private float fogStart; + private float fogEnd; + private @Nullable PoseStack stormFogShadowMapStack; + private @Nullable PoseStack shadowMapStack; + private boolean failedToCopyDepthBuffer; + private boolean needsReload; + private @Nullable RendererInitializeResult initialInitializationResult; + + private SimpleCloudsRenderer(CloudsRendererSettings settings, Minecraft mc) + { + this.settings = settings; + this.mc = mc; + this.worldEffectsManager = new WorldEffects(mc, this); + this.atmoshpericClouds = new AtmosphericCloudsRenderHandler(mc); + } + + public String getClientCloudManagerString() + { + return this.cloudManager != null ? this.cloudManager.toString() : "null"; + } + + public CloudMeshGenerator getMeshGenerator() + { + return this.meshGenerator; + } + + public CloudsRenderPipeline getRenderPipeline() + { + return Objects.requireNonNull(this.renderPipelineThisPass, "Pipeline not determined"); + } + + public WorldEffects getWorldEffectsManager() + { + return this.worldEffectsManager; + } + + public AtmosphericCloudsRenderHandler getAtmosphericCloudRenderer() + { + return this.atmoshpericClouds; + } + + public CloudsRendererSettings getSettings() + { + return this.settings; + } + + public @Nullable RendererInitializeResult getInitialInitializationResult() + { + return this.initialInitializationResult; + } + + public ShadowMapBuffer getStormFogShadowMap() + { + return this.stormFogShadowMap; + } + + public Optional getShadowMap() + { + return this.shadowMap; + } + + public @Nullable PoseStack getStormFogShadowMapStack() + { + return this.stormFogShadowMapStack; + } + + public @Nullable PoseStack getShadowMapStack() + { + return this.shadowMapStack; + } + + public RenderTarget getBlurTarget() + { + return this.blurTarget; + } + + public RenderTarget getStormFogTarget() + { + return this.stormFogTarget; + } + + public RenderTarget getCloudTarget() + { + return this.cloudTarget; + } + + public WeightedBlendingTarget getCloudTransparencyTarget() + { + return this.cloudTransparencyTarget; + } + + public float getFogStart() + { + return this.fogStart; + } + + public float getFogEnd() + { + return this.fogEnd; + } + + public float getFadeFactorForDistance(float distance) + { + return 1.0F - Math.min(Math.max(distance - this.fogStart, 0.0F) / (this.fogEnd - this.fogStart), 1.0F); + } + + public @Nullable Frustum getCullFrustum() + { + return this.cullFrustum; + } + + public void onCloudManagerChange(ClientCloudManager manager) + { + this.cloudManager = manager; + if (this.meshGenerator instanceof MultiRegionCloudMeshGenerator generator) + generator.setCloudGetter(manager); + } + + private void prepareMeshGenerator(float partialTicks) + { + if (this.meshGenerator instanceof SingleRegionCloudMeshGenerator generator) + generator.setFadeDistances((float)SimpleCloudsConfig.CLIENT.singleModeFadeStartPercentage.get() / 100.0F, (float)SimpleCloudsConfig.CLIENT.singleModeFadeEndPercentage.get() / 100.0F); + this.meshGenerator.setTransparencyRenderDistance((float)SimpleCloudsConfig.CLIENT.transparencyRenderDistancePercentage.get() / 100.0F); + this.meshGenerator.setTestFacesFacingAway(SimpleCloudsConfig.CLIENT.testSidesThatAreOccluded.get()); + if (this.mc.level != null) + { + this.meshGenerator.setScroll(this.cloudManager.getScrollX(partialTicks), this.cloudManager.getScrollY(partialTicks), this.cloudManager.getScrollZ(partialTicks)); + } + } + + public boolean needsReinitialization() + { + return this.settings.needsReinitialization(this.meshGenerator); + } + + public void requestReload() + { + LOGGER.debug("Requesting reload..."); + this.needsReload = true; + } + + @Override + public void onResourceManagerReload(ResourceManager manager) + { + RenderSystem.assertOnRenderThreadOrInit(); + + this.initialInitializationResult = null; + + // --- Check OpenGL version --- + + ArtifactVersion openGlVersion = this.openGlVersion; + if (openGlVersion == null) + openGlVersion = new DefaultArtifactVersion(ImmediateWindowHandler.getGLVersion()); + if (openGlVersion.compareTo(REQUIRED_OPENGL_VERSION) < 0) + { + LOGGER.error("Simple Clouds renderer could not initialize. OpenGL version is {}, minimum required is {}", openGlVersion, REQUIRED_OPENGL_VERSION); + this.initialInitializationResult = RendererInitializeResult.builder().errorOpenGL().build(); + this.openGlVersion = openGlVersion; + return; + } + + if (!SimpleCloudsShaders.areShadersInitialized()) + { + LOGGER.error("Simple Clouds renderer could not initialize. Core shaders are not initialized."); + this.initialInitializationResult = RendererInitializeResult.builder().coreShadersNotInitialized(SimpleCloudsShaders.getError()).build(); + saveAndPrintCrashReports(this.mc, this.initialInitializationResult); + return; + } + + RendererInitializeResult compatError = SimpleCloudsCompatHelper.findCompatErrors(); + if (compatError.getState() == RendererInitializeResult.State.ERROR) + { + LOGGER.error("Simple Clouds renderer could not initialize due to compat error(s): {}", compatError.getErrors().stream().map(e -> e.text().getString()).toList()); + this.initialInitializationResult = compatError; + saveAndPrintCrashReports(this.mc, this.initialInitializationResult); + return; + } + + StartupMessageManager.addModMessage("Initializing Simple Clouds renderer"); + + LOGGER.debug("OpenGL {}", openGlVersion); + + Instant started = Instant.now(); + + LOGGER.debug("Beginning Simple Clouds renderer initialization"); + + this.failedToCopyDepthBuffer = false; + + // --- Render Targets --- + + boolean highPrecisionDepth = SimpleCloudsMod.dhLoaded(); + + RenderTarget main = SimpleCloudsCompatHelper.getMainRenderTarget(); + if (main == null) + { + this.initialInitializationResult = RendererInitializeResult.builder().errorUnknown(new NullPointerException("Main framebuffer is null"), "Simple Clouds Renderer").build(); + saveAndPrintCrashReports(this.mc, this.initialInitializationResult); + return; + } + + if (this.cloudTarget != null) + this.cloudTarget.destroyBuffers(); + this.cloudTarget = new CloudRenderTarget(main.width, main.height, Minecraft.ON_OSX, highPrecisionDepth); + this.cloudTarget.setClearColor(0.0F, 0.0F, 0.0F, 0.0F); + + if (this.cloudTransparencyTarget != null) + this.cloudTransparencyTarget.destroyBuffers(); + this.cloudTransparencyTarget = new WeightedBlendingTarget(main.width, main.height, Minecraft.ON_OSX, highPrecisionDepth); + + this.stormFogResolutionDivisor = SimpleCloudsCompatHelper.getStormFogResolutionDivisor(); + if (this.stormFogTarget != null) + this.stormFogTarget.destroyBuffers(); + this.stormFogTarget = new TextureTarget(main.width / this.stormFogResolutionDivisor, main.height / this.stormFogResolutionDivisor, false, Minecraft.ON_OSX); + this.stormFogTarget.setClearColor(0.0F, 0.0F, 0.0F, 0.0F); + this.stormFogTarget.setFilterMode(GL11.GL_LINEAR); + + if (this.blurTarget != null) + this.blurTarget.destroyBuffers(); + this.blurTarget = new TextureTarget(main.width, main.height, false, Minecraft.ON_OSX); + this.blurTarget.setClearColor(0.0F, 0.0F, 0.0F, 0.0F); + this.blurTarget.setFilterMode(GL11.GL_LINEAR); + + // --- Mesh Generator --- + + this.setupMeshGenerator(); // Create/setup the generator + this.prepareMeshGenerator(0.0F); // Prepare it + + RendererInitializeResult result = this.meshGenerator.init(manager); // Initialize + if (this.initialInitializationResult == null) + this.initialInitializationResult = result; + + // --- Shadow Map --- + + if (this.stormFogShadowMap != null) + { + this.stormFogShadowMap.close(); + this.stormFogShadowMap = null; + } + + this.shadowMap.ifPresent(buffer -> { + buffer.close(); + }); + + int span = this.meshGenerator.getLodConfig().getEffectiveChunkSpan() * SimpleCloudsConstants.CHUNK_SIZE * SimpleCloudsConstants.CLOUD_SCALE; + this.stormFogShadowMap = new ShadowMapBuffer(span, span, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 0.0F, 10000.0F, true, false); + + if (SimpleCloudsConfig.CLIENT.distantShadows.get() && SimpleCloudsMod.dhLoaded()) + { + int distantShadowSpan = SimpleCloudsConfig.CLIENT.shadowDistance.get() * 2; + distantShadowSpan = Math.min(distantShadowSpan, span); + this.shadowMap = Optional.of(new ShadowMapBuffer(distantShadowSpan, distantShadowSpan, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 0.0F, 10000.0F, false, true)); + } + else + { + this.shadowMap = Optional.empty(); + } + + // --- Post Processing Shaders --- + + this.destroyPostChains(); + + if (this.lightningBoltPositions != null) + { + BindingManager.freeSSBO(this.lightningBoltPositions); + this.lightningBoltPositions = null; + } + + this.lightningBoltPositions = BindingManager.createSSBO(GL15.GL_DYNAMIC_DRAW); + this.lightningBoltPositions.allocateBuffer(MAX_LIGHTNING_BOLTS * BYTES_PER_LIGHTNING_BOLT); + + this.stormPostProcessing = this.createPostChain(manager, STORM_POST_PROCESSING_LOC, this.stormFogTarget, pass -> + { + EffectInstance effect = pass.getEffect(); + effect.setSampler("ShadowMap", () -> this.stormFogShadowMap.getDepthTexId()); + effect.setSampler("ShadowMapColor", () -> this.stormFogShadowMap.getColorTexId()); + effect.setSampler("DepthSampler", () -> this.cloudTarget.getDepthTextureId()); + this.lightningBoltPositions.optionalBindToProgram("LightningBolts", effect.getId()); + }); + + this.blurPostProcessing = this.createPostChain(manager, BLUR_POST_PROCESSING_LOC, this.blurTarget); + this.blurPostProcessing.getTempTarget("swap").setFilterMode(GL11.GL_LINEAR); + + this.screenSpaceWorldFog = this.createPostChain(manager, SCREEN_SPACE_WORLD_FOG_LOC, main, pass -> + { + EffectInstance effect = pass.getEffect(); + effect.setSampler("StormFogSampler", () -> this.blurTarget.getColorTextureId()); + effect.setSampler("CloudDepthSampler", () -> this.cloudTarget.getDepthTextureId()); + }); + + this.finalComposite = this.createPostChain(manager, this.settings.useTransparency() ? FINAL_COMPOSITE_LOC : FINAL_COMPOSITE_NO_TRANSPARENCY_LOC, main, pass -> + { + EffectInstance effect = pass.getEffect(); + if (this.settings.useTransparency()) + { + effect.setSampler("AccumTexture", () -> this.cloudTransparencyTarget.getColorTextureId()); + effect.setSampler("RevealageTexture", () -> this.cloudTransparencyTarget.getRevealageTextureId()); + } + effect.setSampler("CloudsTexture", () -> this.cloudTarget.getColorTextureId()); + }); + + if (this.shadowMap.isPresent()) + { + ShadowMapBuffer map = this.shadowMap.get(); + this.cloudShadows = this.createPostChain(manager, CLOUD_SHADOWS_LOC, main, pass -> + { + EffectInstance effect = pass.getEffect(); + effect.setSampler("ShadowMap", () -> this.shadowMap.get().getDepthTexId()); + effect.safeGetUniform("ShadowSpan").set((float)Math.min(map.getViewWidth(), map.getViewHeight())); + }); + } + + this.atmoshpericClouds.init(manager); + + // --- Final debug --- + + long duration = Duration.between(started, Instant.now()).toMillis(); + LOGGER.info("Finished initialization, took {} ms", duration); + + LOGGER.debug("Total LODs: {}", this.meshGenerator.getLodConfig().getLods().length + 1); + LOGGER.debug("Highest detail (primary) chunk span: {}", this.meshGenerator.getLodConfig().getPrimaryChunkSpan()); + LOGGER.debug("Effective chunk span with LODs (total viewable area): {}", this.meshGenerator.getLodConfig().getEffectiveChunkSpan()); + LOGGER.debug("Total span in blocks: {}", this.meshGenerator.getLodConfig().getEffectiveChunkSpan() * SimpleCloudsConstants.CHUNK_SIZE * SimpleCloudsConstants.CLOUD_SCALE); + + //Print crash reports if needed + saveAndPrintCrashReports(this.mc, result); + } + + private static void saveAndPrintCrashReports(Minecraft mc, RendererInitializeResult result) + { + switch (result.getState()) + { + case ERROR: + { + List reports = result.createCrashReports(); + LOGGER.error("---------CRASH REPORT BEGIN---------"); + for (CrashReport report : reports) + { + mc.fillReport(report); + LOGGER.error("{}", report.getFriendlyReport()); + } + LOGGER.error("---------CRASH REPORT END---------"); + result.saveCrashReports(mc.gameDirectory); + break; + } + default: + } + } + + private void setupMeshGenerator() + { + if (this.settings.checkAndOrBeginInitialization(this.meshGenerator)) + { + if (this.meshGenerator != null) + { + this.meshGenerator.close(); //Close the current generator + this.meshGenerator = null; + } + + CloudMode mode = this.settings.getCurrentCloudMode(); + boolean isAmbientMode = mode == CloudMode.AMBIENT; + boolean useMultiRegion = isAmbientMode || mode == CloudMode.DEFAULT; + boolean shadedClouds = this.settings.shadedClouds(); + boolean useFixedMeshDataSectionSize = this.settings.useFixedMeshDataSectionSize(); + boolean useTransparency = this.settings.useTransparency(); + LevelOfDetailConfig lod = this.settings.getCurrentLod().getConfig(); + + var builder = CloudMeshGenerator.builder() + .fadeNearOrigin(isAmbientMode) + .shadedClouds(shadedClouds) + .fixedMeshDataSectionSize(useFixedMeshDataSectionSize) + .meshGenInterval(SimpleCloudsRenderer::calculateMeshGenInterval) + .lodConfig(lod) + .useTransparency(useTransparency); + + if (useMultiRegion) //Use the multi-region generator for DEFAULT or AMBIENT cloud mode + { + if (isAmbientMode) + { + builder.fadeStart(SimpleCloudsConstants.AMBIENT_MODE_FADE_START) + .fadeEnd(SimpleCloudsConstants.AMBIENT_MODE_FADE_END); + } + this.meshGenerator = builder.createMultiRegion(); + } + else if (mode == CloudMode.SINGLE) + { + float fadeStart = (float)SimpleCloudsConfig.CLIENT.singleModeFadeStartPercentage.get() / 100.0F; + float fadeEnd = (float)SimpleCloudsConfig.CLIENT.singleModeFadeEndPercentage.get() / 100.0F; + this.meshGenerator = builder.fadeStart(fadeStart).fadeEnd(fadeEnd).createSingleRegion(SimpleCloudsConstants.EMPTY); + } + else + { + throw new IllegalArgumentException("Not sure how to handle cloud mode " + mode); + } + } + + if (this.meshGenerator instanceof MultiRegionCloudMeshGenerator multiRegionGenerator) + { + multiRegionGenerator.setCloudGetter(this.cloudManager != null ? this.cloudManager : CloudGetter.EMPTY); + } + else if (this.meshGenerator instanceof SingleRegionCloudMeshGenerator singleRegionGenerator) + { + //Find the desired single mode cloud type, either from the client-side only context or + //from the synced cloud types from the server + CloudType type = this.settings.getSingleModeCloudType(); + if (!ClientCloudManager.isAvailableServerSide() && !ClientSideCloudTypeManager.isValidClientSideSingleModeCloudType(type)) + type = SimpleCloudsConstants.EMPTY; + if (type == null) + type = SimpleCloudsConstants.EMPTY; + singleRegionGenerator.setCloudType(type); + } + else + { + throw new IllegalArgumentException("Not sure how to handle generator: " + this.meshGenerator); + } + } + + private void destroyPostChains() + { + this.postChains.forEach(PostChain::close); + this.postChains.clear(); + } + + private @Nullable PostChain createPostChain(ResourceManager manager, ResourceLocation loc, RenderTarget target) + { + return this.createPostChain(manager, loc, target, effect -> {}); + } + + private @Nullable PostChain createPostChain(ResourceManager manager, ResourceLocation loc, RenderTarget target, Consumer passConsumer) + { + try + { + PostChain chain = new PostChain(this.mc.getTextureManager(), manager, target, loc); + chain.resize(target.width, target.height); + for (PostPass pass : ((MixinPostChain)chain).simpleclouds$getPostPasses()) + passConsumer.accept(pass); + this.postChains.add(chain); + return chain; + } + catch (JsonSyntaxException e) + { + LOGGER.warn("Failed to parse post shader: {}", loc, e); + } + catch (IOException e) + { + LOGGER.warn("Failed to load post shader: {}", loc, e); + } + + return null; + } + + public void onMainWindowResize(int width, int height) + { + this.atmoshpericClouds.onResize(width, height); + + RenderTarget main = SimpleCloudsCompatHelper.getMainRenderTarget(); + if (main == null) + return; + + width = main.width; + height = main.height; + + if (this.cloudTarget != null) + this.cloudTarget.resize(width, height, Minecraft.ON_OSX); + + if (this.cloudTransparencyTarget != null) + this.cloudTransparencyTarget.resize(width, height, Minecraft.ON_OSX); + + this.stormFogResolutionDivisor = SimpleCloudsCompatHelper.getStormFogResolutionDivisor(); + + if (this.stormFogTarget != null) + { + this.stormFogTarget.resize(width / this.stormFogResolutionDivisor, height / this.stormFogResolutionDivisor, Minecraft.ON_OSX); + this.stormFogTarget.setFilterMode(GL11.GL_LINEAR); + } + + if (this.blurTarget != null) + { + this.blurTarget.resize(width, height, Minecraft.ON_OSX); + this.blurTarget.setFilterMode(GL11.GL_LINEAR); + } + + for (PostChain chain : this.postChains) + { + RenderTarget chainTarget = ((MixinPostChain)chain).simpleclouds$getScreenTarget(); + chain.resize(chainTarget.width, chainTarget.height); + } + + if (this.blurPostProcessing != null) + this.blurPostProcessing.getTempTarget("swap").setFilterMode(GL11.GL_LINEAR); + } + + public void shutdown() + { + if (this.cloudTarget != null) + this.cloudTarget.destroyBuffers(); + if (this.cloudTransparencyTarget != null) + this.cloudTransparencyTarget.destroyBuffers(); + if (this.stormFogTarget != null) + this.stormFogTarget.destroyBuffers();; + if (this.blurTarget != null) + this.blurTarget.destroyBuffers(); + + this.cloudTarget = null; + this.cloudTransparencyTarget = null; + this.stormFogTarget = null; + this.blurTarget = null; + + this.destroyPostChains(); + + if (this.meshGenerator != null) + this.meshGenerator.close(); + + if (this.stormFogShadowMap != null) + { + this.stormFogShadowMap.close(); + this.stormFogShadowMap = null; + } + + if (this.shadowMap.isPresent()) + { + this.shadowMap.get().close(); + this.shadowMap = Optional.empty(); + } + + if (this.lightningBoltPositions != null) + { + BindingManager.freeSSBO(this.lightningBoltPositions); + this.lightningBoltPositions = null; + } + + this.atmoshpericClouds.close(); + } + + public void baseTick() + { + if (this.needsReload) + { + this.onResourceManagerReload(this.mc.getResourceManager()); + this.needsReload = false; + } + } + + public void tick() + { + this.worldEffectsManager.tick(); + + if (this.cloudManager != null) + this.atmoshpericClouds.setWindDirection(this.cloudManager.calculateWindDirection()); + this.atmoshpericClouds.tick(); + + if (this.meshGenerator != null) + this.meshGenerator.worldTick(); + } + + public static void renderCloudsOpaque(CloudMeshGenerator generator, PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, @Nullable Frustum frustum) + { + renderCloudsOpaque(generator, stack, projMat, fogStart, fogEnd, partialTick, r, g, b, frustum, true); + } + + public static void renderCloudsOpaque(CloudMeshGenerator generator, PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, @Nullable Frustum frustum, boolean ditherFade) + { + RenderSystem.assertOnRenderThread(); + + BufferUploader.reset(); + + if (!generator.canRender()) + return; + + RenderSystem.disableBlend(); + RenderSystem.enableDepthTest(); + RenderSystem.disableCull(); + + SingleSSBOShaderInstance shader = SimpleCloudsShaders.getCloudsShader(); + RenderSystem.setShader(() -> shader); + + TextureManager manager = Minecraft.getInstance().getTextureManager(); + AbstractTexture ditherTexture = manager.getTexture(DITHER_TEXTURE); + shader.setSampler("BayerMatrixSampler", ditherTexture); + shader.safeGetUniform("DitherScale").set(DITHER_SCALE); + + SimpleCloudsRenderer.prepareShader(shader, stack.last().pose(), projMat, fogStart, fogEnd); + shader.apply(); + + generator.forRenderableMeshChunks(frustum, MeshChunk::getOpaqueBuffers, (chunk, opaqueBuffers) -> + { + if (ditherFade) + { + RenderSystem.setShaderColor(r, g, b, chunk.getAlpha(partialTick)); + shader.COLOR_MODULATOR.set(RenderSystem.getShaderColor()); + shader.COLOR_MODULATOR.upload(); + } + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), opaqueBuffers.getBufferId()); + generator.getSideMesh().drawInstanced(opaqueBuffers.getElementCount()); + }, ditherFade); + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), 0); + + shader.clear(); + + GL30.glBindVertexArray(0); + + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.enableCull(); + } + + public static void renderCloudsTransparency(CloudMeshGenerator generator, PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, @Nullable Frustum frustum) + { + renderCloudsTransparency(generator, stack, projMat, fogStart, fogEnd, partialTick, r, g, b, frustum, true); + } + + public static void renderCloudsTransparency(CloudMeshGenerator generator, PoseStack stack, Matrix4f projMat, float fogStart, float fogEnd, float partialTick, float r, float g, float b, @Nullable Frustum frustum, boolean ditherFade) + { + RenderSystem.assertOnRenderThread(); + + BufferUploader.reset(); + + if (!generator.canRender() || !generator.transparencyEnabled()) + return; + + RenderSystem.enableDepthTest(); + RenderSystem.depthMask(false); + + SingleSSBOShaderInstance shader = SimpleCloudsShaders.getCloudsTransparencyShader(); + RenderSystem.setShader(() -> shader); + + TextureManager manager = Minecraft.getInstance().getTextureManager(); + AbstractTexture ditherTexture = manager.getTexture(DITHER_TEXTURE); + shader.setSampler("BayerMatrixSampler", ditherTexture); + shader.safeGetUniform("DitherScale").set(DITHER_SCALE); + + SimpleCloudsRenderer.prepareShader(shader, stack.last().pose(), projMat, fogStart, fogEnd); + + shader.apply(); + + GL30.glEnablei(GL11.GL_BLEND, 0); + GL30.glEnablei(GL11.GL_BLEND, 1); + GL40.glBlendEquationi(0, GL14.GL_FUNC_ADD); + GL40.glBlendEquationi(1, GL14.GL_FUNC_ADD); + GL40.glBlendFunci(0, GL11.GL_ONE, GL11.GL_ONE); + GL40.glBlendFunci(1, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_COLOR); + + generator.forRenderableMeshChunks(frustum, c -> c.getTransparentBuffers().get(), (chunk, transparentBuffers) -> + { + if (ditherFade) + { + RenderSystem.setShaderColor(r, g, b, chunk.getAlpha(partialTick)); + shader.COLOR_MODULATOR.set(RenderSystem.getShaderColor()); + shader.COLOR_MODULATOR.upload(); + } + + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), transparentBuffers.getBufferId()); + generator.getCubeMesh().drawInstanced(transparentBuffers.getElementCount()); + }, ditherFade); + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), 0); + + shader.clear(); + + GL30.glDisablei(GL11.GL_BLEND, 0); + GL30.glDisablei(GL11.GL_BLEND, 1); + GL40.glBlendFuncSeparatei(0, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + GL40.glBlendFuncSeparatei(1, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + + GL30.glBindVertexArray(0); + + RenderSystem.depthMask(true); + + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + } + + private PoseStack createShadowMapStack(ShadowMapBuffer shadowMap, double camX, double camY, double camZ, Consumer transformApplier) + { + PoseStack stack = new PoseStack(); + stack.setIdentity(); + double depthCenter = ((double)shadowMap.getNear() + (double)shadowMap.getFar()) * -0.5D; + stack.translate((double)shadowMap.getViewWidth() / 2.0D, (double)shadowMap.getViewHeight() / 2.0D, depthCenter); + transformApplier.accept(stack); + float chunkSizeUpscaled = (float)SimpleCloudsConstants.CHUNK_SIZE * (float)SimpleCloudsConstants.CLOUD_SCALE; + float camOffsetX = ((float)Mth.floor(camX / chunkSizeUpscaled) * chunkSizeUpscaled); + float camOffsetZ = ((float)Mth.floor(camZ / chunkSizeUpscaled) * chunkSizeUpscaled); + stack.translate(-camOffsetX, -(double)this.cloudManager.getCloudHeight(), -camOffsetZ); + return stack; + } + + private void renderShadowMap(ShadowMapBuffer shadowMap, PoseStack stack, SingleSSBOShaderInstance shader, @Nullable Frustum frustum) + { + RenderSystem.assertOnRenderThread(); + + stack.pushPose(); + this.translateClouds(stack, 0.0D, 0.0D, 0.0D); + + RenderSystem.setShader(() -> shader); + prepareShader(shader, stack.last().pose(), shadowMap.getProjMatrix(), this.fogStart, this.fogEnd); + shader.apply(); + + shadowMap.bind(); + shadowMap.clear(Minecraft.ON_OSX); + + this.meshGenerator.forRenderableMeshChunks(frustum, MeshChunk::getOpaqueBuffers, (chunk, opaqueBuffers) -> + { + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), opaqueBuffers.getBufferId()); + this.meshGenerator.getSideMesh().drawInstanced(opaqueBuffers.getElementCount()); + }); + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), 0); + GL30.glBindVertexArray(0); + + shadowMap.unbind(); + + shader.clear(); + + stack.popPose(); + } + + private float determineShadowMapAngle(float partialTick) + { + float timeOfDay = this.mc.level.getTimeOfDay(partialTick); + return 45.0F * Mth.sin(2.0F * (float)Math.PI * timeOfDay); + } + + private void renderShadowMaps(double camX, double camY, double camZ, float partialTick) + { + RenderSystem.assertOnRenderThread(); + + BufferUploader.reset(); + + RenderSystem.disableBlend(); + RenderSystem.enableDepthTest(); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.disableCull(); + + this.stormFogShadowMapStack = this.createShadowMapStack(this.stormFogShadowMap, camX, camY, camZ, s -> + { + Vector2f direction = this.cloudManager.calculateWindDirection(); + float yaw = (float)Mth.atan2((double)direction.x, (double)direction.y); + s.mulPose(Axis.XP.rotationDegrees(SimpleCloudsConfig.CLIENT.stormFogAngle.get().floatValue())); + s.mulPose(Axis.YP.rotation(yaw)); + }); + this.renderShadowMap(this.stormFogShadowMap, this.stormFogShadowMapStack, SimpleCloudsShaders.getStormFogShadowMapShader(), this.cullFrustum); + + this.shadowMapStack = this.shadowMap.map(buffer -> + { + PoseStack stack = this.createShadowMapStack(buffer, camX, camY, camZ, s -> { + s.mulPose(Axis.XP.rotationDegrees(90.0F)); + s.mulPose(Axis.ZN.rotationDegrees(this.determineShadowMapAngle(partialTick))); + }); + this.renderShadowMap(buffer, stack, SimpleCloudsShaders.getCloudsShadowMapShader(), null); + return stack; + }).orElse(null); + + RenderSystem.enableCull(); + + this.mc.getMainRenderTarget().bindWrite(true); + } + + public static void renderCloudsDebug(CloudMeshGenerator generator, PoseStack stack, Matrix4f projMat, float partialTick, float fogStart, float fogEnd, @Nullable Frustum frustum, boolean chunkBoundaries, boolean noiseBoundaries) + { + RenderSystem.assertOnRenderThread(); + + if (!generator.canRender()) + return; + + BufferUploader.reset(); + + RenderSystem.disableBlend(); + RenderSystem.enableDepthTest(); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.disableCull(); + + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder builder = tesselator.getBuilder(); + builder.begin(VertexFormat.Mode.LINES, DefaultVertexFormat.POSITION_COLOR_NORMAL); + + generator.forRenderableMeshChunks(frustum, MeshChunk::getOpaqueBuffers, (chunk, bufferSet) -> + { + PreparedChunk preparedChunk = chunk.getChunkInfo(); + if (chunkBoundaries) + { + int color = Color.HSBtoRGB((float)preparedChunk.lodLevel() / ((float)generator.getLodConfig().getLods().length + 1), 1.0F, 1.0F); + float r = (float)FastColor.ARGB32.red(color) / 255.0F; + float g = (float)FastColor.ARGB32.green(color) / 255.0F; + float b = (float)FastColor.ARGB32.blue(color) / 255.0F; + LevelRenderer.renderLineBox(builder, chunk.getBoundsMinX() + 1.0F, chunk.getBoundsMinY() + 1.0F, chunk.getBoundsMinZ() + 1.0F, chunk.getBoundsMaxX() - 1.0F, chunk.getBoundsMaxY() - 1.0F, chunk.getBoundsMaxZ() - 1.0F, r, g, b, 1.0F); + } + if (noiseBoundaries) + LevelRenderer.renderLineBox(builder, chunk.getBoundsMinX() + 1.0F, chunk.getMinHeight() + 1.0F, chunk.getBoundsMinZ() + 1.0F, chunk.getBoundsMaxX() - 1.0F, chunk.getMaxHeight() - 1.0F, chunk.getBoundsMaxZ() - 1.0F, 1.0F, 1.0F, 0.0F, 1.0F); + }); + + RenderSystem.setShader(GameRenderer::getRendertypeLinesShader); + ShaderInstance shader = RenderSystem.getShader(); + SimpleCloudsRenderer.prepareShader(shader, stack.last().pose(), projMat, fogStart, fogEnd); + shader.LINE_WIDTH.set(2.5F); + shader.FOG_START.set(Float.MAX_VALUE); + shader.apply(); + BufferUploader.draw(builder.end()); + shader.clear(); + + RenderSystem.enableCull(); + + RenderSystem.defaultBlendFunc(); + RenderSystem.enableBlend(); + + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + generator.forRenderableMeshChunks(frustum, MeshChunk::getOpaqueBuffers, (chunk, bufferSet) -> + { + PreparedChunk preparedChunk = chunk.getChunkInfo(); + if (chunkBoundaries) + { + int color = Color.HSBtoRGB((float)preparedChunk.lodLevel() / ((float)generator.getLodConfig().getLods().length + 1), 1.0F, 1.0F); + float r = (float)FastColor.ARGB32.red(color) / 255.0F; + float g = (float)FastColor.ARGB32.green(color) / 255.0F; + float b = (float)FastColor.ARGB32.blue(color) / 255.0F; + renderChunkBox(builder, chunk.getBoundsMinX() + 1.0F, chunk.getBoundsMinY() + 1.0F, chunk.getBoundsMinZ() + 1.0F, chunk.getBoundsMaxX() - 1.0F, chunk.getBoundsMaxY() - 1.0F, chunk.getBoundsMaxZ() - 1.0F, r, g, b, 0.4F); + } + if (noiseBoundaries) + renderChunkBox(builder, chunk.getBoundsMinX() + 1.0F, chunk.getMinHeight() + 1.0F, chunk.getBoundsMinZ() + 1.0F, chunk.getBoundsMaxX() - 1.0F, chunk.getMaxHeight() - 1.0F, chunk.getBoundsMaxZ() - 1.0F, 1.0F, 1.0F, 0.0F, 0.4F); + }); + + RenderSystem.setShader(GameRenderer::getPositionColorShader); + shader = RenderSystem.getShader(); + SimpleCloudsRenderer.prepareShader(shader, stack.last().pose(), projMat, fogStart, fogEnd); + shader.apply(); + BufferUploader.draw(builder.end()); + shader.clear(); + + RenderSystem.disableBlend(); + } + + public float[] getCloudColor(float partialTick) + { + Vec3 cloudCol = this.mc.level.getCloudColor(partialTick); + float factor = this.worldEffectsManager.getDarkenFactor(partialTick, 0.8F); + float skyFlashFactor = Math.max(0.0F, ((float)this.mc.level.getSkyFlashTime() - partialTick) * SimpleCloudsConstants.LIGHTNING_FLASH_STRENGTH); + factor += skyFlashFactor; + float r = Mth.clamp((float)cloudCol.x * factor, 0.0F, 1.0F); + float g = Mth.clamp((float)cloudCol.y * factor, 0.0F, 1.0F); + float b = Mth.clamp((float)cloudCol.z * factor, 0.0F, 1.0F); + return new float[] { r, g, b }; + } + + public void translateClouds(PoseStack stack, double camX, double camY, double camZ) + { + stack.translate(-camX, -camY + (double)this.cloudManager.getCloudHeight(), -camZ); + stack.scale((float)SimpleCloudsConstants.CLOUD_SCALE, (float)SimpleCloudsConstants.CLOUD_SCALE, (float)SimpleCloudsConstants.CLOUD_SCALE); + } + + public void renderWeather(LightTexture texture, float partialTick, double camX, double camY, double camZ) + { + if (SimpleCloudsCompatHelper.renderCustomRain()) + this.worldEffectsManager.renderRain(texture, partialTick, camX, camY, camZ); + if (!SimpleCloudsMod.dhLoaded()) + this.worldEffectsManager.renderLightning(partialTick, camX, camY, camZ); + } + + public void renderBeforeLevel(PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ) + { + if (!SimpleCloudsCompatHelper.renderThisPass()) + return; + + CloudsRenderPipeline pipeline = CompatHelper.areShadersRunning() ? CloudsRenderPipeline.SHADER_SUPPORT : CloudsRenderPipeline.DEFAULT; + DetermineCloudRenderPipelineEvent pipelineEvent = new DetermineCloudRenderPipelineEvent(pipeline); + MinecraftForge.EVENT_BUS.post(pipelineEvent); + this.renderPipelineThisPass = pipeline; + if (pipelineEvent.getOverridenPipeline() != null) + this.renderPipelineThisPass = pipelineEvent.getOverridenPipeline(); + + float factor = this.worldEffectsManager.getDarkenFactor(partialTick); + float renderDistance = (float)this.meshGenerator.getCloudAreaMaxRadius() * (float)SimpleCloudsConstants.CLOUD_SCALE * factor; + if (renderDistance < 2867.0F) + renderDistance = 2867.0F; + ModifyCloudRenderDistanceEvent renderDistEvent = new ModifyCloudRenderDistanceEvent(renderDistance); + MinecraftForge.EVENT_BUS.post(renderDistEvent); + renderDistance = renderDistEvent.getRenderDistance(); + this.fogStart = renderDistance / 4.0F; + this.fogEnd = renderDistance; + + Entity cameraEntity = this.mc.gameRenderer.getMainCamera().getEntity(); + if (cameraEntity instanceof LivingEntity living) + { + var map = living.getActiveEffectsMap(); + if (map.containsKey(MobEffects.BLINDNESS)) + { + MobEffectInstance instance = map.get(MobEffects.BLINDNESS); + float effectFactor = instance.isInfiniteDuration() ? 5.0F : Mth.lerp(Math.min(1.0F, (float)instance.getDuration() / 20.0F), renderDistance, 5.0F); + this.fogStart = 0.0F; + this.fogEnd = effectFactor * 0.8F; + } + else if (map.containsKey(MobEffects.DARKNESS)) + { + MobEffectInstance instance = map.get(MobEffects.DARKNESS); + if (instance.getFactorData().isPresent()) + { + float f = Mth.lerp(instance.getFactorData().get().getFactor(living, partialTick), renderDistance, 15.0F); + this.fogStart = 0.0F; + this.fogEnd = f; + } + } + } + + this.meshGenerator.setCullDistance(this.fogEnd / (float)SimpleCloudsConstants.CLOUD_SCALE); + + this.mc.getProfiler().push("simple_clouds_prepare"); + + this.cullFrustum = new Frustum(stack.last().pose(), projMat); + float scale = (float)SimpleCloudsConstants.CLOUD_SCALE; + double originX = camX / scale; + double originY = (camY - (double)this.cloudManager.getCloudHeight()) / scale; + double originZ = camZ / scale; + this.cullFrustum.prepare(originX, originY, originZ); + + ProfilerFiller p = this.mc.getProfiler(); + + if (SimpleCloudsConfig.CLIENT.generateMesh.get() && SimpleCloudsCompatHelper.isPrimaryPass()) + { + p.push("mesh_generation"); + this.prepareMeshGenerator(partialTick); + this.meshGenerator.genTick(originX, originY, originZ, SimpleCloudsConfig.CLIENT.frustumCulling.get() ? this.cullFrustum : null, partialTick); + p.pop(); + } + + if (SimpleCloudsConfig.CLIENT.renderClouds.get() && SimpleCloudsCompatHelper.isPrimaryPass()) + { + p.push("shadow_map"); + this.renderShadowMaps(camX, camY, camZ, partialTick); + this.getRenderPipeline().prepare(this.mc, this, stack, projMat, partialTick, camX, camY, camZ, this.cullFrustum); + p.pop(); + } + + this.mc.getProfiler().pop(); + } + + public void renderAfterSky(PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ) + { + if (!SimpleCloudsCompatHelper.renderThisPass()) + return; + + this.mc.getProfiler().push("simple_clouds_after_sky"); + this.getRenderPipeline().afterSky(this.mc, this, stack, projMat, partialTick, camX, camY, camZ, this.cullFrustum); + this.mc.getProfiler().pop(); + } + + public void renderBeforeWeather(PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ) + { + if (!SimpleCloudsCompatHelper.renderThisPass()) + return; + + this.mc.getProfiler().push("simple_clouds_before_weather"); + this.getRenderPipeline().beforeWeather(this.mc, this, stack, projMat, partialTick, camX, camY, camZ, this.cullFrustum); + this.mc.getProfiler().pop(); + } + + public void renderAfterLevel(PoseStack stack, Matrix4f projMat, float partialTick, double camX, double camY, double camZ) + { + if (!SimpleCloudsCompatHelper.renderThisPass()) + return; + + this.mc.getProfiler().push("simple_clouds"); + this.getRenderPipeline().afterLevel(this.mc, this, stack, projMat, partialTick, camX, camY, camZ, this.cullFrustum); + this.mc.getProfiler().pop(); + + this.mc.getProfiler().push("world_effects"); + this.worldEffectsManager.renderPost(stack, partialTick, camX, camY, camZ, (float)SimpleCloudsConstants.CLOUD_SCALE); + this.mc.getProfiler().pop(); + } + + public void doBlurPostProcessing(float partialTick) + { + if (this.blurPostProcessing != null) + { + RenderSystem.disableDepthTest(); + RenderSystem.resetTextureMatrix(); + RenderSystem.disableBlend(); + RenderSystem.depthMask(false); + this.blurPostProcessing.process(partialTick); + RenderSystem.depthMask(true); + } + } + + public void doScreenSpaceWorldFog(PoseStack stack, Matrix4f projMat, float partialTick) + { + if (this.screenSpaceWorldFog != null) + { + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + RenderSystem.resetTextureMatrix(); + RenderSystem.depthMask(false); + + Matrix4f invertedProjMat = new Matrix4f(projMat).invert(); + Matrix4f invertedModelViewMat = new Matrix4f(stack.last().pose()).invert(); + for (PostPass pass : ((MixinPostChain)this.screenSpaceWorldFog).simpleclouds$getPostPasses()) + { + EffectInstance effect = pass.getEffect(); + effect.safeGetUniform("InverseWorldProjMat").set(invertedProjMat); + effect.safeGetUniform("InverseModelViewMat").set(invertedModelViewMat); + effect.safeGetUniform("FogStart").set(RenderSystem.getShaderFogStart()); + effect.safeGetUniform("FogEnd").set(RenderSystem.getShaderFogEnd()); + float[] fogCol = RenderSystem.getShaderFogColor(); + effect.safeGetUniform("FogColor").set(fogCol[0], fogCol[1], fogCol[2]); + effect.safeGetUniform("FogShape").set(RenderSystem.getShaderFogShape().getIndex()); + } + + this.screenSpaceWorldFog.process(partialTick); + + RenderSystem.depthMask(true); + } + } + + public void doFinalCompositePass(PoseStack stack, float partialTick, Matrix4f projMat) + { + if (this.finalComposite != null) + { + RenderSystem.disableDepthTest(); + RenderSystem.resetTextureMatrix(); + RenderSystem.depthMask(false); + + this.finalComposite.process(partialTick); + + RenderSystem.depthMask(true); + } + } + + public void doStormPostProcessing(PoseStack stack, float partialTick, Matrix4f projMat, double camX, double camY, double camZ, float r, float g, float b) + { + if (this.stormPostProcessing == null || this.stormFogShadowMapStack == null || this.stormFogShadowMapStack == null) + return; + + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + RenderSystem.resetTextureMatrix(); + RenderSystem.depthMask(false); + + this.stormFogTarget.clear(Minecraft.ON_OSX); + this.stormFogTarget.bindWrite(true); + + MutableInt size = new MutableInt(); + boolean flag = SimpleCloudsConfig.CLIENT.stormFogLightningFlashes.get(); + if (flag) + { + List lightningBolts = this.worldEffectsManager.getLightningBolts(); + size.setValue(Math.min(lightningBolts.size(), MAX_LIGHTNING_BOLTS)); + if (size.getValue() > 0) + { + this.lightningBoltPositions.writeData(buffer -> + { + for (int i = 0; i < size.getValue(); i++) + { + LightningBolt bolt = lightningBolts.get(i); + Vector3f pos = bolt.getPosition(); + buffer.putFloat(pos.x); + buffer.putFloat(pos.y); + buffer.putFloat(pos.z); + buffer.putFloat(bolt.getFade(partialTick)); + } + buffer.rewind(); + }, size.getValue() * BYTES_PER_LIGHTNING_BOLT, false); + } + } + + Matrix4f invertedProjMat = new Matrix4f(projMat).invert(); + Matrix4f invertedModelViewMat = new Matrix4f(stack.last().pose()).invert(); + for (PostPass pass : ((MixinPostChain)this.stormPostProcessing).simpleclouds$getPostPasses()) + { + EffectInstance effect = pass.getEffect(); + effect.safeGetUniform("InverseWorldProjMat").set(invertedProjMat); + effect.safeGetUniform("InverseModelViewMat").set(invertedModelViewMat); + effect.safeGetUniform("ShadowProjMat").set(this.stormFogShadowMap.getProjMatrix()); + effect.safeGetUniform("ShadowModelViewMat").set(this.stormFogShadowMapStack.last().pose()); + effect.safeGetUniform("CameraPos").set((float)camX, (float)camY, (float)camZ); + effect.safeGetUniform("FogStart").set(this.fogEnd / 2.0F); + effect.safeGetUniform("FogEnd").set(this.fogEnd); + effect.safeGetUniform("ColorModulator").set(r, g, b, 1.0F); + float factor = this.worldEffectsManager.getDarkenFactor(partialTick); + effect.safeGetUniform("CutoffDistance").set(1000.0F * factor); + effect.safeGetUniform("TotalLightningBolts").set(size.getValue()); + } + + this.stormPostProcessing.process(partialTick); + + RenderSystem.depthMask(true); + } + + public void doCloudShadowProcessing(PoseStack stack, float partialTick, Matrix4f projMat, double camX, double camY, double camZ, int depthBufferId) + { + if (this.cloudShadows == null || this.shadowMap.isEmpty() || this.shadowMapStack == null) + return; + + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + RenderSystem.resetTextureMatrix(); + RenderSystem.depthMask(false); + + Matrix4f invertedProjMat = new Matrix4f(projMat).invert(); + Matrix4f invertedModelViewMat = new Matrix4f(stack.last().pose()).invert(); + float minimumRadius = this.mc.gameRenderer.getRenderDistance(); + for (PostPass pass : ((MixinPostChain)this.cloudShadows).simpleclouds$getPostPasses()) + { + EffectInstance effect = pass.getEffect(); + effect.setSampler("DepthSampler", () -> depthBufferId); + effect.safeGetUniform("InverseWorldProjMat").set(invertedProjMat); + effect.safeGetUniform("InverseModelViewMat").set(invertedModelViewMat); + effect.safeGetUniform("ShadowProjMat").set(this.shadowMap.get().getProjMatrix()); + effect.safeGetUniform("ShadowModelViewMat").set(this.shadowMapStack.last().pose()); + effect.safeGetUniform("CameraPos").set((float)camX, (float)camY, (float)camZ); + effect.safeGetUniform("MinimumRadius").set(minimumRadius); + } + + this.cloudShadows.process(partialTick); + + RenderSystem.depthMask(true); + } + + public static void prepareShader(ShaderInstance shader, Matrix4f modelView, Matrix4f projMat, float fogStart, float fogEnd) + { + for (int i = 0; i < 12; ++i) + { + int j = RenderSystem.getShaderTexture(i); + shader.setSampler("Sampler" + i, j); + } + + if (shader.MODEL_VIEW_MATRIX != null) + shader.MODEL_VIEW_MATRIX.set(modelView); + + if (shader.PROJECTION_MATRIX != null) + shader.PROJECTION_MATRIX.set(projMat); + + if (shader.INVERSE_VIEW_ROTATION_MATRIX != null) + shader.INVERSE_VIEW_ROTATION_MATRIX.set(RenderSystem.getInverseViewRotationMatrix()); + + if (shader.COLOR_MODULATOR != null) + shader.COLOR_MODULATOR.set(RenderSystem.getShaderColor()); + + if (shader.GLINT_ALPHA != null) + shader.GLINT_ALPHA.set(RenderSystem.getShaderGlintAlpha()); + + if (shader.FOG_START != null) + shader.FOG_START.set(fogStart); + + if (shader.FOG_END != null) + shader.FOG_END.set(fogEnd); + + if (shader.FOG_COLOR != null) + shader.FOG_COLOR.set(RenderSystem.getShaderFogColor()); + + if (shader.FOG_SHAPE != null) + shader.FOG_SHAPE.set(RenderSystem.getShaderFogShape().getIndex()); + + if (shader.TEXTURE_MATRIX != null) + shader.TEXTURE_MATRIX.set(RenderSystem.getTextureMatrix()); + + if (shader.GAME_TIME != null) + shader.GAME_TIME.set(RenderSystem.getShaderGameTime()); + + if (shader.SCREEN_SIZE != null) + { + Window window = Minecraft.getInstance().getWindow(); + shader.SCREEN_SIZE.set((float) window.getWidth(), (float) window.getHeight()); + } + + shader.safeGetUniform("UseNormals").set(SimpleCloudsConfig.CLIENT.cubeNormals.get() ? 1 : 0); + + RenderSystem.setShaderLights(DIFFUSE_LIGHT_0, DIFFUSE_LIGHT_1); + RenderSystem.setupShaderLights(shader); + } + + public void copyDepthFromCloudsToMain() + { + this._copyDepthSafe(this.mc.getMainRenderTarget(), this.cloudTarget); + } + + public void copyDepthFromMainToClouds() + { + this._copyDepthSafe(this.cloudTarget, this.mc.getMainRenderTarget()); + } + + public void copyDepthFromCloudsToTransparency() + { + this._copyDepthSafe(this.cloudTransparencyTarget, this.cloudTarget); + } + + private void _copyDepthSafe(RenderTarget to, RenderTarget from) + { + RenderSystem.assertOnRenderThread(); + GlStateManager._getError(); //Clear old error + if (!this.failedToCopyDepthBuffer) + { + to.bindWrite(false); + to.copyDepthFrom(from); + if (GlStateManager._getError() != GL11.GL_INVALID_OPERATION) + return; + boolean enabledStencil = false; + if (to.isStencilEnabled() && !from.isStencilEnabled()) + { + from.enableStencil(); + enabledStencil = true; + } + else if (from.isStencilEnabled() && !to.isStencilEnabled()) + { + to.enableStencil(); + enabledStencil = true; + } + if (enabledStencil) + { + to.copyDepthFrom(from); + if (GlStateManager._getError() == GL11.GL_INVALID_OPERATION) + { + LOGGER.error("Unable to copy depth between the main and clouds frame buffers, even after enabling stencil. Please note that the clouds may not render properly."); + this.failedToCopyDepthBuffer = true; + } + else + { + LOGGER.info("NOTE: Please ignore the above OpenGL error. Simple Clouds had to toggle stencil in order to copy the depth buffer between the main and clouds frame buffers."); + } + } + else + { + LOGGER.error("Unable to copy depth between the main and clouds frame buffers. Please note that the clouds may not render properly."); + this.failedToCopyDepthBuffer = true; + } + } + } + + public void fillReport(CrashReport report) + { + CrashReportCategory category = report.addCategory("Simple Clouds Renderer"); + category.setDetail("Cloud Mode", this.settings.getCurrentCloudMode()); + category.setDetail("Cloud Target Available", this.cloudTarget != null); + category.setDetail("Storm Fog Target Active", this.stormFogTarget != null); + category.setDetail("Blur Target Active", this.blurTarget != null); + category.setDetail("Transparency Target Active", this.cloudTransparencyTarget != null); + category.setDetail("Post Chains", this.postChains.toString()); + category.setDetail("Lightning Bolt SSBO", this.lightningBoltPositions); + category.setDetail("Clouds Shadow Map", this.stormFogShadowMap); + category.setDetail("Storm Fog Shadow Map", this.stormFogShadowMap); + category.setDetail("Failed to copy depth buffer", this.failedToCopyDepthBuffer); + category.setDetail("Needs Reload", this.needsReload); + + CrashReportCategory meshGenCategory = report.addCategory("Cloud Mesh Generator"); + if (this.meshGenerator != null) + { + meshGenCategory.setDetail("Type", this.meshGenerator.toString()); + this.meshGenerator.fillReport(meshGenCategory); + } + else + { + meshGenCategory.setDetail("Type", "Mesh generator is not initialized"); + } + } + + public static void initialize(CloudsRendererSettings settings) + { + RenderSystem.assertOnRenderThread(); + if (instance != null) + throw new IllegalStateException("Simple Clouds renderer is already initialized"); + instance = new SimpleCloudsRenderer(settings, Minecraft.getInstance()); + LOGGER.debug("Clouds render initialized"); + } + + public static SimpleCloudsRenderer getInstance() + { + return Objects.requireNonNull(instance, "Renderer not initialized!"); + } + + public static Optional getOptionalInstance() + { + return Optional.ofNullable(instance); + } + + public static boolean canRenderInDimension(@Nullable ClientLevel level) + { + if (level == null) + return false; + + List whitelist; + boolean useAsBlacklist; + if (ClientCloudManager.isAvailableServerSide() && SimpleCloudsConfig.SERVER_SPEC.isLoaded()) + { + whitelist = SimpleCloudsConfig.SERVER.dimensionWhitelist.get(); + useAsBlacklist = SimpleCloudsConfig.SERVER.whitelistAsBlacklist.get(); + } + else + { + whitelist = SimpleCloudsConfig.CLIENT.dimensionWhitelist.get(); + useAsBlacklist = SimpleCloudsConfig.CLIENT.whitelistAsBlacklist.get(); + } + + boolean flag = whitelist.stream().anyMatch(val -> { + return level.dimension().location().toString().equals(val); + }); + + return useAsBlacklist ? !flag : flag; + } + + private static void renderChunkBox(VertexConsumer consumer, float minX, float minY, float minZ, float maxX, float maxY, float maxZ, float r, float g, float b, float a) + { + //-X + consumer.vertex(minX, minY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, maxY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, minY, minZ).color(r, g, b, a).endVertex(); + + //+X + consumer.vertex(maxX, minY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, minY, maxZ).color(r, g, b, a).endVertex(); + + //-Y + consumer.vertex(maxX, minY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, minY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, minY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, minY, minZ).color(r, g, b, a).endVertex(); + + //+Y + consumer.vertex(minX, maxY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, minZ).color(r, g, b, a).endVertex(); + + //-Z + consumer.vertex(minX, minY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, maxY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, minZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, minY, minZ).color(r, g, b, a).endVertex(); + + //+Z + consumer.vertex(maxX, minY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(maxX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, maxY, maxZ).color(r, g, b, a).endVertex(); + consumer.vertex(minX, minY, maxZ).color(r, g, b, a).endVertex(); + } + + private static int calculateMeshGenInterval() + { + int fps = Minecraft.getInstance().getFps(); + switch (SimpleCloudsConfig.CLIENT.generationInterval.get()) + { + case STATIC: + { + return SimpleCloudsConfig.CLIENT.framesToGenerateMesh.get(); + } + case DYNAMIC: + { + return Math.max(Mth.ceil((130.0F - (float)fps) / 30.0F) + 5, 1); + } + case TARGET_FPS: + { + return Math.max(Mth.ceil((float)fps / SimpleCloudsConfig.CLIENT.targetMeshGenFps.get()), 1); + } + default: + return 5; + } + } +} diff --git a/to_check_hurricanes/README.md b/to_check_hurricanes/README.md new file mode 100644 index 00000000..8d49da6d --- /dev/null +++ b/to_check_hurricanes/README.md @@ -0,0 +1,16 @@ +# to_check_hurricanes + +Snapshot of the current tornado implementation kept for possible hurricane reuse. + +This folder is archival only. Files here are copied from the active source tree and are not part of the build. + +Copied files: +- src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsTornadoRenderer.java +- src/main/java/net/Gabou/projectatmosphere/client/TornadoClientEffects.java +- src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticle.java +- src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticleData.java +- src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoInstance.java +- src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoSnapshot.java +- src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoManager.java +- src/main/java/net/Gabou/projectatmosphere/modules/weather/StormMotionModel.java +- src/main/java/net/Gabou/projectatmosphere/modules/wind/TornadoWindModel.java \ No newline at end of file diff --git a/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/client/TornadoClientEffects.java b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/client/TornadoClientEffects.java new file mode 100644 index 00000000..06351dd6 --- /dev/null +++ b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/client/TornadoClientEffects.java @@ -0,0 +1,51 @@ +package net.Gabou.projectatmosphere.client; + +import dev.nonamecrackers2.simpleclouds.common.config.SimpleCloudsConfig; +import net.Gabou.projectatmosphere.modules.tornado.TornadoInstance; +import net.Gabou.projectatmosphere.particles.DebrisParticleData; +import net.minecraft.client.multiplayer.ClientLevel; + +public final class TornadoClientEffects { + private static final int LOW_BAND = 0; + private static final int MID_BAND = 1; + private static final int UPPER_BAND = 2; + + private TornadoClientEffects() { + } + + public static void spawnDebrisParticles(TornadoInstance tornado, ClientLevel level) { + double visualHeight = Math.min(tornado.getVisualHeight(), SimpleCloudsConfig.CLIENT.cloudHeight.get()); + double maxRadius = Math.max(4.0, tornado.radius); + float intensity = tornado.getNormalizedIntensity(); + float debrisScore = tornado.getRecentDebrisScore(); + + int lowCount = 7 + Math.round(intensity * 7.0F + debrisScore * 10.0F); + int midCount = 12 + Math.round(intensity * 8.0F + debrisScore * 7.0F); + int upperCount = 4 + Math.round(intensity * 4.0F + debrisScore * 2.0F); + + spawnBand(level, tornado, lowCount, maxRadius * 1.24D, visualHeight * 0.20D, 8.4F, 0.020F, 0.42F, LOW_BAND); + spawnBand(level, tornado, midCount, maxRadius * 0.76D, visualHeight * 0.74D, 16.0F, 0.046F, 0.30F, MID_BAND); + spawnBand(level, tornado, upperCount, maxRadius * 1.24D, visualHeight * 1.06D, 5.4F, 0.026F, 0.46F, UPPER_BAND); + } + + private static void spawnBand(ClientLevel level, TornadoInstance tornado, int count, double maxRadius, double maxHeight, + float angularSpeed, float verticalDrift, float radialJitter, int band) { + for (int i = 0; i < count; i++) { + double radius = Math.sqrt(level.random.nextDouble()) * Math.max(0.6D, maxRadius); + double height = level.random.nextDouble() * Math.max(1.0D, maxHeight); + float localAngularSpeed = (float) (angularSpeed * (0.82F + level.random.nextFloat() * 0.42F)); + float localVerticalDrift = verticalDrift * (0.75F + level.random.nextFloat() * 0.55F); + float localRadialJitter = radialJitter * (0.65F + level.random.nextFloat() * 0.70F); + + level.addParticle( + new DebrisParticleData(tornado, radius, height, localAngularSpeed, localVerticalDrift, localRadialJitter, band), + tornado.position.x, + tornado.position.y, + tornado.position.z, + 0.0, + localVerticalDrift, + 0.0 + ); + } + } +} diff --git a/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsTornadoRenderer.java b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsTornadoRenderer.java new file mode 100644 index 00000000..d5ff86eb --- /dev/null +++ b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/client/render/SimpleCloudsTornadoRenderer.java @@ -0,0 +1,724 @@ +package net.Gabou.projectatmosphere.client.render; + +import com.mojang.blaze3d.platform.MemoryTracker; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferUploader; +import com.mojang.blaze3d.vertex.PoseStack; +import dev.nonamecrackers2.simpleclouds.client.mesh.instancing.InstanceableMesh; +import dev.nonamecrackers2.simpleclouds.client.renderer.SimpleCloudsRenderer; +import dev.nonamecrackers2.simpleclouds.client.shader.SimpleCloudsShaders; +import dev.nonamecrackers2.simpleclouds.client.shader.SingleSSBOShaderInstance; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.BindingManager; +import dev.nonamecrackers2.simpleclouds.client.shader.buffer.ShaderStorageBufferObject; +import dev.nonamecrackers2.simpleclouds.common.cloud.SimpleCloudsConstants; +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.Gabou.projectatmosphere.modules.tornado.TornadoInstance; +import net.Gabou.projectatmosphere.modules.tornado.TornadoManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL14; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL40; +import org.lwjgl.opengl.GL43; +import org.lwjgl.system.MemoryUtil; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class SimpleCloudsTornadoRenderer { + public static final SimpleCloudsTornadoRenderer INSTANCE = new SimpleCloudsTornadoRenderer(); + + private static final ResourceLocation BAYER_MATRIX_TEXTURE = + ResourceLocation.fromNamespaceAndPath("simpleclouds", "textures/shader/bayer_matrix.png"); + + private static final float VOXEL_STEP = 0.34F; + private static final float FRINGE_WIDTH = 0.56F; + private static final float SHELL_HALF_WIDTH = 0.32F; + private static final float CORE_WIDTH = 0.060F; + private static final float CORE_FALLOFF = 0.18F; + private static final float DENSITY_BIAS = 0.16F; + private static final float NOISE_SCALE_PRIMARY = 1.10F; + private static final float NOISE_SCALE_SECONDARY = 2.20F; + private static final float NOISE_VERTICAL_PRIMARY = 0.30F; + private static final float NOISE_VERTICAL_SECONDARY = 0.55F; + private static final float NOISE_PRIMARY_WEIGHT = 0.24F; + private static final float NOISE_SECONDARY_WEIGHT = 0.10F; + private static final float GROUND_RADIUS_FACTOR = 0.24F; + private static final float TOP_RADIUS_FACTOR = 1.16F; + private static final float TOP_FLARE_FACTOR = 1.55F; + private static final float PLUME_START = 0.72F; + private static final float PLUME_RADIUS_GAIN = 1.40F; + private static final float PLUME_SOFTEN_GAIN = 0.42F; + private static final float PLUME_BODY_REDUCTION = 0.55F; + private static final float PM_ATTACHMENT_STRENGTH = 0.70F; + private static final float PM_CENTER_SWAY_STRENGTH = 0.55F; + private static final float PM_CENTER_SWAY_SCROLL = 0.0065F; + private static final float PM_CENTER_SWAY_FREQ = 0.18F; + private static final float UPPER_BRIGHTEN_START = 0.70F; + private static final float UPPER_BRIGHTEN_GAIN = 0.20F; + private static final float SPIRAL_MODULATION_STRENGTH = 0.18F; + private static final float SPIRAL_MODULATION_BANDS = 4.10F; + private static final float SPIRAL_MODULATION_SCROLL = 2.60F; + private static final float PM_HELIX_NOISE_STRENGTH = 0.16F; + private static final float PM_HELIX_NOISE_SCALE_A = 0.055F; + private static final float PM_HELIX_NOISE_SCALE_B = 0.032F; + private static final float PM_GROUND_DUST_STRENGTH = 0.12F; + private static final float PM_GROUND_DUST_HEIGHT = 0.12F; + private static final float MOUTH_FADE_END = 0.08F; + private static final float TOP_FADE_START = 0.985F; + private static final float BRIGHTNESS_BASE = 0.46F; + private static final float BRIGHTNESS_GAIN = 0.18F; + private static final float BODY_FILL_START = 0.24F; + private static final float BODY_FILL_END = 0.68F; + private static final float BODY_DENSITY_STRENGTH = 0.68F; + private static final float BODY_BREAKUP_STRENGTH = 0.08F; + private static final float TORNADO_WHITEOUT_STRENGTH = 0.38F; + private static final float TORNADO_WHITEOUT_THRESHOLD = 0.10F; + private static final float MIN_VISUAL_WORLD_RADIUS = 24.0F; + private static final float MIN_GROUND_RADIUS_CLOUD = 0.72F; + private static final float MIN_TOP_RADIUS_CLOUD = 2.60F; + private static final float CLOUD_BLEND_HEIGHT_ABOVE_BASE_WORLD = 200.0F; + + private static final int OPAQUE_STRIDE_BYTES = Integer.BYTES + 5 * Float.BYTES; + private static final int TRANSPARENT_STRIDE_BYTES = 6 * Float.BYTES; + + private boolean initialized; + private InstanceableMesh sideMesh; + private InstanceableMesh cubeMesh; + private ShaderStorageBufferObject opaqueSsbo; + private ShaderStorageBufferObject transparentSsbo; + + private final List opaqueInstances = new ArrayList<>(); + private final List transparentInstances = new ArrayList<>(); + private ByteBuffer opaqueUploadBuffer; + private ByteBuffer transparentUploadBuffer; + + private ClientLevel preparedLevel; + private long preparedGameTime = Long.MIN_VALUE; + private int opaqueCount; + private int transparentCount; + private long lastRenderOpaqueLogGameTime = Long.MIN_VALUE; + private long lastRenderTransparencyLogGameTime = Long.MIN_VALUE; + + private SimpleCloudsTornadoRenderer() { + } + + public void prepareFrame(ClientLevel level, float partialTick) { + RenderSystem.assertOnRenderThread(); + this.ensureInitialized(); + + if (this.preparedLevel == level && this.preparedGameTime == level.getGameTime()) { + return; + } + + boolean shouldDebugLog = shouldDebugLog(level); + this.preparedLevel = level; + this.preparedGameTime = level.getGameTime(); + this.opaqueInstances.clear(); + this.transparentInstances.clear(); + + if (shouldDebugLog) { + debug( + "prepareFrame gameTime={} activeTornadoes={}", + level.getGameTime(), + TornadoManager.getClientTornadoes().size() + ); + } + + for (TornadoInstance tornado : TornadoManager.getClientTornadoes()) { + this.appendTornado(level, tornado); + } + + this.uploadBuffers(); + if (shouldDebugLog) { + debug( + "prepareFrame complete gameTime={} opaqueCount={} transparentCount={}", + level.getGameTime(), + this.opaqueCount, + this.transparentCount + ); + } + } + + public void renderOpaque(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB) { + ClientLevel level = Minecraft.getInstance().level; + if (shouldDebugLog(level) && level != null && this.lastRenderOpaqueLogGameTime != level.getGameTime()) { + this.lastRenderOpaqueLogGameTime = level.getGameTime(); + debug( + "renderOpaque called gameTime={} opaqueCount={} shadersReady={}", + level.getGameTime(), + this.opaqueCount, + SimpleCloudsShaders.areShadersInitialized() + ); + } + if (this.opaqueCount <= 0 || !SimpleCloudsShaders.areShadersInitialized()) { + return; + } + + BufferUploader.reset(); + RenderSystem.disableBlend(); + RenderSystem.enableDepthTest(); + RenderSystem.disableCull(); + + SingleSSBOShaderInstance shader = SimpleCloudsShaders.getCloudsShader(); + RenderSystem.setShader(() -> shader); + + TextureManager textureManager = Minecraft.getInstance().getTextureManager(); + AbstractTexture ditherTexture = textureManager.getTexture(BAYER_MATRIX_TEXTURE); + shader.setSampler("BayerMatrixSampler", ditherTexture); + shader.safeGetUniform("DitherScale").set(SimpleCloudsRenderer.DITHER_SCALE); + + SimpleCloudsRenderer.prepareShader(shader, stack.last().pose(), projMat, + renderer.getFogStart(), renderer.getFogEnd()); + shader.apply(); + + RenderSystem.setShaderColor(cloudR, cloudG, cloudB, 1.0F); + if (shader.COLOR_MODULATOR != null) { + shader.COLOR_MODULATOR.set(RenderSystem.getShaderColor()); + shader.COLOR_MODULATOR.upload(); + } + + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), this.opaqueSsbo.getId()); + this.sideMesh.drawInstanced(this.opaqueCount); + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), 0); + shader.clear(); + + GL30.glBindVertexArray(0); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.enableCull(); + } + + public void renderTransparency(SimpleCloudsRenderer renderer, PoseStack stack, Matrix4f projMat, + float partialTick, float cloudR, float cloudG, float cloudB) { + ClientLevel level = Minecraft.getInstance().level; + if (shouldDebugLog(level) && level != null && this.lastRenderTransparencyLogGameTime != level.getGameTime()) { + this.lastRenderTransparencyLogGameTime = level.getGameTime(); + debug( + "renderTransparency called gameTime={} transparentCount={} shadersReady={}", + level.getGameTime(), + this.transparentCount, + SimpleCloudsShaders.areShadersInitialized() + ); + } + if (this.transparentCount <= 0 || !SimpleCloudsShaders.areShadersInitialized()) { + return; + } + + BufferUploader.reset(); + RenderSystem.enableDepthTest(); + RenderSystem.depthMask(false); + + SingleSSBOShaderInstance shader = SimpleCloudsShaders.getCloudsTransparencyShader(); + RenderSystem.setShader(() -> shader); + + TextureManager textureManager = Minecraft.getInstance().getTextureManager(); + AbstractTexture ditherTexture = textureManager.getTexture(BAYER_MATRIX_TEXTURE); + shader.setSampler("BayerMatrixSampler", ditherTexture); + shader.safeGetUniform("DitherScale").set(SimpleCloudsRenderer.DITHER_SCALE); + + SimpleCloudsRenderer.prepareShader(shader, stack.last().pose(), projMat, + renderer.getFogStart(), renderer.getFogEnd()); + shader.apply(); + + RenderSystem.setShaderColor(cloudR, cloudG, cloudB, 1.0F); + if (shader.COLOR_MODULATOR != null) { + shader.COLOR_MODULATOR.set(RenderSystem.getShaderColor()); + shader.COLOR_MODULATOR.upload(); + } + + GL30.glEnablei(GL11.GL_BLEND, 0); + GL30.glEnablei(GL11.GL_BLEND, 1); + GL40.glBlendEquationi(0, GL14.GL_FUNC_ADD); + GL40.glBlendEquationi(1, GL14.GL_FUNC_ADD); + GL40.glBlendFunci(0, GL11.GL_ONE, GL11.GL_ONE); + GL40.glBlendFunci(1, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_COLOR); + + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), this.transparentSsbo.getId()); + this.cubeMesh.drawInstanced(this.transparentCount); + GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, shader.getShaderStorageBinding(), 0); + shader.clear(); + + GL30.glDisablei(GL11.GL_BLEND, 0); + GL30.glDisablei(GL11.GL_BLEND, 1); + GL40.glBlendFuncSeparatei(0, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + GL40.glBlendFuncSeparatei(1, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + + GL30.glBindVertexArray(0); + RenderSystem.depthMask(true); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + } + + public float sampleWhiteoutAtCamera(ClientLevel level, Vec3 cameraPos, float partialTick) { + float scale = SimpleCloudsConstants.CLOUD_SCALE; + float cloudHeight = CloudManager.get(level).getCloudHeight(); + float sampleX = (float) cameraPos.x / scale; + float sampleY = ((float) cameraPos.y - cloudHeight) / scale; + float sampleZ = (float) cameraPos.z / scale; + + float strongest = 0.0F; + float animationTime = TornadoManager.getShaderTime(); + for (TornadoInstance tornado : TornadoManager.getClientTornadoes()) { + CloudSpaceTornado cloudTornado = CloudSpaceTornado.from(level, tornado); + float density = this.sampleDensityCloudSpace( + sampleX - cloudTornado.centerX(), + sampleY - cloudTornado.bottomY(), + sampleZ - cloudTornado.centerZ(), + cloudTornado, + animationTime + ); + float whiteout = Mth.clamp((density - TORNADO_WHITEOUT_THRESHOLD) / Math.max(FRINGE_WIDTH, 0.001F), 0.0F, 1.0F) * TORNADO_WHITEOUT_STRENGTH; + strongest = Math.max(strongest, whiteout); + } + return strongest; + } + + public void close() { + if (!this.initialized) { + return; + } + + this.sideMesh.destroy(); + this.cubeMesh.destroy(); + BindingManager.freeSSBO(this.opaqueSsbo); + BindingManager.freeSSBO(this.transparentSsbo); + this.initialized = false; + this.preparedLevel = null; + this.opaqueUploadBuffer = null; + this.transparentUploadBuffer = null; + this.opaqueCount = 0; + this.transparentCount = 0; + } + + private void ensureInitialized() { + if (this.initialized) { + return; + } + + this.sideMesh = InstanceableMesh.defaultSide(); + this.cubeMesh = InstanceableMesh.defaultCube(); + this.opaqueSsbo = BindingManager.createSSBO(GL15.GL_DYNAMIC_DRAW); + this.transparentSsbo = BindingManager.createSSBO(GL15.GL_DYNAMIC_DRAW); + this.initialized = true; + } + + private void appendTornado(ClientLevel level, TornadoInstance tornado) { + CloudSpaceTornado cloudTornado = CloudSpaceTornado.from(level, tornado); + Map densityByCell = new HashMap<>(); + float animationTime = TornadoManager.getShaderTime(); + boolean shouldDebugLog = shouldDebugLog(level); + int startOpaqueCount = this.opaqueInstances.size(); + int startTransparentCount = this.transparentInstances.size(); + int sampledCellCount = 0; + float minDensity = Float.POSITIVE_INFINITY; + float maxDensity = Float.NEGATIVE_INFINITY; + + float maxRadius = Math.max(cloudTornado.baseRadius(), cloudTornado.topRadius()) + FRINGE_WIDTH + VOXEL_STEP; + int minX = Mth.floor((cloudTornado.centerX() - maxRadius) / VOXEL_STEP); + int maxX = Mth.ceil((cloudTornado.centerX() + maxRadius) / VOXEL_STEP); + int minY = Mth.floor(cloudTornado.bottomY() / VOXEL_STEP); + int maxY = Mth.ceil((cloudTornado.bottomY() + cloudTornado.height()) / VOXEL_STEP); + int minZ = Mth.floor((cloudTornado.centerZ() - maxRadius) / VOXEL_STEP); + int maxZ = Mth.ceil((cloudTornado.centerZ() + maxRadius) / VOXEL_STEP); + + if (shouldDebugLog) { + debug( + "appendTornado reached id={} center=({}, {}, {}) boundsX=[{},{}] boundsY=[{},{}] boundsZ=[{},{}] baseRadius={} topRadius={} height={}", + tornado.getId(), + cloudTornado.centerX(), + cloudTornado.bottomY(), + cloudTornado.centerZ(), + minX, + maxX, + minY, + maxY, + minZ, + maxZ, + cloudTornado.baseRadius(), + cloudTornado.topRadius(), + cloudTornado.height() + ); + } + + for (int cellX = minX; cellX <= maxX; cellX++) { + for (int cellY = minY; cellY <= maxY; cellY++) { + for (int cellZ = minZ; cellZ <= maxZ; cellZ++) { + float sampleX = cellX * VOXEL_STEP; + float sampleY = cellY * VOXEL_STEP; + float sampleZ = cellZ * VOXEL_STEP; + sampledCellCount++; + + float density = this.sampleDensityCloudSpace( + sampleX - cloudTornado.centerX(), + sampleY - cloudTornado.bottomY(), + sampleZ - cloudTornado.centerZ(), + cloudTornado, + animationTime + ); + minDensity = Math.min(minDensity, density); + maxDensity = Math.max(maxDensity, density); + + if (density > -FRINGE_WIDTH) { + densityByCell.put(new CellKey(cellX, cellY, cellZ), density); + } + } + } + } + + for (Map.Entry entry : densityByCell.entrySet()) { + CellKey key = entry.getKey(); + float density = entry.getValue(); + + float y = key.y() * VOXEL_STEP; + float y01 = Mth.clamp((y - cloudTornado.bottomY()) / Math.max(cloudTornado.height(), 0.001F), 0.0F, 1.0F); + float upperBrighten = smoothstep(UPPER_BRIGHTEN_START, 1.0F, y01); + float brightness = Mth.clamp(BRIGHTNESS_BASE + y01 * BRIGHTNESS_GAIN + upperBrighten * UPPER_BRIGHTEN_GAIN, 0.0F, 1.0F); + float radius = VOXEL_STEP * 0.5F; + + if (density > 0.0F) { + this.emitOpaqueFaces(densityByCell, key, brightness, radius); + } else { + float alpha = Mth.clamp(1.0F + density / FRINGE_WIDTH, 0.0F, 1.0F); + alpha *= Mth.lerp(upperBrighten, 1.0F, 0.72F); + this.transparentInstances.add(new TransparentCubeInstance( + key.x() * VOXEL_STEP, + key.y() * VOXEL_STEP, + key.z() * VOXEL_STEP, + brightness, + alpha, + radius + )); + } + } + + if (shouldDebugLog) { + debug( + "appendTornado result id={} sampledCells={} retainedCells={} densityMin={} densityMax={} addedOpaque={} addedTransparent={}", + tornado.getId(), + sampledCellCount, + densityByCell.size(), + minDensity, + maxDensity, + this.opaqueInstances.size() - startOpaqueCount, + this.transparentInstances.size() - startTransparentCount + ); + } + } + + private void emitOpaqueFaces(Map densityByCell, CellKey key, float brightness, float radius) { + float x = key.x() * VOXEL_STEP; + float y = key.y() * VOXEL_STEP; + float z = key.z() * VOXEL_STEP; + + if (!this.isSolid(densityByCell, key.x() - 1, key.y(), key.z())) { + this.opaqueInstances.add(new OpaqueSideInstance(0, x, y, z, brightness, radius)); + } + if (!this.isSolid(densityByCell, key.x() + 1, key.y(), key.z())) { + this.opaqueInstances.add(new OpaqueSideInstance(1, x, y, z, brightness, radius)); + } + if (!this.isSolid(densityByCell, key.x(), key.y() - 1, key.z())) { + this.opaqueInstances.add(new OpaqueSideInstance(2, x, y, z, brightness, radius)); + } + if (!this.isSolid(densityByCell, key.x(), key.y() + 1, key.z())) { + this.opaqueInstances.add(new OpaqueSideInstance(3, x, y, z, brightness, radius)); + } + if (!this.isSolid(densityByCell, key.x(), key.y(), key.z() - 1)) { + this.opaqueInstances.add(new OpaqueSideInstance(4, x, y, z, brightness, radius)); + } + if (!this.isSolid(densityByCell, key.x(), key.y(), key.z() + 1)) { + this.opaqueInstances.add(new OpaqueSideInstance(5, x, y, z, brightness, radius)); + } + } + + private boolean isSolid(Map densityByCell, int x, int y, int z) { + Float density = densityByCell.get(new CellKey(x, y, z)); + return density != null && density > 0.0F; + } + + private float sampleDensityCloudSpace(float localX, float localY, float localZ, + CloudSpaceTornado tornado, float animationTime) { + float height = Math.max(tornado.height(), 0.001F); + if (localY <= -FRINGE_WIDTH || localY >= height + FRINGE_WIDTH) { + return -1.0F; + } + + float y01 = Mth.clamp(localY / height, 0.0F, 1.0F); + float percCos = (-Mth.cos(y01 * Mth.PI) + 1.0F) * 0.5F; + Vec3 centerOffset = computePmCenterOffset(tornado, localY, y01, percCos, animationTime); + float shiftedX = localX - (float) centerOffset.x; + float shiftedZ = localZ - (float) centerOffset.z; + float twist = animationTime * 0.18F + tornado.twist() + y01 * 5.8F; + float cos = Mth.cos(twist); + float sin = Mth.sin(twist); + + float qx = shiftedX * cos - shiftedZ * sin; + float qz = shiftedX * sin + shiftedZ * cos; + float funnelRadius = computeFunnelRadius(tornado, y01); + float radialDistance = Mth.sqrt(qx * qx + qz * qz); + + float plumeBlend = smoothstep(PLUME_START, 1.0F, y01); + float shellWidth = SHELL_HALF_WIDTH + plumeBlend * PLUME_SOFTEN_GAIN; + float shell = shellWidth - Math.abs(radialDistance - funnelRadius); + float bodyRadius = Math.max(0.0F, funnelRadius - Mth.lerp(y01, BODY_FILL_START, BODY_FILL_END)); + float body = bodyRadius - radialDistance; + float core = CORE_WIDTH - radialDistance * CORE_FALLOFF; + + float advectX = animationTime * (0.08F + y01 * 0.18F); + float advectZ = animationTime * (0.05F + y01 * 0.12F); + float shellBreakup = fbm( + (qx + advectX) * NOISE_SCALE_PRIMARY, + localY * NOISE_VERTICAL_PRIMARY + animationTime * 0.10F, + (qz - advectZ) * NOISE_SCALE_PRIMARY + ) * NOISE_PRIMARY_WEIGHT; + shellBreakup += fbm( + qx * NOISE_SCALE_SECONDARY + 17.0F + advectX * 1.7F, + localY * NOISE_VERTICAL_SECONDARY + 11.0F, + qz * NOISE_SCALE_SECONDARY - 9.0F - advectZ * 1.3F + ) * NOISE_SECONDARY_WEIGHT; + float helixNoise = pmHelixNoise(qx, qz, localY, radialDistance, animationTime, tornado.twist(), plumeBlend); + float shellMask = 1.0F - Mth.clamp(Math.abs(radialDistance - funnelRadius) / Math.max(shellWidth, 0.001F), 0.0F, 1.0F); + float spiralPhase = (float) Math.atan2(qz, qx) * SPIRAL_MODULATION_BANDS - localY * 2.15F - animationTime * SPIRAL_MODULATION_SCROLL; + float spiralRidge = 1.0F - Mth.abs(Mth.sin(spiralPhase)); + float spiralContrast = (spiralRidge - 0.45F) * SPIRAL_MODULATION_STRENGTH * shellMask * (1.0F - plumeBlend * 0.30F); + float bodyBreakup = shellBreakup * BODY_BREAKUP_STRENGTH; + float groundDust = computeGroundDust(y01, radialDistance, funnelRadius, animationTime, qx, qz); + + float mouthFade = smoothstep(0.0F, MOUTH_FADE_END, y01); + float topFade = 1.0F - smoothstep(TOP_FADE_START, 1.0F, y01); + float plumeBodyStrength = Mth.lerp(plumeBlend, BODY_DENSITY_STRENGTH, BODY_DENSITY_STRENGTH * PLUME_BODY_REDUCTION); + float density = Math.max(shell + shellBreakup + spiralContrast + helixNoise, Math.max(body * plumeBodyStrength + bodyBreakup, core)); + density = Math.max(density, groundDust); + return (density - DENSITY_BIAS) * mouthFade * topFade; + } + + private static Vec3 computePmCenterOffset(CloudSpaceTornado tornado, float localY, float y01, float percCos, float animationTime) { + float ropeMod = Mth.lerp(Mth.clamp((tornado.topRadius() - tornado.baseRadius()) / 3.5F, 0.0F, 1.0F), 3.0F, 1.0F); + float attachNoiseX = noise(tornado.centerX() * 0.004F, tornado.centerZ() * 0.004F, animationTime * PM_CENTER_SWAY_SCROLL) * 0.55F; + float attachNoiseZ = noise(animationTime * PM_CENTER_SWAY_SCROLL, tornado.centerZ() * 0.004F, tornado.centerX() * 0.004F) * 0.55F; + float attachmentX = attachNoiseX * tornado.topRadius() * PM_ATTACHMENT_STRENGTH * ropeMod; + float attachmentZ = attachNoiseZ * tornado.topRadius() * PM_ATTACHMENT_STRENGTH * ropeMod; + + float swayNoiseX = noise( + tornado.centerX() * 0.008F, + animationTime * PM_CENTER_SWAY_SCROLL + localY * PM_CENTER_SWAY_FREQ, + tornado.centerZ() * 0.008F + ); + float swayNoiseZ = noise( + animationTime * PM_CENTER_SWAY_SCROLL + localY * PM_CENTER_SWAY_FREQ, + tornado.centerZ() * 0.008F, + tornado.centerX() * 0.008F + ); + float swayScale = (float) Math.pow(y01, 0.75F) * tornado.baseRadius() * PM_CENTER_SWAY_STRENGTH * ropeMod; + float x = Mth.lerp(percCos, 0.0F, attachmentX) + swayNoiseX * swayScale; + float z = Mth.lerp(percCos, 0.0F, attachmentZ) + swayNoiseZ * swayScale; + return new Vec3(x, 0.0D, z); + } + + private static float pmHelixNoise(float qx, float qz, float localY, float radialDistance, float animationTime, float twist, float plumeBlend) { + float rotA = -twist * 3.0F; + float rotB = -twist / 1.5F; + float angleA = rotA + radialDistance / 1.65F; + float angleB = rotB + radialDistance / 4.80F; + float cosA = Mth.cos(angleA); + float sinA = Mth.sin(angleA); + float cosB = Mth.cos(angleB); + float sinB = Mth.sin(angleB); + + float ax = qx * cosA - qz * sinA; + float az = qx * sinA + qz * cosA; + float bx = qx * cosB - qz * sinB; + float bz = qx * sinB + qz * cosB; + + float n1 = fbm(ax * PM_HELIX_NOISE_SCALE_A, (localY - animationTime * 0.5F) * PM_HELIX_NOISE_SCALE_A, az * PM_HELIX_NOISE_SCALE_A); + float n2 = fbm(bx * PM_HELIX_NOISE_SCALE_B, (localY - animationTime * 0.5F) * PM_HELIX_NOISE_SCALE_B, bz * PM_HELIX_NOISE_SCALE_B); + return Mth.lerp(plumeBlend, n1, Mth.lerp(0.55F, n1, n2)) * PM_HELIX_NOISE_STRENGTH; + } + + private static float computeGroundDust(float y01, float radialDistance, float funnelRadius, float animationTime, float qx, float qz) { + if (y01 > PM_GROUND_DUST_HEIGHT) { + return -1.0F; + } + float dustBand = funnelRadius + 0.7F; + float dustWidth = 0.85F; + float dustMask = 1.0F - Mth.clamp(Math.abs(radialDistance - dustBand) / dustWidth, 0.0F, 1.0F); + float dustNoise = fbm(qx * 0.10F, y01 * 3.0F + animationTime * 0.15F, qz * 0.10F); + return (dustMask * (0.75F + dustNoise * 0.25F) * PM_GROUND_DUST_STRENGTH) - 0.08F; + } + + private void uploadBuffers() { + this.opaqueCount = this.opaqueInstances.size(); + this.transparentCount = this.transparentInstances.size(); + + if (this.opaqueCount > 0) { + int size = this.opaqueCount * OPAQUE_STRIDE_BYTES; + ByteBuffer buffer = this.prepareUploadBuffer(size, true); + for (OpaqueSideInstance instance : this.opaqueInstances) { + instance.write(buffer); + } + buffer.flip(); + this.opaqueSsbo.uploadData(buffer); + } + + if (this.transparentCount > 0) { + int size = this.transparentCount * TRANSPARENT_STRIDE_BYTES; + ByteBuffer buffer = this.prepareUploadBuffer(size, false); + for (TransparentCubeInstance instance : this.transparentInstances) { + instance.write(buffer); + } + buffer.flip(); + this.transparentSsbo.uploadData(buffer); + } + } + + private static boolean shouldDebugLog(ClientLevel level) { + return ProjectAtmosphere.DEBUG_MODE && level != null && level.getGameTime() % 20L == 0L; + } + + private static void debug(String message, Object... args) { + if (ProjectAtmosphere.DEBUG_MODE) { + ProjectAtmosphere.LOGGER.info("[TornadoDebug] " + message, args); + } + } + + private ByteBuffer prepareUploadBuffer(int size, boolean opaque) { + ByteBuffer current = opaque ? this.opaqueUploadBuffer : this.transparentUploadBuffer; + if (current == null || current.capacity() < size) { + if (current != null) { + MemoryUtil.memFree(current); + } + current = MemoryTracker.create(size).order(ByteOrder.nativeOrder()); + if (opaque) { + this.opaqueUploadBuffer = current; + } else { + this.transparentUploadBuffer = current; + } + } + current.clear(); + current.limit(size); + return current; + } + + private static float smoothstep(float edge0, float edge1, float value) { + float t = Mth.clamp((value - edge0) / (edge1 - edge0), 0.0F, 1.0F); + return t * t * (3.0F - 2.0F * t); + } + + private static float fbm(float x, float y, float z) { + float value = 0.0F; + float amplitude = 0.5F; + float frequency = 1.0F; + + for (int i = 0; i < 4; i++) { + value += noise(x * frequency, y * frequency, z * frequency) * amplitude; + frequency *= 2.0F; + amplitude *= 0.5F; + } + + return value; + } + + private static float noise(float x, float y, float z) { + int xi = Mth.floor(x); + int yi = Mth.floor(y); + int zi = Mth.floor(z); + + float xf = x - xi; + float yf = y - yi; + float zf = z - zi; + + float u = xf * xf * (3.0F - 2.0F * xf); + float v = yf * yf * (3.0F - 2.0F * yf); + float w = zf * zf * (3.0F - 2.0F * zf); + + float n000 = hash(xi, yi, zi); + float n100 = hash(xi + 1, yi, zi); + float n010 = hash(xi, yi + 1, zi); + float n110 = hash(xi + 1, yi + 1, zi); + float n001 = hash(xi, yi, zi + 1); + float n101 = hash(xi + 1, yi, zi + 1); + float n011 = hash(xi, yi + 1, zi + 1); + float n111 = hash(xi + 1, yi + 1, zi + 1); + + float x00 = Mth.lerp(u, n000, n100); + float x10 = Mth.lerp(u, n010, n110); + float x01 = Mth.lerp(u, n001, n101); + float x11 = Mth.lerp(u, n011, n111); + float y0 = Mth.lerp(v, x00, x10); + float y1 = Mth.lerp(v, x01, x11); + return Mth.lerp(w, y0, y1); + } + + private static float hash(int x, int y, int z) { + int h = x * 374761393 + y * 668265263 + z * 2147483647; + h = (h ^ (h >>> 13)) * 1274126177; + h ^= (h >>> 16); + return (h & 0x7FFFFFFF) / (float) Integer.MAX_VALUE; + } + + private static float computeFunnelRadius(CloudSpaceTornado tornado, float y01) { + float curve = (float) Math.pow(y01, 0.62F); + float radius = Mth.lerp(curve, tornado.baseRadius(), tornado.topRadius()); + float shoulder = (float) Math.pow(y01, 1.45F) * tornado.topRadius() * 0.28F; + float ropePinch = (1.0F - smoothstep(0.0F, 0.22F, y01)) * tornado.baseRadius() * 0.18F; + radius = radius + shoulder - ropePinch; + float plumeBlend = smoothstep(PLUME_START, 1.0F, y01); + float plumeRadius = radius * TOP_FLARE_FACTOR + tornado.topRadius() * PLUME_RADIUS_GAIN * plumeBlend; + return Mth.lerp(plumeBlend, radius, plumeRadius); + } + + private record CellKey(int x, int y, int z) { + } + + private record CloudSpaceTornado(float centerX, float centerZ, float bottomY, + float height, float baseRadius, float topRadius, float twist) { + static CloudSpaceTornado from(ClientLevel level, TornadoInstance tornado) { + float scale = SimpleCloudsConstants.CLOUD_SCALE; + float cloudHeight = CloudManager.get(level).getCloudHeight(); + float centerX = (float) tornado.position.x / scale; + float centerZ = (float) tornado.position.z / scale; + float bottomY = (tornado.getVisualBottomY() - cloudHeight) / scale; + float baseHeight = tornado.getVisualHeight() / scale; + float minBlendTop = (CLOUD_BLEND_HEIGHT_ABOVE_BASE_WORLD / scale) - bottomY; + float height = Math.max(baseHeight, minBlendTop); + float canopyRadius = Math.max(tornado.radius, MIN_VISUAL_WORLD_RADIUS) / scale; + float groundRadius = Math.max(MIN_GROUND_RADIUS_CLOUD, canopyRadius * GROUND_RADIUS_FACTOR); + float topRadius = Math.max(MIN_TOP_RADIUS_CLOUD, Math.max(groundRadius * 2.0F, canopyRadius * TOP_RADIUS_FACTOR)); + return new CloudSpaceTornado(centerX, centerZ, bottomY, height, groundRadius, topRadius, tornado.getTwist()); + } + } + + private record OpaqueSideInstance(int side, float x, float y, float z, float brightness, float radius) { + void write(ByteBuffer buffer) { + buffer.putInt(this.side); + buffer.putFloat(this.x); + buffer.putFloat(this.y); + buffer.putFloat(this.z); + buffer.putFloat(this.brightness); + buffer.putFloat(this.radius); + } + } + + private record TransparentCubeInstance(float x, float y, float z, float brightness, float alpha, float radius) { + void write(ByteBuffer buffer) { + buffer.putFloat(this.x); + buffer.putFloat(this.y); + buffer.putFloat(this.z); + buffer.putFloat(this.brightness); + buffer.putFloat(this.alpha); + buffer.putFloat(this.radius); + } + } +} diff --git a/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoInstance.java b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoInstance.java new file mode 100644 index 00000000..91978536 --- /dev/null +++ b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoInstance.java @@ -0,0 +1,840 @@ +package net.Gabou.projectatmosphere.modules.tornado; + +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; +import net.Gabou.projectatmosphere.api.common.cloud.region.ITornadoRegion; +import net.Gabou.projectatmosphere.api.common.cloud.region.TornadoDescriptor; +import net.Gabou.projectatmosphere.manager.ForecastOrchestrator; +import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.Gabou.projectatmosphere.modules.weather.StormLifecyclePhase; +import net.Gabou.projectatmosphere.modules.weather.StormMotionModel; +import net.Gabou.projectatmosphere.util.AsyncAtmosphereService; +import net.Gabou.projectatmosphere.util.AtmosphereUtils; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.tags.BlockTags; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + +public class TornadoInstance { + private static final int DEBRIS_RANGE_EXTENSION = 5; + public static final double AMBIENT_WIND_INFLUENCE_EXTENSION = 15.0D; + public static final double WIND_SPEED_SCALING_FACTOR = 0.05D; + public static final double WIND_EFFECT_VERTICAL_MAX_OFFSET = 50.0D; + public static final double WIND_EFFECT_VERTICAL_MIN_OFFSET = -5.0D; + private static final int FLOW_FIELD_INTERVAL_TICKS = 2; + private static final int DEMOLITION_INTERVAL_TICKS = 20; + private static final float MIN_EFFECTIVE_WIND = 73.0F; + private static final float MAX_EFFECTIVE_WIND = 260.0F; + private static final float RANKINE_FACTOR = 4.5F; + private static final float OUTER_FLOW_RADIUS_FACTOR = 1.24F; + private static final float MID_SHELL_CENTER = 0.44F; + private static final float MID_SHELL_WIDTH = 0.24F; + private static final float CORE_ZONE_END = 0.22F; + private static final float PLUME_FLOW_START = 0.70F; + private static final float EJECTION_START_HEIGHT = 0.84F; + private static final float CAPTURE_ENTRY_RADIUS = 0.84F; + + private final UUID id; + public Vec3 position; + public float radius; + public WindVector wind; + private final float maxRadius; + private final float targetVisualHeight; + private float visualBottomY; + private float visualHeight; + private float angularSpeed; + private float normalizedIntensity; + private float targetIntensity; + private float headingRadians; + private Vec3 motion = Vec3.ZERO; + private boolean descriptorMissing; + private int ageTicks; + private int phaseTicks; + private int formationTicks; + private int activeTicks; + private int dissipationTicks; + private long spawnGameTime; + private long lastAmbientWindTick = Long.MIN_VALUE; + private long lastDemolitionTick = Long.MIN_VALUE; + private float anchorX; + private float anchorZ; + private StormLifecyclePhase phase; + private float recentDebrisScore; + private final Map capturedEntities = new HashMap<>(); + + @Nullable + private CloudRegion cloudRegion; + + public TornadoInstance(Vec3 position, float radius, WindVector wind, @Nullable CloudRegion cloudRegion) { + this(UUID.randomUUID(), position, radius, wind, 0.05F, (float) position.y, Math.max(96.0F, radius * 12.0F), cloudRegion); + } + + public TornadoInstance(UUID id, Vec3 position, float radius, WindVector wind, + float visualBottomY, float visualHeight, @Nullable CloudRegion cloudRegion) { + this(id, position, radius, wind, 0.05F, visualBottomY, visualHeight, cloudRegion); + } + + public TornadoInstance(UUID id, Vec3 position, float radius, WindVector wind, float angularSpeed, + float visualBottomY, float visualHeight, @Nullable CloudRegion cloudRegion) { + this.id = id; + this.position = position; + this.radius = radius; + this.maxRadius = radius; + this.wind = wind; + this.angularSpeed = angularSpeed; + this.visualBottomY = visualBottomY; + this.visualHeight = visualHeight; + this.targetVisualHeight = Math.max(visualHeight, 32.0F); + this.cloudRegion = cloudRegion; + this.anchorX = (float) position.x; + this.anchorZ = (float) position.z; + this.phase = StormLifecyclePhase.FORMING; + this.targetIntensity = defaultTargetIntensity(radius, wind); + this.normalizedIntensity = Math.max(0.18F, this.targetIntensity * 0.35F); + this.headingRadians = wind.angleRadians(); + this.formationTicks = 120 + Mth.floor(this.targetIntensity * 120.0F); + this.activeTicks = 1200 + Mth.floor(this.targetIntensity * 1800.0F); + this.dissipationTicks = 160 + Mth.floor(this.targetIntensity * 180.0F); + this.applyIntensityToVisuals(); + } + + public UUID getId() { + return this.id; + } + + @Nullable + public CloudRegion getCloudRegion() { + return this.cloudRegion; + } + + public void setCloudRegion(@Nullable CloudRegion cloudRegion) { + this.cloudRegion = cloudRegion; + } + + public float getVisualBottomY() { + return this.visualBottomY; + } + + public float getVisualHeight() { + return this.visualHeight; + } + + public float getNormalizedIntensity() { + return this.normalizedIntensity; + } + + public StormLifecyclePhase getPhase() { + return this.phase; + } + + public float getRecentDebrisScore() { + return this.recentDebrisScore; + } + + public TornadoLevel getLevel() { + return TornadoLevel.fromWindSpeed(this.getEffectiveWindSpeed()); + } + + public double getSuctionRadius() { + return this.radius + this.getLevel().getBaseDamage() * 1.2D; + } + + public double getDamageMultiplier() { + return this.getLevel().getBaseDamage() * Math.max(0.3D, this.normalizedIntensity); + } + + public float getLifetimeSeconds() { + return this.ageTicks / 20.0F; + } + + public float getTwist() { + float elapsedTicks = this.ageTicks; + return Mth.clamp(elapsedTicks * this.angularSpeed * 0.05F, 0.5F, 5.0F); + } + + public boolean isDescriptorMissing() { + return this.descriptorMissing; + } + + public void markDissipating() { + if (this.phase == StormLifecyclePhase.DISSIPATED || this.phase == StormLifecyclePhase.DISSIPATING) { + return; + } + this.phase = StormLifecyclePhase.DISSIPATING; + this.phaseTicks = 0; + } + + public boolean isDead() { + return this.phase.isTerminal(); + } + + public void tickServer(ServerLevel level, long gameTime) { + this.ageTicks++; + this.phaseTicks++; + if (this.spawnGameTime == 0L) { + this.spawnGameTime = gameTime; + } + + WindVector sampledWind = ForecastOrchestrator.getWind(level, BlockPos.containing(this.position), gameTime); + this.wind = sampledWind; + this.recentDebrisScore = Math.max(0.0F, this.recentDebrisScore - 0.015F); + this.pruneCapturedEntities(level); + this.tickLifecycle(); + this.updateMovement(gameTime); + this.pushStateToDescriptor(); + + if (this.phase == StormLifecyclePhase.ACTIVE || this.phase == StormLifecyclePhase.DISSIPATING) { + if (gameTime - this.lastAmbientWindTick >= FLOW_FIELD_INTERVAL_TICKS) { + this.lastAmbientWindTick = gameTime; + this.applyAmbientWind(level); + } + if (gameTime - this.lastDemolitionTick >= DEMOLITION_INTERVAL_TICKS && this.normalizedIntensity >= 0.35F) { + this.lastDemolitionTick = gameTime; + AsyncAtmosphereService.runStorm(() -> { + try { + this.demolishBlocks(level); + level.getServer().execute(() -> this.playDemolitionSound(level)); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + } + } + + public void tickClient() { + this.ageTicks++; + } + + public TornadoSnapshot snapshot() { + return new TornadoSnapshot( + this.id, + this.position, + this.radius, + this.visualBottomY, + this.visualHeight, + this.wind.baseSpeed(), + this.wind.angleRadians(), + this.wind.gustSpeed(), + this.normalizedIntensity, + this.recentDebrisScore, + this.phase + ); + } + + public void applySnapshot(TornadoSnapshot snapshot, @Nullable CloudRegion region) { + this.position = snapshot.position(); + this.radius = snapshot.radius(); + this.visualBottomY = snapshot.visualBottomY(); + this.visualHeight = snapshot.visualHeight(); + this.wind = new WindVector(snapshot.windSpeed(), snapshot.windAngle(), snapshot.windGust()); + this.normalizedIntensity = snapshot.normalizedIntensity(); + this.recentDebrisScore = snapshot.recentDebrisScore(); + this.phase = snapshot.phase(); + this.cloudRegion = region; + this.anchorX = (float) this.position.x; + this.anchorZ = (float) this.position.z; + } + + public boolean synchronizeWithDescriptor() { + TornadoDescriptor descriptor = this.findDescriptor(); + if (descriptor == null) { + this.descriptorMissing = this.cloudRegion instanceof ITornadoRegion; + return false; + } + + this.descriptorMissing = false; + this.visualBottomY = descriptor.getBottomY(); + this.visualHeight = descriptor.getHeight(); + this.radius = descriptor.getRadius(); + this.position = new Vec3( + this.cloudRegion.getWorldX() + descriptor.getOffsetX(), + descriptor.getBottomY(), + this.cloudRegion.getWorldZ() + descriptor.getOffsetZ() + ); + return true; + } + + public void advanceByWind() { + Vec3 updated = StormMotionModel.advanceTornado( + this.id, + this.position, + this.motion, + this.wind, + Math.max(this.normalizedIntensity, 0.25F), + this.ageTicks, + this.anchorX, + this.anchorZ + ); + this.motion = updated.subtract(this.position); + this.position = new Vec3(updated.x, this.visualBottomY, updated.z); + } + + private void tickLifecycle() { + switch (this.phase) { + case FORMING -> { + float rate = 1.0F / Math.max(1, this.formationTicks); + this.normalizedIntensity = Math.min(this.targetIntensity, this.normalizedIntensity + rate); + if (this.phaseTicks >= this.formationTicks || this.normalizedIntensity >= this.targetIntensity - 0.02F) { + this.phase = StormLifecyclePhase.ACTIVE; + this.phaseTicks = 0; + } + } + case ACTIVE -> { + this.normalizedIntensity = Mth.lerp(0.06F, this.normalizedIntensity, this.targetIntensity); + if (this.phaseTicks >= this.activeTicks) { + this.phase = StormLifecyclePhase.DISSIPATING; + this.phaseTicks = 0; + } + } + case DISSIPATING -> { + float rate = 1.0F / Math.max(1, this.dissipationTicks); + this.normalizedIntensity = Math.max(0.0F, this.normalizedIntensity - rate); + if (this.phaseTicks >= this.dissipationTicks || this.normalizedIntensity <= 0.02F) { + this.phase = StormLifecyclePhase.DISSIPATED; + this.normalizedIntensity = 0.0F; + } + } + case DISSIPATED -> this.normalizedIntensity = 0.0F; + } + this.applyIntensityToVisuals(); + } + + private void applyIntensityToVisuals() { + float growth = Mth.clamp(this.normalizedIntensity, 0.0F, 1.0F); + this.radius = Mth.lerp(growth, this.maxRadius * 0.32F, this.maxRadius); + this.visualHeight = Mth.lerp(growth, this.targetVisualHeight * 0.35F, this.targetVisualHeight); + this.angularSpeed = 0.08F + growth * 0.16F; + } + + private void updateMovement(long gameTime) { + Vec3 updated = StormMotionModel.advanceTornado( + this.id, + this.position, + this.motion, + this.wind, + Math.max(this.normalizedIntensity, 0.08F), + gameTime, + this.anchorX, + this.anchorZ + ); + this.motion = updated.subtract(this.position); + this.headingRadians = (float) Math.atan2(this.motion.z, this.motion.x); + this.position = new Vec3(updated.x, this.visualBottomY, updated.z); + } + + private void pushStateToDescriptor() { + TornadoDescriptor descriptor = this.findDescriptor(); + if (descriptor == null) { + this.descriptorMissing = this.cloudRegion instanceof ITornadoRegion; + return; + } + this.descriptorMissing = false; + descriptor.setBottomY(this.visualBottomY); + descriptor.setHeight(this.visualHeight); + descriptor.setRadius(this.radius); + descriptor.setVelocityX((float) this.motion.x); + descriptor.setVelocityZ((float) this.motion.z); + if (this.cloudRegion != null) { + descriptor.setOffsetX((float) (this.position.x - this.cloudRegion.getWorldX())); + descriptor.setOffsetZ((float) (this.position.z - this.cloudRegion.getWorldZ())); + } + } + + private void applyAmbientWind(Level level) { + if (!(level instanceof ServerLevel serverLevel)) { + return; + } + + double influence = this.getWindfieldWidth() * 1.25D + AMBIENT_WIND_INFLUENCE_EXTENSION; + double minY = this.position.y + WIND_EFFECT_VERTICAL_MIN_OFFSET; + double maxY = this.position.y + 85.0D; + AABB box = new AABB( + this.position.x - influence, minY, + this.position.z - influence, this.position.x + influence, + maxY, this.position.z + influence + ); + + for (Entity entity : serverLevel.getEntities(null, box)) { + if (entity instanceof Player player && (player.isCreative() || player.isSpectator())) { + continue; + } + this.pullEntity(entity, entity instanceof Player ? 2.3F : 1.6F); + } + } + + private void demolishBlocks(ServerLevel level) { + BlockPos center = BlockPos.containing(this.position); + int intRadius = Mth.ceil(this.radius); + double outerSq = (this.radius + 5.0F) * (this.radius + 5.0F); + double innerSq = this.radius * this.radius; + double band = Math.max(1.0, outerSq - innerSq); + double invBand = 1.0 / band; + BlockPos min = center.offset(-intRadius - DEBRIS_RANGE_EXTENSION, 0, -intRadius - DEBRIS_RANGE_EXTENSION); + BlockPos max = center.offset(intRadius + DEBRIS_RANGE_EXTENSION, 8 + intRadius, intRadius + DEBRIS_RANGE_EXTENSION); + + RandomSource random = RandomSource.create(this.id.getLeastSignificantBits() ^ this.ageTicks); + it.unimi.dsi.fastutil.longs.LongArrayList toDestroy = new it.unimi.dsi.fastutil.longs.LongArrayList(2048); + it.unimi.dsi.fastutil.longs.LongArrayList toDestroyGlass = new it.unimi.dsi.fastutil.longs.LongArrayList(2048); + it.unimi.dsi.fastutil.longs.LongArrayList toDestroyWeak = new it.unimi.dsi.fastutil.longs.LongArrayList(1024); + it.unimi.dsi.fastutil.longs.LongArrayList toScourGrass = new it.unimi.dsi.fastutil.longs.LongArrayList(1024); + + for (BlockPos pos : BlockPos.betweenClosed(min, max)) { + if (!level.isLoaded(pos)) { + continue; + } + try { + LevelChunk chunk = level.getChunkSource().getChunk(pos.getX() >> 4, pos.getZ() >> 4, false); + if (chunk == null) { + continue; + } + + BlockState state = chunk.getBlockState(pos); + if (state.isAir()) { + continue; + } + float windEffect = this.getWindEffect(pos.getCenter()); + if (windEffect < 40.0F) { + continue; + } + double distSq = pos.distSqr(center); + if (state.is(BlockTags.LEAVES) || state.is(BlockTags.LOGS)) { + toDestroy.add(pos.asLong()); + } else if (AtmosphereUtils.isGlass(state)) { + if (distSq > outerSq) { + continue; + } + + float pMax = 0.15F + this.normalizedIntensity * 0.35F; + double t = Mth.clamp((outerSq - distSq) * invBand, 0.0, 1.0); + float p = (float) (t * pMax); + if (random.nextFloat() < p) { + toDestroyGlass.add(pos.asLong()); + } + } else if (state.getFluidState().isEmpty()) { + if (state.is(Blocks.GRASS_BLOCK)) { + float scourChance = Mth.clamp((windEffect - 48.0F) / 110.0F, 0.0F, 1.0F) * (0.08F + this.normalizedIntensity * 0.18F); + if (random.nextFloat() < scourChance) { + toScourGrass.add(pos.asLong()); + } + } + float destroySpeed = state.getDestroySpeed(level, pos); + if (destroySpeed >= 0.0F && destroySpeed <= 1.0F) { + float chance = Mth.clamp((windEffect - 85.0F) / 90.0F, 0.0F, 1.0F) * (0.03F + this.normalizedIntensity * 0.08F); + if (random.nextFloat() < chance) { + toDestroyWeak.add(pos.asLong()); + } + } + } + } catch (Throwable ignored) { + } + } + + int perTick = 256; + this.destroyCursor = 0; + this.weakDestroyCursor = 0; + if (!toDestroy.isEmpty()) { + AsyncAtmosphereService.runOnMainThread(() -> this.processLeafLogDestruction(level, toDestroy, perTick)); + } + if (!toDestroyWeak.isEmpty()) { + AsyncAtmosphereService.runOnMainThread(() -> this.processWeakDestruction(level, toDestroyWeak, perTick)); + } + if (!toScourGrass.isEmpty()) { + AsyncAtmosphereService.runOnMainThread(() -> this.processGrassScouring(level, toScourGrass, perTick)); + } + if (!toDestroyGlass.isEmpty()) { + GlassDamageManager.damageGlass(level, toDestroyGlass); + } + float debrisGain = Math.min(1.0F, + toDestroy.size() * 0.012F + + toDestroyWeak.size() * 0.020F + + toScourGrass.size() * 0.010F + + toDestroyGlass.size() * 0.028F); + this.recentDebrisScore = Mth.clamp(this.recentDebrisScore + debrisGain, 0.0F, 1.0F); + } + + private int destroyCursor = 0; + private int weakDestroyCursor = 0; + private int grassScourCursor = 0; + + private void processLeafLogDestruction(ServerLevel level, + it.unimi.dsi.fastutil.longs.LongArrayList list, + int perTick) { + if (this.destroyCursor >= list.size()) { + this.destroyCursor = 0; + return; + } + + int end = Math.min(this.destroyCursor + perTick, list.size()); + for (int i = this.destroyCursor; i < end; i++) { + BlockPos pos = BlockPos.of(list.getLong(i)); + if (!level.isLoaded(pos)) { + continue; + } + + BlockState state = level.getBlockState(pos); + if (!(state.is(BlockTags.LEAVES) || state.is(BlockTags.LOGS))) { + continue; + } + level.destroyBlock(pos, false); + } + + this.destroyCursor = end; + if (this.destroyCursor < list.size()) { + level.getServer().execute(() -> this.processLeafLogDestruction(level, list, perTick)); + } else { + this.destroyCursor = 0; + } + } + + private void processWeakDestruction(ServerLevel level, + it.unimi.dsi.fastutil.longs.LongArrayList list, + int perTick) { + if (this.weakDestroyCursor >= list.size()) { + this.weakDestroyCursor = 0; + return; + } + + int end = Math.min(this.weakDestroyCursor + perTick, list.size()); + for (int i = this.weakDestroyCursor; i < end; i++) { + BlockPos pos = BlockPos.of(list.getLong(i)); + if (!level.isLoaded(pos)) { + continue; + } + + BlockState state = level.getBlockState(pos); + if (state.isAir() || !state.getFluidState().isEmpty()) { + continue; + } + float destroySpeed = state.getDestroySpeed(level, pos); + if (destroySpeed < 0.0F || destroySpeed > 1.0F) { + continue; + } + level.destroyBlock(pos, false); + } + + this.weakDestroyCursor = end; + if (this.weakDestroyCursor < list.size()) { + level.getServer().execute(() -> this.processWeakDestruction(level, list, perTick)); + } else { + this.weakDestroyCursor = 0; + } + } + + private void processGrassScouring(ServerLevel level, + it.unimi.dsi.fastutil.longs.LongArrayList list, + int perTick) { + if (this.grassScourCursor >= list.size()) { + this.grassScourCursor = 0; + return; + } + + int end = Math.min(this.grassScourCursor + perTick, list.size()); + for (int i = this.grassScourCursor; i < end; i++) { + BlockPos pos = BlockPos.of(list.getLong(i)); + if (!level.isLoaded(pos)) { + continue; + } + + BlockState state = level.getBlockState(pos); + if (state.is(Blocks.GRASS_BLOCK)) { + level.setBlockAndUpdate(pos, Blocks.DIRT.defaultBlockState()); + } + } + + this.grassScourCursor = end; + if (this.grassScourCursor < list.size()) { + level.getServer().execute(() -> this.processGrassScouring(level, list, perTick)); + } else { + this.grassScourCursor = 0; + } + } + + private void playDemolitionSound(Level level) { + BlockPos center = BlockPos.containing(this.position); + level.playLocalSound( + center.getX(), center.getY(), center.getZ(), + SoundEvents.GENERIC_EXPLODE, + SoundSource.WEATHER, + 1.0F + this.normalizedIntensity, + 0.6F + level.getRandom().nextFloat() * 0.3F, + false + ); + } + + @Nullable + private TornadoDescriptor findDescriptor() { + if (!(this.cloudRegion instanceof ITornadoRegion tornadoRegion)) { + return null; + } + return tornadoRegion.findTornado(this.id); + } + + private static float defaultTargetIntensity(float radius, WindVector wind) { + float radiusFactor = Mth.clamp((radius - 5.0F) / 20.0F, 0.0F, 1.0F); + float windFactor = Mth.clamp((wind.baseSpeed() - 12.0F) / 28.0F, 0.0F, 1.0F); + return Mth.clamp(0.35F + radiusFactor * 0.4F + windFactor * 0.25F, 0.25F, 1.0F); + } + + private float getWindfieldWidth() { + return Math.max(this.radius * 1.8F, 28.0F); + } + + private float getRankine(double dist, float windfieldWidth) { + float rankineWidth = windfieldWidth / RANKINE_FACTOR; + float perc = 0.0F; + if (dist <= rankineWidth * 0.5F) { + perc = (float) dist / (rankineWidth * 0.5F); + } else if (dist <= windfieldWidth * 2.0F) { + double denom = ((windfieldWidth * 2.0F - rankineWidth) / 2.0F); + perc = Mth.clamp((float) Math.pow(1.0F - (dist - rankineWidth * 0.5F) / denom, 1.5D), 0.0F, 1.0F); + } + return Float.isNaN(perc) ? 0.0F : perc; + } + + private float getWindEffect(Vec3 pos) { + float windfieldWidth = this.getWindfieldWidth(); + Vec3 flatCenter = new Vec3(this.position.x, 0.0D, this.position.z); + Vec3 flatPos = new Vec3(pos.x, 0.0D, pos.z); + double dist = flatCenter.distanceTo(flatPos); + if (dist > windfieldWidth * 2.0F) { + return 0.0F; + } + + float perc = this.getRankine(dist, windfieldWidth); + float affectPerc = (float) Math.sqrt(Math.max(0.0D, 1.0D - dist / (windfieldWidth * 2.0F))); + Vec3 relativePos = pos.subtract(this.position); + Vec3 rotational = new Vec3(relativePos.z, 0.0D, -relativePos.x); + if (rotational.lengthSqr() > 1.0E-4) { + rotational = rotational.normalize(); + } else { + rotational = Vec3.ZERO; + } + + float noise = 1.0F + StormMotionModel.noiseSigned(this.id, this.ageTicks + Mth.floor(dist * 3.0D), 0.035F) * 0.08F; + double realWind = this.getEffectiveWindSpeed() * noise; + Vec3 localMotion = rotational.scale(realWind * perc * 0.08D); + localMotion = localMotion.add(this.motion.scale(10.0D * affectPerc)); + return (float) localMotion.length(); + } + + private void pullEntity(Entity entity, float multiplier) { + float windfieldWidth = this.getWindfieldWidth(); + CapturedEntityState captured = this.capturedEntities.get(entity.getId()); + int worldHeight = entity.level().getHeight( + net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING, + entity.blockPosition().getX(), + entity.blockPosition().getZ() + ); + if (worldHeight > entity.blockPosition().getY()) { + return; + } + + Vec3 targetPos = new Vec3(this.position.x, entity.position().y, this.position.z); + double dist = entity.position().distanceTo(targetPos); + double maxInfluenceDistance = windfieldWidth * (captured != null ? 1.28D : OUTER_FLOW_RADIUS_FACTOR); + if (dist > maxInfluenceDistance) { + return; + } + + Vec3 relativePos = entity.position().subtract(this.position); + double heightDifference = entity.position().y - this.position.y; + if (Math.abs(heightDifference) > 150.0D) { + return; + } + + Vec3 inward = new Vec3(-relativePos.x, 0.0D, -relativePos.z); + if (inward.lengthSqr() > 1.0E-4) { + inward = inward.normalize(); + } else { + inward = Vec3.ZERO; + } + + Vec3 rotational = new Vec3(relativePos.z, 0.0D, -relativePos.x); + if (rotational.lengthSqr() > 1.0E-4) { + rotational = rotational.normalize(); + } else { + rotational = Vec3.ZERO; + } + + double windEffect = this.getWindEffect(entity.position()); + if (windEffect < (captured != null ? 16.0D : 24.0D)) { + return; + } + + float radialNorm = Mth.clamp((float) (dist / Math.max(windfieldWidth, 0.001F)), 0.0F, 1.35F); + float heightNorm = Mth.clamp((float) ((entity.getY() - this.visualBottomY) / Math.max(this.visualHeight, 1.0F)), 0.0F, 1.15F); + float outerReach = 1.0F - smoothStep(0.68F, 1.10F, radialNorm); + float shellZone = 1.0F - Mth.clamp(Math.abs(radialNorm - MID_SHELL_CENTER) / MID_SHELL_WIDTH, 0.0F, 1.0F); + float coreZone = 1.0F - smoothStep(0.05F, CORE_ZONE_END, radialNorm); + float plumeZone = smoothStep(PLUME_FLOW_START, 0.98F, heightNorm); + boolean shouldCapture = dist <= windfieldWidth * CAPTURE_ENTRY_RADIUS + || (shellZone > 0.42F && windEffect >= 58.0D) + || (coreZone > 0.45F && windEffect >= 48.0D); + if (captured == null && shouldCapture) { + captured = this.createCaptureState(entity, windfieldWidth, dist); + this.capturedEntities.put(entity.getId(), captured); + } + + double effectStrength = Mth.clamp( + (float) ((windEffect - 25.0D) / Math.max(this.getEffectiveWindSpeed() * 1.15F, 130.0F)), + 0.0F, + 1.0F + ) * multiplier * (1.02D + this.normalizedIntensity * 1.32D); + + Vec3 add; + boolean releaseCapture = false; + if (captured != null) { + captured.lastSeenAge = this.ageTicks; + captured.captureTicks++; + float captureProgress = smoothStep(3.0F, 16.0F, captured.captureTicks); + float transportProgress = smoothStep(12.0F, 46.0F, captured.captureTicks); + if (!captured.expelling && (heightNorm >= EJECTION_START_HEIGHT || captured.captureTicks > 120)) { + captured.expelling = true; + } + float desiredBand = Mth.clamp( + Mth.lerp(transportProgress, 0.20F + shellZone * 0.04F, Mth.lerp(plumeZone, 0.42F, 0.58F + plumeZone * 0.12F)), + 0.16F, + 0.72F + ); + captured.radialBand = Mth.lerp(0.22F, captured.radialBand, desiredBand); + captured.orbitAngle += (0.22F + this.normalizedIntensity * 0.20F + shellZone * 0.24F + transportProgress * 0.18F) + * (1.0F - plumeZone * 0.22F) + * captured.orbitBias; + + double desiredRadius = windfieldWidth * captured.radialBand; + double desiredHeight = this.visualBottomY + this.visualHeight * Mth.clamp( + heightNorm + 0.02F + captured.liftBias * 0.04F + plumeZone * 0.05F + captureProgress * 0.08F + transportProgress * 0.14F, + 0.04F, + 1.04F + ); + Vec3 orbitTarget = new Vec3( + Math.cos(captured.orbitAngle) * desiredRadius, + desiredHeight, + Math.sin(captured.orbitAngle) * desiredRadius + ).add(this.position.x, 0.0D, this.position.z); + Vec3 towardOrbit = orbitTarget.subtract(entity.position()); + Vec3 orbitCorrection = towardOrbit.lengthSqr() > 1.0E-4 ? towardOrbit.normalize() : Vec3.ZERO; + double inwardStrength = captured.expelling + ? -0.16D + : Mth.lerp(captureProgress, 2.15D + outerReach * 0.95D + shellZone * 0.44D, 0.38D + shellZone * 0.32D - coreZone * 0.05D); + double liftStrength = captured.expelling + ? effectStrength * (0.34D + plumeZone * 0.22D) + : (0.01D + captured.liftBias * 0.08D + plumeZone * 0.08D + captureProgress * 0.10D + transportProgress * 0.42D) * effectStrength; + double tangentialStrength = captured.expelling + ? effectStrength * (0.72D + plumeZone * 0.22D) + : effectStrength * (0.42D + shellZone * 1.65D + captured.orbitBias * 0.48D + transportProgress * 1.28D); + double ejectFactor = captured.expelling + ? 1.35D + plumeZone * 0.78D + : 0.0D; + Vec3 outward = inward.scale(-1.0D); + + add = orbitCorrection.scale(effectStrength * 1.15D) + .add(rotational.scale(tangentialStrength)) + .add(inward.scale(effectStrength * inwardStrength)) + .add(outward.scale(effectStrength * ejectFactor)) + .add(0.0D, liftStrength, 0.0D) + .scale(captured.expelling ? 0.115D : 0.125D); + releaseCapture = captured.expelling && (heightNorm > 1.04F || dist > windfieldWidth * 1.26F); + } else { + double suctionStrength = 1.95D + outerReach * 2.85D + shellZone * 0.28D - coreZone * 0.10D; + double tangentialStrength = 0.08D + shellZone * 0.34D + plumeZone * 0.06D; + double liftStrength = 0.0D + shellZone * 0.02D + plumeZone * 0.04D; + + add = inward.scale(effectStrength * suctionStrength) + .add(rotational.scale(effectStrength * tangentialStrength)) + .add(0.0D, effectStrength * liftStrength, 0.0D) + .scale(0.082D + this.normalizedIntensity * 0.028D); + } + if (captured != null) { + Vec3 current = entity.getDeltaMovement(); + Vec3 damped = captured.expelling + ? new Vec3(current.x * 0.72D, Math.max(current.y * 0.78D, -0.08D), current.z * 0.72D) + : new Vec3(current.x * 0.38D, Math.max(current.y * 0.58D, -0.08D), current.z * 0.38D); + entity.setDeltaMovement(damped.add(add)); + } else { + entity.addDeltaMovement(add); + } + if (releaseCapture) { + this.capturedEntities.remove(entity.getId()); + } + if (entity instanceof Player) { + entity.hurtMarked = true; + } + if (entity.getDeltaMovement().y > -0.25D) { + entity.fallDistance = 0.0F; + } + } + + private CapturedEntityState createCaptureState(Entity entity, float windfieldWidth, double dist) { + double angle = Math.atan2(entity.getZ() - this.position.z, entity.getX() - this.position.x); + float band = Mth.clamp((float) (dist / Math.max(windfieldWidth, 0.001F)), 0.22F, 0.48F); + float orbitBias = 0.85F + (float) StormMotionModel.noise01(this.id, this.ageTicks + entity.getId(), 0.11F) * 0.65F; + float liftBias = 0.45F + (float) StormMotionModel.noise01(this.id, this.ageTicks + entity.getId() * 3L, 0.07F) * 0.55F; + return new CapturedEntityState(angle, band, orbitBias, liftBias, this.ageTicks, 0); + } + + private void pruneCapturedEntities(ServerLevel level) { + Iterator> iterator = this.capturedEntities.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + Entity entity = level.getEntity(entry.getKey()); + if (entity == null || !entity.isAlive()) { + iterator.remove(); + continue; + } + CapturedEntityState state = entry.getValue(); + if ((this.ageTicks - state.lastSeenAge) > 22) { + iterator.remove(); + } + } + } + + private float getEffectiveWindSpeed() { + return Mth.lerp(this.normalizedIntensity, MIN_EFFECTIVE_WIND, MAX_EFFECTIVE_WIND); + } + + private static final class CapturedEntityState { + private double orbitAngle; + private float radialBand; + private final float orbitBias; + private final float liftBias; + private int lastSeenAge; + private int captureTicks; + private boolean expelling; + + private CapturedEntityState(double orbitAngle, float radialBand, float orbitBias, float liftBias, int lastSeenAge, int captureTicks) { + this.orbitAngle = orbitAngle; + this.radialBand = radialBand; + this.orbitBias = orbitBias; + this.liftBias = liftBias; + this.lastSeenAge = lastSeenAge; + this.captureTicks = captureTicks; + this.expelling = false; + } + } + + private static float smoothStep(float edge0, float edge1, float value) { + if (edge0 == edge1) { + return value < edge0 ? 0.0F : 1.0F; + } + float t = Mth.clamp((value - edge0) / (edge1 - edge0), 0.0F, 1.0F); + return t * t * (3.0F - 2.0F * t); + } +} diff --git a/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoManager.java b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoManager.java new file mode 100644 index 00000000..c47f9ba0 --- /dev/null +++ b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoManager.java @@ -0,0 +1,299 @@ +package net.Gabou.projectatmosphere.modules.tornado; + +import dev.nonamecrackers2.simpleclouds.common.cloud.region.CloudRegion; +import dev.nonamecrackers2.simpleclouds.common.world.CloudManager; +import dev.nonamecrackers2.simpleclouds.common.world.SpawnRegion; +import net.Gabou.projectatmosphere.ProjectAtmosphere; +import net.Gabou.projectatmosphere.api.common.cloud.region.ITornadoRegion; +import net.Gabou.projectatmosphere.api.common.cloud.region.TornadoDescriptor; +import net.Gabou.projectatmosphere.config.AtmoCommonConfig; +import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.Gabou.projectatmosphere.network.NetworkHandler; +import net.Gabou.projectatmosphere.network.RemoveTornadoPacket; +import net.Gabou.projectatmosphere.network.SpawnTornadoPacket; +import net.Gabou.projectatmosphere.network.SyncTornadoesPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.network.PacketDistributor; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +public class TornadoManager { + private static final List SERVER_TORNADOES = new ArrayList<>(); + private static final List CLIENT_TORNADOES = new ArrayList<>(); + private static final ResourceLocation RUNTIME_TORNADO_CONTROLLER = + ResourceLocation.fromNamespaceAndPath(ProjectAtmosphere.MODID, "runtime_spawn"); + private static final float MIN_VISUAL_HEIGHT = 96.0F; + private static final float HEIGHT_RADIUS_FACTOR = 6.0F; + private static final float HEIGHT_CLOUD_PADDING = 40.0F; + private static final int SYNC_INTERVAL_TICKS = 5; + + private static float shaderTime = 0.0F; + + @OnlyIn(Dist.CLIENT) + public static void spawnClient(UUID id, Vec3 pos, float radius, WindVector wind, float bottomY, float height) { + applyClientSnapshot(new TornadoSnapshot( + id, + new Vec3(pos.x, bottomY, pos.z), + radius, + bottomY, + height, + wind.baseSpeed(), + wind.angleRadians(), + wind.gustSpeed(), + Mth.clamp((radius - 5.0F) / 20.0F, 0.25F, 1.0F), + 0.0F, + net.Gabou.projectatmosphere.modules.weather.StormLifecyclePhase.FORMING + )); + } + + public static boolean spawnServer(ServerLevel level, Vec3 pos, float radius, WindVector wind) { + if (!AtmoCommonConfig.ENABLE_TORNADOES.get()) { + return false; + } + + CloudRegion cloud = findIntersectingCloud(level, pos, radius); + if (cloud == null) { + return false; + } + + UUID id = UUID.randomUUID(); + TornadoGeometry geometry = computeGeometry(level, pos, radius); + Vec3 spawnPos = new Vec3(pos.x, geometry.bottomY(), pos.z); + TornadoInstance tornado = new TornadoInstance(id, spawnPos, radius, wind, geometry.bottomY(), geometry.height(), cloud); + attachDescriptor(cloud, tornado); + SERVER_TORNADOES.add(tornado); + + NetworkHandler.CHANNEL.send( + PacketDistributor.ALL.noArg(), + new SpawnTornadoPacket(id, spawnPos, radius, wind, geometry.bottomY(), geometry.height()) + ); + broadcastSnapshots(); + return true; + } + + public static List getActiveTornadoes() { + return SERVER_TORNADOES; + } + + public static List getClientTornadoes() { + return CLIENT_TORNADOES; + } + + public static void removeTornado(TornadoInstance tornado) { + if (SERVER_TORNADOES.remove(tornado)) { + removeAttachedDescriptor(tornado); + broadcastRemoval(tornado.getId()); + broadcastSnapshots(); + } + } + + public static void clearTornadoes() { + for (TornadoInstance tornado : new ArrayList<>(SERVER_TORNADOES)) { + removeAttachedDescriptor(tornado); + broadcastRemoval(tornado.getId()); + } + SERVER_TORNADOES.clear(); + broadcastSnapshots(); + } + + public static void removeClientTornado(UUID id) { + CLIENT_TORNADOES.removeIf(tornado -> tornado.getId().equals(id)); + } + + public static void clearClientTornadoes() { + CLIENT_TORNADOES.clear(); + } + + @OnlyIn(Dist.CLIENT) + public static float getShaderTime() { + return shaderTime; + } + + public static void tick(Level level) { + if (level == null) { + return; + } + + if (level.isClientSide) { + shaderTime += 0.05F; + for (TornadoInstance tornado : CLIENT_TORNADOES) { + tornado.tickClient(); + } + return; + } + + ServerLevel serverLevel = (ServerLevel) level; + long gameTime = serverLevel.getGameTime(); + Iterator iterator = SERVER_TORNADOES.iterator(); + while (iterator.hasNext()) { + TornadoInstance tornado = iterator.next(); + CloudRegion currentRegion = findIntersectingCloud(serverLevel, tornado.position, tornado.radius); + if (currentRegion != tornado.getCloudRegion()) { + removeAttachedDescriptor(tornado); + tornado.setCloudRegion(currentRegion); + if (currentRegion != null) { + attachDescriptor(currentRegion, tornado); + } + } else if (currentRegion != null) { + ensureDescriptor(currentRegion, tornado); + } + + if (tornado.getCloudRegion() == null && tornado.getLifetimeSeconds() > 5.0F) { + tornado.markDissipating(); + } + + tornado.tickServer(serverLevel, gameTime); + if (tornado.isDead()) { + removeAttachedDescriptor(tornado); + iterator.remove(); + broadcastRemoval(tornado.getId()); + } + } + + if (gameTime % SYNC_INTERVAL_TICKS == 0L) { + broadcastSnapshots(); + } + } + + @OnlyIn(Dist.CLIENT) + public static void applyClientSnapshots(List snapshots) { + List next = new ArrayList<>(snapshots.size()); + for (TornadoSnapshot snapshot : snapshots) { + TornadoInstance existing = findClient(snapshot.id()); + CloudRegion cloud = findClientCloud(snapshot.position(), snapshot.radius()); + if (existing == null) { + existing = new TornadoInstance( + snapshot.id(), + snapshot.position(), + snapshot.radius(), + new WindVector(snapshot.windSpeed(), snapshot.windAngle(), snapshot.windGust()), + snapshot.visualBottomY(), + snapshot.visualHeight(), + cloud + ); + } + existing.applySnapshot(snapshot, cloud); + next.add(existing); + } + CLIENT_TORNADOES.clear(); + CLIENT_TORNADOES.addAll(next); + } + + @OnlyIn(Dist.CLIENT) + private static void applyClientSnapshot(TornadoSnapshot snapshot) { + TornadoInstance existing = findClient(snapshot.id()); + CloudRegion cloud = findClientCloud(snapshot.position(), snapshot.radius()); + if (existing == null) { + existing = new TornadoInstance( + snapshot.id(), + snapshot.position(), + snapshot.radius(), + new WindVector(snapshot.windSpeed(), snapshot.windAngle(), snapshot.windGust()), + snapshot.visualBottomY(), + snapshot.visualHeight(), + cloud + ); + CLIENT_TORNADOES.add(existing); + } + existing.applySnapshot(snapshot, cloud); + } + + @OnlyIn(Dist.CLIENT) + private static TornadoInstance findClient(UUID id) { + for (TornadoInstance tornado : CLIENT_TORNADOES) { + if (tornado.getId().equals(id)) { + return tornado; + } + } + return null; + } + + @OnlyIn(Dist.CLIENT) + @Nullable + private static CloudRegion findClientCloud(Vec3 pos, float radius) { + Level level = Minecraft.getInstance().level; + if (level == null) { + return null; + } + return findIntersectingCloud(level, pos, radius); + } + + @Nullable + private static CloudRegion findIntersectingCloud(Level level, Vec3 pos, float radius) { + SpawnRegion temporaryRegion = new SpawnRegion(Mth.floor(pos.x), Mth.floor(pos.z), Mth.ceil(radius)); + for (CloudRegion cloud : CloudManager.get(level).getClouds()) { + if (cloud.intersects(temporaryRegion)) { + return cloud; + } + } + return null; + } + + private static TornadoGeometry computeGeometry(Level level, Vec3 pos, float radius) { + float bottomY = (float) pos.y; + float cloudBase = CloudManager.get(level).getCloudHeight(); + float reachToCloudBase = Math.max(0.0F, cloudBase - bottomY); + float height = Math.max(MIN_VISUAL_HEIGHT, reachToCloudBase + radius * HEIGHT_RADIUS_FACTOR + HEIGHT_CLOUD_PADDING); + return new TornadoGeometry(bottomY, height); + } + + private static void attachDescriptor(CloudRegion cloud, TornadoInstance tornado) { + if (cloud instanceof ITornadoRegion tornadoRegion) { + tornadoRegion.replaceTornado(createRuntimeDescriptor(tornado, cloud)); + } + } + + private static void ensureDescriptor(CloudRegion cloud, TornadoInstance tornado) { + if (cloud instanceof ITornadoRegion tornadoRegion && tornadoRegion.findTornado(tornado.getId()) == null) { + tornadoRegion.addTornado(createRuntimeDescriptor(tornado, cloud)); + } + } + + private static TornadoDescriptor createRuntimeDescriptor(TornadoInstance tornado, CloudRegion cloud) { + float offsetX = (float) (tornado.position.x - cloud.getWorldX()); + float offsetZ = (float) (tornado.position.z - cloud.getWorldZ()); + return new TornadoDescriptor( + tornado.getId(), + RUNTIME_TORNADO_CONTROLLER, + offsetX, + offsetZ, + tornado.wind.baseSpeed() * (float) Math.cos(tornado.wind.angleRadians()) * 0.05F, + tornado.wind.baseSpeed() * (float) Math.sin(tornado.wind.angleRadians()) * 0.05F, + tornado.radius, + tornado.getVisualBottomY(), + tornado.getVisualHeight() + ); + } + + private static void removeAttachedDescriptor(TornadoInstance tornado) { + if (tornado.getCloudRegion() instanceof ITornadoRegion tornadoRegion) { + tornadoRegion.removeTornado(tornado.getId()); + } + } + + private static void broadcastRemoval(UUID id) { + NetworkHandler.CHANNEL.send(PacketDistributor.ALL.noArg(), new RemoveTornadoPacket(id)); + } + + private static void broadcastSnapshots() { + List snapshots = new ArrayList<>(SERVER_TORNADOES.size()); + for (TornadoInstance tornado : SERVER_TORNADOES) { + snapshots.add(tornado.snapshot()); + } + NetworkHandler.CHANNEL.send(PacketDistributor.ALL.noArg(), new SyncTornadoesPacket(snapshots)); + } + + private record TornadoGeometry(float bottomY, float height) { + } +} diff --git a/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoSnapshot.java b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoSnapshot.java new file mode 100644 index 00000000..14b4ef3f --- /dev/null +++ b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/tornado/TornadoSnapshot.java @@ -0,0 +1,52 @@ +package net.Gabou.projectatmosphere.modules.tornado; + +import net.Gabou.projectatmosphere.modules.weather.StormLifecyclePhase; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.phys.Vec3; + +import java.util.UUID; + +public record TornadoSnapshot( + UUID id, + Vec3 position, + float radius, + float visualBottomY, + float visualHeight, + float windSpeed, + float windAngle, + float windGust, + float normalizedIntensity, + float recentDebrisScore, + StormLifecyclePhase phase +) { + public void write(FriendlyByteBuf buf) { + buf.writeUUID(this.id); + buf.writeDouble(this.position.x); + buf.writeDouble(this.position.y); + buf.writeDouble(this.position.z); + buf.writeFloat(this.radius); + buf.writeFloat(this.visualBottomY); + buf.writeFloat(this.visualHeight); + buf.writeFloat(this.windSpeed); + buf.writeFloat(this.windAngle); + buf.writeFloat(this.windGust); + buf.writeFloat(this.normalizedIntensity); + buf.writeFloat(this.recentDebrisScore); + buf.writeEnum(this.phase); + } + + public static TornadoSnapshot read(FriendlyByteBuf buf) { + UUID id = buf.readUUID(); + Vec3 position = new Vec3(buf.readDouble(), buf.readDouble(), buf.readDouble()); + float radius = buf.readFloat(); + float bottomY = buf.readFloat(); + float height = buf.readFloat(); + float windSpeed = buf.readFloat(); + float windAngle = buf.readFloat(); + float windGust = buf.readFloat(); + float normalizedIntensity = buf.readFloat(); + float recentDebrisScore = buf.readFloat(); + StormLifecyclePhase phase = buf.readEnum(StormLifecyclePhase.class); + return new TornadoSnapshot(id, position, radius, bottomY, height, windSpeed, windAngle, windGust, normalizedIntensity, recentDebrisScore, phase); + } +} diff --git a/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormMotionModel.java b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormMotionModel.java new file mode 100644 index 00000000..30a8f305 --- /dev/null +++ b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/weather/StormMotionModel.java @@ -0,0 +1,95 @@ +package net.Gabou.projectatmosphere.modules.weather; + +import net.Gabou.projectatmosphere.modules.core.WindVector; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; + +import java.util.UUID; + +public final class StormMotionModel { + private static final float TORNADO_BASE_DRIFT = 0.055F; + private static final float TORNADO_TURN_RATE = 0.020F; + private static final float TORNADO_WANDER_SCALE = 0.28F; + private static final float TORNADO_AMBIENT_INFLUENCE = 0.32F; + private static final float TORNADO_LEASH_RADIUS = 140.0F; + private static final float HURRICANE_BASE_DRIFT = 0.06F; + private static final float HURRICANE_TURN_RATE = 0.012F; + private static final float HURRICANE_WANDER_SCALE = 0.25F; + + private StormMotionModel() { + } + + public static Vec3 advanceTornado(UUID id, Vec3 position, Vec3 currentVelocity, WindVector ambientWind, + float normalizedIntensity, long ageTicks, float anchorX, float anchorZ) { + float ambientSpeed = Math.max(0.6F, ambientWind.baseSpeed()); + float ambientHeading = ambientWind.angleRadians(); + float currentHeading = currentVelocity.lengthSqr() > 1.0E-4 + ? (float) Math.atan2(currentVelocity.z, currentVelocity.x) + : ambientHeading + noiseAngle(id, ageTicks, 0.003F, TORNADO_WANDER_SCALE); + + float selfHeading = currentHeading + + noiseAngle(id, ageTicks, 0.0008F, TORNADO_WANDER_SCALE) + + noiseAngle(id, ageTicks, 0.0026F, 0.12F); + float heading = rotateTowards(currentHeading, selfHeading, TORNADO_TURN_RATE + normalizedIntensity * 0.010F); + + Vec3 selfVector = new Vec3(Math.cos(heading), 0.0D, Math.sin(heading)) + .scale(TORNADO_BASE_DRIFT + normalizedIntensity * 0.07F); + + Vec3 ambientVector = new Vec3(Math.cos(ambientHeading), 0.0D, Math.sin(ambientHeading)) + .scale(ambientSpeed * (0.012F + normalizedIntensity * 0.010F) * TORNADO_AMBIENT_INFLUENCE); + + Vec3 lateral = new Vec3(-selfVector.z, 0.0D, selfVector.x) + .scale(noiseSigned(id, ageTicks, 0.0016F) * (0.015F + normalizedIntensity * 0.020F)); + + float dx = anchorX - (float) position.x; + float dz = anchorZ - (float) position.z; + float anchorDist = Mth.sqrt(dx * dx + dz * dz); + Vec3 leash = Vec3.ZERO; + if (anchorDist > TORNADO_LEASH_RADIUS) { + double leashStrength = Math.min((anchorDist - TORNADO_LEASH_RADIUS) * 0.0025D, 0.12D); + leash = new Vec3(dx / Math.max(anchorDist, 0.001F), 0.0D, dz / Math.max(anchorDist, 0.001F)).scale(leashStrength); + } + + Vec3 targetVelocity = selfVector.add(ambientVector).add(lateral).add(leash); + Vec3 velocity = currentVelocity.lerp(targetVelocity, 0.08D); + return position.add(velocity); + } + + public static Vec3 advanceHurricane(UUID id, Vec3 position, Vec3 currentVelocity, WindVector ambientWind, + float normalizedIntensity, long ageTicks) { + float ambientSpeed = Math.max(1.0F, ambientWind.baseSpeed()); + float targetHeading = ambientWind.angleRadians() + noiseAngle(id, ageTicks, 0.0017F, HURRICANE_WANDER_SCALE); + float currentHeading = currentVelocity.lengthSqr() > 1.0E-4 ? (float) Math.atan2(currentVelocity.z, currentVelocity.x) : targetHeading; + float heading = rotateTowards(currentHeading, targetHeading, HURRICANE_TURN_RATE + normalizedIntensity * 0.004F); + float speed = ambientSpeed * (HURRICANE_BASE_DRIFT + normalizedIntensity * 0.08F); + + Vec3 blended = new Vec3( + Math.cos(heading) * speed, + 0.0D, + Math.sin(heading) * speed + ); + Vec3 velocity = currentVelocity.lerp(blended, 0.08D); + return position.add(velocity); + } + + public static float noise01(UUID id, long tick, float rate) { + long seed = id.getMostSignificantBits() ^ id.getLeastSignificantBits(); + float a = (float) Math.sin(seed * 0.00000013D + tick * rate); + float b = (float) Math.sin(seed * 0.00000029D + tick * rate * 0.63D + 1.7D); + return Mth.clamp((a * 0.6F + b * 0.4F) * 0.5F + 0.5F, 0.0F, 1.0F); + } + + public static float noiseSigned(UUID id, long tick, float rate) { + return noise01(id, tick, rate) * 2.0F - 1.0F; + } + + private static float noiseAngle(UUID id, long tick, float rate, float scale) { + return noiseSigned(id, tick, rate) * scale; + } + + private static float rotateTowards(float current, float target, float maxTurn) { + float delta = Mth.wrapDegrees((float) Math.toDegrees(target - current)); + float deltaRad = (float) Math.toRadians(Mth.clamp(delta, (float) Math.toDegrees(-maxTurn), (float) Math.toDegrees(maxTurn))); + return current + deltaRad; + } +} diff --git a/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/wind/TornadoWindModel.java b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/wind/TornadoWindModel.java new file mode 100644 index 00000000..2d662408 --- /dev/null +++ b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/modules/wind/TornadoWindModel.java @@ -0,0 +1,52 @@ +package net.Gabou.projectatmosphere.modules.wind; + +import net.Gabou.projectatmosphere.modules.tornado.TornadoInstance; +import net.Gabou.projectatmosphere.modules.tornado.TornadoManager; +import net.minecraft.world.phys.Vec3; + +import java.util.List; + +/** + * Computes player-facing tornado forces so visuals and physics stay in sync. + */ +public final class TornadoWindModel { + public record TornadoForces(Vec3 pullVector, Vec3 rotationVector, Vec3 liftVector, TornadoInstance source) { } + + private TornadoWindModel() { } + + public static TornadoForces compute(Vec3 position) { + List active = TornadoManager.getActiveTornadoes(); + TornadoInstance nearest = null; + double nearestDistSq = Double.MAX_VALUE; + for (TornadoInstance tornado : active) { + double distSq = tornado.position.distanceToSqr(position.x, tornado.position.y, position.z); + if (distSq < nearestDistSq) { + nearest = tornado; + nearestDistSq = distSq; + } + } + if (nearest == null) { + return null; + } + + Vec3 center = nearest.position; + double dx = center.x - position.x; + double dz = center.z - position.z; + double horizontalDist = Math.sqrt(dx * dx + dz * dz); + double radius = Math.max(4.0, nearest.getSuctionRadius()); + if (horizontalDist > radius * 3.0) { + return null; + } + + double proximity = Math.max(0.0, 1.0 - Math.min(horizontalDist, radius) / radius); + Vec3 pull = new Vec3(dx, 0.0, dz).normalize().scale(proximity * 0.08 * nearest.getLevel().getMaxWindSpeed()); + + Vec3 swirlDir = horizontalDist > 1e-3 ? new Vec3(-(dz / horizontalDist), 0.0, dx / horizontalDist) : Vec3.ZERO; + Vec3 rotation = swirlDir.scale(proximity * proximity * 0.12 * nearest.getLevel().getMaxWindSpeed()); + + double liftScale = Math.max(0.0, 1.0 - Math.abs(position.y - center.y) / 40.0); + Vec3 lift = new Vec3(0.0, proximity * 0.04 * nearest.getLevel().getMaxWindSpeed() * liftScale, 0.0); + + return new TornadoForces(pull, rotation, lift, nearest); + } +} diff --git a/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticle.java b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticle.java new file mode 100644 index 00000000..8ef1ceae --- /dev/null +++ b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticle.java @@ -0,0 +1,134 @@ +package net.Gabou.projectatmosphere.particles; + +import net.Gabou.projectatmosphere.modules.tornado.TornadoInstance; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.particle.ParticleProvider; +import net.minecraft.client.particle.ParticleRenderType; +import net.minecraft.client.particle.SpriteSet; +import net.minecraft.client.particle.TextureSheetParticle; + +import java.lang.ref.WeakReference; + +/** + * Particle representing debris swirling around a tornado. + */ +public class DebrisParticle extends TextureSheetParticle { + private final WeakReference tornadoRef; + private final double radius; + private final double baseY; + private final float angularSpeed; + private final float verticalDrift; + private final float radialJitter; + private final int band; + private final float startAngle; + + protected DebrisParticle(ClientLevel level, TornadoInstance tornado, double radius, double height, float angularSpeed, + float verticalDrift, float radialJitter, int band) { + super(level, tornado.position.x, tornado.position.y + height, tornado.position.z, 0, 0, 0); + this.tornadoRef = new WeakReference<>(tornado); + this.radius = radius; + this.baseY = height; + this.angularSpeed = angularSpeed; + this.verticalDrift = verticalDrift; + this.radialJitter = radialJitter; + this.band = band; + this.startAngle = level.random.nextFloat() * 360f; + this.lifetime = switch (band) { + case 0 -> 34 + this.random.nextInt(16); + case 1 -> 42 + this.random.nextInt(18); + default -> 48 + this.random.nextInt(24); + }; + this.gravity = 0; + this.friction = 0.94f; + float size = switch (band) { + case 0 -> 0.18f; + case 1 -> 0.14f; + default -> 0.24f; + }; + this.setSize(size, size); + this.alpha = switch (band) { + case 0 -> 0.85f; + case 1 -> 0.72f; + default -> 0.58f; + }; + } + + @Override + public void tick() { + TornadoInstance tornado = tornadoRef.get(); + if (tornado == null) { + remove(); + return; + } + float lifeProgress = this.lifetime <= 0 ? 1.0F : (float) this.age / (float) this.lifetime; + float bandSpinBoost = switch (this.band) { + case 0 -> 1.25F; + case 1 -> 2.20F; + default -> 1.05F; + }; + float angle = startAngle + + tornado.getTwist() * 96.0F * bandSpinBoost + + (tornado.getLifetimeSeconds() * 20 + this.age) * angularSpeed; + double rad = Math.toRadians(angle); + double rise = this.baseY + this.age * this.verticalDrift + switch (this.band) { + case 0 -> Math.sin((this.age + this.startAngle) * 0.10F) * 0.24D; + case 1 -> lifeProgress * 1.8D; + default -> lifeProgress * 2.6D; + }; + double pulse = Math.sin((this.age + this.startAngle) * 0.12F) * this.radialJitter + + Math.cos((this.age + this.startAngle) * 0.05F + this.band) * this.radialJitter * 0.45D; + double spiralOffset = switch (this.band) { + case 0 -> -lifeProgress * this.radius * 0.10D; + case 1 -> -lifeProgress * this.radius * 0.34D; + default -> lifeProgress * this.radius * 0.10D; + }; + double orbitRadius = Math.max(0.2D, this.radius + pulse + spiralOffset); + this.alpha = switch (this.band) { + case 0 -> 0.85F - lifeProgress * 0.35F; + case 1 -> 0.74F - lifeProgress * 0.28F; + default -> 0.60F - lifeProgress * 0.22F; + }; + setPos( + tornado.position.x + Math.cos(rad) * orbitRadius, + tornado.position.y + rise, + tornado.position.z + Math.sin(rad) * orbitRadius + ); + this.yd += this.verticalDrift * 0.4; + super.tick(); + } + + @Override + public ParticleRenderType getRenderType() { + return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT; + } + + public static class Provider implements ParticleProvider { + private final SpriteSet sprites; + + public Provider(SpriteSet sprites) { + this.sprites = sprites; + } + + @Override + public Particle createParticle(DebrisParticleData data, ClientLevel level, double x, double y, double z, double vx, double vy, double vz) { + DebrisParticle particle = new DebrisParticle( + level, + data.tornado(), + data.radius(), + data.height(), + data.angularSpeed(), + data.verticalDrift(), + data.radialJitter(), + data.band() + ); + try { + particle.pickSprite(sprites); + } catch (Throwable ignored) { + // SpriteSet may not be ready yet + } + return particle; + } + } +} + diff --git a/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticleData.java b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticleData.java new file mode 100644 index 00000000..60d76fa1 --- /dev/null +++ b/to_check_hurricanes/src/main/java/net/Gabou/projectatmosphere/particles/DebrisParticleData.java @@ -0,0 +1,46 @@ +package net.Gabou.projectatmosphere.particles; + +import com.mojang.brigadier.StringReader; +import com.mojang.serialization.Codec; +import net.Gabou.projectatmosphere.modules.tornado.TornadoInstance; +import net.Gabou.projectatmosphere.registry.ModParticles; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.network.FriendlyByteBuf; + +/** + * Particle data for {@link DebrisParticle}. + * This implementation is client-only and does not support network or command serialization. + */ +public record DebrisParticleData(TornadoInstance tornado, double radius, double height, float angularSpeed, + float verticalDrift, float radialJitter, int band) implements ParticleOptions { + public static final Codec CODEC = Codec.unit(new DebrisParticleData(null, 0, 0, 0, 0, 0, 0)); + + public static final Deserializer DESERIALIZER = new Deserializer<>() { + @Override + public DebrisParticleData fromCommand(ParticleType type, StringReader reader) { + throw new UnsupportedOperationException(); + } + + @Override + public DebrisParticleData fromNetwork(ParticleType type, FriendlyByteBuf buf) { + throw new UnsupportedOperationException(); + } + }; + + @Override + public ParticleType getType() { + return ModParticles.DEBRIS.get(); + } + + @Override + public void writeToNetwork(FriendlyByteBuf buf) { + throw new UnsupportedOperationException(); + } + + @Override + public String writeToString() { + return "debris"; + } +} + diff --git a/upstream_sc/assets/simpleclouds/shaders/compute/cloud_regions.comp b/upstream_sc/assets/simpleclouds/shaders/compute/cloud_regions.comp new file mode 100644 index 00000000..1e2d9ffd --- /dev/null +++ b/upstream_sc/assets/simpleclouds/shaders/compute/cloud_regions.comp @@ -0,0 +1,82 @@ +#version 430 + +#define EFF ${EDGE_FADE_FACTOR} // Edge Fade Factor + +layout(local_size_x = ${LOCAL_SIZE_X}, local_size_y = ${LOCAL_SIZE_Y}, local_size_z = ${LOCAL_SIZE_Z}) in; + +struct CloudRegion { + float posX; + float posZ; + float index; + float radius; + mat2 transform; +}; + +layout(std430) readonly buffer CloudRegions { + CloudRegion data[]; +} +cloudRegions; + +layout(std430) restrict readonly buffer LodScales { + float data[]; +} +lodScales; + +layout(rg32f) restrict writeonly uniform image2DArray regionTexture; + +uniform int TotalCloudRegions; +uniform vec2 Offset; + +vec3 circle(CloudRegion region, vec2 coord) +{ + vec2 p = vec2(region.posX, region.posZ); + coord = region.transform * (coord - p) + p; + float d = distance(p, coord); + float r = region.radius; + if (d > r + 1.0 / EFF) + return vec3(-1.0); + else if (d < r) + return vec3(min((r - d) * EFF, 1.0), 0.0, region.index); + else + return vec3(0.0, min((d - r) * EFF, 1.0), region.index); +} + +vec2 composite(vec2 old, vec3 data) +{ + if (data.r > 0.0) + { + if (old.r >= 0.0 && old.r == data.b) + return vec2(old.r, mix(old.g, 1.0, data.r)); + else + return data.br; + } + else if (data.g >= 0.0) + { + if (old.r >= 0.0 && old.r == data.b) + return old; + else + return vec2(old.r, old.g * data.g); + } + else + { + return old; + } +} + +void main() +{ + uint lod = gl_GlobalInvocationID.z; + float coordScale = lodScales.data[lod]; + vec2 centerOffset = imageSize(regionTexture).xy / 2.0; + ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy); + vec2 coord = (gl_GlobalInvocationID.xy - centerOffset) * coordScale + Offset; + + vec2 result = vec2(0.0); + for (int i = 0; i < TotalCloudRegions; i++) + { + vec3 data = circle(cloudRegions.data[i], coord); + result = composite(result, data); + } + + imageStore(regionTexture, ivec3(texelCoord, lod), vec4(result, 0.0, 0.0)); +} \ No newline at end of file diff --git a/upstream_sc/assets/simpleclouds/shaders/compute/cube_mesh.comp b/upstream_sc/assets/simpleclouds/shaders/compute/cube_mesh.comp new file mode 100644 index 00000000..ae7b0d9d --- /dev/null +++ b/upstream_sc/assets/simpleclouds/shaders/compute/cube_mesh.comp @@ -0,0 +1,409 @@ +#version 430 + +#define TYPE ${TYPE} //0 for multi-region, 1 for single cloud type +#define FADE_NEAR_ORIGIN ${FADE_NEAR_ORIGIN} //0 to disable, 1 to enable +#define STYLE ${STYLE} //0 for default, 1 for shaded +#define TRANSPARENCY ${TRANSPARENCY} //0 for no transparency, 1 for transparency +#define FIXED_SECTION_SIZE ${FIXED_SECTION_SIZE} //0 for false, 1 for true + +#define SHADE_DIRECTION normalize(vec3(0.5, 0.5, 0.5)) + +#define TILE_PERIOD vec3(32.0, 64.0, 32.0) + +#define LOCAL_SIZE vec3(${LOCAL_SIZE_X}, ${LOCAL_SIZE_Y}, ${LOCAL_SIZE_Z}) +layout(local_size_x = ${LOCAL_SIZE_X}, local_size_y = ${LOCAL_SIZE_Y}, local_size_z = ${LOCAL_SIZE_Z}) in; + +#moj_import + +struct LayerGroup { + int StartIndex; + int EndIndex; + float Storminess; + float StormStart; + float StormFadeDistance; + float TransparencyFade; +}; + +struct NoiseLayer { + float Height; + float ValueOffset; + float ScaleX; + float ScaleY; + float ScaleZ; + float FadeDistance; + float HeightOffset; + float ValueScale; +}; + +// ----- Opaque ----- + +struct SideInfo { + int side; + float x; + float y; + float z; + float brightness; + float radius; +}; + +// ----- Transparent ----- + +#if TRANSPARENCY == 1 + +struct TransparentCubeInfo { + float x; + float y; + float z; + float brightness; + float alpha; + float radius; +}; + +#endif + +// ----------------------- + +//Faces: +//-X = 0 +//+X = 1 +//-Y = 2 +//+Y = 3 +//-Z = 4 +//+Z = 5 + +//const uint sideIndices[6] = { +// 0, 1, 2, 0, 2, 3 +//}; +// +//#if TRANSPARENCY == 1 +// +//const uint transparentCubeIndices[36] = { +// 0, 1, 2, 0, 2, 3, //-z +// 4, 7, 6, 4, 6, 5, //+z +// 7, 0, 3, 7, 3, 6, //-x +// 1, 4, 5, 1, 5, 2, //+x +// 1, 0, 7, 1, 7, 4, //-y +// 5, 6, 3, 5, 3, 2 //+y +//}; +// +//#endif + +// ----- Opaque Buffers ----- + +#if FIXED_SECTION_SIZE == 0 +layout(std430) restrict buffer TotalSides { + uint totalSides; +}; +#endif + +layout(std430) restrict writeonly buffer SideInfoBuffer { + SideInfo data[]; +} +sides; + +layout(std430) restrict buffer SidesPerChunk { + uint data[]; +} +sidesPerChunk; + +// ----- Transparency Buffers ----- + +#if TRANSPARENCY == 1 + +#if FIXED_SECTION_SIZE == 0 +layout(std430) restrict buffer TotalTransparentCubes { + uint totalTransparentCubes; +}; +#endif + +layout(std430) restrict writeonly buffer TransparentCubeInfoBuffer { + TransparentCubeInfo data[]; +} +cubesTransparent; + +layout(std430) restrict buffer TransparentCubesPerChunk { + uint data[]; +} +transparentCubesPerChunk; + +#endif + +// ---------------------------------- + +layout(std430) readonly buffer NoiseLayers { + NoiseLayer data[]; +} +layers; + +layout(std430) readonly buffer LayerGroupings { + LayerGroup data[]; +} +layerGroupings; + +#if TYPE == 0 +uniform sampler2DArray RegionsSampler; +uniform int RegionsTexSize; +#endif + +uniform int LodLevel; +uniform int TotalLodLevels; +uniform vec3 RenderOffset; +uniform float Scale = 1.0; +uniform vec3 Scroll; +uniform float Wiggle; +uniform vec3 Origin; +uniform bool TestFacesFacingAway; +uniform int DoNotOccludeSide = -1; +uniform int ChunkIndex; + +#if FIXED_SECTION_SIZE == 1 +//Offset in number of mesh elements +uniform int OpaqueMeshDataOffset; +uniform int TransparentMeshDataOffset; +#endif + +#if TRANSPARENCY == 1 +uniform int TransparencyDistance = 300; +#endif + +#if TYPE == 0 +uniform vec2 RegionSampleOffset; +#elif TYPE == 1 +uniform float FadeStart; +uniform float FadeEnd; +#endif + +#if FADE_NEAR_ORIGIN == 1 +uniform float FadeStart; +uniform float FadeEnd; +#endif + +float getNoiseForLayer(NoiseLayer layer, float x, float y, float z, out vec3 gradient) +{ + if (y < layer.HeightOffset || y > layer.HeightOffset + layer.Height - 1) + return -10000.0; + vec3 scale = vec3(layer.ScaleX, layer.ScaleY, layer.ScaleZ); + vec3 scrollScaled = Scroll / scale; + vec3 samplePos = vec3(x, y, z) / scale + scrollScaled; + float noise = psrdnoise(samplePos, TILE_PERIOD, Wiggle, gradient) * layer.ValueScale + layer.ValueOffset; + float heightDelta = y - layer.HeightOffset; + noise -= 1.0 - clamp(heightDelta / layer.FadeDistance, 0.0, 1.0); + noise -= 1.0 - clamp((layer.Height - heightDelta) / layer.FadeDistance, 0.0, 1.0); + return noise; +} + +float getNoiseForLayerGroup(LayerGroup group, float x, float y, float z, out vec3 gradient) +{ + int totalLayers = group.EndIndex - group.StartIndex; + if (totalLayers == 0) + { + return -10000.0; + } + else if (totalLayers == 1) + { + return getNoiseForLayer(layers.data[group.StartIndex], x, y, z, gradient); + } + else + { + vec3 finalGradient = vec3(0.0); + float combinedNoise = 0.0; + bool anyValid = false; + for (int i = 0; i < totalLayers; i++) + { + vec3 gradForLayer = vec3(0.0); + float valForLayer = getNoiseForLayer(layers.data[i + group.StartIndex], x, y, z, gradForLayer); + if (valForLayer > -10.0) + { + combinedNoise += valForLayer; + finalGradient += gradForLayer; + anyValid = true; + } + } + gradient = finalGradient; + if (anyValid) + return combinedNoise; + else + return -10000.0; + } + return 0.0; +} + +bool isPosValid(float x, float y, float z, LayerGroup group, float fade) +{ + vec3 gradient = vec3(0.0); + return getNoiseForLayerGroup(group, x, y, z, gradient) + fade > 0.0; +} + +bool isPosValid(float x, float y, float z, int nx, int nz) +{ +#if TYPE == 0 + vec2 texelCoord = gl_GlobalInvocationID.xz + RegionSampleOffset + vec2(nx, nz); + vec4 info = texture(RegionsSampler, vec3(texelCoord / RegionsTexSize, float(LodLevel))); + uint regionId = uint(info.r); + LayerGroup group = layerGroupings.data[regionId]; + float fade = -5.0 * pow(1.0 - info.g, 10.0); +#if FADE_NEAR_ORIGIN == 1 + float len = distance(vec2(x, z), Origin.xz); + fade = min(fade, -5.0 * (1.0 - min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0))); +#endif +#elif TYPE == 1 + LayerGroup group = layerGroupings.data[0]; + float len = distance(vec2(x, z), Origin.xz); + float fade = -5.0 * min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0); +#endif + return isPosValid(x, y, z, group, fade); +} + +bool shouldNotOcclude(int index) +{ + if (DoNotOccludeSide != -1 && index == DoNotOccludeSide) + { + vec3 id = gl_GlobalInvocationID; + vec3 size = gl_NumWorkGroups * LOCAL_SIZE; + if (DoNotOccludeSide == 1) + return id.x == size.x - 1.0; + else if (DoNotOccludeSide == 0) + return id.x == 0.0; + else if (DoNotOccludeSide == 3) + return id.y == size.y - 1.0; + else if (DoNotOccludeSide == 2) + return id.y == 0.0; + else if (DoNotOccludeSide == 5) + return id.z == size.z - 1.0; + else if (DoNotOccludeSide == 4) + return id.z == 0.0; + else + return false; + } + else + { + return false; + } +} + +// ----- Opaque ----- + +void createFace(vec3 center, float cubeRadius, int index, float brightness) +{ +#if FIXED_SECTION_SIZE == 0 + atomicAdd(sidesPerChunk.data[ChunkIndex], 1u); + uint currentFace = atomicAdd(totalSides, 1u); +#elif FIXED_SECTION_SIZE == 1 + uint currentFace = atomicAdd(sidesPerChunk.data[ChunkIndex], 1u); +#endif + SideInfo side; + side.side = index; + side.x = center.x; + side.y = center.y; + side.z = center.z; + side.brightness = brightness; + side.radius = cubeRadius; +#if FIXED_SECTION_SIZE == 0 + sides.data[currentFace] = side; +#elif FIXED_SECTION_SIZE == 1 + sides.data[OpaqueMeshDataOffset + currentFace] = side; +#endif +} + +void createCube(float x, float y, float z, float cubeRadius, float brightness, float fade, LayerGroup group) +{ + vec3 pos = vec3(x, y, z); + vec3 norm = normalize(pos - Origin); + vec3 center = pos + cubeRadius; + //-Y + if ((TestFacesFacingAway || dot(norm, vec3(0.0, -1.0, 0.0)) <= 0.0) && (!isPosValid(x, y - Scale, z, group, fade) || shouldNotOcclude(2))) + createFace(center, cubeRadius, 2, brightness); + //+Y + if ((TestFacesFacingAway || dot(norm, vec3(0.0, 1.0, 0.0)) <= 0.0) && (!isPosValid(x, y + Scale, z, group, fade) || shouldNotOcclude(3))) + createFace(center, cubeRadius, 3, brightness); + //-X + if ((TestFacesFacingAway || dot(norm, vec3(-1.0, 0.0, 0.0)) <= 0.0) && (!isPosValid(x - Scale, y, z, -1, 0) || shouldNotOcclude(0))) + createFace(center, cubeRadius, 0, brightness); + //+X + if ((TestFacesFacingAway || dot(norm, vec3(1.0, 0.0, 0.0)) <= 0.0) && (!isPosValid(x + Scale, y, z, 1, 0) || shouldNotOcclude(1))) + createFace(center, cubeRadius, 1, brightness); + //-Z + if ((TestFacesFacingAway || dot(norm, vec3(0.0, 0.0, -1.0)) <= 0.0) && (!isPosValid(x, y, z - Scale, 0, -1) || shouldNotOcclude(4))) + createFace(center, cubeRadius, 4, brightness); + //+Z + if ((TestFacesFacingAway || dot(norm, vec3(0.0, 0.0, 1.0)) <= 0.0) && (!isPosValid(x, y, z + Scale, 0, 1) || shouldNotOcclude(5))) + createFace(center, cubeRadius, 5, brightness); +} + +// ----- Transparent ----- + +#if TRANSPARENCY == 1 + +void createTransparentCube(float x, float y, float z, float cubeRadius, float brightness, float alpha) +{ +#if FIXED_SECTION_SIZE == 0 + atomicAdd(transparentCubesPerChunk.data[ChunkIndex], 1u); + uint currentCube = atomicAdd(totalTransparentCubes, 1u); +#elif FIXED_SECTION_SIZE == 1 + uint currentCube = atomicAdd(transparentCubesPerChunk.data[ChunkIndex], 1u); +#endif + TransparentCubeInfo cube; + cube.x = x + cubeRadius; + cube.y = y + cubeRadius; + cube.z = z + cubeRadius; + cube.brightness = brightness; + cube.alpha = alpha; + cube.radius = cubeRadius; +#if FIXED_SECTION_SIZE == 0 + cubesTransparent.data[currentCube] = cube; +#elif FIXED_SECTION_SIZE == 1 + cubesTransparent.data[TransparentMeshDataOffset + currentCube] = cube; +#endif +} + +#endif + +// ----------------------- + +void main() +{ + vec3 id = gl_GlobalInvocationID; + float x = id.x * Scale + RenderOffset.x; + float y = id.y * Scale + RenderOffset.y; + float z = id.z * Scale + RenderOffset.z; + +#if TYPE == 0 + vec2 texelCoord = gl_GlobalInvocationID.xz + RegionSampleOffset; + vec4 info = texture(RegionsSampler, vec3(texelCoord / RegionsTexSize, float(LodLevel))); + uint regionId = uint(info.r); + LayerGroup group = layerGroupings.data[regionId]; + float fade = -5.0 * pow(1.0 - info.g, 10.0); +#if FADE_NEAR_ORIGIN == 1 + float len = distance(vec2(x, z), Origin.xz); + fade = min(fade, -5.0 * (1.0 - min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0))); +#endif +#elif TYPE == 1 + LayerGroup group = layerGroupings.data[0]; + float len = distance(vec2(x, z), Origin.xz); + float fade = -5.0 * min(max(len - FadeStart, 0.0) / (FadeEnd - FadeStart), 1.0); +#endif + vec3 gradient = vec3(0.0); + float noise = getNoiseForLayerGroup(group, x, y, z, gradient) + fade; + float storminess = clamp(group.Storminess + fade * 0.1, 0.0, 1.0); + float brightness = clamp(1.0 - storminess * (1.0 - clamp((y - group.StormStart) / group.StormFadeDistance, 0.0, 1.0)), 0.0, 1.0); +#if STYLE == 1 + gradient = normalize(gradient); + float strength = dot(gradient, SHADE_DIRECTION) * 0.5 + 0.5; + brightness = clamp(brightness - strength * 0.1, 0.0, 1.0); +#endif + if (noise > 0.0) + { + createCube(x, y, z, Scale / 2.0, brightness, fade, group); + } +#if TRANSPARENCY == 1 + else if (group.TransparencyFade > 0.01 && noise > -group.TransparencyFade && noise < 0.0) + { + float length = distance(vec2(x, z), Origin.xz); + if (length < TransparencyDistance) + { + float alpha = 1.0 / group.TransparencyFade * (noise + group.TransparencyFade); + createTransparentCube(x, y, z, Scale / 2.0, brightness, alpha); + } + } +#endif +} \ No newline at end of file diff --git a/upstream_sc/assets/simpleclouds/shaders/core/cloud_region_tex.json b/upstream_sc/assets/simpleclouds/shaders/core/cloud_region_tex.json new file mode 100644 index 00000000..ce334106 --- /dev/null +++ b/upstream_sc/assets/simpleclouds/shaders/core/cloud_region_tex.json @@ -0,0 +1,23 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "simpleclouds:cloud_region_tex", + "fragment": "simpleclouds:cloud_region_tex", + "attributes": [ + "Position", + "UV0" + ], + "samplers": [ + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "LodLevel", "type": "int", "count": 1, "values": [ 0 ] }, + { "name": "TotalCloudTypes", "type": "int", "count": 1, "values": [ 1 ] }, + { "name": "Align", "type": "float", "count": 2, "values": [ 0.0, 0.0 ]} + ] +} diff --git a/upstream_sc/assets/simpleclouds/shaders/core/clouds.json b/upstream_sc/assets/simpleclouds/shaders/core/clouds.json new file mode 100644 index 00000000..14e3e286 --- /dev/null +++ b/upstream_sc/assets/simpleclouds/shaders/core/clouds.json @@ -0,0 +1,25 @@ +{ + "vertex": "simpleclouds:clouds", + "fragment": "simpleclouds:clouds", + "attributes": [ + "Position" + ], + "samplers": [ + { "name": "BayerMatrixSampler" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "Light0_Direction", "type": "float", "count": 3, "values": [0.0, 0.0, 0.0] }, + { "name": "Light1_Direction", "type": "float", "count": 3, "values": [0.0, 0.0, 0.0] }, + { "name": "LightPower", "type": "float", "count": 1, "values": [0.4] }, + { "name": "AmbientLight", "type": "float", "count": 1, "values": [0.9] }, + { "name": "DarknessColorModifier", "type": "float", "count": 3, "values": [ 0.0, 0.0, 0.15 ] }, + { "name": "UseNormals", "type": "int", "count": 1, "values": [ 1.0 ]}, + { "name": "DitherScale", "type": "float", "count": 1, "values": [ 0.05 ] }, + { "name": "FogStart", "type": "float", "count": 1, "values": [ 1.0 ] }, + { "name": "FogEnd", "type": "float", "count": 1, "values": [ 2.0 ] }, + { "name": "FogColor", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] } + ] +} \ No newline at end of file diff --git a/upstream_sc/assets/simpleclouds/shaders/core/clouds_shadow_map.json b/upstream_sc/assets/simpleclouds/shaders/core/clouds_shadow_map.json new file mode 100644 index 00000000..56602a67 --- /dev/null +++ b/upstream_sc/assets/simpleclouds/shaders/core/clouds_shadow_map.json @@ -0,0 +1,15 @@ +{ + "vertex": "simpleclouds:clouds_shadow_map", + "fragment": "simpleclouds:clouds_shadow_map", + "attributes": [ + "Position" + ], + "samplers": [ + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "HeightCutoff", "type": "float", "count": 1, "values": [ 128.0 ] } + ] +} \ No newline at end of file diff --git a/upstream_sc/assets/simpleclouds/shaders/core/clouds_transparency.json b/upstream_sc/assets/simpleclouds/shaders/core/clouds_transparency.json new file mode 100644 index 00000000..346ccaed --- /dev/null +++ b/upstream_sc/assets/simpleclouds/shaders/core/clouds_transparency.json @@ -0,0 +1,20 @@ +{ + "vertex": "simpleclouds:clouds_transparency", + "fragment": "simpleclouds:clouds_transparency", + "attributes": [ + "Position" + ], + "samplers": [ + { "name": "BayerMatrixSampler" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "DarknessColorModifier", "type": "float", "count": 3, "values": [ 0.0, 0.0, 0.15 ] }, + { "name": "FogStart", "type": "float", "count": 1, "values": [ 1.0 ] }, + { "name": "FogEnd", "type": "float", "count": 1, "values": [ 2.0 ] }, + { "name": "FogColor", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "DitherScale", "type": "float", "count": 1, "values": [ 0.05 ] } + ] +} \ No newline at end of file diff --git a/upstream_sc/assets/simpleclouds/shaders/core/storm_fog_shadow_map.json b/upstream_sc/assets/simpleclouds/shaders/core/storm_fog_shadow_map.json new file mode 100644 index 00000000..68994ed3 --- /dev/null +++ b/upstream_sc/assets/simpleclouds/shaders/core/storm_fog_shadow_map.json @@ -0,0 +1,16 @@ +{ + "vertex": "simpleclouds:clouds_shadow_map", + "fragment": "simpleclouds:storm_fog_shadow_map", + "attributes": [ + "Position" + ], + "samplers": [ + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "ColorThreshold", "type": "float", "count": 3, "values": [ 0.7, 0.7, 0.7 ] }, + { "name": "HeightCutoff", "type": "float", "count": 1, "values": [ 32.0 ] } + ] +} \ No newline at end of file diff --git a/upstream_sc/assets/simpleclouds/shaders/post/final_composite.json b/upstream_sc/assets/simpleclouds/shaders/post/final_composite.json new file mode 100644 index 00000000..bfdef362 --- /dev/null +++ b/upstream_sc/assets/simpleclouds/shaders/post/final_composite.json @@ -0,0 +1,17 @@ +{ + "targets": [ + "swap" + ], + "passes": [ + { + "name": "simpleclouds:clouds_composite", + "intarget": "minecraft:main", + "outtarget": "swap" + }, + { + "name": "blit", + "intarget": "swap", + "outtarget": "minecraft:main" + } + ] +} \ No newline at end of file diff --git a/upstream_sc/assets/simpleclouds/shaders/post/final_composite_no_transparency.json b/upstream_sc/assets/simpleclouds/shaders/post/final_composite_no_transparency.json new file mode 100644 index 00000000..382b13de --- /dev/null +++ b/upstream_sc/assets/simpleclouds/shaders/post/final_composite_no_transparency.json @@ -0,0 +1,17 @@ +{ + "targets": [ + "output" + ], + "passes": [ + { + "name": "simpleclouds:clouds_composite_no_transparency", + "intarget": "minecraft:main", + "outtarget": "output" + }, + { + "name": "blit", + "intarget": "output", + "outtarget": "minecraft:main" + } + ] +} \ No newline at end of file diff --git a/upstream_sc/assets/simpleclouds/shaders/post/storm_post.json b/upstream_sc/assets/simpleclouds/shaders/post/storm_post.json new file mode 100644 index 00000000..bfb49981 --- /dev/null +++ b/upstream_sc/assets/simpleclouds/shaders/post/storm_post.json @@ -0,0 +1,17 @@ +{ + "targets": [ + "out" + ], + "passes": [ + { + "name": "simpleclouds:storm_fog", + "intarget": "minecraft:main", + "outtarget": "out" + }, + { + "name": "blit", + "intarget": "out", + "outtarget": "minecraft:main" + } + ] +}