Add woodland canopies, sheltered dark oaks and exposed starter ores
Build Sanctuary / build (push) Canceled after 0s

This commit is contained in:
koka
2026-09-08 23:11:51 +02:00
parent f1e48fa969
commit d50d47e149
61 changed files with 4984 additions and 48 deletions
+21
View File
@@ -1,5 +1,26 @@
# Changelog
## 0.1.0-alpha.7 — 2026-09-08
- Forêts de chênes plus denses, y compris au cœur et sur les hauteurs de l’île,
avec bouleaux, clairières fleuries et bois plus secs.
- Vrais chênes noirs, champignons, podzol et mycélium sur les corniches intérieures
humides ; mousse plus ponctuelle et rares poches de jungle/bambou.
- Un emplacement facultatif pour un arbre remarquable (cerisier favorisé,
épicéa, jungle, acacia, palétuvier ou chêne pâle), sans terrain ajouté.
- Nappes de pierre, andésite, diorite et granite sur les affleurements ; dépôts
épais de sable, gravier et argile associés aux bassins. Rares matières de
soufre dans les profondeurs, sans grands bassins souterrains.
- Petits filons de charbon, fer et cuivre autorisés à affleurer, avec des essais
sur les roches de surface et les corniches pour les rendre repérables.
- Nouvelle clé `sanctuary:sanctuary_woodland`, avec formes, hauteur, ciel et
plans deau conservés. Créer un **nouveau monde Sanctuary** ; les sauvegardes
alpha.6 et antérieures conservent leur génération.
Validation : dix tests serveur réussis sur chacune des graines `0`, `42` et
`8675309`, arbres et minerais réellement observés, 1 800 ticks de fluides,
et `check build assemblePack` réussi.
## 0.1.0-alpha.6 — 2026-09-08
- Palette tempérée plus douce, avec des couleurs communes pour lherbe, le
+12 -8
View File
@@ -22,11 +22,15 @@ décrits dans la vision ne sont pas encore implémentés.
- Mod `sanctuary` indépendant et pack Sanctuary construit avec packwiz.
- Preset **Sanctuary** sélectionnable à la création d'un monde, avec une île
flottante finie, un terrain naturel et un extérieur vide.
- Île tempérée aux teintes douces : prairies fleuries, bosquets, mousse et
affleurements rocheux, avec des biomes qui varient aussi selon laltitude. Les
- Île tempérée aux teintes douces : forêts de chênes et de bouleaux, clairières
fleuries et affleurements de pierre, andésite, diorite et granite. Les corniches
intérieures accueillent chênes noirs et champignons ; les essences rares
restent ponctuelles. Les
formes de terrain reprennent le générateur 26.2, adapté à l'API 26.3.
- Petits filons de charbon, fer, cuivre, or, redstone, lapis et diamant à des
altitudes adaptées au terrain relevé. Les stocks se constatent après génération.
altitudes adaptées au terrain relevé. Charbon, fer et cuivre peuvent aussi
affleurer sur les roches de surface et les corniches. Les stocks se constatent
après génération.
- Spawn recherché sur le sol de l'île lors de la création. Les mondes ordinaires
et les choix ultérieurs de spawn ne sont pas remplacés.
- Nouveaux mondes sur 384 blocs (`Y=0..383`), île relevée de 64 blocs,
@@ -48,9 +52,9 @@ le terrain : le spawn cherche un emplacement naturel. Le catalogue
TerraMix, les continents déverrouillables, les océans, les ruines et
les autres fonctionnalités restent à construire par tickets.
Lalpha.6 utilise de nouveaux paramètres de génération ; créer un **nouveau monde**
pour découvrir ses biomes par altitude et ses eaux. Les sauvegardes alpha.2
à alpha.5 conservent leur génération. Dappled Forest est désormais réservé aux
Lalpha.7 utilise de nouveaux paramètres de génération ; créer un **nouveau monde**
pour découvrir ses forêts, ses corniches humides et ses affleurements. Les sauvegardes alpha.2
à alpha.6 conservent leur génération. Dappled Forest est désormais réservé aux
futurs continents et reste présent dans les anciennes sauvegardes alpha.5. La validation de cet incrément est suivie dans [Validation](docs/testing.md).
Le premier champ de recherche climatique est défini : nord froid, sud chaud,
@@ -75,7 +79,7 @@ sera une mise à jour explicite, avec vérification des API et des sauvegardes.
| Fabric API | 0.160.0+26.3 |
| Fabric Loom | 1.17.20 |
| Gradle Wrapper | 9.5.1, distribution vérifiée par SHA-256 |
| Sanctuary / pack | 0.1.0-alpha.6 |
| Sanctuary / pack | 0.1.0-alpha.7 |
Java 25 et Python 3.11 ou plus récent sont nécessaires. Le script pack utilise
uniquement la bibliothèque standard et repère aussi une installation Python
@@ -102,7 +106,7 @@ décrits dans [Validation](docs/testing.md).
Résultats :
- `mods/sanctuary/build/libs/sanctuary-0.1.0-alpha.6.jar` : mod à installer avec
- `mods/sanctuary/build/libs/sanctuary-0.1.0-alpha.7.jar` : mod à installer avec
Fabric API sur la version Minecraft indiquée.
- `build/packwiz/` : pack de développement complet, avec le mod construit et
l'index vérifié. Voir [Installation packwiz](packwiz/README.md).
+3 -1
View File
@@ -11,7 +11,9 @@ Les biomes de départ reprennent les configurations de forêt, plaine, forêt de
bouleaux, forêt fleurie et Dappled Forest de Minecraft 26.3-pre-2, adaptées à
une île finie. Les variantes alpha.6 réutilisent les végétations vanilla pour
les prairies fleuries et les bosquets ; Dappled Forest reste disponible pour
les sauvegardes alpha.5. Les règles des petits minerais sont adaptées à partir des
les sauvegardes alpha.5. Lalpha.7 réemploie les arbres vanilla (dont chêne noir,
cerisier et essences rares), les champignons, le bambou et les blocs de soufre
de cette version, avec des conditions propres à l’île. Les règles des petits minerais sont adaptées à partir des
features vanilla de cette même version. Les références de blocs, végétation et
minerais sont résolues par Minecraft ; leurs textures et modèles ne sont pas inclus.
+27
View File
@@ -173,6 +173,33 @@ dont Dappled Forest, aux futurs continents. L’île combine prairies fleuries,
bosquets et zones rocheuses, avec des teintes cohérentes et une végétation
effective sur les corniches inférieures. Cela ne livre pas encore les continents.
### WG-08 — Forêts de survie et minerais visibles — alpha.7
**Branche :** `codex/woodland-canopy`.
**But :** conserver les formes rocheuses appréciées en alpha.6 tout en donnant
à l’île de vraies forêts de surface et des ressources de départ repérables.
**Critères dacceptation :**
- Chênes majoritaires parmi les arbres de surface observés, bouleaux secondaires,
avec des arbres au cœur de l’île et des clairières fleuries.
- Chênes noirs réels et champignons sur des corniches intérieures naturelles ;
jungle/bambou et matières de soufre rares et localisés.
- Un éventuel arbre remarquable, choisi parmi les essences rares, sans ajout
de terrain ni obligation de fournir chaque essence par graine.
- Affleurements de pierre, andésite, diorite et granite ; sable et gravier
associés aux eaux, avec des couches soutenues.
- Charbon, fer et cuivre visibles au contact de lair sur la roche, avec des
petits filons sans quota corrigé après génération.
- Démonstration dans le moteur, maintien des eaux après 1 800 ticks et
conservation des anciennes générations. Publication packwiz et mise à jour
de linstance Prism existante en conservant ses données personnelles.
**Vérification alpha.7 :** les trois graines de référence passent les tests
du moteur, dont les forêts, les minerais exposés et les eaux après 1 800 ticks.
Les observations et leurs limites sont consignées dans [Validation](testing.md).
## Réserve de thèmes futurs
Ces thèmes servent à retrouver la vision, pas à demander leur implémentation immédiate. On en extrait un ticket seulement lorsqu'il devient utile au prochain incrément jouable.
+8
View File
@@ -37,6 +37,14 @@ La répartition tient compte de laltitude, y compris dans les strates inféri
La variation locale et lhumidité dépendent de la graine ; elles ne
constituent pas encore le champ de recherche directionnel des continents.
Lalpha.7 densifie les forêts de chênes et de bouleaux en surface et donne
aux corniches intérieures humides des chênes noirs et des champignons.
Des champignons vanilla peuvent aussi se rencontrer en surface. Les
clairières fleuries et les affleurements restent présents. Les essences rares
et un éventuel arbre remarquable dépendent du terrain ; chaque graine nest
pas tenue de fournir tous les bois. Les minerais de départ peuvent affleurer,
avec des petits filons et sans correction des stocks après comptage.
## Eau de surface et exploration
L'île de départ doit pouvoir accueillir naturellement des étangs et lacs de
+6 -2
View File
@@ -19,7 +19,7 @@ Après les vérifications, le commit et le push de la branche du ticket, le scri
de publication réalise les étapes de release et de canal ci-dessous :
```sh
python3 scripts/publish_pack.py --notes-file chemin/vers/notes.md --asset build/Sanctuary-0.1.0-alpha.6.mrpack
python3 scripts/publish_pack.py --notes-file chemin/vers/notes.md --asset build/Sanctuary-0.1.0-alpha.7.mrpack
```
Les options sont facultatives. Ce script **publie** sur le Git configuré dans
@@ -38,7 +38,7 @@ La procédure complète, également utilisable manuellement :
3. Préparer les métadonnées de la livraison, avec l'URL exacte du futur JAR :
```sh
python3 scripts/pack.py release https://git.botsu.net/koka/sanctuary-beta/releases/download/v0.1.0-alpha.6/sanctuary-0.1.0-alpha.6.jar
python3 scripts/pack.py release https://git.botsu.net/koka/sanctuary-beta/releases/download/v0.1.0-alpha.7/sanctuary-0.1.0-alpha.7.jar
```
4. Pousser le commit source vérifié. Créer une release Gitea correspondant à ce
@@ -127,6 +127,10 @@ Lalpha.6 utilise à son tour `sanctuary:sanctuary_layered` pour ses biomes pa
altitude et sa nouvelle hydrologie. Créer un nouveau monde Sanctuary ; les
sauvegardes alpha.5 conservent notamment leur palette avec Dappled Forest.
Lalpha.7 sélectionne `sanctuary:sanctuary_woodland` pour ses forêts, ses
roches variées et ses minerais affleurants. Les mondes alpha.6 gardent
`sanctuary:sanctuary_layered` ; aucune régénération de chunks nest effectuée.
Références officielles : [installation packwiz](https://packwiz.infra.link/tutorials/installing/packwiz-installer/),
[commandes Prism](https://prismlauncher.org/wiki/help-pages/custom-commands/),
[bootstrap v0.0.3](https://github.com/packwiz/packwiz-installer-bootstrap/releases/tag/v0.0.3).
+64 -4
View File
@@ -81,17 +81,17 @@ Les neuf tests Sanctuary vérifient :
tandis que les anciens paramètres restent à 256 blocs. Les nuages sont
au-dessus de l'île, à Y=352,33.
6. Aucun plancher solide napparaît sous l’île, à Y=0..15. Dans les générations
alpha.5 et alpha.6, seules les eaux des cascades déclarées peuvent traverser cette marge.
alpha.5 à alpha.7, seules les eaux des cascades déclarées peuvent traverser cette marge.
7. Le relevé après génération compte exactement tous les états des blocs des
chunks demandés, avec des totaux cohérents et une complétude explicite.
Voir [Expansion et ressources](expansion.md) pour le mode complet facultatif.
8. Le plan hydrologique se retrouve dans les vrais chunks après décoration :
profils épais de sédiments, bassins retenus, raccords entre chunks, canne à
sucre valide, niches rocheuses et descente réelle des cascades. Les cinq
biomes et les minerais sont inspectés sur le terrain. Une réserve de lave
sucre valide, niches rocheuses et descente réelle des cascades. La palette
active et les minerais sont inspectés sur le terrain. Une réserve de lave
présente conserve ses 18 sources, ses supports et son accès après simulation.
9. Les anciennes clés gardent leur traitement dorigine. Seuls les nouveaux
paramètres activent les biomes par altitude et les eaux de lalpha.6.
paramètres activent les forêts et les eaux de lalpha.7.
La recherche de spawn parcourt l'île finie, par anneaux de quatre blocs jusqu'à
288 blocs du centre. Elle examine d'abord la hauteur brute pour éviter de
@@ -188,6 +188,66 @@ Pour cet essai visuel en monde de développement créatif, comparer la même vue
Y=63 ni assombrissement d'altitude au passage de Y=32. Les brouillards de l'eau,
de la météo et des effets d'aveuglement ou d'obscurité doivent rester présents.
## Contrôles et validation alpha.7 — 8 septembre 2026
`WoodlandDiagnostics` reprend les preuves hydrologiques et ajoute les forêts,
les champignons sous plafond, les roches exposées et les minerais visibles.
Les arbres sont comptés par pieds de troncs sur le sol naturel, avec au moins
trois bûches verticales et des feuilles réellement observées. Le chêne noir
requiert un pied principal de 2×2 et trois couches de support par pied ; ses
branches retombantes ne comptent pas comme de nouveaux arbres. Podzol et
mycélium sont des sols explicites : le tag `minecraft:dirt` de cette version
ne les contient pas.
Les essais utilisent des mondes neufs, le vrai preset de production et
Minecraft 26.3-pre-2 / Java 25 / Fabric Loader 0.19.5. Les dix tests requis
passent sur chacune des trois graines. `check build assemblePack` passe en
3 min 41 s sur la graine 0, avec les huit tests purs de forme, hydrologie,
lave, écologie et nouveaux bois. Les exports de densité et le relevé général
facultatifs sont désactivés ; les contrôles de densité restent actifs.
| Graine | Chunks FULL inspectés | Chênes de surface | Bouleaux de surface | Chênes à rayon ≤128 | Chênes noirs inférieurs | Minerais exposés dehors : charbon / fer / cuivre |
| --- | ---: | ---: | ---: | ---: | ---: | --- |
| `0` | 167 | 292 | 263 | 218 | 30 | 4 / 19 / 5 |
| `42` | 154 | 313 | 125 | 250 | 13 | 8 / 3 / 13 |
| `8675309` | 167 | 336 | 99 | 272 | 36 | 7 / 6 / 9 |
Ces nombres décrivent un échantillon borné : les eaux et leur voisinage, des
cibles écologiques et lemplacement de larbre remarquable. Ce nest pas un
inventaire complet de l’île ni un pourcentage représentatif de toutes ses
surfaces. Les sols sont mesurés sous les canopées. Les minerais recensés dans
la dernière colonne ont une face dair réelle, ouverte vers le ciel ou vers
une paroi dégagée sur douze blocs ; les faces dans de petites cavités sont
comptées séparément. Les trois roches présentent des nappes contiguës exposées.
La graine 42 démontre un cerisier remarquable en `(-88, 251, -88)`, avec son
tronc, ses feuilles et 21 bûches dans le volume prévu. Le site sélectionné sur
la graine 0 ne produit pas darbre rare : lespace et les conditions après
décoration restent déterminants. Aucun terrain nest ajouté pour le forcer.
Les autres essences rares et le soufre ne sont pas garantis sur chaque graine.
Les eaux sont vérifiées après décoration puis pendant 1 800 ticks réels dans
des chunks actifs, avec témoins indépendants du fonctionnement des fluides.
Les bassins et les dix-huit sources de chaque niche de lave sont conservés ;
les cascades restent reliées à leurs sources et dans lemprise observée.
Les pieds des arbres et les champignons observés gardent leur support. Les
sédiments sont comparés à leur matériau prévu sur toutes leurs couches ; une
roche peut être remplacée seulement par les roches, minerais ou dépôts de
terre effectivement configurés. Les berges de sable et gravier sont comptées
sur les vrais blocs, y compris par profondeur.
Les anciens générateurs restent isolés : les 77 fichiers historiques suivis
gardent leur SHA-256. La réentrée au post-traitement après consommation des
marqueurs respecte une modification ultérieure de bouche de source sur les
trois graines. Aucun monde de joueur nest ouvert par ces tests.
Preuves locales ignorées par Git : `build/alpha7-build.log`,
`build/alpha7-seed42.log`, `build/alpha7-seed8675309.log`, et les fichiers
`woodland-seed-<seed>-<phase>.json`, `-canopy.png`, `-lower-terraces.png` et
profils de sources dans `mods/sanctuary/build/run/gameTest/diagnostics/`.
Les cartes montrent des lectures de blocs ; lappréciation du paysage dans
le client reste à faire lors du prochain essai.
## Contrôles alpha.6
`layeredHydrologySmoke` vérifie les bassins sans ruisseau de secours, les
+14 -10
View File
@@ -66,16 +66,20 @@ remplis d'eau ne sont pas retenus pour cette île. Le terrain doit laisser voir
dessous sculpté et dégagé, dans un monde de **384 blocs de hauteur**, avec des
nuages relevés et sans disque noir artificiel apparaissant sous un seuil d'altitude.
Lambiance de départ doit rester douce et tempérée, avec des bosquets,
des prairies fleuries et de larges zones rocheuses. Les biomes très contrastés
de Minecraft 26.3, dont Dappled Forest, sont réservés aux futurs continents.
Les biomes et la végétation varient aussi en hauteur : les corniches inférieures
peuvent porter des bosquets, de la mousse et des fleurs lorsque leur surface
sy prête. Quelques sources de paroi et ruissellements relient ces étages.
Les plages sinspirent des nappes de
sable des générations Alpha/Beta : elles peuvent relier des poches deau, tandis
que dautres rives restent rocheuses ou enherbées. Les quelques cascades servent
également dascenseurs deau pour circuler verticalement.
Lambiance de départ doit rester douce et tempérée, avec de véritables forêts
et des clairières fleuries, quelques zones plus sèches et de larges masses
rocheuses mêlant pierre, andésite, diorite et granite. Les nappes de sable et
de gravier accompagnent les eaux. Charbon, fer et cuivre doivent aussi pouvoir
affleurer sur la roche exposée pour commencer la survie.
Les bois dominants sont le chêne, le chêne noir et le bouleau : chênes et
bouleaux en surface, chênes noirs et champignons dans lintérieur plus humide,
avec moins de mousse uniforme. Épicéa, jungle, acacia, palétuvier et chêne pâle
restent rares mais possibles ; du bambou et de rares poches de soufre peuvent
se rencontrer dans les profondeurs. Un arbre remarquable isolé peut être un
cerisier rose ou une autre de ces essences rares, lorsque le terrain convient.
Les biomes très contrastés de Minecraft 26.3, dont Dappled Forest, restent
réservés aux futurs continents.
L’île doit offrir les ressources nécessaires aux premières étapes de Minecraft,
en quantités modestes pour pousser à explorer les expansions lors des grands
+67 -3
View File
@@ -47,8 +47,9 @@ de test et les sauvegardes personnelles doivent rester distincts.
Il n'y a ni plancher de bedrock,
ni mer globale, ni reprise du terrain à distance. Le Nether et l'End restent
ceux de Minecraft.
- Les nouveaux mondes utilisent une palette tempérée de prairies, de bosquets,
de corniches moussues et de zones rocheuses, répartie aussi en hauteur.
- Les nouveaux mondes utilisent une palette tempérée de forêts de chênes et de
bouleaux, de clairières fleuries et de zones rocheuses. Les strates intérieures
portent des chênes noirs et des champignons sur leurs corniches naturelles.
Dappled Forest est réservé aux futurs continents. Leur végétation et leurs animaux proviennent
de Minecraft 26.3-pre-2. Les nouveaux identifiants ne reprennent pas les tags
de structures vanilla, afin de ne pas placer leurs structures dans le vide.
@@ -57,7 +58,7 @@ de test et les sauvegardes personnelles doivent rester distincts.
rocheuses. Les arbres et les petits filons ont besoin du terrain existant.
Aucun mod de biomes externe nest nécessaire.
Lalpha.6 associe ces biomes aux plages, aux eaux et aux petits filons décrits
Lalpha.7 associe ces biomes aux plages, aux eaux et aux petits filons décrits
ci-dessous. Les ressources de survie ne se limitent pas au bois et à la pierre,
mais cet incrément ne garantit pas encore une partie Minecraft complète sur
chaque seed. Laccès à chaque progression vanilla et les ressources renouvelables
@@ -450,3 +451,66 @@ chunks sauvegardés.
La version de génération et les emprises doivent être gelées avant les premières
sauvegardes destinées à durer. Ce prototype ne promet pas encore de migration
automatique des mondes entre deux versions de terrain.
## Forêts, corniches humides et affleurements — génération v7
Lalpha.7 crée les nouveaux mondes avec `sanctuary:sanctuary_woodland` et
`sanctuary:woodland_island`. La densité `sanctuary:final_density_384`, les
formes rocheuses, la hauteur et le ciel restent identiques. Les plans des
bassins, sources et petites niches de lave reprennent les algorithmes alpha.6.
Les anciens paramètres, biomes et features sont conservés ; cette livraison
ne modifie pas les mondes existants.
La palette comporte huit biomes :
| Identifiant | Rôle |
| --- | --- |
| `sanctuary:woodland_oak_forest` | Forêt de chênes dominante, bouleaux secondaires |
| `sanctuary:woodland_birch_forest` | Forêt de bouleaux complémentaire |
| `sanctuary:woodland_clearing` | Clairière fleurie |
| `sanctuary:woodland_dry_woodland` | Bois sec plus clairsemé |
| `sanctuary:woodland_rocky_heath` | Masses rocheuses exposées |
| `sanctuary:woodland_dark_grove` | Corniches humides à chênes noirs et champignons |
| `sanctuary:woodland_bamboo_grove` | Rare intérieur à jungle et bambou |
| `sanctuary:woodland_sulfur_depths` | Rare poche profonde de soufre sur roche existante |
Une altitude élevée ne transforme plus automatiquement une forêt en prairie
ou en roche. Les forêts utilisent les véritables arbres vanilla avec leurs
conditions de croissance ; les affleurements sont des patches horizontaux
et des variations de matériau sur le terrain existant.
`woodland_groves` place de vrais chênes noirs sur des sols naturels de 2×2,
avec trois blocs de support et une place suffisante pour le tronc et la
canopée sous le plafond. Des champignons accompagnent les sols de podzol et
de mycélium, avec quelques touches de mousse. La jungle et le bambou restent
limités aux rares poches qui leur conviennent.
Un seul emplacement remarquable est recherché par graine, avec une essence
choisie parmi cerisier (favorisé), épicéa, jungle, acacia, palétuvier et chêne
pâle. Il ne produit pas de terrain : si le site ou les conditions de larbre
vanilla ne conviennent pas, il ne pousse pas. Ce nest pas une garantie de
chaque essence sur chaque île. Aucun cœur de Creaking nest ajouté par larbre
pâle utilisé ici.
Les affleurements mêlent des nappes cohérentes de pierre, andésite, diorite et
granite. Les berges conservent leur excavation limitée et leurs dépôts de
3 à 5 blocs, avec des patches de sable, gravier, argile et roche. Les plantations de `woodland_groves` évitent les sources, leurs exutoires
et les niches de lave. Les décorations vanilla conservent leurs placements ;
le traitement unique après génération peut rouvrir la seule bouche de source
déclarée si une plante lobstrue. Le soufre
reste une matière rare des profondeurs ; les grands bassins et sources
vanilla du biome Sulfur Caves ne sont pas activés.
Les anciens petits filons souterrains de charbon, fer et cuivre étaient peu
susceptibles datteindre les surfaces hautes ; leur réglage écartait aussi une
partie des blocs voisins de lair. Les nouvelles features `woodland_ore_*`
utilisent des tailles vanilla limitées (`size=6` pour le charbon, `size=5`
pour fer/cuivre), sans garantir ce nombre de blocs produits par filon,
autorisent cette exposition et ajoutent des essais aléatoires sur les roches
de surface et les corniches rencontrées depuis lair. Elles remplacent
seulement les roches naturelles reconnues par Minecraft, dont andésite,
diorite et granite. Les autres minerais et les anciens générateurs gardent
leurs réglages ; aucune quantité cible nest réinjectée après mesure.
La version et les graines de validation sont consignées dans [Validation](testing.md).
+2 -2
View File
@@ -8,6 +8,6 @@ loader_version=0.19.5
loom_version=1.17.20
fabric_api_version=0.160.0+26.3
mod_version=0.1.0-alpha.6
pack_version=0.1.0-alpha.6
mod_version=0.1.0-alpha.7
pack_version=0.1.0-alpha.7
maven_group=fr.koka.sanctuary
+17
View File
@@ -130,3 +130,20 @@ tasks.register('layeredEcologySmoke', JavaExec) {
}
tasks.named('check') { dependsOn('layeredEcologySmoke') }
// Alpha.7 keeps previous biome palettes and generators unchanged.
tasks.register('woodlandPaletteSmoke', JavaExec) {
group = 'verification'
description = 'Check surface forest coverage and bounded rare interior biomes.'
dependsOn('testClasses')
classpath = sourceSets.test.runtimeClasspath
mainClass = 'fr.koka.sanctuary.worldgen.WoodlandPaletteSmoke'
}
tasks.register('woodlandGroveSmoke', JavaExec) {
group = 'verification'
description = 'Check natural tree supports and canopy space without creating terrain.'
dependsOn('testClasses')
classpath = sourceSets.test.runtimeClasspath
mainClass = 'fr.koka.sanctuary.worldgen.WoodlandGroveSmoke'
}
tasks.named('check') { dependsOn('woodlandPaletteSmoke', 'woodlandGroveSmoke') }
@@ -3,6 +3,7 @@ package fr.koka.sanctuary.gametest;
import fr.koka.sanctuary.worldgen.SanctuarySpawn;
import fr.koka.sanctuary.worldgen.NaturalHydrologyRuntime;
import fr.koka.sanctuary.worldgen.LayeredHydrologyRuntime;
import fr.koka.sanctuary.worldgen.WoodlandHydrologyRuntime;
import net.minecraft.tags.FluidTags;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import fr.koka.sanctuary.worldgen.IslandShape;
@@ -30,7 +31,8 @@ public final class SanctuaryWorldGameTests {
@GameTest(maxTicks = 3000)
public void generatedSurfaceWaterRemainsContained(GameTestHelper helper) throws java.io.IOException {
var generator = (NoiseBasedChunkGenerator) helper.getLevel().getChunkSource().getGenerator();
if (LayeredHydrologyRuntime.enabled(generator)) LayeredHydrologyDiagnostics.start(helper);
if (WoodlandHydrologyRuntime.enabled(generator)) WoodlandDiagnostics.start(helper);
else if (LayeredHydrologyRuntime.enabled(generator)) LayeredHydrologyDiagnostics.start(helper);
else if (NaturalHydrologyRuntime.enabled(generator)) NaturalHydrologyDiagnostics.start(helper);
else HydrologyDiagnostics.start(helper);
}
@@ -38,7 +40,8 @@ public final class SanctuaryWorldGameTests {
@GameTest(maxTicks = 200)
public void hydrologyDoesNotActivateForSavedLegacySettings(GameTestHelper helper) {
var generator = (NoiseBasedChunkGenerator) helper.getLevel().getChunkSource().getGenerator();
if (LayeredHydrologyRuntime.enabled(generator)) LayeredHydrologyDiagnostics.verifyLegacyIsolation(helper);
if (WoodlandHydrologyRuntime.enabled(generator)) WoodlandDiagnostics.verifyLegacyIsolation(helper);
else if (LayeredHydrologyRuntime.enabled(generator)) LayeredHydrologyDiagnostics.verifyLegacyIsolation(helper);
else if (NaturalHydrologyRuntime.enabled(generator)) NaturalHydrologyDiagnostics.verifyLegacyIsolation(helper);
else HydrologyDiagnostics.verifyLegacyIsolation(helper);
helper.succeed();
@@ -117,7 +120,8 @@ public final class SanctuaryWorldGameTests {
helper.assertTrue(chunk != null, "The lower-margin test needs a fully generated chunk");
if (chunk.getSection(0).hasOnlyAir()) continue;
var generator = (NoiseBasedChunkGenerator) helper.getLevel().getChunkSource().getGenerator();
helper.assertTrue(NaturalHydrologyRuntime.enabled(generator) || LayeredHydrologyRuntime.enabled(generator),
helper.assertTrue(NaturalHydrologyRuntime.enabled(generator) || LayeredHydrologyRuntime.enabled(generator)
|| WoodlandHydrologyRuntime.enabled(generator),
"The legacy island must leave its lower margin entirely empty");
for (int x = chunk.getPos().getMinBlockX(); x <= chunk.getPos().getMaxBlockX(); x++) {
for (int z = chunk.getPos().getMinBlockZ(); z <= chunk.getPos().getMaxBlockZ(); z++) {
@@ -125,7 +129,9 @@ public final class SanctuaryWorldGameTests {
BlockPos p = new BlockPos(x, y, z);
var state = chunk.getBlockState(p);
helper.assertTrue(state.isAir() || state.getFluidState().is(FluidTags.WATER)
&& (LayeredHydrologyRuntime.enabled(generator)
&& (WoodlandHydrologyRuntime.enabled(generator)
? WoodlandDiagnostics.isWithinSpringInspectionFootprint(WoodlandHydrologyRuntime.woodlandPlan(helper.getLevel()), x, z)
: LayeredHydrologyRuntime.enabled(generator)
? LayeredHydrologyDiagnostics.isWithinSpringInspectionFootprint(LayeredHydrologyRuntime.layeredPlan(helper.getLevel()), x, z)
: NaturalHydrologyDiagnostics.isWithinSpringInspectionFootprint(NaturalHydrologyRuntime.naturalPlan(helper.getLevel()), x, z)),
"The lower margin has no artificial floor; water must lie in the finite spring inspection footprint, with provenance checked by the full fluid diagnostic: " + p);
@@ -1,6 +1,9 @@
package fr.koka.sanctuary;
import fr.koka.sanctuary.worldgen.MainIslandDensity;
import fr.koka.sanctuary.worldgen.WoodlandIslandBiomeSource;
import fr.koka.sanctuary.worldgen.WoodlandShoreSugarCaneFeature;
import fr.koka.sanctuary.worldgen.WoodlandGrovesFeature;
import fr.koka.sanctuary.worldgen.NaturalShoreSugarCaneFeature;
import fr.koka.sanctuary.worldgen.TemperateIslandBiomeSource;
import fr.koka.sanctuary.worldgen.LayeredIslandBiomeSource;
@@ -31,6 +34,9 @@ public final class SanctuaryMod implements ModInitializer {
Registry.register(BuiltInRegistries.FEATURE_TYPE, id("layered_shore_sugar_cane"), LayeredShoreSugarCaneFeature.CODEC);
Registry.register(BuiltInRegistries.FEATURE_TYPE, id("lower_terrace_vegetation"), LowerTerraceVegetationFeature.CODEC);
Registry.register(BuiltInRegistries.BIOME_SOURCE, id("layered_island"), LayeredIslandBiomeSource.CODEC);
Registry.register(BuiltInRegistries.BIOME_SOURCE, id("woodland_island"), WoodlandIslandBiomeSource.CODEC);
Registry.register(BuiltInRegistries.FEATURE_TYPE, id("woodland_shore_sugar_cane"), WoodlandShoreSugarCaneFeature.CODEC);
Registry.register(BuiltInRegistries.FEATURE_TYPE, id("woodland_groves"), WoodlandGrovesFeature.CODEC);
LOGGER.info("Sanctuary initialized: the Sanctuary world preset is available.");
}
}
@@ -3,6 +3,7 @@ package fr.koka.sanctuary.mixin;
import fr.koka.sanctuary.worldgen.HydrologyRuntime;
import fr.koka.sanctuary.worldgen.NaturalHydrologyRuntime;
import fr.koka.sanctuary.worldgen.LayeredHydrologyRuntime;
import fr.koka.sanctuary.worldgen.WoodlandHydrologyRuntime;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import net.minecraft.core.Holder;
@@ -27,7 +28,12 @@ public abstract class SurfaceHydrologyMixin {
StructureManager structures, BiomeManager biomeManager, WorldGenRegion region,
Set<Holder<Biome>> biomes, CallbackInfoReturnable<CompletableFuture<ChunkAccess>> callback) {
var generator = (NoiseBasedChunkGenerator) (Object) this;
if (LayeredHydrologyRuntime.enabled(generator)) {
if (WoodlandHydrologyRuntime.enabled(generator)) {
callback.setReturnValue(callback.getReturnValue().thenApply(generated -> {
WoodlandHydrologyRuntime.apply(generator, randomState, generated);
return generated;
}));
} else if (LayeredHydrologyRuntime.enabled(generator)) {
callback.setReturnValue(callback.getReturnValue().thenApply(generated -> {
LayeredHydrologyRuntime.apply(generator, randomState, generated);
return generated;
@@ -0,0 +1,29 @@
package fr.koka.sanctuary.mixin;
import fr.koka.sanctuary.worldgen.WoodlandSpringOutlets;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.LevelChunk;
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;
/** Finish only pending generation markers; completed saved chunks are never repaired on reload. */
@Mixin(LevelChunk.class)
public abstract class WoodlandSpringOutletMixin {
@Unique
private WoodlandSpringOutlets.Pending sanctuary$pendingWoodlandOutlets = WoodlandSpringOutlets.Pending.EMPTY;
@Inject(method = "postProcessGeneration", at = @At("HEAD"))
private void sanctuary$captureWoodlandOutlets(ServerLevel level, CallbackInfo callback) {
sanctuary$pendingWoodlandOutlets = WoodlandSpringOutlets.capturePending(level, (LevelChunk) (Object) this);
}
@Inject(method = "postProcessGeneration", at = @At("TAIL"))
private void sanctuary$finishWoodlandOutlets(ServerLevel level, CallbackInfo callback) {
var pending = sanctuary$pendingWoodlandOutlets;
sanctuary$pendingWoodlandOutlets = WoodlandSpringOutlets.Pending.EMPTY;
WoodlandSpringOutlets.finish(level, (LevelChunk) (Object) this, pending);
}
}
@@ -27,6 +27,8 @@ public final class SanctuarySpawn {
public static final ResourceKey<NoiseGeneratorSettings> LAYERED_SETTINGS =
ResourceKey.create(Registries.NOISE_SETTINGS, SanctuaryMod.id("sanctuary_layered"));
public static final ResourceKey<NoiseGeneratorSettings> WOODLAND_SETTINGS =
ResourceKey.create(Registries.NOISE_SETTINGS, SanctuaryMod.id("sanctuary_woodland"));
private SanctuarySpawn() {}
@@ -36,7 +38,8 @@ public final class SanctuarySpawn {
&& (generator.generatorSettings().is(SETTINGS) || generator.generatorSettings().is(RAISED_SETTINGS)
|| generator.generatorSettings().is(HYDROLOGY_SETTINGS)
|| generator.generatorSettings().is(NATURAL_SETTINGS)
|| generator.generatorSettings().is(LAYERED_SETTINGS));
|| generator.generatorSettings().is(LAYERED_SETTINGS)
|| generator.generatorSettings().is(WOODLAND_SETTINGS));
}
/** Called only during first creation; saved spawns and /setworldspawn survive subsequent loads. */
@@ -0,0 +1,29 @@
package fr.koka.sanctuary.worldgen;
/** Alpha.7 keeps forest at every surface altitude; depth changes the sheltered interior palette. */
public final class WoodlandBiomePalette {
public enum Kind {
OAK_FOREST, BIRCH_FOREST, CLEARING, DRY_WOODLAND, ROCKY_HEATH,
DARK_GROVE, BAMBOO_GROVE, SULFUR_DEPTHS
}
private WoodlandBiomePalette() {}
public static Kind select(int blockY, float variation, float moisture) {
double elevation = blockY - Math.clamp(variation, -1.0F, 1.0F) * 16
- Math.clamp(moisture, -1.0F, 1.0F) * 10;
if (elevation < 112 && variation > 0.62F && moisture < -0.25F) return Kind.SULFUR_DEPTHS;
// Preserve the strongest horizontal rock patches of alpha.6, with a smaller footprint.
// Height alone never converts an otherwise forested mountain into exposed stone.
if (variation > 0.48F) return Kind.ROCKY_HEATH;
if (elevation < 214) {
if (variation < -0.45F && moisture > 0.40F) return Kind.BAMBOO_GROVE;
double shelteredMoisture = moisture + Math.min(0.55, (214 - elevation) / 160);
if (shelteredMoisture > -0.08) return Kind.DARK_GROVE;
}
if (moisture < -0.55F && variation > -0.25F) return Kind.DRY_WOODLAND;
if (Math.abs(variation) < 0.09F && moisture < 0.35F) return Kind.CLEARING;
if (variation < -0.33F) return Kind.BIRCH_FOREST;
return Kind.OAK_FOREST;
}
}
@@ -0,0 +1,46 @@
package fr.koka.sanctuary.worldgen;
/** Natural supports and canopy space; independent of Minecraft blocks and decoration order. */
public final class WoodlandGroveGeometry {
@FunctionalInterface public interface Sampler { float sample(int x, int y, int z); }
private WoodlandGroveGeometry() {}
public static int highestSurface(Sampler sampler, int x, int z) {
return highestSurfaceBelow(sampler, x, z, 383);
}
/** The caller supplies a proven upper bound, including any removed natural surface. */
public static int highestSurfaceBelow(Sampler sampler, int x, int z, int ceiling) {
for (int y = Math.min(383, ceiling); y >= 24; y--) if (sampler.sample(x, y, z) > 0) return y;
return -1;
}
public static boolean naturalFootprint(Sampler sampler, int x, int bedY, int z, int width) {
if (bedY < 32 || bedY > 350) return false;
for (int dx = 0; dx < width; dx++) for (int dz = 0; dz < width; dz++) {
for (int dy = -2; dy <= 0; dy++) if (sampler.sample(x + dx, bedY + dy, z + dz) <= 0) return false;
if (sampler.sample(x + dx, bedY + 1, z + dz) > 0) return false;
}
return true;
}
public static boolean treeRoom(Sampler sampler, int x, int baseY, int z, int width, int radius, int height) {
if (baseY + height >= 384) return false;
for (int y = baseY; y <= baseY + height; y++) {
int from = y < baseY + 3 ? 0 : -radius;
int to = y < baseY + 3 ? width - 1 : radius;
for (int dx = from; dx <= to; dx++) for (int dz = from; dz <= to; dz++) {
if (sampler.sample(x + dx, y, z + dz) > 0) return false;
}
}
return true;
}
public static long seed(long worldSeed, int x, int y, int z, long salt) {
long value = worldSeed ^ ((long) x << 32) ^ (z & 0xffffffffL) ^ y * 0x9e3779b97f4a7c15L ^ salt;
value = (value ^ value >>> 30) * 0xbf58476d1ce4e5b9L;
value = (value ^ value >>> 27) * 0x94d049bb133111ebL;
return value ^ value >>> 31;
}
}
@@ -0,0 +1,236 @@
package fr.koka.sanctuary.worldgen;
import com.mojang.serialization.MapCodec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.WeakHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.worldgen.features.TreeFeatures;
import net.minecraft.data.worldgen.features.VegetationFeatures;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.VegetationBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.feature.Feature;
/** Alpha.7: real vanilla trees on natural lower floors, plus one globally selected remarkable tree.
* Placement runs only in new generation. No world journal or mutable placement counter is retained. */
public final class WoodlandGrovesFeature implements Feature {
public static final MapCodec<WoodlandGrovesFeature> CODEC = MapCodec.unit(WoodlandGrovesFeature::new);
private static final Map<RandomState, Optional<RemarkableTree>> REMARKABLE =
Collections.synchronizedMap(new WeakHashMap<>());
public record RemarkableTree(BlockPos base, String featureId, int radius, int height) {
public RemarkableTree { base = base.immutable(); }
}
private record Species(ResourceKey<Feature> key, int width, int radius, int height) {}
private record Candidate(int x, int z, long rank) {}
private static final Species CHERRY = new Species(TreeFeatures.CHERRY, 1, 9, 15);
private static final Species[] SPECIES = {CHERRY, CHERRY, CHERRY, CHERRY,
new Species(TreeFeatures.SPRUCE, 1, 5, 15), new Species(TreeFeatures.JUNGLE_TREE_NO_VINE, 1, 5, 14),
new Species(TreeFeatures.ACACIA, 1, 7, 14), new Species(TreeFeatures.MANGROVE, 1, 10, 20),
new Species(TreeFeatures.PALE_OAK_BONEMEAL, 2, 6, 12)};
@Override public MapCodec<WoodlandGrovesFeature> codec() { return CODEC; }
public static Optional<RemarkableTree> remarkablePlan(ServerLevel level) {
if (!(level.getChunkSource().getGenerator() instanceof NoiseBasedChunkGenerator noise)) return Optional.empty();
return remarkablePlan(noise, level.getChunkSource().randomState());
}
public static Optional<RemarkableTree> remarkablePlan(NoiseBasedChunkGenerator noise, RandomState state) {
if (!WoodlandHydrologyRuntime.enabled(noise)) return Optional.empty();
return REMARKABLE.computeIfAbsent(state, ignored -> {
var density = noise.generatorSettings().value().noiseRouter().finalDensity();
WoodlandGroveGeometry.Sampler sampler = (x, y, z) -> state.sampleBlockValueUncached(density, x, y, z);
var water = WoodlandHydrologyRuntime.plan(noise, state);
var lava = WoodlandHydrologyRuntime.lavaPlan(noise, state);
Species species = SPECIES[Math.floorMod(WoodlandGroveGeometry.seed(state.seed(), 0, 0, 0, 0x70EEL), SPECIES.length)];
var candidates = new ArrayList<Candidate>();
for (int x = -152; x <= 152; x += 16) for (int z = -152; z <= 152; z += 16) {
if (Math.hypot(x, z) <= 168) candidates.add(new Candidate(x, z,
WoodlandGroveGeometry.seed(state.seed(), x, 0, z, 0x71AFL)));
}
candidates.sort(Comparator.comparingLong(Candidate::rank).thenComparingInt(Candidate::x).thenComparingInt(Candidate::z));
for (Candidate candidate : candidates) {
int bed = WoodlandGroveGeometry.highestSurface(sampler, candidate.x(), candidate.z());
if (!WoodlandGroveGeometry.naturalFootprint(sampler, candidate.x(), bed, candidate.z(), species.width())) continue;
BlockPos base = new BlockPos(candidate.x(), bed + 1, candidate.z());
if (blocked(water, lava, base, species.radius() + 1, species.height(), rootDepth(species))) continue;
if (!WoodlandGroveGeometry.treeRoom(sampler, base.getX(), base.getY(), base.getZ(),
species.width(), species.radius(), species.height())) continue;
return Optional.of(new RemarkableTree(base, species.key().identifier().toString(), species.radius(), species.height()));
}
return Optional.empty();
});
}
@Override
public boolean place(WorldGenLevel level, ChunkGenerator generator, RandomSource ignored, BlockPos origin) {
if (!(generator instanceof NoiseBasedChunkGenerator noise) || !WoodlandHydrologyRuntime.enabled(noise)) return false;
int minX = (origin.getX() >> 4) * 16, minZ = (origin.getZ() >> 4) * 16;
if (Math.abs((long) minX) > 272 || Math.abs((long) minZ) > 272) return false;
var state = level.getLevel().getChunkSource().randomState();
var density = noise.generatorSettings().value().noiseRouter().finalDensity();
WoodlandGroveGeometry.Sampler sampler = (x, y, z) -> state.sampleBlockValueUncached(density, x, y, z);
var water = WoodlandHydrologyRuntime.plan(noise, state);
var lava = WoodlandHydrologyRuntime.lavaPlan(noise, state);
boolean changed = false;
int[] heights = new int[256];
Arrays.fill(heights, Integer.MIN_VALUE);
var remarkable = remarkablePlan(noise, state);
if (remarkable.isPresent()) {
var tree = remarkable.get();
BlockPos base = tree.base();
if ((base.getX() >> 4) == (minX >> 4) && (base.getZ() >> 4) == (minZ >> 4)) {
for (Species species : SPECIES) if (species.key().identifier().toString().equals(tree.featureId())) {
changed |= tree(level, generator, sampler, state.seed(), base, species, water, lava, 0x723BL);
break;
}
}
}
// Four spaced origins per chunk; the 2x2 trunk and full canopy still have to fit the cave.
for (int x = minX + 4; x < minX + 16; x += 8) for (int z = minZ + 4; z < minZ + 16; z += 8) {
int highest = localTop(level, sampler, water, minX, minZ, x, z, heights), placed = 0;
for (int bed = highest - 8; bed >= 40; bed--) {
BlockPos base = new BlockPos(x, bed + 1, z);
if (!ground(level.getBlockState(base.below())) || !level.getBlockState(base).isAir()) continue;
var biome = level.getBiome(base);
boolean dark = biome.is(WoodlandIslandBiomeSource.DARK_GROVE);
boolean bamboo = biome.is(WoodlandIslandBiomeSource.BAMBOO_GROVE);
if (!dark && !bamboo) continue;
long placementSeed = WoodlandGroveGeometry.seed(state.seed(), x, bed, z, 0x730DL);
if (dark) {
if (tree(level, generator, sampler, state.seed(), base,
new Species(TreeFeatures.DARK_OAK, 2, 6, 12), water, lava, 0x730DL)) { changed = true; placed++; }
} else if (Math.floorMod(placementSeed, 8) == 0) {
if (tree(level, generator, sampler, state.seed(), base,
new Species(TreeFeatures.JUNGLE_TREE_NO_VINE, 1, 5, 14), water, lava, 0x735BL)) { changed = true; placed++; }
} else if (Math.floorMod(placementSeed, 3) == 0
&& WoodlandGroveGeometry.naturalFootprint(sampler, x, bed, z, 1)
&& WoodlandGroveGeometry.treeRoom(sampler, x, bed + 1, z, 1, 3, 16)
&& !blocked(water, lava, base, 4, 17) && actualRoom(level, base, 1, 3, 16)) {
setBlock(level, base.below(), Blocks.PODZOL.defaultBlockState());
changed |= level.registryAccess().lookupOrThrow(Registries.FEATURE).getOrThrow(VegetationFeatures.BAMBOO_NO_PODZOL)
.value().place(level, generator, RandomSource.create(placementSeed), base);
placed++;
}
if (placed >= 2) break;
}
}
// Ground cover uses coherent patches; mushrooms can survive on podzol/mycelium in shade.
for (int x = minX; x < minX + 16; x++) for (int z = minZ; z < minZ + 16; z++) {
int highest = localTop(level, sampler, water, minX, minZ, x, z, heights), floors = 0;
for (int bed = highest - 8; bed >= 32; bed--) {
BlockPos floor = new BlockPos(x, bed, z), plant = floor.above();
if (!ground(level.getBlockState(floor)) || !level.getBlockState(plant).isAir()) continue;
var biome = level.getBiome(plant);
if (!biome.is(WoodlandIslandBiomeSource.DARK_GROVE) && !biome.is(WoodlandIslandBiomeSource.BAMBOO_GROVE)) continue;
if (!WoodlandGroveGeometry.naturalFootprint(sampler, x, bed, z, 1) || blocked(water, lava, plant, 1, 2)) continue;
long patch = WoodlandGroveGeometry.seed(state.seed(), Math.floorDiv(x, 7), 0, Math.floorDiv(z, 7), 0x742DL);
BlockState soil = Math.floorMod(patch, 20) == 0 ? Blocks.MOSS_BLOCK.defaultBlockState()
: Math.floorMod(patch, 3) == 0 ? Blocks.MYCELIUM.defaultBlockState() : Blocks.PODZOL.defaultBlockState();
setBlock(level, floor, soil);
long detail = WoodlandGroveGeometry.seed(state.seed(), x, bed, z, 0x743EL);
if (Math.floorMod(detail, 5) == 0) {
BlockState mushroom = (Math.floorMod(detail, 2) == 0 ? Blocks.BROWN_MUSHROOM : Blocks.RED_MUSHROOM).defaultBlockState();
if (mushroom.canSurvive(level, plant)) setBlock(level, plant, mushroom);
}
changed = true;
if (++floors >= 3) break;
}
}
return changed;
}
private boolean tree(WorldGenLevel level, ChunkGenerator generator, WoodlandGroveGeometry.Sampler sampler, long seed,
BlockPos base, Species species, LayeredHydrology.Plan water, LayeredLavaDeposit.Plan lava, long salt) {
if (blocked(water, lava, base, species.radius() + 1, species.height(), rootDepth(species))
|| !WoodlandGroveGeometry.naturalFootprint(sampler, base.getX(), base.getY() - 1, base.getZ(), species.width())
|| !actualRoom(level, base, species.width(), species.radius(), species.height())
|| !WoodlandGroveGeometry.treeRoom(sampler, base.getX(), base.getY(), base.getZ(), species.width(), species.radius(), species.height())) return false;
for (int dx = 0; dx < species.width(); dx++) for (int dz = 0; dz < species.width(); dz++) {
BlockPos floor = base.offset(dx, -1, dz);
if (!ground(level.getBlockState(floor))) return false;
}
for (int dx = 0; dx < species.width(); dx++) for (int dz = 0; dz < species.width(); dz++) {
setBlock(level, base.offset(dx, -1, dz), Blocks.PODZOL.defaultBlockState());
}
return level.registryAccess().lookupOrThrow(Registries.FEATURE).getOrThrow(species.key()).value()
.place(level, generator, RandomSource.create(WoodlandGroveGeometry.seed(seed, base.getX(), base.getY(), base.getZ(), salt)), base);
}
private static boolean actualRoom(WorldGenLevel level, BlockPos base, int width, int radius, int height) {
for (int y = 0; y <= height; y++) {
int from = y < 3 ? 0 : -radius, to = y < 3 ? width - 1 : radius;
for (int dx = from; dx <= to; dx++) for (int dz = from; dz <= to; dz++) {
BlockState current = level.getBlockState(base.offset(dx, y, dz));
if (!current.getFluidState().isEmpty() || !(current.isAir() || current.is(BlockTags.LEAVES)
|| current.getBlock() instanceof VegetationBlock)) return false;
}
}
return true;
}
private static boolean ground(BlockState state) {
return state.is(Blocks.STONE) || state.is(Blocks.DIRT) || state.is(Blocks.GRASS_BLOCK) || state.is(Blocks.PODZOL)
|| state.is(Blocks.MYCELIUM) || state.is(Blocks.ROOTED_DIRT) || state.is(Blocks.COARSE_DIRT)
|| state.is(Blocks.GRANITE) || state.is(Blocks.DIORITE) || state.is(Blocks.ANDESITE) || state.is(Blocks.TUFF);
}
private static int localTop(WorldGenLevel level, WoodlandGroveGeometry.Sampler sampler, LayeredHydrology.Plan water,
int minX, int minZ, int x, int z, int[] heights) {
int index = (x - minX) * 16 + z - minZ;
if (heights[index] != Integer.MIN_VALUE) return heights[index];
// This feature runs first in the vegetation step. Ores preserve the surface; hydrology
// is the only preceding operation that removes it and records the original carveTop.
int ceiling = level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z) - 1;
var modified = water.cellAt(x, z);
if (modified != null) ceiling = Math.max(ceiling, modified.carveTop());
return heights[index] = WoodlandGroveGeometry.highestSurfaceBelow(sampler, x, z, ceiling);
}
private static int rootDepth(Species species) { return species.key().equals(TreeFeatures.MANGROVE) ? 16 : 4; }
private static boolean blocked(LayeredHydrology.Plan water, LayeredLavaDeposit.Plan lava, BlockPos base, int radius, int height) {
return blocked(water, lava, base, radius, height, 4);
}
private static boolean blocked(LayeredHydrology.Plan water, LayeredLavaDeposit.Plan lava, BlockPos base,
int radius, int height, int rootDepth) {
int low = base.getY() - rootDepth, high = base.getY() + height;
for (int x = base.getX() - radius; x <= base.getX() + radius; x++) {
for (int z = base.getZ() - radius; z <= base.getZ() + radius; z++) {
var cell = water.cellAt(x, z);
if (cell != null && low <= cell.carveTop() && high >= cell.bedY() - cell.sedimentDepth() - 1) return true;
}
}
for (var spring : water.springs()) {
var bounds = spring.flowBounds();
if (base.getX() + radius >= bounds.minX() - 2 && base.getX() - radius <= bounds.maxX() + 2
&& base.getZ() + radius >= bounds.minZ() - 2 && base.getZ() - radius <= bounds.maxZ() + 2
&& low <= bounds.maxY() + 2 && high >= bounds.minY() - 2) return true;
var source = spring.source();
if (Math.abs(base.getX() - source.x()) <= radius + 3 && Math.abs(base.getZ() - source.z()) <= radius + 3
&& low <= source.y() + 3 && high >= source.y() - 3) return true;
}
for (var cell : lava.cells()) if (Math.abs(base.getX() - cell.x()) <= radius + 2 && Math.abs(base.getZ() - cell.z()) <= radius + 2
&& low <= cell.y() + 2 && high >= cell.y() - 2) return true;
for (var support : lava.supports()) if (Math.abs(base.getX() - support.x()) <= radius + 2 && Math.abs(base.getZ() - support.z()) <= radius + 2
&& low <= support.y() + 2 && high >= support.y() - 2) return true;
return false;
}
}
@@ -0,0 +1,142 @@
package fr.koka.sanctuary.worldgen;
import fr.koka.sanctuary.SanctuaryMod;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
import java.util.WeakHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.RandomState;
/** Alpha.7 only: immutable plans cached by world state, applied to fresh terrain before decoration. */
public final class WoodlandHydrologyRuntime {
private static final Map<RandomState, LayeredHydrology.Plan> PLANS =
Collections.synchronizedMap(new WeakHashMap<>());
private static final Map<RandomState, LayeredLavaDeposit.Plan> LAVA_PLANS =
Collections.synchronizedMap(new WeakHashMap<>());
private WoodlandHydrologyRuntime() {}
public static boolean enabled(NoiseBasedChunkGenerator generator) {
return generator.generatorSettings().is(SanctuarySpawn.WOODLAND_SETTINGS);
}
public static LayeredHydrology.Plan woodlandPlan(ServerLevel level) {
return plan((NoiseBasedChunkGenerator) level.getChunkSource().getGenerator(),
level.getChunkSource().randomState());
}
public static LayeredHydrology.Plan plan(NoiseBasedChunkGenerator generator, RandomState randomState) {
if (!enabled(generator)) throw new IllegalArgumentException("Woodland hydrology is not enabled for this generator");
return PLANS.computeIfAbsent(randomState, state -> {
long start = System.nanoTime();
var density = generator.generatorSettings().value().noiseRouter().finalDensity();
var plan = LayeredHydrology.create(state.seed(),
(x, y, z) -> state.sampleBlockValueUncached(density, x, y, z));
SanctuaryMod.LOGGER.info("Sanctuary woodland hydrology: seed {}, {} features, {} columns, {} springs, {} ms",
state.seed(), plan.features().size(), plan.cells().size(), plan.springs().size(),
(System.nanoTime() - start) / 1_000_000);
return plan;
});
}
public static LayeredLavaDeposit.Plan lavaPlan(ServerLevel level) {
return lavaPlan((NoiseBasedChunkGenerator) level.getChunkSource().getGenerator(),
level.getChunkSource().randomState());
}
public static LayeredLavaDeposit.Plan lavaPlan(NoiseBasedChunkGenerator generator, RandomState randomState) {
// Acquire the water plan before the lava cache lock; the water planner never acquires it.
var water = plan(generator, randomState);
return LAVA_PLANS.computeIfAbsent(randomState, state -> {
long start = System.nanoTime();
var density = generator.generatorSettings().value().noiseRouter().finalDensity();
var lava = LayeredLavaDeposit.create(state.seed(),
(x, y, z) -> state.sampleBlockValueUncached(density, x, y, z), water);
SanctuaryMod.LOGGER.info("Sanctuary starter lava: seed {}, {} sources, access {}, {} ms",
state.seed(), lava.lavaCells().size(), lava.access(), (System.nanoTime() - start) / 1_000_000);
return lava;
});
}
public static void apply(NoiseBasedChunkGenerator generator, RandomState randomState, ChunkAccess chunk) {
if (!enabled(generator)) return;
var chunkPos = chunk.getPos();
if (Math.abs((long) chunkPos.x() * 16) > IslandShape.TERRAIN_LIMIT + 16L
|| Math.abs((long) chunkPos.z() * 16) > IslandShape.TERRAIN_LIMIT + 16L) return;
var plan = plan(generator, randomState);
var cells = plan.cellsInChunk(chunkPos.x(), chunkPos.z());
var springs = plan.springsInChunk(chunkPos.x(), chunkPos.z());
var outlets = plan.springs().stream().map(LayeredHydrology.Spring::outlet)
.filter(outlet -> (outlet.x() >> 4) == chunkPos.x() && (outlet.z() >> 4) == chunkPos.z()).toList();
var lava = lavaPlan(generator, randomState);
var lavaCells = lava.cellsInChunk(chunkPos.x(), chunkPos.z());
var lavaSupports = lava.supportsInChunk(chunkPos.x(), chunkPos.z());
if (cells.isEmpty() && springs.isEmpty() && outlets.isEmpty() && lavaCells.isEmpty() && lavaSupports.isEmpty()) return;
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
// Validate all replacements and two intact support layers before modifying this chunk.
for (var cell : cells) {
for (int y = cell.bedY() - cell.sedimentDepth() - 1; y <= cell.bedY(); y++) {
requireSolid(chunk, pos.set(cell.x(), y, cell.z()), randomState.seed());
}
}
for (var spring : springs) {
var source = spring.source();
requireSolid(chunk, pos.set(source.x(), source.y(), source.z()), randomState.seed());
}
for (var support : lavaSupports) {
requireSolid(chunk, pos.set(support.x(), support.y(), support.z()), randomState.seed());
if (chunk.getBlockState(pos).ignitedByLava()) {
throw new IllegalStateException("Lava pocket requires nonflammable natural support at " + pos);
}
}
for (var cell : lavaCells) {
requireSolid(chunk, pos.set(cell.x(), cell.y(), cell.z()), randomState.seed());
}
for (var cell : cells) {
for (int depth = 0; depth < cell.sedimentDepth(); depth++) {
pos.set(cell.x(), cell.bedY() - depth, cell.z());
BlockState material = WoodlandMaterials.sedimentBlock(cell, randomState.seed(), depth);
chunk.setBlockState(pos, material, 0);
}
for (int y = cell.bedY() + 1; y <= Math.max(cell.waterY(), cell.carveTop()); y++) {
pos.set(cell.x(), y, cell.z());
chunk.setBlockState(pos, cell.hasWater() && y <= cell.waterY()
? Blocks.WATER.defaultBlockState() : Blocks.AIR.defaultBlockState(), 0);
if (cell.hasWater() && y <= cell.waterY()) chunk.markPosForPostProcessing(pos);
}
}
for (var spring : springs) {
var source = spring.source();
pos.set(source.x(), source.y(), source.z());
chunk.setBlockState(pos, Blocks.WATER.defaultBlockState(), 0);
// Only the source is generated. Vanilla fluid ticks create the descending waterfall.
chunk.markPosForPostProcessing(pos);
}
for (var cell : lavaCells) {
pos.set(cell.x(), cell.y(), cell.z());
chunk.setBlockState(pos, (cell.lava() ? Blocks.LAVA : Blocks.AIR).defaultBlockState(), 0);
if (cell.lava()) chunk.markPosForPostProcessing(pos);
}
// This existing vanilla queue is consumed after neighbouring chunks finish decoration.
// Mark the outlet in its own chunk, including when its source belongs to a neighbour.
for (var outlet : outlets) {
pos.set(outlet.x(), outlet.y(), outlet.z());
chunk.markPosForPostProcessing(pos);
}
Heightmap.primeHeightmaps(chunk, EnumSet.of(Heightmap.Types.WORLD_SURFACE_WG,
Heightmap.Types.OCEAN_FLOOR_WG));
}
private static void requireSolid(ChunkAccess chunk, BlockPos pos, long seed) {
if (!chunk.getBlockState(pos).isCollisionShapeFullBlock(chunk, pos)) {
throw new IllegalStateException("Woodland hydrology differs from terrain at " + pos + " for seed " + seed);
}
}
}
@@ -0,0 +1,89 @@
package fr.koka.sanctuary.worldgen;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import fr.koka.sanctuary.SanctuaryMod;
import java.util.stream.Stream;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeResolver;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.Climate;
/** Seeded alpha.7 woodland climate, separate from all saved earlier biome-source codecs. */
public final class WoodlandIslandBiomeSource extends BiomeSource {
public static final ResourceKey<Biome> OAK_FOREST = key("woodland_oak_forest");
public static final ResourceKey<Biome> BIRCH_FOREST = key("woodland_birch_forest");
public static final ResourceKey<Biome> CLEARING = key("woodland_clearing");
public static final ResourceKey<Biome> DRY_WOODLAND = key("woodland_dry_woodland");
public static final ResourceKey<Biome> ROCKY_HEATH = key("woodland_rocky_heath");
public static final ResourceKey<Biome> DARK_GROVE = key("woodland_dark_grove");
public static final ResourceKey<Biome> BAMBOO_GROVE = key("woodland_bamboo_grove");
public static final ResourceKey<Biome> SULFUR_DEPTHS = key("woodland_sulfur_depths");
public static final MapCodec<WoodlandIslandBiomeSource> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
Biome.CODEC.fieldOf("oak_forest").forGetter(source -> source.oakForest),
Biome.CODEC.fieldOf("birch_forest").forGetter(source -> source.birchForest),
Biome.CODEC.fieldOf("clearing").forGetter(source -> source.clearing),
Biome.CODEC.fieldOf("dry_woodland").forGetter(source -> source.dryWoodland),
Biome.CODEC.fieldOf("rocky_heath").forGetter(source -> source.rockyHeath),
Biome.CODEC.fieldOf("dark_grove").forGetter(source -> source.darkGrove),
Biome.CODEC.fieldOf("bamboo_grove").forGetter(source -> source.bambooGrove),
Biome.CODEC.fieldOf("sulfur_depths").forGetter(source -> source.sulfurDepths)
).apply(instance, WoodlandIslandBiomeSource::new));
private final Holder<Biome> oakForest;
private final Holder<Biome> birchForest;
private final Holder<Biome> clearing;
private final Holder<Biome> dryWoodland;
private final Holder<Biome> rockyHeath;
private final Holder<Biome> darkGrove;
private final Holder<Biome> bambooGrove;
private final Holder<Biome> sulfurDepths;
public WoodlandIslandBiomeSource(Holder<Biome> oakForest, Holder<Biome> birchForest, Holder<Biome> clearing,
Holder<Biome> dryWoodland, Holder<Biome> rockyHeath, Holder<Biome> darkGrove,
Holder<Biome> bambooGrove, Holder<Biome> sulfurDepths) {
this.oakForest = oakForest;
this.birchForest = birchForest;
this.clearing = clearing;
this.dryWoodland = dryWoodland;
this.rockyHeath = rockyHeath;
this.darkGrove = darkGrove;
this.bambooGrove = bambooGrove;
this.sulfurDepths = sulfurDepths;
}
@Override
protected MapCodec<WoodlandIslandBiomeSource> codec() { return CODEC; }
@Override
protected Stream<Holder<Biome>> collectPossibleBiomes() {
return Stream.of(oakForest, birchForest, clearing, dryWoodland, rockyHeath, darkGrove, bambooGrove, sulfurDepths);
}
@Override
public BiomeResolver createResolver(Climate.Sampler sampler) {
return (quartX, quartY, quartZ) -> {
int x = quartX * 4, y = quartY * 4, z = quartZ * 4;
float variation = sampler.temperature().sampleValue(x, 0, z);
float moisture = sampler.humidity().sampleValue(x, 0, z);
return switch (WoodlandBiomePalette.select(y, variation, moisture)) {
case OAK_FOREST -> oakForest;
case BIRCH_FOREST -> birchForest;
case CLEARING -> clearing;
case DRY_WOODLAND -> dryWoodland;
case ROCKY_HEATH -> rockyHeath;
case DARK_GROVE -> darkGrove;
case BAMBOO_GROVE -> bambooGrove;
case SULFUR_DEPTHS -> sulfurDepths;
};
};
}
private static ResourceKey<Biome> key(String path) {
return ResourceKey.create(Registries.BIOME, SanctuaryMod.id(path));
}
}
@@ -0,0 +1,43 @@
package fr.koka.sanctuary.worldgen;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
/** Alpha.7 shore deposits: broad continuous patches through the existing sediment thickness. */
public final class WoodlandMaterials {
private WoodlandMaterials() {}
public static BlockState sedimentBlock(LayeredHydrology.Cell cell, long seed, int depth) {
if (cell.material() == LayeredHydrology.ShoreMaterial.GRASS) {
return (depth == 0 ? Blocks.GRASS_BLOCK : Blocks.DIRT).defaultBlockState();
}
double patch = patch(seed, cell.x(), cell.z(), 32);
Block material;
if (cell.hasWater() && patch < 0.24) material = Blocks.CLAY;
else if (patch < 0.53) material = Blocks.SAND;
else if (patch < 0.68) material = Blocks.GRAVEL;
else {
double rock = patch(seed ^ 0x4c6179657273L, cell.x(), cell.z(), 40);
material = rock < 0.28 ? Blocks.GRANITE : rock < 0.45 ? Blocks.DIORITE
: rock < 0.65 ? Blocks.ANDESITE : Blocks.STONE;
}
return material.defaultBlockState();
}
private static double patch(long seed, int x, int z, int scale) {
int gx = Math.floorDiv(x, scale), gz = Math.floorDiv(z, scale);
double tx = smooth(Math.floorMod(x, scale) / (double) scale);
double tz = smooth(Math.floorMod(z, scale) / (double) scale);
double near = lerp(value(seed, gx, gz), value(seed, gx + 1, gz), tx);
double far = lerp(value(seed, gx, gz + 1), value(seed, gx + 1, gz + 1), tx);
return lerp(near, far, tz);
}
private static double value(long seed, int x, int z) {
return (WoodlandGroveGeometry.seed(seed, x, 0, z, 0x53484f5245L) >>> 11) * 0x1.0p-53;
}
private static double smooth(double t) { return t * t * (3 - 2 * t); }
private static double lerp(double a, double b, double t) { return a + (b - a) * t; }
}
@@ -0,0 +1,41 @@
package fr.koka.sanctuary.worldgen;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.feature.Feature;
/** A few plants on existing suitable shores, after the forest has been decorated. */
public final class WoodlandShoreSugarCaneFeature implements Feature {
public static final MapCodec<WoodlandShoreSugarCaneFeature> CODEC = MapCodec.unit(WoodlandShoreSugarCaneFeature::new);
@Override
public MapCodec<WoodlandShoreSugarCaneFeature> codec() {
return CODEC;
}
@Override
public boolean place(WorldGenLevel level, ChunkGenerator generator, RandomSource random, BlockPos origin) {
if (!(generator instanceof NoiseBasedChunkGenerator noise) || !WoodlandHydrologyRuntime.enabled(noise)) return false;
var plan = WoodlandHydrologyRuntime.plan(noise, level.getLevel().getChunkSource().randomState());
var cane = Blocks.SUGAR_CANE.defaultBlockState();
boolean placed = false;
for (var cell : plan.cellsInChunk(origin.getX() >> 4, origin.getZ() >> 4)) {
if (cell.hasWater() || random.nextInt(7) != 0) continue;
BlockPos base = new BlockPos(cell.x(), cell.bedY() + 1, cell.z());
if (!level.getBlockState(base).isAir() || !cane.canSurvive(level, base)) continue;
int height = 1 + random.nextInt(3);
for (int y = 0; y < height; y++) {
BlockPos pos = base.above(y);
if (!level.getBlockState(pos).isAir() || !cane.canSurvive(level, pos)) break;
setBlock(level, pos, cane);
placed = true;
}
}
return placed;
}
}
@@ -0,0 +1,94 @@
package fr.koka.sanctuary.worldgen;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.VegetationBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.material.Fluids;
/** One-time alpha.7 outlet finishing, using vanilla's persisted and consumed generation queue. */
public final class WoodlandSpringOutlets {
public record Pending(List<LayeredHydrology.Position> sources, List<LayeredHydrology.Position> outlets) {
public static final Pending EMPTY = new Pending(List.of(), List.of());
public Pending { sources = List.copyOf(sources); outlets = List.copyOf(outlets); }
public boolean isEmpty() { return sources.isEmpty() && outlets.isEmpty(); }
}
private WoodlandSpringOutlets() {}
/** Called before vanilla consumes the queue; a fully processed/reloaded chunk has no work. */
public static Pending capturePending(ServerLevel level, LevelChunk chunk) {
if (!(level.getChunkSource().getGenerator() instanceof NoiseBasedChunkGenerator noise)
|| !WoodlandHydrologyRuntime.enabled(noise)) return Pending.EMPTY;
boolean queued = false;
for (var section : chunk.getPostProcessing()) if (section != null && !section.isEmpty()) {
queued = true;
break;
}
if (!queued) return Pending.EMPTY;
var plan = WoodlandHydrologyRuntime.plan(noise, level.getChunkSource().randomState());
List<LayeredHydrology.Position> sources = new ArrayList<>(), outlets = new ArrayList<>();
for (var spring : plan.springs()) {
if (markedHere(chunk, spring.source())) sources.add(spring.source());
if (markedHere(chunk, spring.outlet())) outlets.add(spring.outlet());
}
return new Pending(sources, outlets);
}
/** All neighbouring FEATURES have finished before a chunk reaches this stage. */
public static void finish(ServerLevel level, LevelChunk chunk, Pending pending) {
if (pending.isEmpty() || !(level.getChunkSource().getGenerator() instanceof NoiseBasedChunkGenerator noise)
|| !WoodlandHydrologyRuntime.enabled(noise)) return;
var random = level.getChunkSource().randomState();
var density = noise.generatorSettings().value().noiseRouter().finalDensity();
for (var outlet : pending.outlets()) {
if (!owns(chunk, outlet)) throw new IllegalArgumentException("An outlet belongs to another chunk: " + outlet);
if (random.sampleBlockValueUncached(density, outlet.x(), outlet.y(), outlet.z()) > 0) {
throw new IllegalStateException("A spring outlet must already be natural air: " + outlet);
}
BlockPos pos = blockPos(outlet);
BlockState current = chunk.getBlockState(pos);
if (removableDecoration(current)) {
// Only this declared air cell is reopened. Neighbour notification wakes the
// adjacent source even if its own chunk was post-processed first.
level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
}
}
for (var source : pending.sources()) {
if (!owns(chunk, source)) throw new IllegalArgumentException("A source belongs to another chunk: " + source);
BlockPos pos = blockPos(source);
BlockState current = chunk.getBlockState(pos);
if (current.is(Blocks.WATER) && current.getFluidState().isSource()) {
level.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level));
}
}
}
private static boolean markedHere(LevelChunk chunk, LayeredHydrology.Position position) {
if (!owns(chunk, position)) return false;
var queue = chunk.getPostProcessing()[chunk.getSectionIndex(position.y())];
return queue != null && queue.contains(ProtoChunk.packOffsetCoordinates(blockPos(position)));
}
private static boolean owns(LevelChunk chunk, LayeredHydrology.Position position) {
return (position.x() >> 4) == chunk.getPos().x() && (position.z() >> 4) == chunk.getPos().z();
}
private static BlockPos blockPos(LayeredHydrology.Position position) {
return new BlockPos(position.x(), position.y(), position.z());
}
private static boolean removableDecoration(BlockState state) {
if (state.hasBlockEntity()) return false;
return state.is(Blocks.GLOW_LICHEN) || state.is(Blocks.VINE) || state.is(Blocks.MOSS_CARPET)
|| state.getBlock() instanceof VegetationBlock || state.is(BlockTags.LEAVES) || state.is(BlockTags.LOGS);
}
}
@@ -11,5 +11,13 @@
"biome.sanctuary.layered_flower_meadow": "Flowered Highland Meadow",
"biome.sanctuary.layered_lower_grove": "Ledge Woodland",
"biome.sanctuary.layered_mossy_terraces": "Mossy Ledges",
"biome.sanctuary.layered_rocky_terraces": "Rocky Terraces"
"biome.sanctuary.layered_rocky_terraces": "Rocky Terraces",
"biome.sanctuary.woodland_oak_forest": "Sanctuary Oak Woodland",
"biome.sanctuary.woodland_birch_forest": "Sanctuary Birch Woodland",
"biome.sanctuary.woodland_clearing": "Flowered Clearing",
"biome.sanctuary.woodland_dry_woodland": "Dry Woodland",
"biome.sanctuary.woodland_rocky_heath": "Rocky Heath",
"biome.sanctuary.woodland_dark_grove": "Dark Undergrowth",
"biome.sanctuary.woodland_bamboo_grove": "Bamboo Grove",
"biome.sanctuary.woodland_sulfur_depths": "Sulfur Depths"
}
@@ -11,5 +11,13 @@
"biome.sanctuary.layered_flower_meadow": "Prairie fleurie des hauteurs",
"biome.sanctuary.layered_lower_grove": "Sous-bois des corniches",
"biome.sanctuary.layered_mossy_terraces": "Corniches moussues",
"biome.sanctuary.layered_rocky_terraces": "Terrasses rocheuses"
"biome.sanctuary.layered_rocky_terraces": "Terrasses rocheuses",
"biome.sanctuary.woodland_oak_forest": "Chênaie de Sanctuary",
"biome.sanctuary.woodland_birch_forest": "Bétulaie de Sanctuary",
"biome.sanctuary.woodland_clearing": "Clairière fleurie",
"biome.sanctuary.woodland_dry_woodland": "Bois sec",
"biome.sanctuary.woodland_rocky_heath": "Lande rocheuse",
"biome.sanctuary.woodland_dark_grove": "Sous-bois sombre",
"biome.sanctuary.woodland_bamboo_grove": "Bosquet de bambous",
"biome.sanctuary.woodland_sulfur_depths": "Profondeurs soufrées"
}
@@ -0,0 +1,173 @@
{
"attributes": {
"minecraft:audio/background_music": {
"default": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.overworld.sparse_jungle"
}
},
"minecraft:gameplay/natural_mob_spawns": {
"argument": {
"spawn_costs": {},
"spawns_by_category": {
"ambient": [
{
"type": "minecraft:bat",
"count": 8,
"weight": 10
}
],
"creature": [
{
"type": "minecraft:sheep",
"count": 4,
"weight": 12
},
{
"type": "minecraft:pig",
"count": 4,
"weight": 10
},
{
"type": "minecraft:chicken",
"count": 4,
"weight": 10
},
{
"type": "minecraft:cow",
"count": 4,
"weight": 8
},
{
"type": "minecraft:chicken",
"count": 4,
"weight": 10
},
{
"type": "minecraft:wolf",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 4,
"min_inclusive": 2
},
"weight": 8
}
],
"monster": [
{
"type": "minecraft:spider",
"count": 4,
"weight": 100
},
{
"type": "minecraft:zombie",
"count": 4,
"weight": 95
},
{
"type": "minecraft:zombie_villager",
"count": 1,
"weight": 5
},
{
"type": "minecraft:skeleton",
"count": 4,
"weight": 100
},
{
"type": "minecraft:creeper",
"count": 4,
"weight": 100
},
{
"type": "minecraft:slime",
"count": 4,
"weight": 100
},
{
"type": "minecraft:enderman",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 4,
"min_inclusive": 1
},
"weight": 10
},
{
"type": "minecraft:witch",
"count": 1,
"weight": 5
}
],
"underground_water_creature": [
{
"type": "minecraft:glow_squid",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 6,
"min_inclusive": 4
},
"weight": 10
}
]
}
},
"modifier": "overlay"
},
"minecraft:visual/sky_color": "#79a6ff"
},
"carvers": [],
"downfall": 0.95,
"effects": {
"water_color": "#3f76e4",
"grass_color": "#88b96b",
"foliage_color": "#79aa61",
"dry_foliage_color": "#b9a766"
},
"features": [
[],
[],
[],
[],
[],
[],
[
"minecraft:ore_dirt",
"minecraft:ore_granite_upper",
"minecraft:ore_granite_lower",
"minecraft:ore_diorite_upper",
"minecraft:ore_diorite_lower",
"minecraft:ore_andesite_upper",
"minecraft:ore_andesite_lower",
"minecraft:ore_tuff",
"sanctuary:woodland_ore_coal",
"sanctuary:woodland_ore_iron",
"sanctuary:woodland_ore_copper",
"sanctuary:island_ore_gold",
"sanctuary:island_ore_redstone",
"sanctuary:island_ore_lapis",
"sanctuary:island_ore_diamond",
"sanctuary:woodland_ore_coal_surface",
"sanctuary:woodland_ore_iron_surface",
"sanctuary:woodland_ore_copper_surface",
"sanctuary:woodland_ore_coal_ledges",
"sanctuary:woodland_ore_iron_ledges",
"sanctuary:woodland_ore_copper_ledges"
],
[],
[],
[
"sanctuary:woodland_groves",
"minecraft:glow_lichen",
"minecraft:brown_mushroom_normal",
"minecraft:red_mushroom_normal",
"sanctuary:woodland_shore_sugar_cane"
],
[
"minecraft:freeze_top_layer"
]
],
"has_precipitation": true,
"temperature": 0.8
}
@@ -0,0 +1,168 @@
{
"attributes": {
"minecraft:audio/background_music": {
"default": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.overworld.forest"
}
},
"minecraft:gameplay/natural_mob_spawns": {
"argument": {
"spawn_costs": {},
"spawns_by_category": {
"ambient": [
{
"type": "minecraft:bat",
"count": 8,
"weight": 10
}
],
"creature": [
{
"type": "minecraft:sheep",
"count": 4,
"weight": 12
},
{
"type": "minecraft:pig",
"count": 4,
"weight": 10
},
{
"type": "minecraft:chicken",
"count": 4,
"weight": 10
},
{
"type": "minecraft:cow",
"count": 4,
"weight": 8
}
],
"monster": [
{
"type": "minecraft:spider",
"count": 4,
"weight": 100
},
{
"type": "minecraft:zombie",
"count": 4,
"weight": 95
},
{
"type": "minecraft:zombie_villager",
"count": 1,
"weight": 5
},
{
"type": "minecraft:skeleton",
"count": 4,
"weight": 100
},
{
"type": "minecraft:creeper",
"count": 4,
"weight": 100
},
{
"type": "minecraft:slime",
"count": 4,
"weight": 100
},
{
"type": "minecraft:enderman",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 4,
"min_inclusive": 1
},
"weight": 10
},
{
"type": "minecraft:witch",
"count": 1,
"weight": 5
}
],
"underground_water_creature": [
{
"type": "minecraft:glow_squid",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 6,
"min_inclusive": 4
},
"weight": 10
}
]
}
},
"modifier": "overlay"
},
"minecraft:visual/sky_color": "#79a6ff"
},
"carvers": [],
"downfall": 0.7,
"effects": {
"water_color": "#3f76e4",
"grass_color": "#88b96b",
"foliage_color": "#79aa61",
"dry_foliage_color": "#b9a766"
},
"features": [
[],
[],
[],
[],
[],
[],
[
"minecraft:ore_dirt",
"minecraft:ore_granite_upper",
"minecraft:ore_granite_lower",
"minecraft:ore_diorite_upper",
"minecraft:ore_diorite_lower",
"minecraft:ore_andesite_upper",
"minecraft:ore_andesite_lower",
"minecraft:ore_tuff",
"sanctuary:woodland_ore_coal",
"sanctuary:woodland_ore_iron",
"sanctuary:woodland_ore_copper",
"sanctuary:island_ore_gold",
"sanctuary:island_ore_redstone",
"sanctuary:island_ore_lapis",
"sanctuary:island_ore_diamond",
"sanctuary:woodland_ore_coal_surface",
"sanctuary:woodland_ore_iron_surface",
"sanctuary:woodland_ore_copper_surface",
"sanctuary:woodland_ore_coal_ledges",
"sanctuary:woodland_ore_iron_ledges",
"sanctuary:woodland_ore_copper_ledges"
],
[],
[],
[
"sanctuary:woodland_groves",
"minecraft:glow_lichen",
"minecraft:forest_flowers",
"minecraft:wildflowers_birch_forest",
"minecraft:trees_birch",
"minecraft:patch_bush",
"minecraft:flower_default",
"minecraft:patch_grass_forest",
"minecraft:brown_mushroom_normal",
"minecraft:red_mushroom_normal",
"minecraft:patch_pumpkin",
"minecraft:patch_sugar_cane",
"minecraft:patch_firefly_bush_near_water",
"sanctuary:woodland_shore_sugar_cane"
],
[
"minecraft:freeze_top_layer"
]
],
"has_precipitation": true,
"temperature": 0.7
}
@@ -0,0 +1,170 @@
{
"attributes": {
"minecraft:audio/background_music": {
"default": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.overworld.meadow"
}
},
"minecraft:gameplay/natural_mob_spawns": {
"argument": {
"spawn_costs": {},
"spawns_by_category": {
"ambient": [
{
"type": "minecraft:bat",
"count": 8,
"weight": 10
}
],
"creature": [
{
"type": "minecraft:donkey",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 2,
"min_inclusive": 1
},
"weight": 1
},
{
"type": "minecraft:rabbit",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 6,
"min_inclusive": 2
},
"weight": 2
},
{
"type": "minecraft:sheep",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 4,
"min_inclusive": 2
},
"weight": 2
}
],
"monster": [
{
"type": "minecraft:spider",
"count": 4,
"weight": 100
},
{
"type": "minecraft:zombie",
"count": 4,
"weight": 95
},
{
"type": "minecraft:zombie_villager",
"count": 1,
"weight": 5
},
{
"type": "minecraft:skeleton",
"count": 4,
"weight": 100
},
{
"type": "minecraft:creeper",
"count": 4,
"weight": 100
},
{
"type": "minecraft:slime",
"count": 4,
"weight": 100
},
{
"type": "minecraft:enderman",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 4,
"min_inclusive": 1
},
"weight": 10
},
{
"type": "minecraft:witch",
"count": 1,
"weight": 5
}
],
"underground_water_creature": [
{
"type": "minecraft:glow_squid",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 6,
"min_inclusive": 4
},
"weight": 10
}
]
}
},
"modifier": "overlay"
},
"minecraft:visual/sky_color": "#79a6ff"
},
"carvers": [],
"downfall": 0.75,
"effects": {
"water_color": "#3f76e4",
"grass_color": "#88b96b",
"foliage_color": "#79aa61",
"dry_foliage_color": "#b9a766"
},
"features": [
[],
[],
[],
[],
[],
[],
[
"minecraft:ore_dirt",
"minecraft:ore_granite_upper",
"minecraft:ore_granite_lower",
"minecraft:ore_diorite_upper",
"minecraft:ore_diorite_lower",
"minecraft:ore_andesite_upper",
"minecraft:ore_andesite_lower",
"minecraft:ore_tuff",
"sanctuary:woodland_ore_coal",
"sanctuary:woodland_ore_iron",
"sanctuary:woodland_ore_copper",
"sanctuary:island_ore_gold",
"sanctuary:island_ore_redstone",
"sanctuary:island_ore_lapis",
"sanctuary:island_ore_diamond",
"sanctuary:woodland_ore_coal_surface",
"sanctuary:woodland_ore_iron_surface",
"sanctuary:woodland_ore_copper_surface",
"sanctuary:woodland_ore_coal_ledges",
"sanctuary:woodland_ore_iron_ledges",
"sanctuary:woodland_ore_copper_ledges"
],
[],
[],
[
"sanctuary:woodland_groves",
"minecraft:glow_lichen",
"minecraft:patch_tall_grass_2",
"minecraft:patch_grass_meadow",
"minecraft:flower_meadow",
"minecraft:trees_meadow",
"minecraft:wildflowers_meadow",
"minecraft:flower_flower_forest",
"sanctuary:woodland_shore_sugar_cane"
],
[
"minecraft:freeze_top_layer"
]
],
"has_precipitation": true,
"temperature": 0.7
}
@@ -0,0 +1,159 @@
{
"attributes": {
"minecraft:audio/background_music": {
"default": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.overworld.forest"
}
},
"minecraft:gameplay/natural_mob_spawns": {
"argument": {
"spawn_costs": {},
"spawns_by_category": {
"ambient": [
{
"type": "minecraft:bat",
"count": 8,
"weight": 10
}
],
"creature": [
{
"type": "minecraft:sheep",
"count": 4,
"weight": 12
},
{
"type": "minecraft:pig",
"count": 4,
"weight": 10
},
{
"type": "minecraft:chicken",
"count": 4,
"weight": 10
},
{
"type": "minecraft:cow",
"count": 4,
"weight": 8
}
],
"monster": [
{
"type": "minecraft:spider",
"count": 4,
"weight": 100
},
{
"type": "minecraft:zombie",
"count": 4,
"weight": 95
},
{
"type": "minecraft:zombie_villager",
"count": 1,
"weight": 5
},
{
"type": "minecraft:skeleton",
"count": 4,
"weight": 100
},
{
"type": "minecraft:creeper",
"count": 4,
"weight": 100
},
{
"type": "minecraft:slime",
"count": 4,
"weight": 100
},
{
"type": "minecraft:enderman",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 4,
"min_inclusive": 1
},
"weight": 10
},
{
"type": "minecraft:witch",
"count": 1,
"weight": 5
}
],
"underground_water_creature": [
{
"type": "minecraft:glow_squid",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 6,
"min_inclusive": 4
},
"weight": 10
}
]
}
},
"modifier": "overlay"
},
"minecraft:visual/sky_color": "#79a6ff"
},
"carvers": [],
"downfall": 0.9,
"effects": {
"water_color": "#3f76e4",
"grass_color": "#88b96b",
"foliage_color": "#79aa61",
"dry_foliage_color": "#b9a766"
},
"features": [
[],
[],
[],
[],
[],
[],
[
"minecraft:ore_dirt",
"minecraft:ore_granite_upper",
"minecraft:ore_granite_lower",
"minecraft:ore_diorite_upper",
"minecraft:ore_diorite_lower",
"minecraft:ore_andesite_upper",
"minecraft:ore_andesite_lower",
"minecraft:ore_tuff",
"sanctuary:woodland_ore_coal",
"sanctuary:woodland_ore_iron",
"sanctuary:woodland_ore_copper",
"sanctuary:island_ore_gold",
"sanctuary:island_ore_redstone",
"sanctuary:island_ore_lapis",
"sanctuary:island_ore_diamond",
"sanctuary:woodland_ore_coal_surface",
"sanctuary:woodland_ore_iron_surface",
"sanctuary:woodland_ore_copper_surface",
"sanctuary:woodland_ore_coal_ledges",
"sanctuary:woodland_ore_iron_ledges",
"sanctuary:woodland_ore_copper_ledges"
],
[],
[],
[
"sanctuary:woodland_groves",
"minecraft:glow_lichen",
"minecraft:brown_mushroom_normal",
"minecraft:red_mushroom_normal",
"sanctuary:woodland_shore_sugar_cane"
],
[
"minecraft:freeze_top_layer"
]
],
"has_precipitation": true,
"temperature": 0.7
}
@@ -0,0 +1,169 @@
{
"attributes": {
"minecraft:audio/background_music": {
"default": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.overworld.forest"
}
},
"minecraft:gameplay/natural_mob_spawns": {
"argument": {
"spawn_costs": {},
"spawns_by_category": {
"ambient": [
{
"type": "minecraft:bat",
"count": 8,
"weight": 10
}
],
"creature": [
{
"type": "minecraft:sheep",
"count": 4,
"weight": 12
},
{
"type": "minecraft:pig",
"count": 4,
"weight": 10
},
{
"type": "minecraft:chicken",
"count": 4,
"weight": 10
},
{
"type": "minecraft:cow",
"count": 4,
"weight": 8
},
{
"type": "minecraft:wolf",
"count": 4,
"weight": 5
}
],
"monster": [
{
"type": "minecraft:spider",
"count": 4,
"weight": 100
},
{
"type": "minecraft:zombie",
"count": 4,
"weight": 95
},
{
"type": "minecraft:zombie_villager",
"count": 1,
"weight": 5
},
{
"type": "minecraft:skeleton",
"count": 4,
"weight": 100
},
{
"type": "minecraft:creeper",
"count": 4,
"weight": 100
},
{
"type": "minecraft:slime",
"count": 4,
"weight": 100
},
{
"type": "minecraft:enderman",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 4,
"min_inclusive": 1
},
"weight": 10
},
{
"type": "minecraft:witch",
"count": 1,
"weight": 5
}
],
"underground_water_creature": [
{
"type": "minecraft:glow_squid",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 6,
"min_inclusive": 4
},
"weight": 10
}
]
}
},
"modifier": "overlay"
},
"minecraft:visual/sky_color": "#79a6ff"
},
"carvers": [],
"downfall": 0.4,
"effects": {
"water_color": "#3f76e4",
"grass_color": "#88b96b",
"foliage_color": "#79aa61",
"dry_foliage_color": "#b9a766"
},
"features": [
[],
[],
[],
[],
[],
[],
[
"minecraft:ore_dirt",
"minecraft:ore_granite_upper",
"minecraft:ore_granite_lower",
"minecraft:ore_diorite_upper",
"minecraft:ore_diorite_lower",
"minecraft:ore_andesite_upper",
"minecraft:ore_andesite_lower",
"minecraft:ore_tuff",
"sanctuary:woodland_ore_coal",
"sanctuary:woodland_ore_iron",
"sanctuary:woodland_ore_copper",
"sanctuary:island_ore_gold",
"sanctuary:island_ore_redstone",
"sanctuary:island_ore_lapis",
"sanctuary:island_ore_diamond",
"sanctuary:woodland_ore_coal_surface",
"sanctuary:woodland_ore_iron_surface",
"sanctuary:woodland_ore_copper_surface",
"sanctuary:woodland_ore_coal_ledges",
"sanctuary:woodland_ore_iron_ledges",
"sanctuary:woodland_ore_copper_ledges"
],
[],
[],
[
"sanctuary:woodland_groves",
"minecraft:glow_lichen",
"minecraft:forest_flowers",
"sanctuary:woodland_sparse_trees",
"minecraft:patch_bush",
"minecraft:flower_default",
"minecraft:patch_grass_forest",
"minecraft:brown_mushroom_normal",
"minecraft:red_mushroom_normal",
"sanctuary:woodland_shore_sugar_cane"
],
[
"minecraft:freeze_top_layer"
]
],
"has_precipitation": true,
"temperature": 0.75
}
@@ -0,0 +1,172 @@
{
"attributes": {
"minecraft:audio/background_music": {
"default": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.overworld.forest"
}
},
"minecraft:gameplay/natural_mob_spawns": {
"argument": {
"spawn_costs": {},
"spawns_by_category": {
"ambient": [
{
"type": "minecraft:bat",
"count": 8,
"weight": 10
}
],
"creature": [
{
"type": "minecraft:sheep",
"count": 4,
"weight": 12
},
{
"type": "minecraft:pig",
"count": 4,
"weight": 10
},
{
"type": "minecraft:chicken",
"count": 4,
"weight": 10
},
{
"type": "minecraft:cow",
"count": 4,
"weight": 8
},
{
"type": "minecraft:wolf",
"count": 4,
"weight": 5
}
],
"monster": [
{
"type": "minecraft:spider",
"count": 4,
"weight": 100
},
{
"type": "minecraft:zombie",
"count": 4,
"weight": 95
},
{
"type": "minecraft:zombie_villager",
"count": 1,
"weight": 5
},
{
"type": "minecraft:skeleton",
"count": 4,
"weight": 100
},
{
"type": "minecraft:creeper",
"count": 4,
"weight": 100
},
{
"type": "minecraft:slime",
"count": 4,
"weight": 100
},
{
"type": "minecraft:enderman",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 4,
"min_inclusive": 1
},
"weight": 10
},
{
"type": "minecraft:witch",
"count": 1,
"weight": 5
}
],
"underground_water_creature": [
{
"type": "minecraft:glow_squid",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 6,
"min_inclusive": 4
},
"weight": 10
}
]
}
},
"modifier": "overlay"
},
"minecraft:visual/sky_color": "#79a6ff"
},
"carvers": [],
"downfall": 0.8,
"effects": {
"water_color": "#3f76e4",
"grass_color": "#88b96b",
"foliage_color": "#79aa61",
"dry_foliage_color": "#b9a766"
},
"features": [
[],
[],
[],
[],
[],
[],
[
"minecraft:ore_dirt",
"minecraft:ore_granite_upper",
"minecraft:ore_granite_lower",
"minecraft:ore_diorite_upper",
"minecraft:ore_diorite_lower",
"minecraft:ore_andesite_upper",
"minecraft:ore_andesite_lower",
"minecraft:ore_tuff",
"sanctuary:woodland_ore_coal",
"sanctuary:woodland_ore_iron",
"sanctuary:woodland_ore_copper",
"sanctuary:island_ore_gold",
"sanctuary:island_ore_redstone",
"sanctuary:island_ore_lapis",
"sanctuary:island_ore_diamond",
"sanctuary:woodland_ore_coal_surface",
"sanctuary:woodland_ore_iron_surface",
"sanctuary:woodland_ore_copper_surface",
"sanctuary:woodland_ore_coal_ledges",
"sanctuary:woodland_ore_iron_ledges",
"sanctuary:woodland_ore_copper_ledges"
],
[],
[],
[
"sanctuary:woodland_groves",
"minecraft:glow_lichen",
"minecraft:forest_flowers",
"minecraft:trees_birch_and_oak_leaf_litter",
"minecraft:patch_bush",
"minecraft:flower_default",
"minecraft:patch_grass_forest",
"minecraft:brown_mushroom_normal",
"minecraft:red_mushroom_normal",
"minecraft:patch_pumpkin",
"minecraft:patch_sugar_cane",
"minecraft:patch_firefly_bush_near_water",
"sanctuary:woodland_shore_sugar_cane"
],
[
"minecraft:freeze_top_layer"
]
],
"has_precipitation": true,
"temperature": 0.7
}
@@ -0,0 +1,164 @@
{
"attributes": {
"minecraft:audio/background_music": {
"default": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.overworld.meadow"
}
},
"minecraft:gameplay/natural_mob_spawns": {
"argument": {
"spawn_costs": {},
"spawns_by_category": {
"ambient": [
{
"type": "minecraft:bat",
"count": 8,
"weight": 10
}
],
"creature": [
{
"type": "minecraft:donkey",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 2,
"min_inclusive": 1
},
"weight": 1
},
{
"type": "minecraft:rabbit",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 6,
"min_inclusive": 2
},
"weight": 2
},
{
"type": "minecraft:sheep",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 4,
"min_inclusive": 2
},
"weight": 2
}
],
"monster": [
{
"type": "minecraft:spider",
"count": 4,
"weight": 100
},
{
"type": "minecraft:zombie",
"count": 4,
"weight": 95
},
{
"type": "minecraft:zombie_villager",
"count": 1,
"weight": 5
},
{
"type": "minecraft:skeleton",
"count": 4,
"weight": 100
},
{
"type": "minecraft:creeper",
"count": 4,
"weight": 100
},
{
"type": "minecraft:slime",
"count": 4,
"weight": 100
},
{
"type": "minecraft:enderman",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 4,
"min_inclusive": 1
},
"weight": 10
},
{
"type": "minecraft:witch",
"count": 1,
"weight": 5
}
],
"underground_water_creature": [
{
"type": "minecraft:glow_squid",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 6,
"min_inclusive": 4
},
"weight": 10
}
]
}
},
"modifier": "overlay"
},
"minecraft:visual/sky_color": "#79a6ff"
},
"carvers": [],
"downfall": 0.65,
"effects": {
"water_color": "#3f76e4",
"grass_color": "#88b96b",
"foliage_color": "#79aa61",
"dry_foliage_color": "#b9a766"
},
"features": [
[],
[],
[],
[],
[],
[],
[
"minecraft:ore_dirt",
"minecraft:ore_granite_upper",
"minecraft:ore_granite_lower",
"minecraft:ore_diorite_upper",
"minecraft:ore_diorite_lower",
"minecraft:ore_andesite_upper",
"minecraft:ore_andesite_lower",
"minecraft:ore_tuff",
"sanctuary:woodland_ore_coal",
"sanctuary:woodland_ore_iron",
"sanctuary:woodland_ore_copper",
"sanctuary:island_ore_gold",
"sanctuary:island_ore_redstone",
"sanctuary:island_ore_lapis",
"sanctuary:island_ore_diamond",
"sanctuary:woodland_ore_coal_surface",
"sanctuary:woodland_ore_iron_surface",
"sanctuary:woodland_ore_copper_surface",
"sanctuary:woodland_ore_coal_ledges",
"sanctuary:woodland_ore_iron_ledges",
"sanctuary:woodland_ore_copper_ledges"
],
[],
[],
[
"sanctuary:woodland_groves",
"minecraft:glow_lichen",
"sanctuary:woodland_shore_sugar_cane"
],
[
"minecraft:freeze_top_layer"
]
],
"has_precipitation": true,
"temperature": 0.7
}
@@ -0,0 +1,162 @@
{
"attributes": {
"minecraft:audio/background_music": {
"default": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.overworld.forest"
}
},
"minecraft:gameplay/natural_mob_spawns": {
"argument": {
"spawn_costs": {},
"spawns_by_category": {
"ambient": [
{
"type": "minecraft:bat",
"count": 8,
"weight": 10
}
],
"creature": [
{
"type": "minecraft:sheep",
"count": 4,
"weight": 12
},
{
"type": "minecraft:pig",
"count": 4,
"weight": 10
},
{
"type": "minecraft:chicken",
"count": 4,
"weight": 10
},
{
"type": "minecraft:cow",
"count": 4,
"weight": 8
},
{
"type": "minecraft:wolf",
"count": 4,
"weight": 5
}
],
"monster": [
{
"type": "minecraft:spider",
"count": 4,
"weight": 100
},
{
"type": "minecraft:zombie",
"count": 4,
"weight": 95
},
{
"type": "minecraft:zombie_villager",
"count": 1,
"weight": 5
},
{
"type": "minecraft:skeleton",
"count": 4,
"weight": 100
},
{
"type": "minecraft:creeper",
"count": 4,
"weight": 100
},
{
"type": "minecraft:slime",
"count": 4,
"weight": 100
},
{
"type": "minecraft:enderman",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 4,
"min_inclusive": 1
},
"weight": 10
},
{
"type": "minecraft:witch",
"count": 1,
"weight": 5
}
],
"underground_water_creature": [
{
"type": "minecraft:glow_squid",
"count": {
"type": "minecraft:uniform",
"max_inclusive": 6,
"min_inclusive": 4
},
"weight": 10
}
]
}
},
"modifier": "overlay"
},
"minecraft:visual/sky_color": "#79a6ff"
},
"carvers": [],
"downfall": 0.5,
"effects": {
"water_color": "#3f76e4",
"grass_color": "#88b96b",
"foliage_color": "#79aa61",
"dry_foliage_color": "#b9a766"
},
"features": [
[],
[],
[],
[],
[],
[],
[
"minecraft:ore_dirt",
"minecraft:ore_granite_upper",
"minecraft:ore_granite_lower",
"minecraft:ore_diorite_upper",
"minecraft:ore_diorite_lower",
"minecraft:ore_andesite_upper",
"minecraft:ore_andesite_lower",
"minecraft:ore_tuff",
"sanctuary:woodland_ore_coal",
"sanctuary:woodland_ore_iron",
"sanctuary:woodland_ore_copper",
"sanctuary:island_ore_gold",
"sanctuary:island_ore_redstone",
"sanctuary:island_ore_lapis",
"sanctuary:island_ore_diamond",
"sanctuary:woodland_ore_coal_surface",
"sanctuary:woodland_ore_iron_surface",
"sanctuary:woodland_ore_copper_surface",
"sanctuary:woodland_ore_coal_ledges",
"sanctuary:woodland_ore_iron_ledges",
"sanctuary:woodland_ore_copper_ledges"
],
[],
[],
[
"sanctuary:woodland_groves",
"minecraft:glow_lichen",
"sanctuary:woodland_shore_sugar_cane"
],
[
"minecraft:freeze_top_layer"
]
],
"has_precipitation": true,
"temperature": 0.8
}
@@ -0,0 +1,83 @@
{
"type": "minecraft:ore",
"discard_chance_on_air_exposure": 0.0,
"size": 6,
"targets": [
{
"state": "minecraft:coal_ore",
"target": {
"predicate_type": "minecraft:any_of",
"rules": [
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
},
{
"max_inclusive": 2031,
"min_inclusive": 0,
"predicate_type": "minecraft:height_match"
}
]
},
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:not",
"rule": {
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
}
},
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:stone_ore_replaceables"
}
]
}
]
}
},
{
"state": "minecraft:deepslate_coal_ore",
"target": {
"predicate_type": "minecraft:any_of",
"rules": [
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
},
{
"max_inclusive": 8,
"min_inclusive": -2032,
"predicate_type": "minecraft:height_match"
}
]
},
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:not",
"rule": {
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
}
},
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:deepslate_ore_replaceables"
}
]
}
]
}
}
]
}
@@ -0,0 +1,83 @@
{
"type": "minecraft:ore",
"discard_chance_on_air_exposure": 0.0,
"size": 5,
"targets": [
{
"state": "minecraft:copper_ore",
"target": {
"predicate_type": "minecraft:any_of",
"rules": [
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
},
{
"max_inclusive": 2031,
"min_inclusive": 0,
"predicate_type": "minecraft:height_match"
}
]
},
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:not",
"rule": {
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
}
},
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:stone_ore_replaceables"
}
]
}
]
}
},
{
"state": "minecraft:deepslate_copper_ore",
"target": {
"predicate_type": "minecraft:any_of",
"rules": [
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
},
{
"max_inclusive": 8,
"min_inclusive": -2032,
"predicate_type": "minecraft:height_match"
}
]
},
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:not",
"rule": {
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
}
},
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:deepslate_ore_replaceables"
}
]
}
]
}
}
]
}
@@ -0,0 +1,83 @@
{
"type": "minecraft:ore",
"discard_chance_on_air_exposure": 0.0,
"size": 5,
"targets": [
{
"state": "minecraft:iron_ore",
"target": {
"predicate_type": "minecraft:any_of",
"rules": [
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
},
{
"max_inclusive": 2031,
"min_inclusive": 0,
"predicate_type": "minecraft:height_match"
}
]
},
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:not",
"rule": {
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
}
},
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:stone_ore_replaceables"
}
]
}
]
}
},
{
"state": "minecraft:deepslate_iron_ore",
"target": {
"predicate_type": "minecraft:any_of",
"rules": [
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
},
{
"max_inclusive": 8,
"min_inclusive": -2032,
"predicate_type": "minecraft:height_match"
}
]
},
{
"predicate_type": "minecraft:all_of",
"rules": [
{
"predicate_type": "minecraft:not",
"rule": {
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:height_specific_ore_replaceables"
}
},
{
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:deepslate_ore_replaceables"
}
]
}
]
}
}
]
}
@@ -0,0 +1,65 @@
{
"type": "minecraft:sequence",
"sequence": [
{
"type": "minecraft:condition",
"if_true": {
"type": "minecraft:biome",
"biome_is": "sanctuary:woodland_sulfur_depths"
},
"then_run": {
"type": "minecraft:condition",
"if_true": {
"type": "minecraft:noise_threshold",
"noise": "sanctuary:woodland_strata",
"min_threshold": 0.0,
"max_threshold": 1000,
"is_3d": true
},
"then_run": {
"type": "minecraft:block",
"result_state": "minecraft:sulfur"
}
}
},
{
"type": "minecraft:condition",
"if_true": {
"type": "minecraft:biome",
"biome_is": "sanctuary:woodland_rocky_heath"
},
"then_run": "sanctuary:woodland_strata"
},
{
"type": "minecraft:condition",
"if_true": {
"type": "minecraft:noise_threshold",
"noise": "sanctuary:woodland_exposure",
"min_threshold": 0.85,
"max_threshold": 1000,
"is_3d": false
},
"then_run": "sanctuary:woodland_strata"
},
{
"type": "minecraft:condition",
"if_true": {
"type": "minecraft:biome",
"biome_is": "sanctuary:woodland_dry_woodland"
},
"then_run": {
"type": "minecraft:condition",
"if_true": {
"type": "minecraft:noise_threshold",
"noise": "sanctuary:woodland_exposure",
"min_threshold": 0.15,
"max_threshold": 1000,
"is_3d": false
},
"then_run": "sanctuary:woodland_strata"
}
},
"sanctuary:starter_island",
"sanctuary:woodland_strata"
]
}
@@ -0,0 +1,51 @@
{
"type": "minecraft:sequence",
"sequence": [
{
"type": "minecraft:condition",
"if_true": {
"type": "minecraft:noise_threshold",
"noise": "sanctuary:woodland_strata",
"min_threshold": -1000,
"max_threshold": -0.36,
"is_3d": true
},
"then_run": {
"type": "minecraft:block",
"result_state": "minecraft:granite"
}
},
{
"type": "minecraft:condition",
"if_true": {
"type": "minecraft:noise_threshold",
"noise": "sanctuary:woodland_strata",
"min_threshold": 0.36,
"max_threshold": 1000,
"is_3d": true
},
"then_run": {
"type": "minecraft:block",
"result_state": "minecraft:diorite"
}
},
{
"type": "minecraft:condition",
"if_true": {
"type": "minecraft:noise_threshold",
"noise": "sanctuary:woodland_strata",
"min_threshold": -0.14,
"max_threshold": 0.14,
"is_3d": true
},
"then_run": {
"type": "minecraft:block",
"result_state": "minecraft:andesite"
}
},
{
"type": "minecraft:block",
"result_state": "minecraft:stone"
}
]
}
@@ -0,0 +1,5 @@
{
"base_amplitude": 1.0,
"base_octave": -7,
"octave_count": 2
}
@@ -0,0 +1,5 @@
{
"base_amplitude": 1.0,
"base_octave": -6,
"octave_count": 2
}
@@ -0,0 +1,23 @@
{
"default_block": "minecraft:stone",
"default_fluid": "minecraft:air",
"disable_mob_generation": false,
"legacy_random_source": true,
"material_rule": "sanctuary:woodland_island",
"noise": {
"height": 384,
"min_y": 0
},
"noise_router": {
"chunk_surface_level": 0.0,
"continents": 0.0,
"depth": 0.0,
"erosion": 0.0,
"final_density": "sanctuary:final_density_384",
"ridges": 0.0,
"temperature": "sanctuary:temperate_variation",
"vegetation": "sanctuary:temperate_moisture"
},
"sea_level": -64,
"spawn_target": []
}
@@ -0,0 +1,6 @@
{
"feature": {
"type": "sanctuary:woodland_groves"
},
"placement": []
}
@@ -0,0 +1,27 @@
{
"feature": "sanctuary:woodland_ore_coal",
"placement": [
{
"type": "minecraft:count",
"count": 2
},
{
"type": "minecraft:in_square"
},
{
"type": "minecraft:height_range",
"height": {
"type": "minecraft:trapezoid",
"min_inclusive": {
"absolute": 96
},
"max_inclusive": {
"absolute": 288
}
}
},
{
"type": "minecraft:biome"
}
]
}
@@ -0,0 +1,53 @@
{
"feature": "sanctuary:woodland_ore_coal",
"placement": [
{
"type": "minecraft:rarity_filter",
"chance": 3
},
{
"type": "minecraft:in_square"
},
{
"type": "minecraft:height_range",
"height": {
"type": "minecraft:uniform",
"min_inclusive": {
"absolute": 48
},
"max_inclusive": {
"absolute": 272
}
}
},
{
"type": "minecraft:block_predicate_filter",
"predicate": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:air"
}
},
{
"type": "minecraft:environment_scan",
"direction_of_search": "down",
"max_steps": 32,
"target_condition": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:stone_ore_replaceables"
},
"allowed_search_condition": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:air"
}
},
{
"type": "minecraft:offset",
"x": 0,
"y": 1,
"z": 0
},
{
"type": "minecraft:biome"
}
]
}
@@ -0,0 +1,31 @@
{
"feature": "sanctuary:woodland_ore_coal",
"placement": [
{
"type": "minecraft:rarity_filter",
"chance": 3
},
{
"type": "minecraft:in_square"
},
{
"type": "minecraft:heightmap",
"heightmap": "OCEAN_FLOOR_WG"
},
{
"type": "minecraft:block_predicate_filter",
"predicate": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:stone_ore_replaceables",
"offset": [
0,
-1,
0
]
}
},
{
"type": "minecraft:biome"
}
]
}
@@ -0,0 +1,27 @@
{
"feature": "sanctuary:woodland_ore_copper",
"placement": [
{
"type": "minecraft:count",
"count": 1
},
{
"type": "minecraft:in_square"
},
{
"type": "minecraft:height_range",
"height": {
"type": "minecraft:trapezoid",
"min_inclusive": {
"absolute": 96
},
"max_inclusive": {
"absolute": 288
}
}
},
{
"type": "minecraft:biome"
}
]
}
@@ -0,0 +1,53 @@
{
"feature": "sanctuary:woodland_ore_copper",
"placement": [
{
"type": "minecraft:rarity_filter",
"chance": 4
},
{
"type": "minecraft:in_square"
},
{
"type": "minecraft:height_range",
"height": {
"type": "minecraft:uniform",
"min_inclusive": {
"absolute": 48
},
"max_inclusive": {
"absolute": 272
}
}
},
{
"type": "minecraft:block_predicate_filter",
"predicate": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:air"
}
},
{
"type": "minecraft:environment_scan",
"direction_of_search": "down",
"max_steps": 32,
"target_condition": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:stone_ore_replaceables"
},
"allowed_search_condition": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:air"
}
},
{
"type": "minecraft:offset",
"x": 0,
"y": 1,
"z": 0
},
{
"type": "minecraft:biome"
}
]
}
@@ -0,0 +1,31 @@
{
"feature": "sanctuary:woodland_ore_copper",
"placement": [
{
"type": "minecraft:rarity_filter",
"chance": 4
},
{
"type": "minecraft:in_square"
},
{
"type": "minecraft:heightmap",
"heightmap": "OCEAN_FLOOR_WG"
},
{
"type": "minecraft:block_predicate_filter",
"predicate": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:stone_ore_replaceables",
"offset": [
0,
-1,
0
]
}
},
{
"type": "minecraft:biome"
}
]
}
@@ -0,0 +1,27 @@
{
"feature": "sanctuary:woodland_ore_iron",
"placement": [
{
"type": "minecraft:count",
"count": 2
},
{
"type": "minecraft:in_square"
},
{
"type": "minecraft:height_range",
"height": {
"type": "minecraft:trapezoid",
"min_inclusive": {
"absolute": 88
},
"max_inclusive": {
"absolute": 288
}
}
},
{
"type": "minecraft:biome"
}
]
}
@@ -0,0 +1,53 @@
{
"feature": "sanctuary:woodland_ore_iron",
"placement": [
{
"type": "minecraft:rarity_filter",
"chance": 4
},
{
"type": "minecraft:in_square"
},
{
"type": "minecraft:height_range",
"height": {
"type": "minecraft:uniform",
"min_inclusive": {
"absolute": 48
},
"max_inclusive": {
"absolute": 272
}
}
},
{
"type": "minecraft:block_predicate_filter",
"predicate": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:air"
}
},
{
"type": "minecraft:environment_scan",
"direction_of_search": "down",
"max_steps": 32,
"target_condition": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:stone_ore_replaceables"
},
"allowed_search_condition": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:air"
}
},
{
"type": "minecraft:offset",
"x": 0,
"y": 1,
"z": 0
},
{
"type": "minecraft:biome"
}
]
}
@@ -0,0 +1,31 @@
{
"feature": "sanctuary:woodland_ore_iron",
"placement": [
{
"type": "minecraft:rarity_filter",
"chance": 4
},
{
"type": "minecraft:in_square"
},
{
"type": "minecraft:heightmap",
"heightmap": "OCEAN_FLOOR_WG"
},
{
"type": "minecraft:block_predicate_filter",
"predicate": {
"type": "minecraft:matching_block_tag",
"tag": "minecraft:stone_ore_replaceables",
"offset": [
0,
-1,
0
]
}
},
{
"type": "minecraft:biome"
}
]
}
@@ -0,0 +1,6 @@
{
"feature": {
"type": "sanctuary:woodland_shore_sugar_cane"
},
"placement": []
}
@@ -0,0 +1,23 @@
{
"feature": "minecraft:trees_birch_and_oak_leaf_litter",
"placement": [
{
"type": "minecraft:count",
"count": 2
},
{
"type": "minecraft:in_square"
},
{
"type": "minecraft:surface_water_depth_filter",
"max_water_depth": 0
},
{
"type": "minecraft:heightmap",
"heightmap": "OCEAN_FLOOR"
},
{
"type": "minecraft:biome"
}
]
}
@@ -5,14 +5,17 @@
"generator": {
"type": "minecraft:noise",
"biome_source": {
"type": "sanctuary:layered_island",
"meadow": "sanctuary:layered_meadow",
"flower_meadow": "sanctuary:layered_flower_meadow",
"lower_grove": "sanctuary:layered_lower_grove",
"mossy_terraces": "sanctuary:layered_mossy_terraces",
"rocky_terraces": "sanctuary:layered_rocky_terraces"
"type": "sanctuary:woodland_island",
"oak_forest": "sanctuary:woodland_oak_forest",
"birch_forest": "sanctuary:woodland_birch_forest",
"clearing": "sanctuary:woodland_clearing",
"dry_woodland": "sanctuary:woodland_dry_woodland",
"dark_grove": "sanctuary:woodland_dark_grove",
"bamboo_grove": "sanctuary:woodland_bamboo_grove",
"sulfur_depths": "sanctuary:woodland_sulfur_depths",
"rocky_heath": "sanctuary:woodland_rocky_heath"
},
"settings": "sanctuary:sanctuary_layered"
"settings": "sanctuary:sanctuary_woodland"
}
},
"minecraft:the_end": {
@@ -5,7 +5,8 @@
"mixins": [
"InitialSpawnMixin",
"SurfaceHydrologyMixin",
"LayeredSpringOutletMixin"
"LayeredSpringOutletMixin",
"WoodlandSpringOutletMixin"
],
"client": [
"client.SanctuarySkyMixin",
@@ -0,0 +1,19 @@
package fr.koka.sanctuary.worldgen;
public final class WoodlandGroveSmoke {
public static void main(String[] args) {
WoodlandGroveGeometry.Sampler cave = (x,y,z) -> y >= 80 && y <= 100 || y >= 120 && y <= 130 ? 1 : -1;
require(WoodlandGroveGeometry.highestSurface(cave, 0, 0) == 130, "Highest roof must stay distinct from the lower floor");
require(WoodlandGroveGeometry.naturalFootprint(cave, 0, 100, 0, 2), "A true 2x2 dark oak foot should fit");
require(WoodlandGroveGeometry.treeRoom(cave, 0, 101, 0, 2, 6, 12), "Dark oak needs an actual free crown below the roof");
require(!WoodlandGroveGeometry.treeRoom(cave, 0, 101, 0, 2, 6, 20), "Do not clip a canopy through its ceiling");
WoodlandGroveGeometry.Sampler thin = (x,y,z) -> y == 100 ? 1 : -1;
require(!WoodlandGroveGeometry.naturalFootprint(thin, 0, 100, 0, 2), "A floating one-block floor must not support a tree");
WoodlandGroveGeometry.Sampler pierced = (x,y,z) -> x == 1 && z == 1 && y <= 100 ? -1 : cave.sample(x,y,z);
require(!WoodlandGroveGeometry.naturalFootprint(pierced, 0, 100, 0, 2), "Every one of the four trunk supports must exist");
require(WoodlandGroveGeometry.seed(42, 3, 4, 5, 9) == WoodlandGroveGeometry.seed(42, 3, 4, 5, 9), "Placement must replay");
require(WoodlandGroveGeometry.seed(42, 3, 4, 5, 9) != WoodlandGroveGeometry.seed(43, 3, 4, 5, 9), "World seed must affect placement");
System.out.println("WoodlandGroveSmoke: natural multi-block feet and real canopy clearance passed.");
}
private static void require(boolean condition, String message) { if (!condition) throw new AssertionError(message); }
}
@@ -0,0 +1,40 @@
package fr.koka.sanctuary.worldgen;
import java.util.EnumSet;
/** Regression: a higher hill must not replace ordinary oak/birch climate with exposed stone. */
public final class WoodlandPaletteSmoke {
public static void main(String[] args) {
for (int y = 240; y < 384; y++) {
require(WoodlandBiomePalette.select(y, 0.2F, 0.2F) == WoodlandBiomePalette.Kind.OAK_FOREST,
"Raising a temperate forest must retain its oak cover");
require(WoodlandBiomePalette.select(y, -0.45F, 0.1F) == WoodlandBiomePalette.Kind.BIRCH_FOREST,
"A high birch woodland must not become a meadow or bare stone because of altitude");
}
require(WoodlandBiomePalette.select(144, 0.2F, 0.2F) == WoodlandBiomePalette.Kind.DARK_GROVE,
"The corresponding sheltered lower layer must have a distinct humid woodland");
require(WoodlandBiomePalette.select(144, -0.6F, 0.7F) == WoodlandBiomePalette.Kind.BAMBOO_GROVE,
"A warm humid lower pocket must support the rare bamboo palette");
require(WoodlandBiomePalette.select(80, 0.75F, -0.5F) == WoodlandBiomePalette.Kind.SULFUR_DEPTHS,
"The deep geological pocket must remain reachable");
for (int y = 144; y < 384; y++) require(
WoodlandBiomePalette.select(y, 0.75F, -0.5F) != WoodlandBiomePalette.Kind.SULFUR_DEPTHS,
"Sulfur pockets must not cover ordinary upper surfaces");
for (int y = 144; y < 384; y += 8) require(
WoodlandBiomePalette.select(y, 0.6F, 0.2F) == WoodlandBiomePalette.Kind.ROCKY_HEATH,
"The strongest horizontal rock patches must remain recognizable across layers");
EnumSet<WoodlandBiomePalette.Kind> reachable = EnumSet.noneOf(WoodlandBiomePalette.Kind.class);
for (int y = 32; y < 384; y += 8) for (int variation = -10; variation <= 10; variation++) {
for (int moisture = -10; moisture <= 10; moisture++) {
reachable.add(WoodlandBiomePalette.select(y, variation / 10.0F, moisture / 10.0F));
}
}
require(reachable.equals(EnumSet.allOf(WoodlandBiomePalette.Kind.class)),
"All eight declared palettes must be reachable within the dimension");
System.out.println("WoodlandPaletteSmoke: persistent upper forests, sheltered groves, bounded sulfur depths and horizontal rock patches passed.");
}
private static void require(boolean condition, String message) {
if (!condition) throw new AssertionError(message);
}
}
+1 -1
View File
@@ -51,7 +51,7 @@ Pour produire un fichier importable par un lanceur compatible Modrinth :
```sh
cd build/packwiz # depuis la racine du dépôt
packwiz modrinth export --output ../Sanctuary-0.1.0-alpha.6.mrpack
packwiz modrinth export --output ../Sanctuary-0.1.0-alpha.7.mrpack
```
L'export contient le JAR Sanctuary local et la référence vérifiée de Fabric API.
+1 -1
View File
@@ -1,6 +1,6 @@
name = "Sanctuary"
author = "KOKA99CAB"
version = "0.1.0-alpha.6"
version = "0.1.0-alpha.7"
pack-format = "packwiz:1.1.0"
[index]