Public Access
This commit is contained in:
1 parent
48fbae9be9
commit
a7a73d3c17
23 files changed
+967
-24
No files matched your search
@@ -14,10 +14,11 @@ The gameplay guide below describes the **Minecraft 1.21.1 release**. A separate,
|
||||
incomplete **Minecraft 26.3 beta** is available: Java 25, NeoForge 26.3.0.99,
|
||||
and a first-party Age backend (no Dynamic Dimensions or Bad Packets). It now
|
||||
supports authored terrain/biomes, structure and vegetation pages, independent
|
||||
Age weather, and a growing set of generation features,
|
||||
Age weather/lighting, Overworld/Nether/End and animated Static skies,
|
||||
and a growing set of generation features,
|
||||
but does not yet provide full 1.21.1 gameplay or optional-integration parity.
|
||||
See the [26.3 beta guide](ports/mc263/README.md) and
|
||||
[latest beta release notes](ports/mc263/RELEASE_NOTES_1.0.1.65-beta.1.md).
|
||||
[latest beta release notes](ports/mc263/RELEASE_NOTES_1.0.1.71-beta.1.md).
|
||||
Keep the builds and their saves separate, and back up worlds before updating.
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -900,6 +900,171 @@ loaded; that fixture is not claimed as tested. The server shut down cleanly,
|
||||
and its temporary world was deleted. Actual player arrival in the affected
|
||||
Age and the unchanged Cave/Linking Book paths remain manual checks.
|
||||
|
||||
The local `1.0.1.66-beta.1` slice ports the three 1.21.1 lighting controls.
|
||||
Normal surface types keep the host Overworld holder; Dark disables skylight,
|
||||
Bright sets ambient light to 0.25. Cave Bright is explicitly supported with
|
||||
0.25 ambient and no skylight; Cave Normal/Dark have 0.0 ambient and no skylight.
|
||||
The native vanilla registry-sync path supplies types to clients before travel.
|
||||
Generated data derives three variants from the exact 26.3 vanilla Overworld
|
||||
resource, preserving every key except has_skylight and ambient_light. The
|
||||
generator has declared inputs/outputs and depends on Minecraft artifacts.
|
||||
|
||||
The Age index has an optional lighting map, defaulting old entries to Normal.
|
||||
Creation supplies the choice before constructing the level and saving the
|
||||
index. Restore/reload/reseed read it from the index; retirement removes it and
|
||||
failed retirement restores it before reloading. Pages share grammar/tint and
|
||||
the positive biome-page filter, avoiding the weather-only compiler regression.
|
||||
Missing lighting stays Normal until the random-trait port; Bright instability
|
||||
is pending with the instability system. No sky/celestial renderer is added.
|
||||
The static variants derive vanilla data, not arbitrary data-pack overrides
|
||||
to the host's Overworld type; broader custom type inheritance is not claimed.
|
||||
|
||||
Build/diff checks and generated-data equality checks pass (only the two
|
||||
lighting fields differ from vanilla). A disposable server with a normal
|
||||
Overworld compiled all six surface/Cave lighting-page combinations and
|
||||
reported the expected type, skylight and ambient values. Cave Bright survived
|
||||
unload/reload and reseeding, and all six choices survived a full restart.
|
||||
Overworld skylight/ambient stayed unchanged. An earlier Superflat-host fixture
|
||||
could test surface variants but could not create Cave terrain because the
|
||||
existing backend requires a noise-based host generator; it was discarded and
|
||||
the Cave checks ran on a fresh normal-host fixture. Test servers stopped
|
||||
cleanly and both temporary worlds were deleted. Client-side appearance,
|
||||
authoring, travel, placed lights and multiplayer registry behavior are manual
|
||||
checks, not inferred from the server probes. No commit/push/publication.
|
||||
|
||||
The local `1.0.1.67-beta.1` corrects the visual failure found in slice 66:
|
||||
LightmapRenderStateExtractor and lightmap.fsh use AMBIENT_LIGHT_COLOR, while
|
||||
dimension ambient_light only affects other brightness queries. Merely porting
|
||||
1.21.1's 0.25 field was insufficient. Bright types now provide white native
|
||||
ambient color and ambient_light 1.0; Cave Bright retains no skylight. A scoped
|
||||
NeoForge spawn-placement handler denies natural/chunk-generation MONSTER
|
||||
spawns in Bright runtime Ages, leaving passive/existing mobs, spawners,
|
||||
commands/eggs and event spawns untouched. It does not manufacture propagated
|
||||
block light or alter crop light checks. This supersedes slice 66's Bright
|
||||
appearance and natural-spawn limitation; existing Bright types update on boot.
|
||||
|
||||
Requested follow-up for the color-page port: add ambient-light color as a
|
||||
separate authorable target using minecraft:visual/ambient_light_color. Keep
|
||||
the existing number/color mixing semantics and white Bright default; do not
|
||||
confuse ambient tint with sky/celestial color or its server spawn policy.
|
||||
Color pages themselves remain unported in this slice.
|
||||
Build/diff and generated attribute checks pass. A disposable dedicated server
|
||||
verified the registered event handler denies a successful-default natural
|
||||
zombie placement in surface/Cave Bright, leaves spawner zombies and natural
|
||||
cows unchanged, and leaves Normal unaffected. Those are isolated event-policy
|
||||
probes, not a long-running natural-spawn census. The server shut down and its
|
||||
temporary world was deleted. Actual client appearance awaits player validation.
|
||||
|
||||
The local `1.0.1.68-beta.1` removes the requested-against MobSpawnEvent veto.
|
||||
An Age-scoped LevelLightEngine.getRawBrightness return modifier contributes
|
||||
Bright's full ambient minimum of 15 to combined brightness on both sides.
|
||||
Vanilla Monster.isDarkEnoughToSpawn now sees that light, rather than being
|
||||
overridden or filtered by mob category/spawn reason. Light-ignoring mobs and
|
||||
special spawns retain vanilla rules; no spawner exemptions are introduced.
|
||||
Block/sky storage and propagation remain physical, while combined-light
|
||||
consumers (including plant checks) see ambient light. Cave Bright retains
|
||||
no skylight, and the native white render attribute remains. This supersedes
|
||||
slice 67's separate spawn policy and unchanged-crop-light behavior.
|
||||
|
||||
The lighting probe now calls vanilla's darkness predicate and combined
|
||||
brightness instead of constructing synthetic spawn events. Ambient-color
|
||||
authoring remains a planned part of the color-page port.
|
||||
Build/diff checks pass. A disposable dedicated server loaded the required
|
||||
mixin and reported effective brightness 15 / vanilla darkness false for
|
||||
surface/Cave Bright, versus 0 / true for Dark. These are direct brightness/
|
||||
vanilla-predicate probes, not a natural-spawn census or a fully generated
|
||||
underground-room test; an unloaded Normal column returned native sky light 15.
|
||||
The server stopped cleanly and its temporary world was deleted. Client visual
|
||||
validation remains with the player; no publication is performed.
|
||||
|
||||
The local `1.0.1.69-beta.1` slice completes missing weather/lighting choices
|
||||
at book binding. PortEnvironmentSelection resolves explicit supported pages
|
||||
first and uses independent seed-salted Legacy-rank rolls for omissions.
|
||||
Normal is rank 1, remaining supported modes rank 2: 3:1:1 lighting and
|
||||
6:1:1:1:1:1 weather. These weights derive from the active enum pool and reuse
|
||||
the existing weighted ticket routine, rather than duplicating random-choice
|
||||
logic or enabling unported Cloudy/Rain/Snow modes. It is rank-policy parity,
|
||||
not exact seed-stream parity with 1.21.1's broader trait pool.
|
||||
|
||||
The selected weather/lighting are passed through the existing save-before-
|
||||
catalog creation path. Restoring Ages never calls the resolver, so old Ages
|
||||
retain their saved values. Cave weather is always Off; Cave lighting remains
|
||||
independent and supports Bright. Explicit developer fixtures remain controlled,
|
||||
but create_random now uses the same resolver as pageless books. A read-only
|
||||
environment probe checks seed repeatability and explicit overrides. Superseded
|
||||
Normal-only fromPages helpers are removed. Random feature, sky/celestial,
|
||||
blocklist policy and instability work remain pending.
|
||||
Build/diff checks pass. Disposable dedicated-server probes repeated seeds
|
||||
0/4/7, verified explicit overrides and independent one-field completion, and
|
||||
confirmed Cave weather Off with Bright lighting. Three random Ages saved
|
||||
Always/Normal, Normal/Dark and Fast/Bright respectively; weather/lighting
|
||||
choices and live weather timers persisted through a full server restart.
|
||||
Overworld weather/type stayed unchanged. The server stopped cleanly and its
|
||||
temporary world was deleted. Player book-authoring/visual checks remain
|
||||
manual. No commit, push or publication is performed.
|
||||
|
||||
The local `1.0.1.70-beta.1` slice ports native Overworld/Nether/End sky styles.
|
||||
The shared grammar accepts one style and the biome compiler ignores it by
|
||||
category. Sky is saved in an optional Age-index map with Overworld fallback;
|
||||
restore/reseed/retirement rollback use the saved choice. It does not alter
|
||||
the missing-weather/lighting seed-completion rules, add random sky selection,
|
||||
or enable Static Sky/color/individual-celestial pages prematurely.
|
||||
|
||||
The dimension-data generator now produces 11 sky/lighting variants from the
|
||||
vanilla Overworld baseline. It changes visual skybox/fog/cloud settings only
|
||||
in addition to lighting. Geometry, scale, ceiling, monster settings, default
|
||||
clock and gameplay attributes are retained; no Nether gameplay or End dragon
|
||||
fight is inherited. A derived native day timeline omits only sky/fog/cloud
|
||||
color tracks for non-Overworld styles, preserving schedules and illumination.
|
||||
Native End/no-sky rendering needs no optional renderer or new artwork. The
|
||||
Legacy animated Static Sky renderer cannot be replaced by native flat gray
|
||||
without losing its behavior, so its page remains red/unimplemented.
|
||||
|
||||
Bright detection includes all registered sky variants, preserving both white
|
||||
ambient rendering and the effective brightness floor for surface/Cave Bright.
|
||||
Data equality checks confirm all 11 variants preserve geometry/gameplay and
|
||||
the day timeline keeps native tracks except the three excluded visual colors.
|
||||
Its global time markers remain defined only by minecraft:day, not duplicated
|
||||
in the derived timeline. The grouped timeline list is registered as a tag.
|
||||
Client appearance and multiplayer travel validation remain manual.
|
||||
Registry validation initially caught an invalid inline tag reference and
|
||||
duplicate global clock markers in the derived day timeline; both were fixed
|
||||
before handoff. The sky diagnostic also now supplies a position when sampling
|
||||
the spatial fog attribute. A disposable dedicated server then restored all
|
||||
sampled types after restart, including Nether normal/Bright and End normal/
|
||||
Cave Bright, with the expected reddish/purple fog at the sample position,
|
||||
unchanged geometry/gameplay flags, and effective light 15 on Bright variants.
|
||||
Cave Bright End retained its type after unload/reload; a reseeded Nether Age
|
||||
retained its style after restart. Servers stopped cleanly and temporary test
|
||||
directories/worlds were deleted. These are server/data checks, not a rendered
|
||||
client pass. No commit/push/publication is performed.
|
||||
|
||||
## Static Sky renderer slice
|
||||
|
||||
`1.0.1.71-beta.1` ports the 1.21.1 Legacy-style animated Static Sky, superseding
|
||||
the previous unsupported/red status. The six independent 48×48 noise faces
|
||||
use the existing default gray tint (146 per channel), noise range 65–224,
|
||||
and a two-world-tick refresh. A cached 24-vertex cube and a single 288×48
|
||||
dynamic texture use the native textured sky pipeline instead of rebuilding
|
||||
thousands of colored cells every frame. No new asset or dependency is needed.
|
||||
|
||||
Client-only mixins carry the animation frame in extracted sky state, reset
|
||||
it with the state, and own/close GPU resources with SkyRenderer. Static types
|
||||
have native skybox NONE; the extracted state alone requests a sky pass and
|
||||
the custom renderer replaces it. End flashes and End gameplay are not enabled.
|
||||
Normal/Dark/Bright/Cave Bright variants preserve the existing lighting policy,
|
||||
geometry and gameplay. Existing persistence handles STATIC as a sky enum value.
|
||||
Random sky selection, color pages, and individual celestial bodies remain
|
||||
separate unfinished work; this slice does not claim complete sky parity.
|
||||
|
||||
Verification: port build passed (no automated unit tests configured). A
|
||||
disposable dedicated server created Static Standard Normal and Static Cave
|
||||
Bright Ages, confirmed their gray fog, NONE native skybox and correct skylight/
|
||||
ambient values, then restored both choices after restart. It stopped cleanly;
|
||||
the temporary server/world was deleted. Client animation, travel between sky
|
||||
styles, and restart appearance still require manual validation. No user world
|
||||
was modified, and nothing was committed, pushed or published.
|
||||
|
||||
## Verification of this first slice
|
||||
|
||||
The 1.21.1 `compileJava` and `build` tasks passed offline after the backend
|
||||
|
||||
+141
-1
@@ -5,7 +5,7 @@ with the Minecraft 1.21.1 release. The 26.3 backend persists separate Ages and
|
||||
their seeds. Books, the Book Binder, and the Ink Mixer work, and the Binder can
|
||||
author Standard, Amplified, Island, Flat, Void, or Cave terrain with supported
|
||||
biome-distribution pages, selected material/vegetation/structure features, and
|
||||
independent Age weather. Celestial/lighting systems, instability, crystal
|
||||
independent Age weather and lighting. Celestial systems, instability, crystal
|
||||
portals, and much of the 1.21.1 gameplay remain unported. Use a test world and keep a backup; do not
|
||||
substitute this JAR for the 1.21.1 mod or import 1.21.1 saves.
|
||||
|
||||
@@ -558,6 +558,146 @@ Operator-only `/rivencraft_port age surface_probe <name> <x> <z>` reports the
|
||||
actual highest obstruction and a safe nearby surface without creating a
|
||||
platform or teleporting. It can generate chunks while inspecting the region.
|
||||
|
||||
The local `1.0.1.66-beta.1` slice ports Normal, Dark, and Bright Lighting.
|
||||
One lighting page is accepted per book and its choice is saved in the Age
|
||||
index before catalog publication. Missing lighting currently defaults to
|
||||
Normal; random lighting selection and Bright's instability cost await those
|
||||
systems' later ports. Lighting pages should no longer be tinted red.
|
||||
|
||||
Normal surface Ages retain the Overworld type. Dark removes skylight with
|
||||
ambient light 0.0; Bright uses ambient light 0.25 and retains surface skylight,
|
||||
matching 1.21.1. **Cave + Bright is supported:** it has ambient light 0.25
|
||||
without skylight. Cave Normal/Dark use no skylight and ambient light 0.0.
|
||||
Placed light sources continue working; ambient light is not a placed light
|
||||
level or a guarantee that monsters cannot spawn.
|
||||
|
||||
The build derives three registered dimension-type variants from the current
|
||||
vanilla Overworld JSON, changing only has_skylight and ambient_light. This
|
||||
preserves native attributes/timelines and synchronizes types through the
|
||||
normal login registry exchange; no dynamic client registry mutation is used.
|
||||
These variants currently use the vanilla type, not custom runtime changes to
|
||||
the host Overworld dimension type made by another data pack.
|
||||
Saved lighting survives reload, reseed, restart and retirement rollback.
|
||||
Existing non-Cave Ages without a choice remain Normal; old Cave Ages now use
|
||||
the no-skylight baseline. Use new Ages for clear visual comparisons and avoid
|
||||
confusing previously lit chunks with the current dimension settings.
|
||||
|
||||
Manual check: create Normal/Dark/Bright surface books and Cave + Bright;
|
||||
compare an unlit enclosed room, check placed torches, revisit/restart and
|
||||
confirm the Overworld stays unchanged. Operator diagnostics:
|
||||
`/rivencraft_port age create_lighting <name> <seed> <mode> <terrain>` and
|
||||
`/rivencraft_port age lighting_probe <name>` report the registered type,
|
||||
skylight and ambient light. Test JAR: `1.0.1.66-beta.1`.
|
||||
|
||||
The local `1.0.1.67-beta.1` correction supersedes slice 66's Bright rendering:
|
||||
26.3's lightmap uses `minecraft:visual/ambient_light_color`, not the old
|
||||
dimension `ambient_light` field. Bright surface/Cave types now set that native
|
||||
attribute to white and ambient_light to 1.0. Cave still has no skylight.
|
||||
Bright Ages reject natural/chunk-generation hostile spawns independently of
|
||||
stored light levels. Existing mobs, passive mobs, spawners, eggs, commands
|
||||
and special-event spawns are not deleted or globally disabled. Actual block
|
||||
light propagation and plant light requirements are unchanged. Existing Bright
|
||||
Ages receive the corrected registered types after restart; no new Age needed.
|
||||
|
||||
Planned color-page target: **ambient light color**, alongside sky/celestial
|
||||
colors. Reuse the native 26.3 attribute for authored tints; Bright defaults to
|
||||
neutral white until the color-page port. This does not enable color pages yet.
|
||||
|
||||
The local `1.0.1.68-beta.1` replaces slice 67's special spawn veto with actual
|
||||
effective ambient brightness. The spawn event handler is removed. Bright's
|
||||
combined light-engine reading now has a minimum of 15 on server and client,
|
||||
including after sky darkening. Its white rendering attribute is retained.
|
||||
Vanilla darkness checks therefore reject light-sensitive monsters naturally;
|
||||
light-ignoring mobs, spawners and other spawn sources use their native rules,
|
||||
not Rivencraft exemptions or bans. This does not remove existing mobs.
|
||||
|
||||
Ambient light is not stored as emitted block light or fabricated skylight.
|
||||
Consumers of combined brightness, including plant light checks, now see the
|
||||
ambient contribution. Normal/Dark and non-Rivencraft dimensions are unchanged.
|
||||
Cave Bright remains without skylight. Existing Bright Ages update on restart.
|
||||
This supersedes slice 67's statement that crop light checks are unchanged.
|
||||
|
||||
The local `1.0.1.69-beta.1` slice seed-completes missing weather and lighting
|
||||
pages for newly bound books, including Link-Panel-only random Ages. Explicit
|
||||
pages take precedence. Cave weather remains Off, while Cave lighting can
|
||||
still choose Bright. Old Ages retain their saved choices; they are not rerolled.
|
||||
|
||||
The supported pool uses the 1.21.1 Legacy-rank rule: Normal is rank 1, other
|
||||
choices rank 2. Lighting weights are 3:1:1 for Normal/Dark/Bright; weather
|
||||
weights are 6:1:1:1:1:1 for Normal/Fast/Slow/Off/Always/Storm. The common weight
|
||||
is derived from the supported pool size. Unported Cloudy/Rain/Snow modes are
|
||||
not selected. Independent seed salts keep a missing lighting roll unchanged
|
||||
when weather is explicitly written, and vice versa. This matches the rank
|
||||
policy, not the exact 1.21.1 seed stream/pool. No random feature list, sky or
|
||||
celestial choices are added by this slice.
|
||||
|
||||
`/rivencraft_port age create_random <name> <seed>` uses the same environment
|
||||
resolver and logs its frozen plan. `/rivencraft_port age environment_probe
|
||||
<seed> <terrain>` inspects deterministic defaults and explicit Storm/Bright
|
||||
precedence without creating an Age. Other explicit operator fixtures keep
|
||||
their Normal defaults to isolate the feature under test.
|
||||
|
||||
For manual checking, create several Link-Panel-only books and verify weather/
|
||||
lighting varies across Ages and persists on revisit/restart. Then author an
|
||||
explicit Normal Weather + Normal Lighting book, and a Cave + Bright book.
|
||||
Include explicit Normal pages when comparing terrain without random lighting
|
||||
or weather changes. Random blocklist policy and instability remain pending.
|
||||
|
||||
The local `1.0.1.70-beta.1` slice ports the Overworld, Nether, and End Sky
|
||||
pages. One sky style is accepted per book and persisted independently of
|
||||
terrain, weather and lighting. Missing sky pages still use Overworld; random
|
||||
sky selection is not part of this slice. Static Sky remains unsupported/red
|
||||
until its Legacy animated renderer is ported, rather than being replaced by
|
||||
a flat gray backdrop. Color and individual celestial pages are also pending.
|
||||
|
||||
Nether Sky uses the native no-sky backdrop and reddish fog; End Sky uses the
|
||||
native End backdrop. Neither changes dimension height, coordinate scale,
|
||||
terrain, Nether water/bed rules, or enables an End dragon fight. All lighting
|
||||
choices work with these styles, including Cave + Bright. Bright's render
|
||||
attribute and effective ambient level 15 are retained on each variant.
|
||||
|
||||
The registered variants derive the vanilla Overworld type and preserve its
|
||||
gameplay attributes. Non-Overworld skies use a derived day timeline without
|
||||
the three sky/fog/cloud tint tracks, retaining native gameplay schedules and
|
||||
illumination while avoiding Overworld day tint on those backdrops. Their
|
||||
cloud color is transparent. Biome/weather fog modifiers still apply. Types
|
||||
sync through normal registry login; no new rendering dependency is required.
|
||||
Sky choices survive reload/reseed/restart and retirement rollback. Old Ages
|
||||
without a sky choice retain Overworld style.
|
||||
|
||||
For visual checks, author Plains + Normal Lighting + No Weather with each
|
||||
sky page; compare the sky/fog and verify the same terrain and normal bed/water
|
||||
behavior. Then try Cave + Bright + End Sky and revisit after restarting.
|
||||
Static Sky should remain red. Operator fixtures:
|
||||
`/rivencraft_port age create_sky <name> <seed> <sky> <lighting> <terrain>` and
|
||||
`/rivencraft_port age sky_probe <name>`. Use new books to author a sky choice;
|
||||
this does not rewrite the sky choice of already-bound books.
|
||||
|
||||
The local `1.0.1.71-beta.1` slice adds the previously pending Static Sky.
|
||||
Its client renderer adapts the 1.21.1 Legacy-style background: six independent
|
||||
48×48 noise faces, default gray tint `#929292`, and noise intensity 65–224.
|
||||
The noise updates every two world ticks. A single small dynamic texture and
|
||||
cached cube replace thousands of per-cell draw vertices; resources are closed
|
||||
with the sky renderer. No artwork or rendering dependency is added.
|
||||
|
||||
Static Sky is now supported rather than red, and persists through the same
|
||||
Age index as other sky styles. Four registered lighting variants retain the
|
||||
existing terrain/gameplay rules. Their native skybox is NONE (no End flashes);
|
||||
only the extracted client state schedules the custom background sky pass.
|
||||
Animation state resets between frames and does not consult a different live
|
||||
world while rendering. Color pages, celestial pages, and random sky selection
|
||||
remain pending.
|
||||
|
||||
Build and disposable dedicated-server creation/restart checks passed for
|
||||
Static + Normal + Standard and Static + Bright + Cave. These do not validate
|
||||
client rendering. For manual testing, bind a new Static Sky + Normal Lighting
|
||||
+ No Weather book, look around/above/below for animated gray static, then
|
||||
visit an Overworld/End Sky Age and return to check there is no sky-state leak.
|
||||
Revisit after restarting. Also try Static Sky + Cave + Bright (look outside
|
||||
terrain or in spectator mode). The fixture accepts `static` as its sky value.
|
||||
The dedicated test server was stopped and its temporary world deleted.
|
||||
Changes remain local; no commit/push/publication was requested.
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Rivencraft 26.3 beta 1.0.1.71-beta.1
|
||||
|
||||
This beta collects the player-tested lighting and sky port slices since
|
||||
1.0.1.65-beta.1. It is not yet feature-complete with Minecraft 1.21.1.
|
||||
|
||||
## Lighting and seeded environments
|
||||
|
||||
- Added Normal, Dark, and Bright lighting pages with saved per-Age settings.
|
||||
- Bright works in Cave terrain too, without adding skylight or weather.
|
||||
- Bright supplies effective ambient light level 15 to native combined-light
|
||||
checks and the white ambient rendering attribute. Vanilla darkness-based
|
||||
monster checks respond to that lighting; there is no separate mob-spawn
|
||||
veto. Mobs that ignore light retain their normal rules.
|
||||
- Missing weather and lighting controllers now resolve deterministically from
|
||||
the Age seed, using independent choices and Legacy-style Normal weighting.
|
||||
Explicit pages win; Cave terrain still forces weather Off. Existing Ages
|
||||
keep their saved choices rather than rerolling on load.
|
||||
|
||||
## Sky styles
|
||||
|
||||
- Added Overworld, Nether, End, and animated Static Sky pages, independently
|
||||
of terrain, weather, and lighting. Choices persist on reload and restart.
|
||||
- Nether Sky uses a reddish fog backdrop; End Sky uses the native End sky.
|
||||
These are visual styles, not Nether/End terrain or gameplay conversions:
|
||||
dimension geometry, coordinate scale, bed/water rules, and dragon behavior
|
||||
are not changed.
|
||||
- Static Sky adapts the existing Legacy-style renderer: six independent
|
||||
48×48 animated gray noise faces. A cached cube and small dynamic texture
|
||||
avoid rebuilding thousands of per-cell vertices each frame. Client-only
|
||||
rendering resources are released with the sky renderer.
|
||||
- Non-Overworld backgrounds have transparent clouds and a derived day
|
||||
timeline that avoids Overworld sky/fog/cloud color tracks while retaining
|
||||
gameplay and illumination schedules.
|
||||
- Updated supported-page feedback and added operator sky/environment probes.
|
||||
|
||||
## Current limits and testing
|
||||
|
||||
- Missing Sky pages still default to Overworld; random sky selection, color
|
||||
pages and individual celestial bodies remain pending. Number-controlled
|
||||
Static Sky resolution is tracked separately in issue #109 for both versions.
|
||||
- The writing/discovery loop, instability, portals, and optional-integration
|
||||
parity are still incomplete. Creative availability is not a support claim;
|
||||
check page tooltips and the beta guide for supported combinations.
|
||||
- Previously generated chunks are not regenerated. Save compatibility during
|
||||
the beta is not guaranteed; keep backups and keep both version tracks apart.
|
||||
|
||||
The player reported in-game passes for the included slices, including Static
|
||||
Sky. Build checks passed for both version tracks. Targeted disposable-server
|
||||
checks covered environment defaults, explicit choices, registered sky/lighting
|
||||
variants, persistence and restart, including Cave + Bright and Static Sky.
|
||||
This is not a claim of complete dedicated-server or optional-mod coverage.
|
||||
Test servers were stopped and temporary worlds removed.
|
||||
|
||||
Requires Minecraft **26.3**, NeoForge **26.3.0.99** and **Java 25**. Dynamic
|
||||
Dimensions and Bad Packets are not required by this build. The 1.21.1 release
|
||||
and its dependencies are unchanged.
|
||||
|
||||
Report issues and help improve the wiki at
|
||||
[BlackSheep Git](https://git.paddedcell.org/dnorberg/rivencraft).
|
||||
@@ -121,6 +121,71 @@ def generatePortLanguage = tasks.register('generatePortLanguage', GeneratePortLa
|
||||
}
|
||||
sourceSets.main.resources.srcDir generatePortLanguage
|
||||
|
||||
// Derive registered presentation variants without duplicating vanilla gameplay settings.
|
||||
abstract class GeneratePortDimensionTypes extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getMinecraftJar()
|
||||
@OutputDirectory abstract DirectoryProperty getOutputDir()
|
||||
|
||||
@TaskAction void generate() {
|
||||
new java.util.zip.ZipFile(minecraftJar.get().asFile).withCloseable { archive ->
|
||||
def entry = archive.getEntry('data/minecraft/dimension_type/overworld.json')
|
||||
if (entry == null) throw new GradleException('Missing vanilla Overworld dimension type')
|
||||
String template = archive.getInputStream(entry).withCloseable { it.getText('UTF-8') }
|
||||
def dayEntry = archive.getEntry('data/minecraft/timeline/day.json')
|
||||
if (dayEntry == null) throw new GradleException('Missing vanilla day timeline')
|
||||
def day = archive.getInputStream(dayEntry).withCloseable {
|
||||
new groovy.json.JsonSlurper().parseText(it.getText('UTF-8'))
|
||||
}
|
||||
// Keep gameplay schedules and illumination, but do not tint these
|
||||
// native non-Overworld sky backgrounds blue/black with the day cycle.
|
||||
['fog_color', 'sky_color', 'cloud_color'].each { day.tracks.remove("minecraft:visual/${it}".toString()) }
|
||||
// Markers are global per clock and already owned by minecraft:day.
|
||||
day.remove('time_markers')
|
||||
File dayFile = new File(outputDir.get().asFile, 'data/rivencraft/timeline/age_sky_day.json')
|
||||
dayFile.parentFile.mkdirs()
|
||||
dayFile.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(day)) + '\n'
|
||||
File timelineTag = new File(outputDir.get().asFile, 'data/rivencraft/tags/timeline/age_sky.json')
|
||||
timelineTag.parentFile.mkdirs()
|
||||
timelineTag.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson([
|
||||
values: ['#minecraft:universal', 'rivencraft:age_sky_day',
|
||||
'minecraft:moon', 'minecraft:early_game']])) + '\n'
|
||||
['overworld', 'nether', 'end', 'static'].each { sky ->
|
||||
['age_normal', 'age_dark', 'age_bright', 'age_cave_bright'].each { lighting ->
|
||||
if (sky == 'overworld' && lighting == 'age_normal') return
|
||||
String name = lighting + (sky == 'overworld' ? '' : "_sky_${sky}")
|
||||
def definition = new groovy.json.JsonSlurper().parseText(template)
|
||||
definition.has_skylight = lighting in ['age_normal', 'age_bright']
|
||||
definition.ambient_light = lighting in ['age_bright', 'age_cave_bright'] ? 1.0 : 0.0
|
||||
// 26.3's lightmap reads this attribute, not ambient_light.
|
||||
if (definition.ambient_light == 1.0)
|
||||
definition.attributes['minecraft:visual/ambient_light_color'] = '#ffffff'
|
||||
if (sky != 'overworld') {
|
||||
definition.skybox = sky == 'end' ? 'end' : 'none'
|
||||
definition.attributes['minecraft:visual/fog_color'] = sky == 'static' ? '#929292' : sky == 'end' ? '#181318' : '#6b2725'
|
||||
definition.attributes['minecraft:visual/sky_color'] = sky == 'static' ? '#929292' : sky == 'end' ? '#000000' : '#6b2725'
|
||||
definition.attributes['minecraft:visual/cloud_color'] = '#00000000'
|
||||
if (sky == 'nether') {
|
||||
definition.attributes['minecraft:visual/fog_start_distance'] = 10.0
|
||||
definition.attributes['minecraft:visual/fog_end_distance'] = 96.0
|
||||
}
|
||||
definition.timelines = '#rivencraft:age_sky'
|
||||
}
|
||||
File result = new File(outputDir.get().asFile, "data/rivencraft/dimension_type/${name}.json")
|
||||
result.parentFile.mkdirs()
|
||||
result.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(definition)) + '\n'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def generatePortDimensionTypes = tasks.register('generatePortDimensionTypes', GeneratePortDimensionTypes) {
|
||||
dependsOn 'createMinecraftArtifacts'
|
||||
minecraftJar.set(layout.buildDirectory.file("moddev/artifacts/minecraft-patched-${neo_version}.jar"))
|
||||
outputDir.set(layout.buildDirectory.dir('generated/resources/dimensionTypes'))
|
||||
}
|
||||
sourceSets.main.resources.srcDir generatePortDimensionTypes
|
||||
|
||||
tasks.named('processResources', ProcessResources).configure {
|
||||
// Reuse the existing sprite and base model without copying binary assets.
|
||||
from('../../src/main/resources') {
|
||||
|
||||
@@ -7,4 +7,4 @@ neo_version=26.3.0.99
|
||||
mod_id=rivencraft
|
||||
mod_name=Rivencraft
|
||||
mod_license=LGPL-3.0-or-later
|
||||
mod_version=1.0.1.65-beta.1
|
||||
mod_version=1.0.1.71-beta.1
|
||||
@@ -8,6 +8,13 @@
|
||||
"item.rivencraft.page.unresolved": "Unknown Symbol Page",
|
||||
"item.rivencraft.page.symbol.implemented": "Implemented in 26.3; book and terrain requirements still apply.",
|
||||
"symbol.rivencraft.weather.normal.description": "Use an independent normal weather cycle. Cave terrain keeps weather off.",
|
||||
"symbol.rivencraft.sky.overworld.description": "Use the native Overworld sky. Terrain and lighting remain independently authored.",
|
||||
"symbol.rivencraft.sky.nether.description": "Use a Nether-style sky and reddish fog, without Nether terrain or gameplay rules.",
|
||||
"symbol.rivencraft.sky.end.description": "Use the native End sky backdrop, without End terrain or a dragon fight.",
|
||||
"symbol.rivencraft.sky.static.description": "An animated gray static sky surrounds the Age.",
|
||||
"symbol.rivencraft.lighting.normal.description": "Use normal skylight and ambient brightness. Cave terrain remains without skylight.",
|
||||
"symbol.rivencraft.lighting.dark.description": "Remove skylight from this Age; placed light sources still work.",
|
||||
"symbol.rivencraft.lighting.bright.description": "Provide full ambient brightness and effective light level 15. Vanilla light-based spawn rules apply, including in Cave terrain without skylight.",
|
||||
"symbol.rivencraft.weather.fast.description": "Use the faster Legacy weather cycle in this Age only. Cave terrain keeps weather off.",
|
||||
"symbol.rivencraft.weather.slow.description": "Use the slower Legacy weather cycle in this Age only. Cave terrain keeps weather off.",
|
||||
"symbol.rivencraft.weather.off.description": "Keep this Age clear without changing Overworld weather.",
|
||||
|
||||
@@ -34,7 +34,11 @@ final class AgeLevelIndex extends SavedData {
|
||||
Codec.unboundedMap(Identifier.CODEC, PortLakePlan.CODEC)
|
||||
.optionalFieldOf("lakes", Map.of()).forGetter(index -> index.lakes),
|
||||
Codec.unboundedMap(Identifier.CODEC, PortTerrainFeaturePlan.CODEC)
|
||||
.optionalFieldOf("terrain_features", Map.of()).forGetter(index -> index.terrainFeatures)
|
||||
.optionalFieldOf("terrain_features", Map.of()).forGetter(index -> index.terrainFeatures),
|
||||
Codec.unboundedMap(Identifier.CODEC, PortLighting.CODEC)
|
||||
.optionalFieldOf("lighting", Map.of()).forGetter(index -> index.lighting),
|
||||
Codec.unboundedMap(Identifier.CODEC, PortSkyStyle.CODEC)
|
||||
.optionalFieldOf("sky", Map.of()).forGetter(index -> index.sky)
|
||||
).apply(instance, AgeLevelIndex::new));
|
||||
|
||||
static final SavedDataType<AgeLevelIndex> TYPE = new SavedDataType<>(
|
||||
@@ -53,6 +57,8 @@ final class AgeLevelIndex extends SavedData {
|
||||
private final Map<Identifier, Integer> dungeons;
|
||||
private final Map<Identifier, PortLakePlan> lakes;
|
||||
private final Map<Identifier, PortTerrainFeaturePlan> terrainFeatures;
|
||||
private final Map<Identifier, PortLighting> lighting;
|
||||
private final Map<Identifier, PortSkyStyle> sky;
|
||||
|
||||
private AgeLevelIndex() {
|
||||
this.ages = new LinkedHashMap<>();
|
||||
@@ -65,6 +71,8 @@ final class AgeLevelIndex extends SavedData {
|
||||
this.dungeons = new LinkedHashMap<>();
|
||||
this.lakes = new LinkedHashMap<>();
|
||||
this.terrainFeatures = new LinkedHashMap<>();
|
||||
this.lighting = new LinkedHashMap<>();
|
||||
this.sky = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
private AgeLevelIndex(Map<Identifier, Long> ages, List<Identifier> retired,
|
||||
@@ -75,7 +83,9 @@ final class AgeLevelIndex extends SavedData {
|
||||
Map<Identifier, PortVegetationPlan> vegetation,
|
||||
Map<Identifier, Integer> dungeons,
|
||||
Map<Identifier, PortLakePlan> lakes,
|
||||
Map<Identifier, PortTerrainFeaturePlan> terrainFeatures) {
|
||||
Map<Identifier, PortTerrainFeaturePlan> terrainFeatures,
|
||||
Map<Identifier, PortLighting> lighting,
|
||||
Map<Identifier, PortSkyStyle> sky) {
|
||||
this.ages = new LinkedHashMap<>(ages);
|
||||
this.retired = new LinkedHashSet<>(retired);
|
||||
this.biomeOverrides = new LinkedHashMap<>(biomeOverrides);
|
||||
@@ -86,6 +96,8 @@ final class AgeLevelIndex extends SavedData {
|
||||
this.dungeons = new LinkedHashMap<>(dungeons);
|
||||
this.lakes = new LinkedHashMap<>(lakes);
|
||||
this.terrainFeatures = new LinkedHashMap<>(terrainFeatures);
|
||||
this.lighting = new LinkedHashMap<>(lighting);
|
||||
this.sky = new LinkedHashMap<>(sky);
|
||||
}
|
||||
|
||||
Map<Identifier, Long> entries() {
|
||||
@@ -116,10 +128,12 @@ final class AgeLevelIndex extends SavedData {
|
||||
int dungeons(Identifier id) { return dungeons.getOrDefault(id, 0); }
|
||||
PortLakePlan lakes(Identifier id) { return lakes.get(id); }
|
||||
PortTerrainFeaturePlan terrainFeatures(Identifier id) { return terrainFeatures.get(id); }
|
||||
PortLighting lighting(Identifier id) { return lighting.getOrDefault(id, PortLighting.NORMAL); }
|
||||
PortSkyStyle sky(Identifier id) { return sky.getOrDefault(id, PortSkyStyle.OVERWORLD); }
|
||||
|
||||
void add(Identifier id, long seed, PortBiomePlan biomePlan, PortAgeTerrain ageTerrain,
|
||||
PortRockPlan rockPlan, PortVegetationPlan vegetationPlan, int dungeonCount,
|
||||
PortLakePlan lakePlan, PortTerrainFeaturePlan terrainFeaturePlan) {
|
||||
PortLakePlan lakePlan, PortTerrainFeaturePlan terrainFeaturePlan, PortLighting light, PortSkyStyle skyStyle) {
|
||||
if (contains(id)) {
|
||||
throw new IllegalArgumentException("Age already exists: " + id);
|
||||
}
|
||||
@@ -131,6 +145,8 @@ final class AgeLevelIndex extends SavedData {
|
||||
if (dungeonCount > 0) dungeons.put(id, dungeonCount);
|
||||
if (lakePlan != null) lakes.put(id, lakePlan);
|
||||
if (terrainFeaturePlan != null) terrainFeatures.put(id, terrainFeaturePlan);
|
||||
if (light != PortLighting.NORMAL) lighting.put(id, light);
|
||||
if (skyStyle != PortSkyStyle.OVERWORLD) sky.put(id, skyStyle);
|
||||
setDirty();
|
||||
}
|
||||
|
||||
@@ -144,6 +160,8 @@ final class AgeLevelIndex extends SavedData {
|
||||
dungeons.remove(id);
|
||||
lakes.remove(id);
|
||||
terrainFeatures.remove(id);
|
||||
lighting.remove(id);
|
||||
sky.remove(id);
|
||||
retired.add(id);
|
||||
setDirty();
|
||||
}
|
||||
@@ -151,7 +169,8 @@ final class AgeLevelIndex extends SavedData {
|
||||
void restoreAfterFailedRetirement(Identifier id, long seed, PortBiomePlan biomePlan,
|
||||
PortAgeTerrain ageTerrain, PortRockPlan rockPlan,
|
||||
PortVegetationPlan vegetationPlan, int dungeonCount,
|
||||
PortLakePlan lakePlan, PortTerrainFeaturePlan terrainFeaturePlan) {
|
||||
PortLakePlan lakePlan, PortTerrainFeaturePlan terrainFeaturePlan, PortLighting light,
|
||||
PortSkyStyle skyStyle) {
|
||||
if (!retired.remove(id)) throw new IllegalStateException("Age is not retired: " + id);
|
||||
ages.put(id, seed);
|
||||
if (biomePlan != null) biomePlans.put(id, biomePlan);
|
||||
@@ -161,6 +180,8 @@ final class AgeLevelIndex extends SavedData {
|
||||
if (dungeonCount > 0) dungeons.put(id, dungeonCount);
|
||||
if (lakePlan != null) lakes.put(id, lakePlan);
|
||||
if (terrainFeaturePlan != null) terrainFeatures.put(id, terrainFeaturePlan);
|
||||
if (light != PortLighting.NORMAL) lighting.put(id, light);
|
||||
if (skyStyle != PortSkyStyle.OVERWORLD) sky.put(id, skyStyle);
|
||||
setDirty();
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,32 @@ public final class PortAgeCommands {
|
||||
return 1;
|
||||
})))
|
||||
.then(Commands.literal("age")
|
||||
.then(Commands.literal("create_sky")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.then(Commands.argument("seed", LongArgumentType.longArg())
|
||||
.then(Commands.argument("sky", StringArgumentType.word())
|
||||
.then(Commands.argument("lighting", StringArgumentType.word())
|
||||
.then(Commands.argument("terrain", StringArgumentType.word())
|
||||
.executes(PortAgeCommands::createSkyAge)))))))
|
||||
.then(Commands.literal("sky_probe")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.executes(PortAgeCommands::probeSky)))
|
||||
.then(Commands.literal("environment_probe")
|
||||
.then(Commands.argument("seed", LongArgumentType.longArg())
|
||||
.then(Commands.argument("terrain", StringArgumentType.word())
|
||||
.executes(PortAgeCommands::probeEnvironment))))
|
||||
.then(Commands.literal("create_lighting")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.then(Commands.argument("seed", LongArgumentType.longArg())
|
||||
.then(Commands.argument("mode", StringArgumentType.word())
|
||||
.suggests((context, builder) -> net.minecraft.commands.SharedSuggestionProvider.suggest(
|
||||
java.util.Arrays.stream(PortLighting.values())
|
||||
.map(mode -> mode.name().toLowerCase(Locale.ROOT)), builder))
|
||||
.then(Commands.argument("terrain", StringArgumentType.word())
|
||||
.executes(PortAgeCommands::createLightingAge))))))
|
||||
.then(Commands.literal("lighting_probe")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.executes(PortAgeCommands::probeLighting)))
|
||||
.then(Commands.literal("surface_probe")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.then(Commands.argument("x", IntegerArgumentType.integer())
|
||||
@@ -137,10 +163,13 @@ public final class PortAgeCommands {
|
||||
long seed = LongArgumentType.getLong(context, "seed");
|
||||
PortRandomAgeSelection.Selection selected = PortRandomAgeSelection.select(
|
||||
context.getSource().getServer(), seed);
|
||||
int created = createAge(context, selected.terrain(), selected.biomes());
|
||||
var environment = PortEnvironmentSelection.resolve(java.util.List.of(), seed, selected.terrain());
|
||||
int created = createAge(context, selected.terrain(), selected.biomes(),
|
||||
null, null, 0, null, null, environment);
|
||||
if (created != 0) context.getSource().sendSuccess(() -> Component.literal(
|
||||
"Random Age plan: terrain=" + selected.terrain()
|
||||
+ " biomes=" + selected.biomes()), false);
|
||||
+ " biomes=" + selected.biomes()
|
||||
+ " environment=" + environment), false);
|
||||
return created;
|
||||
}))))
|
||||
.then(Commands.literal("create_rocks")
|
||||
@@ -318,6 +347,115 @@ public final class PortAgeCommands {
|
||||
.executes(PortAgeCommands::sampleBiome)))))));
|
||||
}
|
||||
|
||||
private static int createSkyAge(CommandContext<CommandSourceStack> context) {
|
||||
try {
|
||||
PortSkyStyle sky = PortSkyStyle.valueOf(StringArgumentType.getString(context, "sky").toUpperCase(Locale.ROOT));
|
||||
PortLighting lighting = PortLighting.valueOf(StringArgumentType.getString(context, "lighting").toUpperCase(Locale.ROOT));
|
||||
PortAgeTerrain terrain = PortAgeTerrain.valueOf(StringArgumentType.getString(context, "terrain").toUpperCase(Locale.ROOT));
|
||||
var pages = java.util.List.of(
|
||||
Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "sky/" + sky.name().toLowerCase(Locale.ROOT)),
|
||||
Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "lighting/" + lighting.name().toLowerCase(Locale.ROOT)),
|
||||
Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "terrain/" + terrain.name().toLowerCase(Locale.ROOT)),
|
||||
Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "weather/off"));
|
||||
var server = context.getSource().getServer();
|
||||
var biomes = net.dnorberg.rivencraft.writing.PortAuthoredWriting.biomePlan(pages, server.registryAccess());
|
||||
RuntimeAgeBackend.create(server, ageId(context), LongArgumentType.getLong(context, "seed"),
|
||||
biomes, terrain, null, null, 0, null, null, PortWeatherMode.OFF, lighting, sky);
|
||||
return probeSky(context);
|
||||
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||
context.getSource().sendFailure(Component.literal(exception.getMessage()));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int probeSky(CommandContext<CommandSourceStack> context) {
|
||||
Identifier id = ageId(context);
|
||||
var server = context.getSource().getServer();
|
||||
var level = server.getLevel(ResourceKey.create(Registries.DIMENSION, id));
|
||||
if (level == null) {
|
||||
context.getSource().sendFailure(Component.literal("Age not loaded: " + id));
|
||||
return 0;
|
||||
}
|
||||
var type = level.dimensionType();
|
||||
context.getSource().sendSuccess(() -> Component.literal("Sky probe " + id
|
||||
+ " type=" + level.dimensionTypeRegistration().unwrapKey().orElseThrow().identifier()
|
||||
+ " skybox=" + type.skybox() + " skylight=" + type.hasSkyLight() + " ambient=" + type.ambientLight()
|
||||
+ " fog=" + level.environmentAttributes().getValue(net.minecraft.world.attribute.EnvironmentAttributes.FOG_COLOR,
|
||||
new net.minecraft.core.BlockPos(0, 80, 0))
|
||||
+ " ceiling=" + type.hasCeiling() + " scale=" + type.coordinateScale()
|
||||
+ " minY=" + type.minY() + " height=" + type.height() + " dragon=" + type.hasEnderDragonFight()), false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int probeEnvironment(CommandContext<CommandSourceStack> context) {
|
||||
try {
|
||||
long seed = LongArgumentType.getLong(context, "seed");
|
||||
PortAgeTerrain terrain = PortAgeTerrain.valueOf(
|
||||
StringArgumentType.getString(context, "terrain").toUpperCase(Locale.ROOT));
|
||||
var selected = PortEnvironmentSelection.resolve(java.util.List.of(), seed, terrain);
|
||||
var explicitPages = java.util.List.of(
|
||||
Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "weather/storm"),
|
||||
Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "lighting/bright"));
|
||||
var explicit = PortEnvironmentSelection.resolve(explicitPages, seed, terrain);
|
||||
var weatherOnly = PortEnvironmentSelection.resolve(java.util.List.of(explicitPages.get(0)), seed, terrain);
|
||||
var lightingOnly = PortEnvironmentSelection.resolve(java.util.List.of(explicitPages.get(1)), seed, terrain);
|
||||
if (!selected.equals(PortEnvironmentSelection.resolve(java.util.List.of(), seed, terrain))
|
||||
|| explicit.lighting() != PortLighting.BRIGHT
|
||||
|| explicit.weather() != (terrain == PortAgeTerrain.CAVE ? PortWeatherMode.OFF : PortWeatherMode.STORM)
|
||||
|| weatherOnly.lighting() != selected.lighting() || lightingOnly.weather() != selected.weather())
|
||||
throw new IllegalStateException("Environment selection invariant failed");
|
||||
context.getSource().sendSuccess(() -> Component.literal("Environment probe seed=" + seed
|
||||
+ " terrain=" + terrain + " selected=" + selected + " explicit=" + explicit
|
||||
+ " deterministic=true"), false);
|
||||
return 1;
|
||||
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||
context.getSource().sendFailure(Component.literal(exception.getMessage()));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int createLightingAge(CommandContext<CommandSourceStack> context) {
|
||||
try {
|
||||
PortLighting mode = PortLighting.valueOf(
|
||||
StringArgumentType.getString(context, "mode").toUpperCase(Locale.ROOT));
|
||||
PortAgeTerrain terrain = PortAgeTerrain.valueOf(
|
||||
StringArgumentType.getString(context, "terrain").toUpperCase(Locale.ROOT));
|
||||
var pages = java.util.List.of(
|
||||
Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "lighting/" + mode.name().toLowerCase(Locale.ROOT)),
|
||||
Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "terrain/" + terrain.name().toLowerCase(Locale.ROOT)));
|
||||
var server = context.getSource().getServer();
|
||||
var biomePlan = net.dnorberg.rivencraft.writing.PortAuthoredWriting.biomePlan(pages, server.registryAccess());
|
||||
RuntimeAgeBackend.create(server, ageId(context), LongArgumentType.getLong(context, "seed"),
|
||||
biomePlan, terrain, null, null, 0, null, null, PortWeatherMode.NORMAL, mode);
|
||||
return probeLighting(context);
|
||||
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||
context.getSource().sendFailure(Component.literal(exception.getMessage()));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int probeLighting(CommandContext<CommandSourceStack> context) {
|
||||
Identifier id = ageId(context);
|
||||
var server = context.getSource().getServer();
|
||||
ServerLevel level = server.getLevel(ResourceKey.create(Registries.DIMENSION, id));
|
||||
if (level == null) {
|
||||
context.getSource().sendFailure(Component.literal("Age not loaded: " + id));
|
||||
return 0;
|
||||
}
|
||||
context.getSource().sendSuccess(() -> Component.literal("Lighting probe " + id
|
||||
+ " type=" + level.dimensionTypeRegistration().unwrapKey().orElseThrow().identifier()
|
||||
+ " skylight=" + level.dimensionType().hasSkyLight()
|
||||
+ " ambient=" + level.dimensionType().ambientLight()
|
||||
+ " undergroundBrightness=" + level.getMaxLocalRawBrightness(
|
||||
new net.minecraft.core.BlockPos(0, level.getMinY() + 2, 0))
|
||||
+ " vanillaDarkEnough=" + net.minecraft.world.entity.monster.Monster.isDarkEnoughToSpawn(
|
||||
level, new net.minecraft.core.BlockPos(0, level.getMinY() + 2, 0),
|
||||
net.minecraft.util.RandomSource.create(0))
|
||||
+ " overworldSkylight=" + server.overworld().dimensionType().hasSkyLight()
|
||||
+ " overworldAmbient=" + server.overworld().dimensionType().ambientLight()), false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int probeSurface(CommandContext<CommandSourceStack> context) {
|
||||
Identifier id = ageId(context);
|
||||
ServerLevel level = context.getSource().getServer().getLevel(ResourceKey.create(Registries.DIMENSION, id));
|
||||
@@ -602,13 +740,23 @@ public final class PortAgeCommands {
|
||||
PortAgeTerrain terrain, PortBiomePlan biomePlan,
|
||||
PortRockPlan rockPlan, PortVegetationPlan vegetationPlan,
|
||||
int dungeons, PortLakePlan lakes, PortTerrainFeaturePlan terrainFeatures) {
|
||||
return createAge(context, terrain, biomePlan, rockPlan, vegetationPlan, dungeons, lakes, terrainFeatures,
|
||||
new PortEnvironmentSelection(PortWeatherMode.NORMAL, PortLighting.NORMAL));
|
||||
}
|
||||
|
||||
private static int createAge(CommandContext<CommandSourceStack> context,
|
||||
PortAgeTerrain terrain, PortBiomePlan biomePlan,
|
||||
PortRockPlan rockPlan, PortVegetationPlan vegetationPlan,
|
||||
int dungeons, PortLakePlan lakes, PortTerrainFeaturePlan terrainFeatures,
|
||||
PortEnvironmentSelection environment) {
|
||||
String name = StringArgumentType.getString(context, "name");
|
||||
Identifier id = Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "age/" + name);
|
||||
long seed = LongArgumentType.getLong(context, "seed");
|
||||
ServerLevel level;
|
||||
try {
|
||||
level = RuntimeAgeBackend.create(context.getSource().getServer(), id, seed, biomePlan,
|
||||
terrain, rockPlan, vegetationPlan, dungeons, lakes, terrainFeatures);
|
||||
terrain, rockPlan, vegetationPlan, dungeons, lakes, terrainFeatures,
|
||||
environment.weather(), environment.lighting());
|
||||
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||
context.getSource().sendFailure(Component.literal(exception.getMessage()));
|
||||
return 0;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package net.dnorberg.rivencraft.age;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
/** Seed-completes missing environment controllers once, before an Age is saved. */
|
||||
public record PortEnvironmentSelection(PortWeatherMode weather, PortLighting lighting) {
|
||||
public static PortEnvironmentSelection resolve(List<Identifier> pages, long seed, PortAgeTerrain terrain) {
|
||||
PortLighting lighting = pages.stream().map(PortLighting::forPage).filter(java.util.Objects::nonNull)
|
||||
.findFirst().orElseGet(() -> rankedDefault(new Random(seed ^ 0x4C49474854494E47L), PortLighting.values()));
|
||||
PortWeatherMode weather = terrain == PortAgeTerrain.CAVE ? PortWeatherMode.OFF
|
||||
: pages.stream().map(PortWeatherMode::forPage).filter(java.util.Objects::nonNull)
|
||||
.findFirst().orElseGet(() -> rankedDefault(new Random(seed ^ 0x574541544845525FL), PortWeatherMode.values()));
|
||||
return new PortEnvironmentSelection(weather, lighting);
|
||||
}
|
||||
|
||||
private static <T> T rankedDefault(Random random, T[] choices) {
|
||||
// LegacyRankChoice: the first (Normal) option is rank 1, the rest rank 2.
|
||||
// Its rank-1 weight is the complete rare group plus one. Derive this
|
||||
// from the supported pool, rather than selecting unported weather.
|
||||
int[] weights = new int[choices.length];
|
||||
Arrays.fill(weights, 1);
|
||||
weights[0] = choices.length;
|
||||
return PortRandomAgeSelection.weighted(random, List.of(choices), weights);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package net.dnorberg.rivencraft.age;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import java.util.Locale;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/** Authored lighting choices using statically registered, client-synchronized types. */
|
||||
public enum PortLighting {
|
||||
NORMAL, DARK, BRIGHT;
|
||||
|
||||
public static final Codec<PortLighting> CODEC = Codec.STRING.xmap(
|
||||
name -> valueOf(name.toUpperCase(Locale.ROOT)), mode -> mode.name().toLowerCase(Locale.ROOT));
|
||||
|
||||
public static PortLighting forPage(Identifier page) {
|
||||
if (!page.getNamespace().equals(Rivencraft.MOD_ID)) return null;
|
||||
return switch (page.getPath()) {
|
||||
case "lighting/normal" -> NORMAL;
|
||||
case "lighting/dark" -> DARK;
|
||||
case "lighting/bright" -> BRIGHT;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean isBright(Level level) {
|
||||
return level.dimensionTypeRegistration().unwrapKey().map(key -> {
|
||||
Identifier id = key.identifier();
|
||||
return id.getNamespace().equals(Rivencraft.MOD_ID)
|
||||
&& (id.getPath().equals("age_bright") || id.getPath().equals("age_cave_bright")
|
||||
|| id.getPath().startsWith("age_bright_sky_")
|
||||
|| id.getPath().startsWith("age_cave_bright_sky_"));
|
||||
}).orElse(false);
|
||||
}
|
||||
|
||||
Holder<DimensionType> dimensionType(MinecraftServer server, PortAgeTerrain terrain,
|
||||
Holder<DimensionType> overworldType, PortSkyStyle sky) {
|
||||
boolean cave = terrain == PortAgeTerrain.CAVE;
|
||||
if (this == NORMAL && !cave && sky == PortSkyStyle.OVERWORLD) return overworldType;
|
||||
String variant = this == BRIGHT ? (cave ? "age_cave_bright" : "age_bright")
|
||||
: this == DARK || cave ? "age_dark" : "age_normal";
|
||||
if (sky != PortSkyStyle.OVERWORLD) variant += "_sky_" + sky.name().toLowerCase(Locale.ROOT);
|
||||
return server.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(
|
||||
ResourceKey.create(Registries.DIMENSION_TYPE,
|
||||
Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, variant)));
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public final class PortRandomAgeSelection {
|
||||
return surface.isEmpty() ? List.of(Identifier.withDefaultNamespace("plains")) : surface;
|
||||
}
|
||||
|
||||
private static <T> T weighted(Random random, List<T> choices, int[] weights) {
|
||||
static <T> T weighted(Random random, List<T> choices, int[] weights) {
|
||||
int total = 0;
|
||||
for (int weight : weights) total += weight;
|
||||
int ticket = random.nextInt(total);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.dnorberg.rivencraft.age;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
/** Persisted sky styles, including the client-rendered Legacy static background. */
|
||||
public enum PortSkyStyle {
|
||||
OVERWORLD, NETHER, END, STATIC;
|
||||
|
||||
public static final Codec<PortSkyStyle> CODEC = Codec.STRING.xmap(
|
||||
name -> valueOf(name.toUpperCase(Locale.ROOT)), sky -> sky.name().toLowerCase(Locale.ROOT));
|
||||
|
||||
public static PortSkyStyle forPage(Identifier page) {
|
||||
if (!page.getNamespace().equals(Rivencraft.MOD_ID)) return null;
|
||||
return switch (page.getPath()) {
|
||||
case "sky/overworld" -> OVERWORLD;
|
||||
case "sky/nether" -> NETHER;
|
||||
case "sky/end" -> END;
|
||||
case "sky/static" -> STATIC;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public static PortSkyStyle fromPages(List<Identifier> pages) {
|
||||
return pages.stream().map(PortSkyStyle::forPage).filter(java.util.Objects::nonNull)
|
||||
.findFirst().orElse(OVERWORLD);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.dnorberg.rivencraft.age;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import java.util.List;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
@@ -26,8 +25,4 @@ public enum PortWeatherMode {
|
||||
};
|
||||
}
|
||||
|
||||
public static PortWeatherMode fromPages(List<Identifier> pages) {
|
||||
return pages.stream().map(PortWeatherMode::forPage).filter(java.util.Objects::nonNull)
|
||||
.findFirst().orElse(NORMAL);
|
||||
}
|
||||
}
|
||||
@@ -113,13 +113,32 @@ public final class RuntimeAgeBackend {
|
||||
PortBiomePlan biomePlan, PortAgeTerrain terrain, PortRockPlan rocks,
|
||||
PortVegetationPlan vegetation, int dungeons, PortLakePlan lakes,
|
||||
PortTerrainFeaturePlan terrainFeatures, PortWeatherMode weather) {
|
||||
return create(server, id, seed, biomePlan, terrain, rocks, vegetation, dungeons, lakes,
|
||||
terrainFeatures, weather, PortLighting.NORMAL);
|
||||
}
|
||||
|
||||
public static ServerLevel create(MinecraftServer server, Identifier id, long seed,
|
||||
PortBiomePlan biomePlan, PortAgeTerrain terrain, PortRockPlan rocks,
|
||||
PortVegetationPlan vegetation, int dungeons, PortLakePlan lakes,
|
||||
PortTerrainFeaturePlan terrainFeatures, PortWeatherMode weather,
|
||||
PortLighting lighting) {
|
||||
return create(server, id, seed, biomePlan, terrain, rocks, vegetation, dungeons, lakes,
|
||||
terrainFeatures, weather, lighting, PortSkyStyle.OVERWORLD);
|
||||
}
|
||||
|
||||
public static ServerLevel create(MinecraftServer server, Identifier id, long seed,
|
||||
PortBiomePlan biomePlan, PortAgeTerrain terrain, PortRockPlan rocks,
|
||||
PortVegetationPlan vegetation, int dungeons, PortLakePlan lakes,
|
||||
PortTerrainFeaturePlan terrainFeatures, PortWeatherMode weather,
|
||||
PortLighting lighting, PortSkyStyle sky) {
|
||||
checkServerThread(server);
|
||||
ageName(id);
|
||||
AgeLevelIndex index = index(server);
|
||||
if (index.contains(id)) throw new IllegalArgumentException("Age already exists: " + id);
|
||||
ServerLevel level = load(server, id, seed, biomePlan, terrain, rocks, vegetation, dungeons, lakes, terrainFeatures);
|
||||
ServerLevel level = load(server, id, seed, biomePlan, terrain, rocks, vegetation, dungeons, lakes,
|
||||
terrainFeatures, lighting, sky);
|
||||
((PortAgeLevel) level).configureWeather(weather);
|
||||
index.add(id, seed, biomePlan, terrain, rocks, vegetation, dungeons, lakes, terrainFeatures);
|
||||
index.add(id, seed, biomePlan, terrain, rocks, vegetation, dungeons, lakes, terrainFeatures, lighting, sky);
|
||||
// A newly bound book may be used immediately. Persist its Age identity
|
||||
// before publishing the catalog so a later crash cannot leave that
|
||||
// book pointing at a world omitted from the on-disk index.
|
||||
@@ -194,6 +213,8 @@ public final class RuntimeAgeBackend {
|
||||
PortTerrainFeaturePlan terrainFeatures = index.terrainFeatures(id);
|
||||
boolean loaded = server.getLevel(dimensionKey(id)) != null;
|
||||
ArchivePaths paths = archivePaths(server, name);
|
||||
PortLighting lighting = index.lighting(id);
|
||||
PortSkyStyle sky = index.sky(id);
|
||||
if (loaded) unloadLoaded(server, id);
|
||||
// Persist the tombstone before moving files: a crash mid-archive must
|
||||
// not let an old book silently regenerate this identity.
|
||||
@@ -211,7 +232,7 @@ public final class RuntimeAgeBackend {
|
||||
} catch (IOException | RuntimeException exception) {
|
||||
try {
|
||||
index.restoreAfterFailedRetirement(id, seed, biomePlan, terrain, rocks, vegetation, dungeons, lakes,
|
||||
terrainFeatures);
|
||||
terrainFeatures, lighting, sky);
|
||||
server.overworld().getDataStorage().saveAndJoin();
|
||||
} catch (RuntimeException rollbackFailure) {
|
||||
exception.addSuppressed(rollbackFailure);
|
||||
@@ -337,6 +358,14 @@ public final class RuntimeAgeBackend {
|
||||
PortBiomePlan biomePlan, PortAgeTerrain terrain, PortRockPlan rocks,
|
||||
PortVegetationPlan vegetation, int dungeons, PortLakePlan lakes,
|
||||
PortTerrainFeaturePlan terrainFeatures) {
|
||||
return load(server, id, seed, biomePlan, terrain, rocks, vegetation, dungeons, lakes,
|
||||
terrainFeatures, index(server).lighting(id), index(server).sky(id));
|
||||
}
|
||||
|
||||
private static ServerLevel load(MinecraftServer server, Identifier id, long seed,
|
||||
PortBiomePlan biomePlan, PortAgeTerrain terrain, PortRockPlan rocks,
|
||||
PortVegetationPlan vegetation, int dungeons, PortLakePlan lakes,
|
||||
PortTerrainFeaturePlan terrainFeatures, PortLighting lighting, PortSkyStyle sky) {
|
||||
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
|
||||
if (server.getLevel(key) != null) throw new IllegalArgumentException("Level already loaded: " + id);
|
||||
|
||||
@@ -409,7 +438,8 @@ public final class RuntimeAgeBackend {
|
||||
}
|
||||
if (rocks != null || vegetation != null || dungeons > 0 || lakes != null || terrainFeatures != null)
|
||||
generator = new PortFeatureChunkGenerator(generator, rocks, vegetation, dungeons, lakes, terrainFeatures, seed);
|
||||
LevelStem stem = new LevelStem(overworldStem.type(), generator, OptionalLong.of(seed));
|
||||
LevelStem stem = new LevelStem(lighting.dimensionType(server, terrain, overworldStem.type(), sky),
|
||||
generator, OptionalLong.of(seed));
|
||||
long biomeZoomSeed = BiomeManager.obfuscateSeed(server.getWorldGenSettings().options().seed());
|
||||
ServerLevel level = new PortAgeLevel(server, key, stem, biomeZoomSeed, terrain);
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package net.dnorberg.rivencraft.client;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.BufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.MeshData;
|
||||
import com.mojang.renderpearl.api.buffers.GpuBuffer;
|
||||
import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
|
||||
import com.mojang.renderpearl.api.pipeline.PrimitiveTopology;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
/** Legacy's six 48x48 noise faces, uploaded once per animation frame rather than per cell. */
|
||||
public final class PortStaticSky implements AutoCloseable {
|
||||
private static final int GRID = 48;
|
||||
private final DynamicTexture texture = new DynamicTexture("Rivencraft static sky", GRID * 6, GRID, false);
|
||||
private final GpuBuffer cube = buildCube();
|
||||
private long uploadedFrame = Long.MIN_VALUE;
|
||||
|
||||
public void render(RenderTarget target, GpuBufferSlice fog, long frame) {
|
||||
if (uploadedFrame != frame) {
|
||||
RandomSource random = RandomSource.create(frame);
|
||||
var image = texture.getPixels();
|
||||
for (int face = 0; face < 6; face++) {
|
||||
for (int y = 0; y < GRID; y++) {
|
||||
for (int x = 0; x < GRID; x++) {
|
||||
int gray = 146 * (65 + random.nextInt(160)) / 255;
|
||||
image.setPixel(face * GRID + x, y, 0xff000000 | gray << 16 | gray << 8 | gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
texture.upload();
|
||||
uploadedFrame = frame;
|
||||
}
|
||||
RenderSystem.setShaderFog(fog);
|
||||
var indices = RenderSystem.getSequentialBuffer(PrimitiveTopology.QUADS);
|
||||
var indexBuffer = indices.getBuffer(36);
|
||||
var transform = RenderSystem.getDynamicUniforms().writeTransform(RenderSystem.getModelViewMatrixCopy());
|
||||
try (var pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(
|
||||
() -> "Rivencraft static sky", target.getColorTextureView(), Optional.empty(),
|
||||
target.getDepthTextureView(), OptionalDouble.empty())) {
|
||||
pass.setPipeline(RenderSystem.getCompiledPipeline(RenderPipelines.END_SKY));
|
||||
RenderSystem.bindDefaultUniforms(pass);
|
||||
pass.setUniform("DynamicTransforms", transform);
|
||||
pass.setUniform("Sampler0", texture.getTextureView(), texture.getSampler());
|
||||
pass.setVertexBuffer(0, cube.slice());
|
||||
pass.setIndexBuffer(indexBuffer, indices.type());
|
||||
pass.drawIndexed(36, 1, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static GpuBuffer buildCube() {
|
||||
var format = DefaultVertexFormat.POSITION_TEX_COLOR;
|
||||
try (var bytes = ByteBufferBuilder.exactlySized(24 * format.getVertexSize())) {
|
||||
var builder = new BufferBuilder(bytes, PrimitiveTopology.QUADS, format);
|
||||
for (int face = 0; face < 6; face++) {
|
||||
var pose = new Matrix4f();
|
||||
switch (face) {
|
||||
case 1 -> pose.rotationX((float) Math.PI / 2);
|
||||
case 2 -> pose.rotationX((float) -Math.PI / 2);
|
||||
case 3 -> pose.rotationX((float) Math.PI);
|
||||
case 4 -> pose.rotationZ((float) Math.PI / 2);
|
||||
case 5 -> pose.rotationZ((float) -Math.PI / 2);
|
||||
}
|
||||
float u0 = face / 6f, u1 = (face + 1) / 6f;
|
||||
builder.addVertex(pose, -100, -100, -100).setUv(u0, 0).setColor(-1);
|
||||
builder.addVertex(pose, -100, -100, 100).setUv(u0, 1).setColor(-1);
|
||||
builder.addVertex(pose, 100, -100, 100).setUv(u1, 1).setColor(-1);
|
||||
builder.addVertex(pose, 100, -100, -100).setUv(u1, 0).setColor(-1);
|
||||
}
|
||||
try (MeshData mesh = builder.buildOrThrow()) {
|
||||
return RenderSystem.getDevice().createBuffer(() -> "Rivencraft static sky cube", 40, mesh.vertexBuffer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cube.close();
|
||||
texture.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.dnorberg.rivencraft.client;
|
||||
|
||||
/** Carries the extracted animation frame without consulting the live world during drawing. */
|
||||
public interface StaticSkyState {
|
||||
long rivencraft$staticFrame();
|
||||
void rivencraft$staticFrame(long frame);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.dnorberg.rivencraft.mixin;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import net.dnorberg.rivencraft.age.PortLighting;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.lighting.LevelLightEngine;
|
||||
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;
|
||||
|
||||
/** Ambient light contributes to effective brightness, not fake block/sky propagation. */
|
||||
@Mixin(LevelLightEngine.class)
|
||||
public abstract class AgeAmbientLightMixin {
|
||||
@Shadow @Final protected LevelHeightAccessor levelHeightAccessor;
|
||||
|
||||
@ModifyReturnValue(method = "getRawBrightness", at = @At("RETURN"))
|
||||
private int rivencraft$ambientBrightness(int brightness) {
|
||||
return levelHeightAccessor instanceof Level level && PortLighting.isBright(level)
|
||||
? Math.max(brightness, 15) : brightness;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package net.dnorberg.rivencraft.mixin;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
|
||||
import net.dnorberg.rivencraft.client.PortStaticSky;
|
||||
import net.dnorberg.rivencraft.client.StaticSkyState;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.SkyRenderer;
|
||||
import net.minecraft.client.renderer.state.level.SkyRenderState;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
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.callback.CallbackInfo;
|
||||
|
||||
@Mixin(SkyRenderer.class)
|
||||
public class StaticSkyRendererMixin {
|
||||
@Shadow @Final private RenderTarget renderTarget;
|
||||
@Unique private PortStaticSky rivencraft$staticSky;
|
||||
|
||||
@Inject(method = "extractRenderState", at = @At("TAIL"))
|
||||
private void rivencraft$extract(ClientLevel level, float partialTicks, Camera camera,
|
||||
SkyRenderState state, CallbackInfo ci) {
|
||||
boolean enabled = level.dimensionTypeRegistration().unwrapKey().map(key ->
|
||||
key.identifier().getNamespace().equals("rivencraft")
|
||||
&& key.identifier().getPath().endsWith("_sky_static")).orElse(false);
|
||||
((StaticSkyState) state).rivencraft$staticFrame(enabled ? level.getGameTime() / 2 : Long.MIN_VALUE);
|
||||
// Only the extracted presentation uses END to schedule the sky pass. The actual
|
||||
// dimension has NONE, so it never acquires End flashes or End gameplay behavior.
|
||||
if (enabled) state.skybox = DimensionType.Skybox.END;
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At("HEAD"), cancellable = true)
|
||||
private void rivencraft$render(GpuBufferSlice fog, SkyRenderState state, CallbackInfo ci) {
|
||||
long frame = ((StaticSkyState) state).rivencraft$staticFrame();
|
||||
if (frame == Long.MIN_VALUE) return;
|
||||
if (rivencraft$staticSky == null) rivencraft$staticSky = new PortStaticSky();
|
||||
rivencraft$staticSky.render(renderTarget, fog, frame);
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "close", at = @At("HEAD"))
|
||||
private void rivencraft$close(CallbackInfo ci) {
|
||||
if (rivencraft$staticSky != null) {
|
||||
rivencraft$staticSky.close();
|
||||
rivencraft$staticSky = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.dnorberg.rivencraft.mixin;
|
||||
|
||||
import net.dnorberg.rivencraft.client.StaticSkyState;
|
||||
import net.minecraft.client.renderer.state.level.SkyRenderState;
|
||||
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;
|
||||
|
||||
@Mixin(SkyRenderState.class)
|
||||
public class StaticSkyStateMixin implements StaticSkyState {
|
||||
@Unique private long rivencraft$frame = Long.MIN_VALUE;
|
||||
public long rivencraft$staticFrame() { return rivencraft$frame; }
|
||||
public void rivencraft$staticFrame(long frame) { rivencraft$frame = frame; }
|
||||
|
||||
@Inject(method = "reset", at = @At("TAIL"))
|
||||
private void rivencraft$reset(CallbackInfo ci) { rivencraft$frame = Long.MIN_VALUE; }
|
||||
}
|
||||
@@ -134,13 +134,15 @@ public final class PortBookNetworking {
|
||||
terrain = random.terrain();
|
||||
biomePlan = random.biomes();
|
||||
}
|
||||
var environment = net.dnorberg.rivencraft.age.PortEnvironmentSelection.resolve(pages, seed, terrain);
|
||||
RuntimeAgeBackend.create(server, id, seed, biomePlan, terrain,
|
||||
PortAuthoredWriting.rockPlan(pages, seed),
|
||||
PortAuthoredWriting.vegetationPlan(pages),
|
||||
PortAuthoredWriting.dungeonCount(pages),
|
||||
PortAuthoredWriting.lakePlan(pages, seed),
|
||||
PortAuthoredWriting.terrainFeaturePlan(pages, seed),
|
||||
net.dnorberg.rivencraft.age.PortWeatherMode.fromPages(pages));
|
||||
environment.weather(), environment.lighting(),
|
||||
net.dnorberg.rivencraft.age.PortSkyStyle.fromPages(pages));
|
||||
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||
player.sendOverlayMessage(Component.literal("Age creation failed: " + exception.getMessage()));
|
||||
return null;
|
||||
|
||||
@@ -15,6 +15,8 @@ import net.dnorberg.rivencraft.age.PortTerrainFeaturePlan;
|
||||
import net.dnorberg.rivencraft.age.PortStructurePages;
|
||||
import net.dnorberg.rivencraft.age.PortCarverPages;
|
||||
import net.dnorberg.rivencraft.age.PortWeatherMode;
|
||||
import net.dnorberg.rivencraft.age.PortLighting;
|
||||
import net.dnorberg.rivencraft.age.PortSkyStyle;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
@@ -53,7 +55,8 @@ public final class PortAuthoredWriting {
|
||||
if (page == null || !page.getNamespace().equals(Rivencraft.MOD_ID)) return false;
|
||||
var entry = PortSymbolPages.find(page);
|
||||
if (entry == null) return false;
|
||||
if (isTerrain(page) || controller(page) != null || PortWeatherMode.forPage(page) != null) return true;
|
||||
if (isTerrain(page) || controller(page) != null || PortWeatherMode.forPage(page) != null
|
||||
|| PortLighting.forPage(page) != null || PortSkyStyle.forPage(page) != null) return true;
|
||||
if (entry.category().equals("rivencraft:biome") && PortRegistryPages.biomeId(page) != null) return true;
|
||||
if (isBlockPage(page) && (entry.category().equals("rivencraft:block")
|
||||
|| entry.category().equals("rivencraft:fluid"))) return true;
|
||||
@@ -79,6 +82,8 @@ public final class PortAuthoredWriting {
|
||||
int denseOres = 0;
|
||||
int starFissures = 0;
|
||||
int weather = 0;
|
||||
int lighting = 0;
|
||||
int sky = 0;
|
||||
Map<Identifier, Integer> terrainFeatures = new HashMap<>();
|
||||
Map<Identifier, Integer> structures = new HashMap<>();
|
||||
Map<Identifier, Integer> carvers = new HashMap<>();
|
||||
@@ -88,6 +93,10 @@ public final class PortAuthoredWriting {
|
||||
|| pendingMaterial.equals("rivencraft:fluid") && PortLakePlan.isLake(page))) return false;
|
||||
if (isTerrain(page) && !terrain) {
|
||||
terrain = true;
|
||||
} else if (PortSkyStyle.forPage(page) != null && sky++ == 0) {
|
||||
// Sky presentation, independent of terrain and illumination.
|
||||
} else if (PortLighting.forPage(page) != null && lighting++ == 0) {
|
||||
// A single lighting controller; omitted choices resolve from the Age seed.
|
||||
} else if (PortWeatherMode.forPage(page) != null && weather++ == 0) {
|
||||
// A single independent weather policy for this Age.
|
||||
} else if (controller(page) != null && controllers++ == 0) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"minVersion": "0.8",
|
||||
"package": "net.dnorberg.rivencraft.mixin",
|
||||
"compatibilityLevel": "JAVA_25",
|
||||
"mixins": ["StructurePlacementAccessor", "AgeStructureBiomeMixin", "AgeCarversMixin"],
|
||||
"mixins": ["StructurePlacementAccessor", "AgeStructureBiomeMixin", "AgeCarversMixin", "AgeAmbientLightMixin"],
|
||||
"client": ["StaticSkyStateMixin", "StaticSkyRendererMixin"],
|
||||
"injectors": {"defaultRequire": 1}
|
||||
}
|
||||
Reference in new issue
Block a user