Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4820c736f4 | ||
|
|
a16347140a | ||
|
|
75fa88cfc5 | ||
|
|
f04aeb02b2 | ||
|
|
4ca42cb18a | ||
|
|
985e8b9192 | ||
|
|
1d8ab0b30a | ||
|
|
083c3449d5 | ||
|
|
51d630037d | ||
|
|
341f1eb8e5 | ||
|
|
10b606d01d |
@@ -8,6 +8,19 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve
|
||||
|
||||
## Non publié
|
||||
|
||||
Modules modifiés : **Sanctuary `0.0.0-alpha.132`** et **Red-Stoner `0.0.0-alpha.15`**.
|
||||
|
||||
- étend `sanctuary:backroom` à toute la hauteur Y -64 → 383 et empile cinq niveaux thématiques de haut en bas : seuil modulaire, archives, ville, services et glitch ;
|
||||
- remplace les volumes ouverts par des pièces et couloirs raccordés, quatre angles de salle déclinés en cinq matériaux, des bibliothèques immenses, des tours plus serrées, des autoroutes continues et de rares parcs ;
|
||||
- ajoute au niveau inférieur une grille de prismes 3D et un labyrinthe déterministe connecté, avec portes physiquement ouvertes, salles fusionnées et trous volontaires vers le vide ;
|
||||
- fait correspondre exactement chaque ouverture entre le sol supérieur et le plafond inférieur, place une torche récupérable à chaque arrivée et raréfie fortement les lits de sortie ;
|
||||
- supprime le ciel, les astres, la lumière ambiante et tous les monstres, y compris ceux injectés par un autre système ;
|
||||
- conserve les IDs, les données Sanctuary v14 et le protocole réseau 24 ; seuls les nouveaux chunks Backroom utilisent la nouvelle architecture complète ;
|
||||
- rend le champ de recherche du terminal inactif à l’ouverture puis focalisable au clic, libère le focus au clic extérieur et laisse toujours la touche d’inventaire fermer le terminal ;
|
||||
- permet à la recherche du terminal de retrouver une boîte de Shulker par le nom ou l’identifiant d’un objet qu’elle contient, tout en conservant la boîte comme résultat afin que son aperçu vanilla reste directement consultable ;
|
||||
|
||||
## `26.2.0-alpha.227`
|
||||
|
||||
Modules modifiés : **Sanctuary `0.0.0-alpha.131`** et **Another World `0.0.0-alpha.57`**.
|
||||
|
||||
- ajoute la dimension persistante `sanctuary:backroom`, accessible en s’accroupissant devant un lit et quittée uniquement par un lit, avec arrivées variées dans une zone architecturale de 2 000 blocs ;
|
||||
@@ -16,6 +29,7 @@ Modules modifiés : **Sanctuary `0.0.0-alpha.131`** et **Another World `0.0.0-al
|
||||
- transforme tous les coffres de la dimension en butin renouvelable, pauvre la plupart du temps, qui privilégie les pertes personnelles dans les premiers coffres avant les pertes anciennes partagées ;
|
||||
- génère des villes, bureaux, plaines, agrégats flottants et pièces visuellement retournées à partir de palettes composées, en réservant les matériaux d’Another World aux anomalies et les blocs précieux à de rares cachettes ;
|
||||
- fait enregistrer la mousse turquoise par Another World via l’API publique de matériaux Backroom, sans dépendance circulaire ;
|
||||
- actualise Fabric API vers `0.159.0+26.2` et JEI vers `30.29.0.199`, avec empreintes Packwiz et Prism synchronisées ;
|
||||
- migre Sanctuary vers les données v14 sans changer le protocole réseau 24, sans réinitialiser les sauvegardes ni effacer les constructions.
|
||||
|
||||
## `26.2.0-alpha.226`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Sanctuary 26.2
|
||||
|
||||
Workspace Fabric multi-projet de Sanctuary 2.0. Il construit dix JAR indépendants sur Minecraft 26.2, Fabric Loader 0.19.3, Fabric API 0.158.0+26.2, Loom 1.17, Gradle 9.5.1 et Java 25.
|
||||
Workspace Fabric multi-projet de Sanctuary 2.0. Il construit dix JAR indépendants sur Minecraft 26.2, Fabric Loader 0.19.3, Fabric API 0.159.0+26.2, Loom 1.17, Gradle 9.5.1 et Java 25.
|
||||
|
||||
Auteur : **KOKA99CAB**
|
||||
Licence : **GNU GPL 3.0 or Later** (`GPL-3.0-or-later`)
|
||||
@@ -22,7 +22,11 @@ Licence : **GNU GPL 3.0 or Later** (`GPL-3.0-or-later`)
|
||||
|
||||
Les descriptions FR/EN/RU sont déclarées dans chaque `fabric.mod.json` et dans les fichiers de langue. Les images fournies servent d'icônes Fabric de leur module.
|
||||
|
||||
Version courante du pack distribué : **`26.2.0-alpha.226`**. Cette release distribue Sanctuary **`0.0.0-alpha.130`** ; les neuf autres modules conservent leur version issue de l'alpha.225.
|
||||
Version courante du pack distribué : **`26.2.0-alpha.227`**. Cette release distribue Another World **`0.0.0-alpha.57`** et Sanctuary **`0.0.0-alpha.131`** ; les huit autres modules conservent leur version issue de l'alpha.226.
|
||||
|
||||
Le prochain module Sanctuary **`0.0.0-alpha.132`** reconstruit la Backroom sur cinq niveaux empilés entre Y -64 et Y 383. Le monde devient entièrement noir et sans monstres ; l’arrivée est signalée par une torche récupérable. Les nouveaux chunks composent seuils modulaires, bibliothèques et bureaux, ville dense avec autoroutes et parcs, couloirs techniques, puis une grille 3D et un labyrinthe connecté dont certains trous mènent au vide. Les chunks déjà générés restent intacts.
|
||||
|
||||
L’alpha.227 ajoute la dimension persistante `sanctuary:backroom`. On y entre accroupi devant un lit et on n’en ressort que par un lit. L’équipement d’entrée est transféré atomiquement vers la Mailbox, tandis que les objets perdus ou abandonnés dans la dimension alimentent des coffres renouvelables sans duplication. Les anciennes sauvegardes sont migrées vers Sanctuary data v14 sans réinitialisation ; le protocole réseau reste en version 24.
|
||||
|
||||
L’alpha.225 corrige quatre tickets : posséder un coffre-fort devient obligatoire pour en braquer un autre, les familiers temporaires ne donnent plus de butin, d’expérience, de lootbox ni de progression de quête, les seize variantes Canaplia survivent à tout leur cycle de culture, et le Magic Carpet retrouve la texture historique du Totem de Notch à la place de la laine violette. Les IDs, sauvegardes, formats de données et protocoles restent compatibles.
|
||||
|
||||
@@ -40,7 +44,7 @@ L’alpha.220 transforme le terminal de stockage en véritable menu de conteneur
|
||||
|
||||
L’alpha.219 porte l'île finie Minecraft Indev dans `sanctuary:alpha` : 128 × 128 × 64 blocs déterministes, plages, cavernes, minerais, fluides, végétation et maison historique, le tout borné sans chunks parasites. Le cadre 4 × 5 en cobblestone moussue est maintenant actif et crée une paire persistante à une arrivée sûre hors de la maison. Ambiance peut enfin composer localement Golden Days Base puis Alpha uniquement dans cette dimension, sans redistribuer leurs textures et en restaurant exactement la pile précédente. Voir le [contrat Alpha](sanctuary/ALPHA.md), le [contrat des portails](iliketomoveit/PORTALS.md) et le [guide Golden Days](ambiance/GOLDEN_DAYS.md).
|
||||
|
||||
L’alpha.218 ajoute le terminal de stockage Red-Stoner : cette demi-dalle verticale se fixe sur un coffre, un coffre piégé ou un baril et donne accès, par pages défilantes de 54 types, à un réseau contigu borné à 128 conteneurs chargés. Les retraits et dépôts sont validés par le serveur, les composants d’items restent strictement séparés et le terminal endgame est exclu du Shop, du Black Market et des récompenses Only Fun.
|
||||
L’alpha.218 ajoute le terminal de stockage Red-Stoner : cette demi-dalle verticale se fixe sur un coffre, un coffre piégé ou un baril et donne accès, par pages défilantes de 54 types, à un réseau contigu borné à 128 conteneurs chargés. Les retraits et dépôts sont validés par le serveur, les composants d’items restent strictement séparés et le terminal endgame est exclu du Shop, du Black Market et des récompenses Only Fun. La recherche retrouve aussi une boîte de Shulker d’après son contenu direct ; la boîte reste affichée dans la grille afin que son aperçu vanilla soit consultable sans modifier les règles de retrait.
|
||||
|
||||
L’alpha.217 tentait de compenser la hauteur trop grave du disque blanc par un facteur OpenAL de `4/3`. Cette compensation est retirée par l’alpha.221, qui corrige directement la conversion stéréo vers mono.
|
||||
|
||||
|
||||
+353
-241
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -6,7 +6,7 @@ org.gradle.configuration-cache=false
|
||||
minecraft_version=26.2
|
||||
loader_version=0.19.3
|
||||
loom_version=1.17-SNAPSHOT
|
||||
fabric_api_version=0.158.0+26.2
|
||||
fabric_api_version=0.159.0+26.2
|
||||
|
||||
# Sanctuary 26.2
|
||||
# Standby modules stay at zero until their migration really begins.
|
||||
@@ -14,7 +14,7 @@ mod_version=0.0.0-alpha.0
|
||||
module_lifecycle=standby
|
||||
ambiance_version=0.0.0-alpha.22
|
||||
ambiance_lifecycle=active
|
||||
redstoner_version=0.0.0-alpha.13
|
||||
redstoner_version=0.0.0-alpha.15
|
||||
redstoner_lifecycle=active
|
||||
anotherworld_version=0.0.0-alpha.57
|
||||
anotherworld_lifecycle=active
|
||||
@@ -28,7 +28,7 @@ iliketomoveit_version=0.0.0-alpha.18
|
||||
iliketomoveit_lifecycle=active
|
||||
onlyfun_version=0.0.0-alpha.27
|
||||
onlyfun_lifecycle=active
|
||||
sanctuary_version=0.0.0-alpha.131
|
||||
sanctuary_version=0.0.0-alpha.132
|
||||
sanctuary_lifecycle=active
|
||||
pack_version=26.2.0-alpha.226
|
||||
pack_version=26.2.0-alpha.227
|
||||
maven_group=fr.koka99cab.sanctuary26
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"status": "released",
|
||||
"source": {
|
||||
"pack_version": "26.2.0-alpha.226",
|
||||
"modules": {
|
||||
"anotherworld": "0.0.0-alpha.56",
|
||||
"sanctuary": "0.0.0-alpha.130"
|
||||
}
|
||||
},
|
||||
"target": {
|
||||
"pack_version": "26.2.0-alpha.227",
|
||||
"modules": {
|
||||
"anotherworld": "0.0.0-alpha.57",
|
||||
"sanctuary": "0.0.0-alpha.131"
|
||||
}
|
||||
},
|
||||
"backroom": {
|
||||
"owner": "sanctuary",
|
||||
"dimension": "sanctuary:backroom",
|
||||
"entry_requires_sneaking_at_bed": true,
|
||||
"exit_requires_bed": true,
|
||||
"arrival_radius_blocks": 2000,
|
||||
"entry_inventory_to_mailbox": true,
|
||||
"entry_transfer_atomic": true,
|
||||
"entry_transfer_idempotent": true,
|
||||
"lost_items_persisted": true,
|
||||
"lost_item_registry_bounded": true,
|
||||
"renewable_chests": true,
|
||||
"personal_losses_prioritized": true,
|
||||
"shared_old_losses_supported": true,
|
||||
"anotherworld_material_api": "BackroomMaterialApi"
|
||||
},
|
||||
"compatibility": {
|
||||
"existing_saves_supported": true,
|
||||
"new_world_required": false,
|
||||
"save_reset_required": false,
|
||||
"sanctuary_source_data_version": 13,
|
||||
"sanctuary_target_data_version": 14,
|
||||
"sanctuary_network_protocol": 24,
|
||||
"network_protocol_changed": false,
|
||||
"client_server_update_required": true
|
||||
},
|
||||
"distribution": {
|
||||
"pack_release_updated": true,
|
||||
"packwiz_index_refreshed": true,
|
||||
"prism_modrinth_index_updated": true,
|
||||
"shared_client_server_mrpack": true,
|
||||
"fabric_api": {
|
||||
"source": "0.158.0+26.2",
|
||||
"target": "0.159.0+26.2",
|
||||
"modrinth_version_id": "BgeCGgGZ"
|
||||
},
|
||||
"jei": {
|
||||
"source": "30.29.0.198",
|
||||
"target": "30.29.0.199",
|
||||
"modrinth_version_id": "astSvyl6"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"status": "pending_release",
|
||||
"status": "released",
|
||||
"module": "sanctuary",
|
||||
"source_module_version": "0.0.0-alpha.130",
|
||||
"target_module_version": "0.0.0-alpha.131",
|
||||
|
||||
+14
-14
@@ -14,24 +14,24 @@ hash = "41f4f4c88c71d0a23446d5014f7407d55ad477441847099cad24492b032335a0"
|
||||
|
||||
[[files]]
|
||||
file = "mods/ambiance-0.0.0-alpha.22.jar"
|
||||
hash = "0e862db1c5efb5c0745b82f991d5480f4305822c3d3f53ed38e2d2d47efabee1"
|
||||
hash = "ac54481beb8c038482fd99ba1605dcc86c912751df64e590273e4608a5c981b8"
|
||||
|
||||
[[files]]
|
||||
file = "mods/anotherworld-0.0.0-alpha.56.jar"
|
||||
hash = "cec8ed493f3c86e4c4481081be110382407f7a5c631b7cf27dd80f80c6f2e92a"
|
||||
file = "mods/anotherworld-0.0.0-alpha.57.jar"
|
||||
hash = "3d825230943b5bb95ecfa40b1d948bf8ddfaaccd25e372f203b86e735dbdaa65"
|
||||
|
||||
[[files]]
|
||||
file = "mods/events-0.0.0-alpha.0.jar"
|
||||
hash = "16bf9fc194988086f51ba6987546b6aa5bc634fe8fcb118b88540ed16991049c"
|
||||
hash = "2e9f170a91982cba15a91b5203eef96c2986c6a6c93f74f98fecca95a34430d2"
|
||||
|
||||
[[files]]
|
||||
file = "mods/fabric-api.pw.toml"
|
||||
hash = "df032a6b051d0262cf24945c43e0e6b915e8cc1d62af62fe933932e7f35960c4"
|
||||
hash = "b8ea5f403a0cdfbfd5ab1704bb61278e8dcf31b525db9c242b22047852d34f85"
|
||||
metafile = true
|
||||
|
||||
[[files]]
|
||||
file = "mods/iliketomoveit-0.0.0-alpha.18.jar"
|
||||
hash = "94044341de08c9b89e0c1e6af6857da64b472fadbc94051ab76fc3cf1a0f0006"
|
||||
hash = "21749e842ca20bb0a433356ac8515a47577e0c528f3e6f30da61dab11342cee2"
|
||||
|
||||
[[files]]
|
||||
file = "mods/iris.pw.toml"
|
||||
@@ -40,32 +40,32 @@ metafile = true
|
||||
|
||||
[[files]]
|
||||
file = "mods/itsalive-0.0.0-alpha.38.jar"
|
||||
hash = "a42885703439646687ac281b1c124d1cddb3ca0595a41d930733b94bd8f1aa2f"
|
||||
hash = "93970ce1392670c1398097e94c5992b9f635c9f68c9f5e8d23a9eac61f4392e3"
|
||||
|
||||
[[files]]
|
||||
file = "mods/jei.pw.toml"
|
||||
hash = "cffb13bae33cd8df605c62e7fa66089428dc684e0ab973f662a8a710c45c306d"
|
||||
hash = "9cc4a91f57effc48b94f6acc1f43067bb8eb99fd1df3397612c55761de9ec2c2"
|
||||
metafile = true
|
||||
|
||||
[[files]]
|
||||
file = "mods/masterkey-0.0.0-alpha.9.jar"
|
||||
hash = "979c14e0a527be63688e97af8349be07b027d3149107c3e9e728a2b1e1ec65ff"
|
||||
hash = "dc4abe7a53b01b0b71de787701df8acf2c3979216a117b2d927e92ce8b210774"
|
||||
|
||||
[[files]]
|
||||
file = "mods/onlyfun-0.0.0-alpha.27.jar"
|
||||
hash = "023ba80a8f3326a6b534b343753d5630d3be0700d25613ea21057cbfe44fdc73"
|
||||
hash = "14f230d2d3ebbfab4ceb34fc4087b4fb92b8764a65cc4f9ab007af8ee7084995"
|
||||
|
||||
[[files]]
|
||||
file = "mods/ouch-0.0.0-alpha.11.jar"
|
||||
hash = "2c15c9d0adeb315b2c3a6796f27c9dc62dd33365f35ea584552d97500b833eb8"
|
||||
hash = "db1a98ddedce2411b5a2583e22c664c0eff3b5d0d8ee91a20a0e863bc5d4f559"
|
||||
|
||||
[[files]]
|
||||
file = "mods/redstoner-0.0.0-alpha.13.jar"
|
||||
hash = "753835558c1037e39ca669737190013bd59a3ab16f7d5156c52e940c54ff9c60"
|
||||
hash = "3fe72a3e31cc32f51b4992097e258fb1c21c7e3adeef5d40310e3d271ff0ef12"
|
||||
|
||||
[[files]]
|
||||
file = "mods/sanctuary-0.0.0-alpha.130.jar"
|
||||
hash = "8906be26de369903a0d5152d80a95a1c47b3468e4afca17ab4683b8300ad69a9"
|
||||
file = "mods/sanctuary-0.0.0-alpha.131.jar"
|
||||
hash = "89b6d772e408c2162a2b68198b877409d4cbe79aeddbdc62a135f91d6e395a00"
|
||||
|
||||
[[files]]
|
||||
file = "mods/simple-voice-chat.pw.toml"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
name = "Fabric API"
|
||||
filename = "fabric-api-0.158.0+26.2.jar"
|
||||
filename = "fabric-api-0.159.0+26.2.jar"
|
||||
side = "both"
|
||||
pin = true
|
||||
|
||||
[download]
|
||||
url = "https://cdn.modrinth.com/data/P7dR8mSH/versions/NqwNSxwA/fabric-api-0.158.0%2B26.2.jar"
|
||||
url = "https://cdn.modrinth.com/data/P7dR8mSH/versions/BgeCGgGZ/fabric-api-0.159.0%2B26.2.jar"
|
||||
hash-format = "sha512"
|
||||
hash = "4c2c1ebe74ffd54875a01ff371b53ba3d8674ac98d561f7dae02a96d3d37fbdbc5f5abc6e820f73b6154d6f873ddd05a442b0998ed2d456863dc0ad972e040a6"
|
||||
hash = "4d5d9c3daafe19be187f638f2d6de3db26420b8720ef5a266cfa81f79d9019cab8c86e6d47478bbb0b0ee0b8f769204b72c843caa2e4aabd94adc41f3dafb518"
|
||||
|
||||
[update]
|
||||
[update.modrinth]
|
||||
mod-id = "P7dR8mSH"
|
||||
version = "NqwNSxwA"
|
||||
version = "BgeCGgGZ"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
name = "Just Enough Items (JEI)"
|
||||
filename = "jei-26.2-fabric-30.29.0.198.jar"
|
||||
filename = "jei-26.2-fabric-30.29.0.199.jar"
|
||||
side = "both"
|
||||
pin = true
|
||||
|
||||
[download]
|
||||
url = "https://cdn.modrinth.com/data/u6dRKJwZ/versions/67Dvn8sL/jei-26.2-fabric-30.29.0.198.jar"
|
||||
url = "https://cdn.modrinth.com/data/u6dRKJwZ/versions/astSvyl6/jei-26.2-fabric-30.29.0.199.jar"
|
||||
hash-format = "sha512"
|
||||
hash = "06131c9528549840e07ddc9634e23454cd454380d7749a23d818f0efe0dacc02874e967ceb74a24ee82efb13e0fe2df4026f988a68f54f890586967e91dcaa5b"
|
||||
hash = "2849ff6a4cc91822798f0c70e7749c298b911c1037fc74e9ab0881734b8d5cb6507fb3169b4a240ef6c37eb6f7c573794c46073ff352f160feee245233b2abe5"
|
||||
|
||||
[update]
|
||||
[update.modrinth]
|
||||
mod-id = "u6dRKJwZ"
|
||||
version = "67Dvn8sL"
|
||||
version = "astSvyl6"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
name = "Sanctuary 26.2"
|
||||
author = "KOKA99CAB"
|
||||
version = "26.2.0-alpha.226"
|
||||
version = "26.2.0-alpha.227"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
file = "index.toml"
|
||||
hash-format = "sha256"
|
||||
hash = "d3b929a596081adc3960866a2f939d4155d1b67ecb22bfe6050931a7d1d0f0a2"
|
||||
hash = "4e40f4d06d1f9f1b440a0be817a855a60861e3a2cf8041d6101a93a66e4c9097"
|
||||
|
||||
[versions]
|
||||
fabric = "0.19.3"
|
||||
|
||||
@@ -3,37 +3,37 @@
|
||||
"game": "minecraft",
|
||||
"name": "Sanctuary 26.2",
|
||||
"summary": "Sanctuary 26.2 — progression, exploration, worldgen and multiplayer systems by KOKA99CAB.",
|
||||
"versionId": "26.2.0-alpha.226",
|
||||
"versionId": "26.2.0-alpha.227",
|
||||
"files": [
|
||||
{
|
||||
"path": "mods/fabric-api-0.158.0+26.2.jar",
|
||||
"path": "mods/fabric-api-0.159.0+26.2.jar",
|
||||
"hashes": {
|
||||
"sha1": "7d0fb560579d28b933bdbc36b47449efe78d3bda",
|
||||
"sha512": "4c2c1ebe74ffd54875a01ff371b53ba3d8674ac98d561f7dae02a96d3d37fbdbc5f5abc6e820f73b6154d6f873ddd05a442b0998ed2d456863dc0ad972e040a6"
|
||||
"sha1": "5307b87f8336f27e8f963263ac514f4bc6175d90",
|
||||
"sha512": "4d5d9c3daafe19be187f638f2d6de3db26420b8720ef5a266cfa81f79d9019cab8c86e6d47478bbb0b0ee0b8f769204b72c843caa2e4aabd94adc41f3dafb518"
|
||||
},
|
||||
"env": {
|
||||
"client": "required",
|
||||
"server": "required"
|
||||
},
|
||||
"downloads": [
|
||||
"https://cdn.modrinth.com/data/P7dR8mSH/versions/NqwNSxwA/fabric-api-0.158.0%2B26.2.jar"
|
||||
"https://cdn.modrinth.com/data/P7dR8mSH/versions/BgeCGgGZ/fabric-api-0.159.0%2B26.2.jar"
|
||||
],
|
||||
"fileSize": 2535765
|
||||
"fileSize": 2535793
|
||||
},
|
||||
{
|
||||
"path": "mods/jei-26.2-fabric-30.29.0.198.jar",
|
||||
"path": "mods/jei-26.2-fabric-30.29.0.199.jar",
|
||||
"hashes": {
|
||||
"sha1": "652e964c71a699fdfdd96cda265ea703b13c6ef4",
|
||||
"sha512": "06131c9528549840e07ddc9634e23454cd454380d7749a23d818f0efe0dacc02874e967ceb74a24ee82efb13e0fe2df4026f988a68f54f890586967e91dcaa5b"
|
||||
"sha1": "1d6ab9269be16f85fb13a34a36b34ad07a9b32a7",
|
||||
"sha512": "2849ff6a4cc91822798f0c70e7749c298b911c1037fc74e9ab0881734b8d5cb6507fb3169b4a240ef6c37eb6f7c573794c46073ff352f160feee245233b2abe5"
|
||||
},
|
||||
"env": {
|
||||
"client": "required",
|
||||
"server": "required"
|
||||
},
|
||||
"downloads": [
|
||||
"https://cdn.modrinth.com/data/u6dRKJwZ/versions/67Dvn8sL/jei-26.2-fabric-30.29.0.198.jar"
|
||||
"https://cdn.modrinth.com/data/u6dRKJwZ/versions/astSvyl6/jei-26.2-fabric-30.29.0.199.jar"
|
||||
],
|
||||
"fileSize": 1839279
|
||||
"fileSize": 1849188
|
||||
},
|
||||
{
|
||||
"path": "mods/voicechat-fabric-2.6.22+26.2.jar",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"pack_version": "26.2.0-alpha.226",
|
||||
"pack_version": "26.2.0-alpha.227",
|
||||
"release_channel": "alpha",
|
||||
"minecraft_version": "26.2",
|
||||
"fabric_loader_version": "0.19.3",
|
||||
@@ -9,7 +9,7 @@
|
||||
"project_id": "FgOqyAXd",
|
||||
"project_slug": "sanctuary-koka99",
|
||||
"project_url": "https://modrinth.com/modpack/sanctuary-koka99",
|
||||
"primary_file": "sanctuary-26.2.0-alpha.226.mrpack",
|
||||
"primary_file": "sanctuary-26.2.0-alpha.227.mrpack",
|
||||
"shared_client_server_pack": true,
|
||||
"update_owner": "packwiz-installer",
|
||||
"check_on_launch": true,
|
||||
|
||||
+12
-4
@@ -46,7 +46,6 @@ public final class StorageTerminalScreen extends AbstractContainerScreen<Storage
|
||||
searchBox.setHint(Component.translatable("screen.redstoner.storage_terminal.search"));
|
||||
searchBox.setValue(searchValue);
|
||||
searchBox.setResponder(this::searchChanged);
|
||||
setInitialFocus(searchBox);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,6 +113,10 @@ public final class StorageTerminalScreen extends AbstractContainerScreen<Storage
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
|
||||
if (searchBox != null && searchBox.isFocused()
|
||||
&& !searchBox.isMouseOver(event.x(), event.y())) {
|
||||
clearSearchFocus();
|
||||
}
|
||||
if (event.button() == 0
|
||||
&& isHovering(SCROLL_X - 2, TERMINAL_TOP, SCROLL_WIDTH + 4, TERMINAL_HEIGHT,
|
||||
event.x(), event.y())) {
|
||||
@@ -127,9 +130,9 @@ public final class StorageTerminalScreen extends AbstractContainerScreen<Storage
|
||||
|
||||
@Override
|
||||
public boolean keyPressed(KeyEvent event) {
|
||||
if (searchBox != null && searchBox.isFocused() && event.key() != 256) {
|
||||
searchBox.keyPressed(event);
|
||||
return true;
|
||||
if (searchBox != null && searchBox.isFocused() && minecraft != null
|
||||
&& minecraft.options.keyInventory.matches(event)) {
|
||||
clearSearchFocus();
|
||||
}
|
||||
return super.keyPressed(event);
|
||||
}
|
||||
@@ -173,6 +176,11 @@ public final class StorageTerminalScreen extends AbstractContainerScreen<Storage
|
||||
}
|
||||
}
|
||||
|
||||
private void clearSearchFocus() {
|
||||
searchBox.setFocused(false);
|
||||
setFocused(null);
|
||||
}
|
||||
|
||||
private void extractScrollBar(GuiGraphicsExtractor graphics) {
|
||||
int x = leftPos + SCROLL_X;
|
||||
int y = topPos + TERMINAL_TOP;
|
||||
|
||||
+21
-4
@@ -5,12 +5,16 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.ShulkerBoxBlock;
|
||||
|
||||
/** Server-side matching for the bounded terminal search field. */
|
||||
public final class StorageTerminalSearch {
|
||||
private static final int SHULKER_SLOT_COUNT = 27;
|
||||
private static final Pattern COMBINING_MARKS = Pattern.compile("\\p{M}+");
|
||||
private static final Pattern NON_ALPHANUMERIC = Pattern.compile("[^\\p{L}\\p{N}]+");
|
||||
|
||||
@@ -38,14 +42,27 @@ public final class StorageTerminalSearch {
|
||||
}
|
||||
|
||||
private static boolean matches(ItemStack stack, String[] terms) {
|
||||
StringBuilder searchable = new StringBuilder(stack.getHoverName().getString());
|
||||
Identifier id = BuiltInRegistries.ITEM.getKey(stack.getItem());
|
||||
if (id != null) searchable.append(' ').append(id);
|
||||
searchable.append(' ').append(stack.getItem().getDescriptionId());
|
||||
StringBuilder searchable = new StringBuilder();
|
||||
appendSearchableText(searchable, stack);
|
||||
if (stack.getItem() instanceof BlockItem blockItem
|
||||
&& blockItem.getBlock() instanceof ShulkerBoxBlock) {
|
||||
var contents = stack.get(DataComponents.CONTAINER);
|
||||
if (contents != null) {
|
||||
contents.nonEmptyItemCopyStream().limit(SHULKER_SLOT_COUNT).forEach(contained ->
|
||||
appendSearchableText(searchable, contained));
|
||||
}
|
||||
}
|
||||
String normalized = normalize(searchable.toString());
|
||||
for (String term : terms) {
|
||||
if (!normalized.contains(term)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void appendSearchableText(StringBuilder searchable, ItemStack stack) {
|
||||
searchable.append(' ').append(stack.getHoverName().getString());
|
||||
Identifier id = BuiltInRegistries.ITEM.getKey(stack.getItem());
|
||||
if (id != null) searchable.append(' ').append(id);
|
||||
searchable.append(' ').append(stack.getItem().getDescriptionId());
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -14,6 +14,7 @@ import net.minecraft.world.SimpleContainer;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.component.ItemContainerContents;
|
||||
|
||||
public final class StorageTerminalSmoke {
|
||||
private StorageTerminalSmoke() {
|
||||
@@ -115,6 +116,14 @@ public final class StorageTerminalSmoke {
|
||||
ItemStack russian = new ItemStack(stackable(Items.REDSTONE), 3);
|
||||
russian.set(DataComponents.CUSTOM_NAME, Component.literal("Красный камень"));
|
||||
container.setItem(2, russian);
|
||||
Holder<Item> shulkerItem = new Holder.Direct<>(Items.SHULKER_BOX, DataComponentMap.builder()
|
||||
.set(DataComponents.MAX_STACK_SIZE, 1).build());
|
||||
ItemStack shulker = new ItemStack(shulkerItem);
|
||||
ItemStack nestedDiamond = new ItemStack(stackable(Items.DIAMOND), 7);
|
||||
nestedDiamond.set(DataComponents.CUSTOM_NAME, Component.literal("Trésor intérieur"));
|
||||
shulker.set(DataComponents.CONTAINER, ItemContainerContents.fromItems(List.of(
|
||||
nestedDiamond, new ItemStack(stackable(Items.OAK_LOG), 32))));
|
||||
container.setItem(3, shulker);
|
||||
|
||||
List<StorageTerminalInventory.Entry> entries = StorageTerminalInventory.aggregate(List.of(container));
|
||||
require(StorageTerminalSearch.filter(entries, "minecraft:cobblestone").size() == 1,
|
||||
@@ -125,6 +134,12 @@ public final class StorageTerminalSmoke {
|
||||
"Terminal search did not ignore accents in a custom name");
|
||||
require(StorageTerminalSearch.filter(entries, "красный камень").size() == 1,
|
||||
"Terminal search did not preserve non-Latin keyboard input");
|
||||
require(StorageTerminalSearch.filter(entries, "minecraft:oak_log").size() == 1,
|
||||
"Terminal search did not find an item id inside a Shulker box");
|
||||
require(StorageTerminalSearch.filter(entries, "tresor interieur oak log").size() == 1,
|
||||
"Terminal search did not index the composed contents of a Shulker box");
|
||||
require(StorageTerminalSearch.filter(entries, "shulker box").size() == 1,
|
||||
"Searching Shulker contents broke matching against the box itself");
|
||||
require(StorageTerminalSearch.filter(entries, "missing").isEmpty(),
|
||||
"Terminal search retained an unrelated item");
|
||||
}
|
||||
|
||||
+35
-10
@@ -22,6 +22,9 @@ Sa zone architecturale forme un disque de 2 000 blocs autour de l’origine.
|
||||
- Toute autre téléportation vers la Backroom est refusée. Une téléportation forcée vers l’extérieur
|
||||
reste possible pour l’administration, mais replace d’abord le butin porté dans le registre : elle
|
||||
ne constitue jamais une extraction réussie.
|
||||
- Chaque arrivée se fait sur le dernier plancher du niveau 0. Le serveur libère exactement les deux
|
||||
blocs nécessaires au joueur et pose une torche adjacente : elle peut être récupérée et devient le
|
||||
premier outil d’exploration dans une dimension autrement noire.
|
||||
|
||||
## Objets perdus et coffres
|
||||
|
||||
@@ -44,19 +47,41 @@ Pendant une expédition, chaque coffre distinct est un essai personnel. Le premi
|
||||
de proposer une perte récente du joueur, le deuxième 90 %, le troisième 100 %, puis les suivants
|
||||
65 %. Sans perte personnelle disponible, le coffre suit simplement son tirage général.
|
||||
|
||||
## Architecture
|
||||
## Architecture verticale
|
||||
|
||||
Les plans sont déterministes par grandes zones et utilisent cinq familles composées :
|
||||
La liste [Normal Levels I](https://backrooms-wiki.wikidot.com/normal-levels-i) sert uniquement de
|
||||
référence d’ambiance et de vocabulaire. Les plans, palettes et règles ci-dessous sont originaux au
|
||||
pack ; aucun texte ni visuel du wiki n’est embarqué.
|
||||
|
||||
- mégalopoles en béton, verre, pierre et voirie ;
|
||||
- bureaux superposés en bois, clay, textile et éléments techniques ;
|
||||
- plaines naturelles avec arbres et rares ruines ;
|
||||
- agrégats de plateformes et de pièces flottantes reliées verticalement ;
|
||||
- pièces construites visuellement à 90° ou à l’envers, sans modifier la gravité globale.
|
||||
La dimension couvre maintenant toute la hauteur de **Y = -64 à Y = 383**. Ses cinq niveaux sont
|
||||
numérotés de haut en bas afin qu’une chute fasse toujours avancer vers le niveau suivant :
|
||||
|
||||
Les matériaux très marqués d’Another World n’apparaissent que ponctuellement aux anomalies. Aucun
|
||||
bloc précieux ne sert de matériau structurel ; de très rares blocs de diamant sont cachés dans les
|
||||
fondations.
|
||||
- **niveau 0 — Seuil, Y 295 à 383** : cinq plans de pièces jaunes et sableuses, avec couloirs,
|
||||
cloisons à 90°, colonnes et quatre angles de salle. Chaque angle possède cinq variantes de
|
||||
matériaux ; des trous rares relient les plans superposés ;
|
||||
- **niveau 1 — Archives, Y 206 à 294** : quatre étages de bureaux et de bibliothèques pouvant
|
||||
couvrir plusieurs chunks, avec longues rangées de rayonnages, grandes fenêtres intérieures,
|
||||
salles de lecture et cloisons ouvertes ;
|
||||
- **niveau 2 — Ville, Y 117 à 205** : bâtiments rapprochés séparés par de petites ruelles,
|
||||
autoroutes orthogonales continues, tours à plusieurs étages et rares parcs enfermés sous le
|
||||
plafond de la dimension ;
|
||||
- **niveau 3 — Services, Y 28 à 116** : cinq plans de couloirs techniques en briques, grilles et
|
||||
béton, dont toutes les jonctions de portes restent physiquement ouvertes ;
|
||||
- **niveau 4 — Glitch, Y -64 à 27** : prismes de briques en grille 3D autour d’un toit où l’on peut
|
||||
tomber, puis réseau déterministe de salles et couloirs. Chaque cellule du labyrinthe conserve une
|
||||
route ouverte vers son origine ; certaines cloisons disparaissent pour fusionner des salles de
|
||||
tailles différentes. De rares trous dans le dernier plancher mènent au vrai vide.
|
||||
|
||||
Les ouvertures entre deux niveaux sont calculées une seule fois puis appliquées à la fois au sol
|
||||
supérieur et au plafond inférieur : une chute annoncée ne débouche donc pas sur un bloc fermé. Les
|
||||
imperfections, morceaux suspendus et raccords volontairement cassés font partie de l’esthétique,
|
||||
mais les portes des couloirs restent traversables. Les lits de sortie générés sont désormais rares.
|
||||
|
||||
La Backroom ne possède ni ciel, ni soleil, ni lune, ni étoiles, ni lumière ambiante. Son biome ne
|
||||
déclare aucune créature et le serveur retire aussi toute entité de catégorie monstre qui y serait
|
||||
injectée par un autre système. Les matériaux très marqués d’Another World n’apparaissent que
|
||||
ponctuellement aux anomalies. Aucun bloc précieux ne sert de matériau structurel ; de très rares
|
||||
blocs de diamant restent cachés dans les fondations.
|
||||
|
||||
## Exploitation
|
||||
|
||||
|
||||
+28
-14
@@ -199,7 +199,7 @@ tasks.register("verifyBlackMarket") {
|
||||
def questData = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardData.java").text
|
||||
def quests = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardService.java").text
|
||||
def inventoryView = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/inventory/SanctuaryPlayerInventoryView.java").text
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| !data.contains('SAVE_KEY = "black_market"') || !data.contains("DATA_VERSION = 2")
|
||||
|| !data.contains("ItemStack.CODEC.fieldOf(\"item\")") || !data.contains("replaceListing")
|
||||
|| !service.contains("LISTING_FEE_RUBIES = 1") || !service.contains("COMMISSION_PERCENT = 5")
|
||||
@@ -2915,7 +2915,7 @@ tasks.register("verifyShopCurrencyRowsRelease") {
|
||||
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketService.java").text
|
||||
def shop = file(
|
||||
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopService.java").text
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.117"
|
||||
|| migration.shop_currency_inventory?.all_unlocked_rows_counted != true
|
||||
|| migration.shop_currency_inventory?.overflow_rows_counted != true
|
||||
@@ -3081,7 +3081,7 @@ tasks.register("verifyProgressivePauseMenu") {
|
||||
"pack/migrations/26.2.0-alpha.190-progressive-pause-menu-to-alpha.191.json"))
|
||||
def pauseMenu = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/PauseScreenMixin.java").text
|
||||
def costs = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/progression/SanctuaryProgressionCosts.java").text
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| migration.target?.pack_version != "26.2.0-alpha.191"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.109"
|
||||
|| migration.pause_menu?.progression_full_width != true
|
||||
@@ -3119,7 +3119,7 @@ tasks.register("verifyShopPricingLayout") {
|
||||
"pack/migrations/26.2.0-alpha.192-shop-pricing-layout-to-alpha.193.json"))
|
||||
def screen = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryShopScreen.java").text
|
||||
def progression = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopProgression.java").text
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.110"
|
||||
|| migration.shop_layout?.locked_offer_buttons != 6
|
||||
|| migration.shop_layout?.button_slots != [4, 5, 6, 7, 8, 9]
|
||||
@@ -3150,7 +3150,7 @@ tasks.register("verifyShopCsvBossGates") {
|
||||
"pack/migrations/26.2.0-alpha.194-shop-csv-boss-gates-to-alpha.195.json"))
|
||||
def provider = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopCatalogProvider.java").text
|
||||
def catalog = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopOfferCatalog.java").text
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| migration.target?.pack_version != "26.2.0-alpha.195"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.111"
|
||||
|| migration.compatibility?.sanctuary_data_version != 11
|
||||
@@ -3181,7 +3181,7 @@ tasks.register("verifyDeliveryBoxOrientation") {
|
||||
def blockstate = new JsonSlurper().parse(
|
||||
file("src/main/resources/assets/sanctuary/blockstates/delivery_box.json"))
|
||||
def expectedVariants = ["facing=north", "facing=east", "facing=south", "facing=west"].toSet()
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.112"
|
||||
|| migration.mailbox?.id != "sanctuary:delivery_box"
|
||||
|| migration.mailbox?.horizontal_facing != true
|
||||
@@ -3209,7 +3209,7 @@ tasks.register("verifyInventoryShiftClick") {
|
||||
def mixin = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/AbstractContainerMenuMixin.java").text
|
||||
def screen = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/AbstractContainerScreenMixin.java").text
|
||||
def gui = file("INVENTORY_GUI.md").text
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| !transfer.contains("shouldAppendOverflow")
|
||||
|| !transfer.contains("isVanillaStorageRange")
|
||||
|| !transfer.contains("hotbarStart")
|
||||
@@ -3255,7 +3255,7 @@ tasks.register("verifyQuestRequests") {
|
||||
new JsonSlurper().parse(file("src/main/resources/data/sanctuary/loot_table/blocks/${path}.json"))
|
||||
.pools[0].entries[0].name
|
||||
}
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.113"
|
||||
|| migration.quest_requests?.save_key != "quest_requests"
|
||||
|| migration.quest_requests?.data_version != 1
|
||||
@@ -3313,7 +3313,7 @@ tasks.register("verifyQuestRequestContracts") {
|
||||
def locales = ["fr_fr", "en_us", "ru_ru"].collectEntries { locale ->
|
||||
[(locale): new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${locale}.json"))]
|
||||
}
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| migration.source?.pack_version != "26.2.0-alpha.198"
|
||||
|| migration.source?.modules?.sanctuary != "0.0.0-alpha.114"
|
||||
|| migration.target?.pack_version != "26.2.0-alpha.199"
|
||||
@@ -3382,7 +3382,7 @@ tasks.register("verifyQuestRequestEscrow") {
|
||||
def locales = ["fr_fr", "en_us", "ru_ru"].collectEntries { locale ->
|
||||
[(locale): new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${locale}.json"))]
|
||||
}
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| migration.source?.pack_version != "26.2.0-alpha.200"
|
||||
|| migration.source?.modules?.sanctuary != "0.0.0-alpha.115"
|
||||
|| migration.target?.pack_version != "26.2.0-alpha.201"
|
||||
@@ -3461,7 +3461,7 @@ tasks.register("verifyCraftableBannerCape") {
|
||||
def locales = ["fr_fr", "en_us", "ru_ru"].collect {
|
||||
new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json"))
|
||||
}
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.130"
|
||||
|| migration.white_cape?.crafting_material_count != 6
|
||||
|| migration.white_cape?.maximum_pattern_layers != 6
|
||||
@@ -3516,7 +3516,7 @@ tasks.register("verifyAlphaIndev") {
|
||||
def locales = ["fr_fr", "en_us", "ru_ru"].collect {
|
||||
new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json"))
|
||||
}
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.122"
|
||||
|| migration.alpha_world?.dimension != "sanctuary:alpha"
|
||||
|| migration.alpha_world?.legacy_dimension != "portals:alpha"
|
||||
@@ -3550,12 +3550,15 @@ tasks.register("verifyBackroom") {
|
||||
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/backroom/BackroomMaterialApi.java"),
|
||||
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/DeliveryBoxBlockEntity.java"),
|
||||
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/worldgen/feature/BackroomArchitectureFeature.java"),
|
||||
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/worldgen/feature/BackroomLayout.java"),
|
||||
file("src/main/resources/data/minecraft/worldgen/world_preset/normal.json"),
|
||||
file("src/main/resources/data/sanctuary/worldgen/world_preset/overworld.json"),
|
||||
file("src/main/resources/data/sanctuary/worldgen/world_preset/sanctuary.json"))
|
||||
doLast {
|
||||
def migration = new JsonSlurper().parse(rootProject.file("pack/migrations/sanctuary-backroom-data-v14.json"))
|
||||
def dimensionType = new JsonSlurper().parse(file("src/main/resources/data/sanctuary/dimension_type/backroom.json"))
|
||||
def noiseSettings = new JsonSlurper().parse(file("src/main/resources/data/sanctuary/worldgen/noise_settings/backroom.json"))
|
||||
def biome = new JsonSlurper().parse(file("src/main/resources/data/sanctuary/worldgen/biome/backroom.json"))
|
||||
def presets = [
|
||||
file("src/main/resources/data/minecraft/worldgen/world_preset/normal.json"),
|
||||
file("src/main/resources/data/sanctuary/worldgen/world_preset/overworld.json"),
|
||||
@@ -3567,12 +3570,13 @@ tasks.register("verifyBackroom") {
|
||||
def chest = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/backroom/BackroomChestService.java").text
|
||||
def mailbox = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/DeliveryBoxBlockEntity.java").text
|
||||
def architecture = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/worldgen/feature/BackroomArchitectureFeature.java").text
|
||||
def layout = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/worldgen/feature/BackroomLayout.java").text
|
||||
def materialApi = file(
|
||||
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/backroom/BackroomMaterialApi.java").text
|
||||
def locales = ["fr_fr", "en_us", "ru_ru"].collect {
|
||||
new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json"))
|
||||
}
|
||||
if (project.version.toString() != "0.0.0-alpha.131"
|
||||
if (project.version.toString() != "0.0.0-alpha.132"
|
||||
|| migration.source_data_version != 13 || migration.target_data_version != 14
|
||||
|| migration.integration_modules?.anotherworld?.source != "0.0.0-alpha.56"
|
||||
|| migration.integration_modules?.anotherworld?.target != "0.0.0-alpha.57"
|
||||
@@ -3580,6 +3584,12 @@ tasks.register("verifyBackroom") {
|
||||
|| presets.any { it.dimensions?.get("sanctuary:backroom") == null }
|
||||
|| dimensionType.attributes?.get("minecraft:gameplay/bed_rule")?.can_set_spawn != "never"
|
||||
|| dimensionType.attributes?.get("minecraft:gameplay/bed_rule")?.can_sleep != "never"
|
||||
|| dimensionType.min_y != -64 || dimensionType.height != 448 || dimensionType.logical_height != 448
|
||||
|| dimensionType.ambient_light != 0.0 || dimensionType.has_ceiling != true
|
||||
|| dimensionType.attributes?.get("minecraft:visual/fog_color") != "#000000"
|
||||
|| noiseSettings.noise?.min_y != -64 || noiseSettings.noise?.height != 448
|
||||
|| noiseSettings.disable_mob_generation != true
|
||||
|| biome.creature_spawn_probability != 0.0 || !biome.spawners?.monster?.isEmpty()
|
||||
|| !data.contains("MAX_LOST_ITEMS = 16_384") || !data.contains("MAX_CHEST_STATES = 65_536")
|
||||
|| !data.contains("PERSONAL_RESERVATION_TICKS = 24_000L")
|
||||
|| !data.contains("indexOfLoss(marker)") || !data.contains("BACKROOM_LOSS_COMPONENT")
|
||||
@@ -3587,17 +3597,21 @@ tasks.register("verifyBackroom") {
|
||||
|| !service.contains("ARRIVAL_RADIUS = 2_000") || !service.contains("insertTransaction")
|
||||
|| !service.contains("AUTHORIZED_TRANSITIONS")
|
||||
|| !service.contains("level -> !isBackroom(level)")
|
||||
|| !service.contains("MobCategory.MONSTER") || !service.contains("Blocks.TORCH")
|
||||
|| !mailbox.contains("MAX_PROCESSED_TRANSACTIONS = 128")
|
||||
|| !chest.contains("REFRESH_TICKS = 12_000L") || !chest.contains("ordinal == 3")
|
||||
|| !architecture.contains("PLAYABLE_RADIUS = 2_000")
|
||||
|| !architecture.contains("BackroomMaterialApi.anomalyMaterial()")
|
||||
|| !layout.contains("WORLD_MIN_Y = -64") || !layout.contains("WORLD_MAX_Y_EXCLUSIVE = 384")
|
||||
|| !layout.contains("LEVEL_0(295, 383)") || !layout.contains("LEVEL_4(-64, 27)")
|
||||
|| !layout.contains("mazeConnected") || !layout.contains("RoomCorner")
|
||||
|| !materialApi.contains("registerAnomalyMaterial")
|
||||
|| locales.any { !it.containsKey("dimension.sanctuary.backroom")
|
||||
|| !it.containsKey("message.sanctuary.backroom.bed_entry_only")
|
||||
|| !it.containsKey("message.sanctuary.backroom.registry_busy")
|
||||
|| !it.containsKey("message.sanctuary.backroom.mailbox_full")
|
||||
|| !it.containsKey("message.sanctuary.backroom.exited_with_losses") }) {
|
||||
throw new GradleException("Sanctuary alpha.131 Backroom contract is incomplete")
|
||||
throw new GradleException("Sanctuary alpha.132 Backroom contract is incomplete")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-10
@@ -3,12 +3,15 @@ package fr.koka99cab.sanctuary26.sanctuary.backroom;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.SanctuaryMod;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.shop.ShopData;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.world.SanctuaryDimensions;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.worldgen.feature.BackroomArchitectureFeature;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.worldgen.feature.BackroomLayout;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
@@ -18,8 +21,9 @@ import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.portal.TeleportTransition;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
@@ -36,6 +40,9 @@ public final class BackroomService {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> reconcile(handler.player));
|
||||
ServerEntityEvents.ENTITY_LOAD.register((entity, level) -> {
|
||||
if (isBackroom(level) && entity.getType().getCategory() == MobCategory.MONSTER) entity.discard();
|
||||
});
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
|
||||
dispatcher.register(Commands.literal("backroom")
|
||||
.requires(Commands.hasPermission(Commands.LEVEL_GAMEMASTERS))
|
||||
@@ -101,6 +108,7 @@ public final class BackroomService {
|
||||
data.finishEntry(player.getUUID());
|
||||
|
||||
BlockPos arrival = chooseArrival(destination, player.getUUID(), data);
|
||||
placeArrivalTorch(destination, arrival);
|
||||
data.beginExpedition(player.getUUID(), arrival.asLong());
|
||||
TeleportTransition transition = new TeleportTransition(destination, Vec3.atBottomCenterOf(arrival),
|
||||
Vec3.ZERO, player.getYRot(), player.getXRot(),
|
||||
@@ -166,15 +174,14 @@ public final class BackroomService {
|
||||
private static BlockPos chooseArrival(ServerLevel level, UUID player, BackroomData data) {
|
||||
RandomSource random = level.getRandom();
|
||||
List<Long> recent = data.recentArrivals(player);
|
||||
int arrivalY = BackroomLayout.ENTRY_FLOOR_Y + 1;
|
||||
BlockPos best = null;
|
||||
for (int attempt = 0; attempt < ARRIVAL_ATTEMPTS; attempt++) {
|
||||
double angle = random.nextDouble() * Math.PI * 2.0D;
|
||||
double radius = Math.sqrt(random.nextDouble()) * ARRIVAL_RADIUS;
|
||||
int x = Mth.floor(Math.cos(angle) * radius);
|
||||
int z = Mth.floor(Math.sin(angle) * radius);
|
||||
int y = Mth.clamp(level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z),
|
||||
level.getMinY() + 2, level.getMaxY() - 3);
|
||||
BlockPos candidate = new BlockPos(x, y, z);
|
||||
BlockPos candidate = new BlockPos(x, arrivalY, z);
|
||||
best = candidate;
|
||||
if (farFromRecent(candidate, recent) && safe(level, candidate)) return candidate;
|
||||
}
|
||||
@@ -182,9 +189,7 @@ public final class BackroomService {
|
||||
if (fallback != null) return fallback;
|
||||
fallback = findSafeNear(level, BlockPos.ZERO, 48);
|
||||
if (fallback != null) return fallback;
|
||||
int originY = Mth.clamp(level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, 0, 0),
|
||||
level.getMinY() + 2, level.getMaxY() - 3);
|
||||
return new BlockPos(0, originY, 0);
|
||||
return new BlockPos(8, arrivalY, 8);
|
||||
}
|
||||
|
||||
private static boolean farFromRecent(BlockPos candidate, List<Long> recent) {
|
||||
@@ -207,15 +212,32 @@ public final class BackroomService {
|
||||
if (ring > 0 && Math.abs(dx) != ring && Math.abs(dz) != ring) continue;
|
||||
int x = center.getX() + dx, z = center.getZ() + dz;
|
||||
if ((long) x * x + (long) z * z > (long) ARRIVAL_RADIUS * ARRIVAL_RADIUS) continue;
|
||||
int y = Mth.clamp(level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z),
|
||||
level.getMinY() + 2, level.getMaxY() - 3);
|
||||
BlockPos candidate = new BlockPos(x, y, z);
|
||||
BlockPos candidate = new BlockPos(x, BackroomLayout.ENTRY_FLOOR_Y + 1, z);
|
||||
if (safe(level, candidate)) return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void placeArrivalTorch(ServerLevel level, BlockPos arrival) {
|
||||
level.setBlock(arrival.below(), Blocks.BIRCH_PLANKS.defaultBlockState(), 3);
|
||||
level.setBlock(arrival, Blocks.AIR.defaultBlockState(), 3);
|
||||
level.setBlock(arrival.above(), Blocks.AIR.defaultBlockState(), 3);
|
||||
for (var direction : net.minecraft.core.Direction.Plane.HORIZONTAL) {
|
||||
BlockPos torch = arrival.relative(direction);
|
||||
if (!BackroomArchitectureFeature.withinPlayableArea(torch.getX(), torch.getZ())) continue;
|
||||
if (level.getBlockState(torch).isAir()
|
||||
&& level.getBlockState(torch.below()).isCollisionShapeFullBlock(level, torch.below())) {
|
||||
level.setBlock(torch, Blocks.TORCH.defaultBlockState(), 3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
BlockPos torch = arrival.east();
|
||||
if (!BackroomArchitectureFeature.withinPlayableArea(torch.getX(), torch.getZ())) torch = arrival.west();
|
||||
level.setBlock(torch.below(), Blocks.BIRCH_PLANKS.defaultBlockState(), 3);
|
||||
level.setBlock(torch, Blocks.TORCH.defaultBlockState(), 3);
|
||||
}
|
||||
|
||||
private static void notify(ServerPlayer player, String translationKey) {
|
||||
player.sendOverlayMessage(Component.translatable(translationKey));
|
||||
}
|
||||
|
||||
+382
-137
@@ -1,8 +1,9 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.worldgen.feature;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.SanctuaryMod;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.api.backroom.BackroomMaterialApi;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.worldgen.feature.BackroomLayout.BackroomLevel;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.worldgen.feature.BackroomLayout.RoomCorner;
|
||||
import java.util.Random;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
@@ -19,20 +20,42 @@ import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
|
||||
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
|
||||
|
||||
/**
|
||||
* Deterministic, chunk-local architecture for the persistent Backroom.
|
||||
* Macro cells select authored palettes; randomness only varies plans inside a coherent family.
|
||||
* Five deterministic Backroom levels stacked from Y=384 to Y=-64. Generation is chunk-local,
|
||||
* while every wall, doorway, highway and hole is derived from world coordinates so chunk seams
|
||||
* remain open and coherent.
|
||||
*/
|
||||
public final class BackroomArchitectureFeature extends Feature<NoneFeatureConfiguration> {
|
||||
public static final int PLAYABLE_RADIUS = 2_000;
|
||||
private static final int CHUNK_SIZE = 16;
|
||||
private static final int MACRO_CHUNKS = 8;
|
||||
private static final int[] THRESHOLD_DECKS = {295, 313, 331, 349, 367};
|
||||
private static final int[] ARCHIVE_DECKS = {206, 228, 250, 272};
|
||||
private static final int[] UTILITY_DECKS = {28, 46, 64, 82, 100};
|
||||
private static final int[] LANDMARK_FLOORS = {349, 250, 117, 64, BackroomLayout.MAZE_FLOOR_Y};
|
||||
|
||||
private static final BlockState CONCRETE_GRAY = Blocks.CONCRETE.pick(DyeColor.GRAY).defaultBlockState();
|
||||
private static final BlockState CONCRETE_LIGHT = Blocks.CONCRETE.pick(DyeColor.LIGHT_GRAY).defaultBlockState();
|
||||
private static final BlockState CONCRETE_WHITE = Blocks.CONCRETE.pick(DyeColor.WHITE).defaultBlockState();
|
||||
private static final BlockState GLASS_BLACK = Blocks.STAINED_GLASS.pick(DyeColor.BLACK).defaultBlockState();
|
||||
private static final BlockState GLASS_BROWN = Blocks.STAINED_GLASS.pick(DyeColor.BROWN).defaultBlockState();
|
||||
private static final BlockState GLASS_CYAN = Blocks.STAINED_GLASS.pick(DyeColor.CYAN).defaultBlockState();
|
||||
private static final BlockState CLAY_BROWN = Blocks.DYED_TERRACOTTA.pick(DyeColor.BROWN).defaultBlockState();
|
||||
private static final BlockState CLAY_ORANGE = Blocks.DYED_TERRACOTTA.pick(DyeColor.ORANGE).defaultBlockState();
|
||||
private static final BlockState CLAY_YELLOW = Blocks.DYED_TERRACOTTA.pick(DyeColor.YELLOW).defaultBlockState();
|
||||
private static final BlockState LEAVES = Blocks.OAK_LEAVES.defaultBlockState().setValue(LeavesBlock.PERSISTENT, true);
|
||||
private static final BlockState[] CORNER_MATERIALS = {
|
||||
Blocks.BRICKS.defaultBlockState(),
|
||||
Blocks.MUD_BRICKS.defaultBlockState(),
|
||||
Blocks.STONE_BRICKS.defaultBlockState(),
|
||||
CLAY_YELLOW,
|
||||
Blocks.SMOOTH_SANDSTONE.defaultBlockState()
|
||||
};
|
||||
private static final BlockState[] GLITCH_MATERIALS = {
|
||||
Blocks.BRICKS.defaultBlockState(),
|
||||
Blocks.MUD_BRICKS.defaultBlockState(),
|
||||
Blocks.STONE_BRICKS.defaultBlockState(),
|
||||
Blocks.CRACKED_STONE_BRICKS.defaultBlockState(),
|
||||
CLAY_BROWN
|
||||
};
|
||||
|
||||
public BackroomArchitectureFeature(Codec<NoneFeatureConfiguration> codec) {
|
||||
super(codec);
|
||||
@@ -47,17 +70,15 @@ public final class BackroomArchitectureFeature extends Feature<NoneFeatureConfig
|
||||
int startZ = chunkZ * CHUNK_SIZE;
|
||||
if (!withinPlayableArea(startX + 8, startZ + 8)) return false;
|
||||
|
||||
long seed = mix(level.getSeed(), chunkX, chunkZ);
|
||||
Random random = new Random(seed);
|
||||
Architecture architecture = architecture(level.getSeed(), chunkX, chunkZ);
|
||||
switch (architecture) {
|
||||
case CITY -> city(level, startX, startZ, random);
|
||||
case OFFICES -> offices(level, startX, startZ, random);
|
||||
case PLAINS -> plains(level, startX, startZ, random);
|
||||
case AGGREGATES -> aggregates(level, startX, startZ, random);
|
||||
case ROTATED -> rotated(level, startX, startZ, random);
|
||||
}
|
||||
placeLootLandmarks(level, startX, startZ, random, architecture);
|
||||
long worldSeed = level.getSeed();
|
||||
Random random = new Random(fingerprint(worldSeed, chunkX, chunkZ));
|
||||
buildLevelShells(level, worldSeed, startX, startZ);
|
||||
buildThreshold(level, worldSeed, startX, startZ);
|
||||
buildArchives(level, worldSeed, startX, startZ);
|
||||
buildCity(level, worldSeed, startX, startZ, chunkX, chunkZ);
|
||||
buildUtilities(level, worldSeed, startX, startZ);
|
||||
buildGlitchMaze(level, worldSeed, startX, startZ);
|
||||
placeLootLandmarks(level, startX, startZ, random);
|
||||
placeRareAnomaly(level, startX, startZ, random);
|
||||
return true;
|
||||
}
|
||||
@@ -66,161 +87,387 @@ public final class BackroomArchitectureFeature extends Feature<NoneFeatureConfig
|
||||
return (long) x * x + (long) z * z <= (long) PLAYABLE_RADIUS * PLAYABLE_RADIUS;
|
||||
}
|
||||
|
||||
public static Architecture architecture(long worldSeed, int chunkX, int chunkZ) {
|
||||
int macroX = Math.floorDiv(chunkX, MACRO_CHUNKS);
|
||||
int macroZ = Math.floorDiv(chunkZ, MACRO_CHUNKS);
|
||||
int index = Math.floorMod((int) mix(worldSeed ^ 0x4f1bbcdcL, macroX, macroZ), Architecture.values().length);
|
||||
return Architecture.values()[index];
|
||||
}
|
||||
|
||||
/** Stable fingerprint used to prevent palette selection drift. */
|
||||
/** Stable fingerprint used to prevent authored layout drift. */
|
||||
public static long fingerprint(long worldSeed, int chunkX, int chunkZ) {
|
||||
return mix(worldSeed ^ architecture(worldSeed, chunkX, chunkZ).ordinal(), chunkX, chunkZ);
|
||||
return mix(worldSeed ^ 0x4f1bbcdcL, chunkX, chunkZ);
|
||||
}
|
||||
|
||||
private static void city(WorldGenLevel level, int x0, int z0, Random random) {
|
||||
int y = 64;
|
||||
for (int x = x0; x < x0 + 16; x++) for (int z = z0; z < z0 + 16; z++) {
|
||||
boolean road = Math.floorMod(x, 32) < 5 || Math.floorMod(z, 32) < 5;
|
||||
set(level, x, y, z, road ? CONCRETE_GRAY : CONCRETE_LIGHT);
|
||||
if (road && (Math.floorMod(x, 8) == 0 || Math.floorMod(z, 8) == 0)) set(level, x, y, z, Blocks.SMOOTH_STONE.defaultBlockState());
|
||||
}
|
||||
if (Math.floorMod(x0, 32) < 5 || Math.floorMod(z0, 32) < 5) return;
|
||||
int minX = x0 + 2, maxX = x0 + 13, minZ = z0 + 2, maxZ = z0 + 13;
|
||||
int height = 18 + random.nextInt(42);
|
||||
for (int floor = 0; floor <= height; floor++) {
|
||||
int py = y + 1 + floor;
|
||||
boolean slab = floor == 0 || floor == height || floor % 5 == 0;
|
||||
for (int x = minX; x <= maxX; x++) for (int z = minZ; z <= maxZ; z++) {
|
||||
boolean edge = x == minX || x == maxX || z == minZ || z == maxZ;
|
||||
if (slab) set(level, x, py, z, CONCRETE_LIGHT);
|
||||
else if (edge) set(level, x, py, z, floor % 5 == 1 ? CONCRETE_WHITE : GLASS_CYAN);
|
||||
private static void buildLevelShells(WorldGenLevel level, long seed, int x0, int z0) {
|
||||
for (BackroomLevel zone : BackroomLevel.values()) {
|
||||
for (int x = x0; x < x0 + CHUNK_SIZE; x++) for (int z = z0; z < z0 + CHUNK_SIZE; z++) {
|
||||
if (!floorHole(seed, zone, x, z)) set(level, x, zone.floorY(), z, floorMaterial(zone));
|
||||
if (!ceilingHole(seed, zone, x, z)) set(level, x, zone.ceilingY(), z, ceilingMaterial(zone));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void offices(WorldGenLevel level, int x0, int z0, Random random) {
|
||||
for (int baseY : new int[]{72, 84, 96}) {
|
||||
for (int x = x0; x < x0 + 16; x++) for (int z = z0; z < z0 + 16; z++) {
|
||||
set(level, x, baseY, z, Blocks.OAK_PLANKS.defaultBlockState());
|
||||
set(level, x, baseY + 9, z, CLAY_ORANGE);
|
||||
boolean wall = Math.floorMod(x, 12) == 0 || Math.floorMod(z, 12) == 0;
|
||||
if (wall) for (int y = baseY + 1; y < baseY + 9; y++) {
|
||||
BlockState material = y == baseY + 4 ? Blocks.STAINED_GLASS.pick(DyeColor.BROWN).defaultBlockState() : CLAY_BROWN;
|
||||
set(level, x, y, z, material);
|
||||
private static boolean floorHole(long seed, BackroomLevel zone, int x, int z) {
|
||||
return zone == BackroomLevel.LEVEL_4
|
||||
? BackroomLayout.voidHole(seed, x, z)
|
||||
: BackroomLayout.transitionHole(seed, zone.ordinal(), x, z);
|
||||
}
|
||||
|
||||
private static boolean ceilingHole(long seed, BackroomLevel zone, int x, int z) {
|
||||
return zone != BackroomLevel.LEVEL_0
|
||||
&& BackroomLayout.transitionHole(seed, zone.ordinal() - 1, x, z);
|
||||
}
|
||||
|
||||
private static BlockState floorMaterial(BackroomLevel zone) {
|
||||
return switch (zone) {
|
||||
case LEVEL_0 -> Blocks.BIRCH_PLANKS.defaultBlockState();
|
||||
case LEVEL_1 -> Blocks.DARK_OAK_PLANKS.defaultBlockState();
|
||||
case LEVEL_2 -> CONCRETE_GRAY;
|
||||
case LEVEL_3 -> Blocks.POLISHED_ANDESITE.defaultBlockState();
|
||||
case LEVEL_4 -> Blocks.STONE_BRICKS.defaultBlockState();
|
||||
};
|
||||
}
|
||||
|
||||
private static BlockState ceilingMaterial(BackroomLevel zone) {
|
||||
return switch (zone) {
|
||||
case LEVEL_0 -> CLAY_YELLOW;
|
||||
case LEVEL_1 -> CONCRETE_LIGHT;
|
||||
case LEVEL_2 -> CONCRETE_GRAY;
|
||||
case LEVEL_3 -> Blocks.BRICKS.defaultBlockState();
|
||||
case LEVEL_4 -> Blocks.MUD_BRICKS.defaultBlockState();
|
||||
};
|
||||
}
|
||||
|
||||
private static void buildThreshold(WorldGenLevel level, long seed, int x0, int z0) {
|
||||
int cellX = Math.floorDiv(x0, CHUNK_SIZE);
|
||||
int cellZ = Math.floorDiv(z0, CHUNK_SIZE);
|
||||
for (int deck = 0; deck < THRESHOLD_DECKS.length; deck++) {
|
||||
int floorY = THRESHOLD_DECKS[deck];
|
||||
int roofY = deck + 1 < THRESHOLD_DECKS.length ? THRESHOLD_DECKS[deck + 1]
|
||||
: BackroomLevel.LEVEL_0.ceilingY();
|
||||
if (deck > 0) fillDeck(level, seed, floorY, x0, z0, Blocks.BIRCH_PLANKS.defaultBlockState());
|
||||
int pattern = BackroomLayout.roomPattern(seed, BackroomLevel.LEVEL_0, cellX, cellZ, deck);
|
||||
BlockState wall = pattern % 2 == 0 ? CLAY_YELLOW : Blocks.SMOOTH_SANDSTONE.defaultBlockState();
|
||||
for (int x = x0; x < x0 + CHUNK_SIZE; x++) for (int z = z0; z < z0 + CHUNK_SIZE; z++) {
|
||||
int localX = Math.floorMod(x, CHUNK_SIZE), localZ = Math.floorMod(z, CHUNK_SIZE);
|
||||
boolean boundaryX = localX == 0, boundaryZ = localZ == 0;
|
||||
if (!boundaryX && !boundaryZ) continue;
|
||||
boolean doorway = boundaryX != boundaryZ && doorway(boundaryX ? localZ : localX, 7, 9);
|
||||
if (doorway) continue;
|
||||
for (int y = floorY + 1; y < roofY; y++) set(level, x, y, z, wall);
|
||||
}
|
||||
placeThresholdPattern(level, x0, floorY, z0, roofY, pattern, wall);
|
||||
for (RoomCorner corner : RoomCorner.values()) {
|
||||
placeCorner(level, seed, x0, floorY, z0, cellX, cellZ, deck, corner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void placeThresholdPattern(WorldGenLevel level, int x0, int floorY, int z0,
|
||||
int roofY, int pattern, BlockState wall) {
|
||||
switch (pattern) {
|
||||
case 1 -> partitionX(level, x0 + 5, floorY, roofY, z0 + 2, z0 + 13, z0 + 8, wall, false);
|
||||
case 2 -> partitionZ(level, z0 + 10, floorY, roofY, x0 + 2, x0 + 13, x0 + 8, wall, false);
|
||||
case 3 -> {
|
||||
partitionX(level, x0 + 5, floorY, roofY, z0 + 2, z0 + 11, z0 + 7, wall, false);
|
||||
partitionZ(level, z0 + 10, floorY, roofY, x0 + 5, x0 + 13, x0 + 10, wall, false);
|
||||
}
|
||||
case 4 -> partitionX(level, x0 + 8, floorY, roofY, z0 + 2, z0 + 13, z0 + 8, wall, true);
|
||||
case 5 -> {
|
||||
for (int x : new int[]{x0 + 5, x0 + 10}) for (int z : new int[]{z0 + 5, z0 + 10}) {
|
||||
for (int y = floorY + 1; y <= Math.min(floorY + 6, roofY - 1); y++) {
|
||||
set(level, x, y, z, y % 2 == 0 ? CLAY_ORANGE : CLAY_BROWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (random.nextInt(3) == 0) {
|
||||
int deskX = x0 + 5 + random.nextInt(6), deskZ = z0 + 5 + random.nextInt(6);
|
||||
set(level, deskX, baseY + 1, deskZ, Blocks.OAK_SLAB.defaultBlockState());
|
||||
set(level, deskX + 1, baseY + 1, deskZ, Blocks.BOOKSHELF.defaultBlockState());
|
||||
set(level, deskX, baseY + 1, deskZ + 1, Blocks.CARPET.pick(DyeColor.BROWN).defaultBlockState());
|
||||
}
|
||||
default -> { }
|
||||
}
|
||||
}
|
||||
|
||||
private static void plains(WorldGenLevel level, int x0, int z0, Random random) {
|
||||
int baseY = 68 + random.nextInt(3);
|
||||
for (int x = x0; x < x0 + 16; x++) for (int z = z0; z < z0 + 16; z++) {
|
||||
set(level, x, baseY - 2, z, Blocks.STONE.defaultBlockState());
|
||||
set(level, x, baseY - 1, z, Blocks.DIRT.defaultBlockState());
|
||||
set(level, x, baseY, z, Blocks.GRASS_BLOCK.defaultBlockState());
|
||||
}
|
||||
if (random.nextInt(3) == 0) tree(level, x0 + 4 + random.nextInt(8), baseY + 1, z0 + 4 + random.nextInt(8));
|
||||
if (random.nextInt(7) == 0) {
|
||||
for (int x = x0 + 3; x <= x0 + 11; x++) for (int z = z0 + 3; z <= z0 + 11; z++) {
|
||||
boolean edge = x == x0 + 3 || x == x0 + 11 || z == z0 + 3 || z == z0 + 11;
|
||||
if (edge) for (int y = baseY + 1; y <= baseY + 4; y++) set(level, x, y, z, Blocks.MUD_BRICKS.defaultBlockState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void aggregates(WorldGenLevel level, int x0, int z0, Random random) {
|
||||
int baseY = 74 + random.nextInt(10);
|
||||
platform(level, x0, z0, baseY, CONCRETE_LIGHT);
|
||||
boxRoom(level, x0 + 2, baseY + 1, z0 + 2, x0 + 12, baseY + 7, z0 + 12,
|
||||
Blocks.SMOOTH_STONE.defaultBlockState(), Blocks.OAK_PLANKS.defaultBlockState());
|
||||
if (random.nextBoolean()) {
|
||||
int upper = baseY + 20 + random.nextInt(12);
|
||||
platform(level, x0 + 4, z0 + 4, upper, CLAY_ORANGE);
|
||||
for (int y = baseY + 1; y <= upper; y++) set(level, x0 + 8, y, z0 + 8, Blocks.IRON_CHAIN.defaultBlockState());
|
||||
}
|
||||
}
|
||||
|
||||
private static void rotated(WorldGenLevel level, int x0, int z0, Random random) {
|
||||
int y0 = 70 + random.nextInt(12);
|
||||
for (int x = x0; x < x0 + 16; x++) for (int z = z0; z < z0 + 16; z++) set(level, x, y0, z, CONCRETE_LIGHT);
|
||||
boolean alongX = random.nextBoolean();
|
||||
for (int depth = 2; depth <= 13; depth++) for (int y = y0 + 1; y <= y0 + 13; y++) {
|
||||
int x = alongX ? x0 + depth : x0 + 3;
|
||||
int z = alongX ? z0 + 3 : z0 + depth;
|
||||
BlockState state = (y == y0 + 1 || y == y0 + 13 || depth == 2 || depth == 13)
|
||||
? CLAY_BROWN : Blocks.AIR.defaultBlockState();
|
||||
set(level, x, y, z, state);
|
||||
}
|
||||
// Furniture fixed to the vertical "floor" sells the rotated-room illusion without changing gravity.
|
||||
for (int offset = 4; offset <= 10; offset += 3) {
|
||||
int x = alongX ? x0 + offset : x0 + 4;
|
||||
int z = alongX ? z0 + 4 : z0 + offset;
|
||||
set(level, x, y0 + 4, z, Blocks.BOOKSHELF.defaultBlockState());
|
||||
}
|
||||
}
|
||||
|
||||
private static void placeLootLandmarks(WorldGenLevel level, int x0, int z0, Random random, Architecture architecture) {
|
||||
int y = switch (architecture) {
|
||||
case CITY -> 65;
|
||||
case OFFICES -> 73;
|
||||
case PLAINS -> 72;
|
||||
case AGGREGATES -> 83;
|
||||
case ROTATED -> 84;
|
||||
private static void placeCorner(WorldGenLevel level, long seed, int x0, int floorY, int z0,
|
||||
int cellX, int cellZ, int deck, RoomCorner corner) {
|
||||
int x = switch (corner) {
|
||||
case NORTH_WEST, SOUTH_WEST -> x0 + 2;
|
||||
case NORTH_EAST, SOUTH_EAST -> x0 + 13;
|
||||
};
|
||||
int x = x0 + 7, z = z0 + 7;
|
||||
while (!level.getBlockState(new BlockPos(x, y, z)).isAir() && y < 180) y++;
|
||||
while (!level.getBlockState(new BlockPos(x, y - 1, z)).isCollisionShapeFullBlock(level, new BlockPos(x, y - 1, z)) && y > 2) y--;
|
||||
if (random.nextInt(5) == 0) set(level, x, y, z, Blocks.CHEST.defaultBlockState());
|
||||
if (random.nextInt(17) == 0) placeBed(level, x0 + 9, y, z0 + 8);
|
||||
int z = switch (corner) {
|
||||
case NORTH_WEST, NORTH_EAST -> z0 + 2;
|
||||
case SOUTH_WEST, SOUTH_EAST -> z0 + 13;
|
||||
};
|
||||
BlockState material = CORNER_MATERIALS[BackroomLayout.cornerVariant(
|
||||
seed ^ (long) deck * 31L, BackroomLevel.LEVEL_0, cellX, cellZ, corner)];
|
||||
for (int y = floorY + 1; y <= floorY + 4; y++) set(level, x, y, z, material);
|
||||
int towardX = corner == RoomCorner.NORTH_WEST || corner == RoomCorner.SOUTH_WEST ? 1 : -1;
|
||||
int towardZ = corner == RoomCorner.NORTH_WEST || corner == RoomCorner.NORTH_EAST ? 1 : -1;
|
||||
for (int distance = 1; distance <= 2; distance++) {
|
||||
set(level, x + towardX * distance, floorY + 1, z, material);
|
||||
set(level, x, floorY + 1, z + towardZ * distance, material);
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildArchives(WorldGenLevel level, long seed, int x0, int z0) {
|
||||
int macroX = Math.floorDiv(x0, 64), macroZ = Math.floorDiv(z0, 64);
|
||||
for (int deck = 0; deck < ARCHIVE_DECKS.length; deck++) {
|
||||
int floorY = ARCHIVE_DECKS[deck];
|
||||
int roofY = deck + 1 < ARCHIVE_DECKS.length ? ARCHIVE_DECKS[deck + 1]
|
||||
: BackroomLevel.LEVEL_1.ceilingY();
|
||||
if (deck > 0) fillDeck(level, seed, floorY, x0, z0, Blocks.DARK_OAK_PLANKS.defaultBlockState());
|
||||
boolean library = Math.floorMod((int) fingerprint(seed ^ deck, macroX, macroZ), 3) != 0;
|
||||
for (int x = x0; x < x0 + CHUNK_SIZE; x++) for (int z = z0; z < z0 + CHUNK_SIZE; z++) {
|
||||
int localX = Math.floorMod(x, 32), localZ = Math.floorMod(z, 32);
|
||||
boolean wallX = localX == 0, wallZ = localZ == 0;
|
||||
if (!wallX && !wallZ) continue;
|
||||
boolean open = wallX != wallZ && doorway(wallX ? localZ : localX, 14, 18);
|
||||
if (open) continue;
|
||||
for (int y = floorY + 1; y < roofY; y++) {
|
||||
BlockState state = y >= floorY + 5 && y <= floorY + 9 ? GLASS_BROWN : CONCRETE_LIGHT;
|
||||
set(level, x, y, z, state);
|
||||
}
|
||||
}
|
||||
if (library) buildLibraryRoom(level, x0, floorY, z0, roofY);
|
||||
else buildOfficeRoom(level, seed, x0, floorY, z0, roofY, deck);
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildLibraryRoom(WorldGenLevel level, int x0, int floorY, int z0, int roofY) {
|
||||
for (int x = x0 + 1; x < x0 + 15; x++) for (int z = z0 + 1; z < z0 + 15; z++) {
|
||||
int roomX = Math.floorMod(x, 32), roomZ = Math.floorMod(z, 32);
|
||||
boolean shelf = (Math.floorMod(x, 8) == 3 || Math.floorMod(x, 8) == 4)
|
||||
&& roomX > 3 && roomX < 28 && (roomZ < 12 || roomZ > 20);
|
||||
if (!shelf) continue;
|
||||
for (int y = floorY + 1; y <= Math.min(floorY + 6, roofY - 1); y++) {
|
||||
set(level, x, y, z, Blocks.BOOKSHELF.defaultBlockState());
|
||||
}
|
||||
}
|
||||
if (Math.floorMod(x0 + z0 + floorY, 3) == 0) {
|
||||
for (int x = x0 + 6; x <= x0 + 9; x++) set(level, x, floorY + 1, z0 + 8,
|
||||
Blocks.OAK_SLAB.defaultBlockState());
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildOfficeRoom(WorldGenLevel level, long seed, int x0, int floorY, int z0,
|
||||
int roofY, int deck) {
|
||||
boolean alongX = (fingerprint(seed, Math.floorDiv(x0, 16), Math.floorDiv(z0, 16)) + deck & 1L) == 0L;
|
||||
if (alongX) partitionX(level, x0 + 8, floorY, roofY, z0 + 1, z0 + 14, z0 + 8, CONCRETE_WHITE, true);
|
||||
else partitionZ(level, z0 + 8, floorY, roofY, x0 + 1, x0 + 14, x0 + 8, CONCRETE_WHITE, true);
|
||||
set(level, x0 + 4, floorY + 1, z0 + 4, Blocks.OAK_SLAB.defaultBlockState());
|
||||
set(level, x0 + 5, floorY + 1, z0 + 4, Blocks.BOOKSHELF.defaultBlockState());
|
||||
set(level, x0 + 11, floorY + 1, z0 + 11, Blocks.OAK_SLAB.defaultBlockState());
|
||||
}
|
||||
|
||||
private static void buildCity(WorldGenLevel level, long seed, int x0, int z0, int chunkX, int chunkZ) {
|
||||
boolean roadFree = true;
|
||||
for (int x = x0; x < x0 + CHUNK_SIZE; x++) for (int z = z0; z < z0 + CHUNK_SIZE; z++) {
|
||||
if (BackroomLayout.transitionHole(seed, BackroomLevel.LEVEL_2.ordinal(), x, z)) continue;
|
||||
boolean highway = BackroomLayout.cityHighway(x, z);
|
||||
if (highway) roadFree = false;
|
||||
BlockState surface = highway ? CONCRETE_GRAY : CONCRETE_LIGHT;
|
||||
if (highway && (Math.floorMod(x, 8) == 0 || Math.floorMod(z, 8) == 0)) surface = CONCRETE_WHITE;
|
||||
set(level, x, BackroomLevel.LEVEL_2.floorY(), z, surface);
|
||||
}
|
||||
boolean park = roadFree && BackroomLayout.cityPark(seed, chunkX, chunkZ);
|
||||
if (park) {
|
||||
buildPark(level, seed, x0, z0);
|
||||
return;
|
||||
}
|
||||
if (!roadFree) return;
|
||||
|
||||
Random random = new Random(fingerprint(seed ^ 0x514e7a2c39d8b60fL, chunkX, chunkZ));
|
||||
int floorY = BackroomLevel.LEVEL_2.floorY();
|
||||
int roofY = floorY + 24 + random.nextInt(56);
|
||||
int minX = x0 + 1, maxX = x0 + 14, minZ = z0 + 1, maxZ = z0 + 14;
|
||||
BlockState frame = random.nextBoolean() ? CONCRETE_LIGHT : Blocks.STONE_BRICKS.defaultBlockState();
|
||||
BlockState glass = random.nextBoolean() ? GLASS_CYAN : GLASS_BLACK;
|
||||
for (int y = floorY + 1; y <= roofY; y++) {
|
||||
boolean slab = y == roofY || Math.floorMod(y - floorY, 7) == 0;
|
||||
for (int x = minX; x <= maxX; x++) for (int z = minZ; z <= maxZ; z++) {
|
||||
boolean edge = x == minX || x == maxX || z == minZ || z == maxZ;
|
||||
boolean entrance = y <= floorY + 4 && (((x == minX || x == maxX) && doorway(z - z0, 7, 9))
|
||||
|| ((z == minZ || z == maxZ) && doorway(x - x0, 7, 9)));
|
||||
if (entrance) continue;
|
||||
if (slab) set(level, x, y, z, frame);
|
||||
else if (edge) set(level, x, y, z, Math.floorMod(y - floorY, 7) <= 2 ? frame : glass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildPark(WorldGenLevel level, long seed, int x0, int z0) {
|
||||
int floorY = BackroomLevel.LEVEL_2.floorY();
|
||||
for (int x = x0 + 1; x < x0 + 15; x++) for (int z = z0 + 1; z < z0 + 15; z++) {
|
||||
if (BackroomLayout.transitionHole(seed, BackroomLevel.LEVEL_2.ordinal(), x, z)) continue;
|
||||
BlockState surface = x == x0 + 8 || z == z0 + 8
|
||||
? Blocks.SMOOTH_STONE.defaultBlockState() : Blocks.GRASS_BLOCK.defaultBlockState();
|
||||
set(level, x, floorY, z, surface);
|
||||
}
|
||||
Random random = new Random(fingerprint(seed ^ 0x1974acb65d203e8fL,
|
||||
Math.floorDiv(x0, 16), Math.floorDiv(z0, 16)));
|
||||
int trees = 1 + random.nextInt(2);
|
||||
for (int index = 0; index < trees; index++) {
|
||||
tree(level, x0 + 4 + random.nextInt(8), floorY + 1, z0 + 4 + random.nextInt(8));
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildUtilities(WorldGenLevel level, long seed, int x0, int z0) {
|
||||
for (int deck = 0; deck < UTILITY_DECKS.length; deck++) {
|
||||
int floorY = UTILITY_DECKS[deck];
|
||||
int roofY = deck + 1 < UTILITY_DECKS.length ? UTILITY_DECKS[deck + 1]
|
||||
: BackroomLevel.LEVEL_3.ceilingY();
|
||||
if (deck > 0) fillDeck(level, seed, floorY, x0, z0, Blocks.POLISHED_ANDESITE.defaultBlockState());
|
||||
for (int x = x0; x < x0 + CHUNK_SIZE; x++) for (int z = z0; z < z0 + CHUNK_SIZE; z++) {
|
||||
int localX = Math.floorMod(x, 24), localZ = Math.floorMod(z, 24);
|
||||
boolean wallX = localX == 0, wallZ = localZ == 0;
|
||||
if (wallX || wallZ) {
|
||||
boolean open = wallX != wallZ && doorway(wallX ? localZ : localX, 10, 14);
|
||||
if (!open) for (int y = floorY + 1; y < roofY; y++) {
|
||||
BlockState state = y == floorY + 8 ? Blocks.IRON_BARS.defaultBlockState()
|
||||
: (deck + Math.floorDiv(x + z, 24)) % 2 == 0
|
||||
? Blocks.BRICKS.defaultBlockState() : Blocks.MUD_BRICKS.defaultBlockState();
|
||||
set(level, x, y, z, state);
|
||||
}
|
||||
}
|
||||
boolean overheadGrid = floorY + 12 < roofY
|
||||
&& ((localX == 4 && localZ >= 7 && localZ <= 17)
|
||||
|| (localZ == 4 && localX >= 7 && localX <= 17));
|
||||
if (overheadGrid) set(level, x, floorY + 12, z, Blocks.IRON_BARS.defaultBlockState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildGlitchMaze(WorldGenLevel level, long seed, int x0, int z0) {
|
||||
for (int x = x0; x < x0 + CHUNK_SIZE; x++) for (int z = z0; z < z0 + CHUNK_SIZE; z++) {
|
||||
if (!BackroomLayout.deckHole(seed, BackroomLayout.MAZE_FLOOR_Y, x, z)) {
|
||||
set(level, x, BackroomLayout.MAZE_FLOOR_Y, z, Blocks.STONE_BRICKS.defaultBlockState());
|
||||
}
|
||||
if (!BackroomLayout.mazeRoofHole(seed, x, z)) {
|
||||
set(level, x, BackroomLayout.MAZE_ROOF_Y, z, Blocks.MUD_BRICKS.defaultBlockState());
|
||||
}
|
||||
for (int y = BackroomLevel.LEVEL_4.floorY() + 1; y < BackroomLevel.LEVEL_4.ceilingY(); y++) {
|
||||
if (y >= BackroomLayout.MAZE_FLOOR_Y && y <= BackroomLayout.MAZE_ROOF_Y) continue;
|
||||
boolean edgeX = Math.floorMod(x, 8) == 0;
|
||||
boolean edgeY = Math.floorMod(y - BackroomLevel.LEVEL_4.floorY(), 8) == 0;
|
||||
boolean edgeZ = Math.floorMod(z, 8) == 0;
|
||||
if ((edgeX && edgeY) || (edgeX && edgeZ) || (edgeY && edgeZ)) {
|
||||
set(level, x, y, z, glitchMaterial(seed, x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = x0; x < x0 + CHUNK_SIZE; x++) for (int z = z0; z < z0 + CHUNK_SIZE; z++) {
|
||||
boolean wallX = Math.floorMod(x, CHUNK_SIZE) == 0;
|
||||
boolean wallZ = Math.floorMod(z, CHUNK_SIZE) == 0;
|
||||
if (!wallX && !wallZ) continue;
|
||||
boolean open = false;
|
||||
if (wallX != wallZ) {
|
||||
if (wallX) {
|
||||
int rightX = Math.floorDiv(x, CHUNK_SIZE), cellZ = Math.floorDiv(z, CHUNK_SIZE);
|
||||
boolean connected = BackroomLayout.mazeConnected(seed, rightX - 1, cellZ, rightX, cellZ);
|
||||
boolean merged = BackroomLayout.mergedMazeRooms(seed, rightX - 1, cellZ, rightX, cellZ);
|
||||
open = merged || connected && doorway(Math.floorMod(z, CHUNK_SIZE), 7, 9);
|
||||
} else {
|
||||
int cellX = Math.floorDiv(x, CHUNK_SIZE), southZ = Math.floorDiv(z, CHUNK_SIZE);
|
||||
boolean connected = BackroomLayout.mazeConnected(seed, cellX, southZ - 1, cellX, southZ);
|
||||
boolean merged = BackroomLayout.mergedMazeRooms(seed, cellX, southZ - 1, cellX, southZ);
|
||||
open = merged || connected && doorway(Math.floorMod(x, CHUNK_SIZE), 7, 9);
|
||||
}
|
||||
}
|
||||
if (open) continue;
|
||||
for (int y = BackroomLayout.MAZE_FLOOR_Y + 1; y < BackroomLayout.MAZE_ROOF_Y; y++) {
|
||||
BlockState state = y >= BackroomLayout.MAZE_FLOOR_Y + 4
|
||||
&& y <= BackroomLayout.MAZE_FLOOR_Y + 6 ? GLASS_BLACK : glitchMaterial(seed, x, y, z);
|
||||
set(level, x, y, z, state);
|
||||
}
|
||||
}
|
||||
|
||||
int cellX = Math.floorDiv(x0, CHUNK_SIZE), cellZ = Math.floorDiv(z0, CHUNK_SIZE);
|
||||
int pattern = BackroomLayout.roomPattern(seed, BackroomLevel.LEVEL_4, cellX, cellZ, 0);
|
||||
BlockState wall = GLITCH_MATERIALS[Math.floorMod(pattern, GLITCH_MATERIALS.length)];
|
||||
placeThresholdPattern(level, x0, BackroomLayout.MAZE_FLOOR_Y, z0,
|
||||
BackroomLayout.MAZE_ROOF_Y, pattern, wall);
|
||||
}
|
||||
|
||||
private static BlockState glitchMaterial(long seed, int x, int y, int z) {
|
||||
int index = Math.floorMod((int) fingerprint(seed ^ y, Math.floorDiv(x, 8), Math.floorDiv(z, 8)),
|
||||
GLITCH_MATERIALS.length);
|
||||
return GLITCH_MATERIALS[index];
|
||||
}
|
||||
|
||||
private static void fillDeck(WorldGenLevel level, long seed, int floorY, int x0, int z0,
|
||||
BlockState state) {
|
||||
for (int x = x0; x < x0 + CHUNK_SIZE; x++) for (int z = z0; z < z0 + CHUNK_SIZE; z++) {
|
||||
if (!BackroomLayout.deckHole(seed, floorY, x, z)) set(level, x, floorY, z, state);
|
||||
}
|
||||
}
|
||||
|
||||
private static void partitionX(WorldGenLevel level, int x, int floorY, int roofY,
|
||||
int minZ, int maxZ, int doorZ, BlockState wall, boolean window) {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
if (Math.abs(z - doorZ) <= 1) continue;
|
||||
for (int y = floorY + 1; y < roofY; y++) {
|
||||
set(level, x, y, z, window && y >= floorY + 5 && y <= floorY + 8 ? GLASS_BROWN : wall);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void partitionZ(WorldGenLevel level, int z, int floorY, int roofY,
|
||||
int minX, int maxX, int doorX, BlockState wall, boolean window) {
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
if (Math.abs(x - doorX) <= 1) continue;
|
||||
for (int y = floorY + 1; y < roofY; y++) {
|
||||
set(level, x, y, z, window && y >= floorY + 5 && y <= floorY + 8 ? GLASS_BROWN : wall);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean doorway(int coordinate, int minimum, int maximum) {
|
||||
return coordinate >= minimum && coordinate <= maximum;
|
||||
}
|
||||
|
||||
private static void placeLootLandmarks(WorldGenLevel level, int x0, int z0, Random random) {
|
||||
if (random.nextInt(5) == 0) {
|
||||
int floorY = LANDMARK_FLOORS[random.nextInt(LANDMARK_FLOORS.length)];
|
||||
int x = x0 + 7, y = floorY + 1, z = z0 + 7;
|
||||
set(level, x, floorY, z, floorMaterial(BackroomLayout.levelAt(floorY)));
|
||||
set(level, x, y, z, Blocks.CHEST.defaultBlockState());
|
||||
set(level, x, y + 1, z, Blocks.AIR.defaultBlockState());
|
||||
}
|
||||
if (random.nextInt(96) == 0) {
|
||||
int floorY = 272;
|
||||
int x = x0 + 7, z = z0 + 8;
|
||||
set(level, x, floorY, z, Blocks.DARK_OAK_PLANKS.defaultBlockState());
|
||||
set(level, x + 1, floorY, z, Blocks.DARK_OAK_PLANKS.defaultBlockState());
|
||||
for (int dx = 0; dx <= 1; dx++) for (int dy = 1; dy <= 2; dy++) {
|
||||
set(level, x + dx, floorY + dy, z, Blocks.AIR.defaultBlockState());
|
||||
}
|
||||
placeBed(level, x, floorY + 1, z);
|
||||
}
|
||||
}
|
||||
|
||||
private static void placeRareAnomaly(WorldGenLevel level, int x0, int z0, Random random) {
|
||||
if (random.nextInt(96) == 0) {
|
||||
Block anomaly = BackroomMaterialApi.anomalyMaterial();
|
||||
for (int i = 0; i < 5; i++) set(level, x0 + 5 + random.nextInt(6), 66 + random.nextInt(20),
|
||||
z0 + 5 + random.nextInt(6), anomaly.defaultBlockState());
|
||||
int floorY = LANDMARK_FLOORS[random.nextInt(LANDMARK_FLOORS.length)];
|
||||
for (int index = 0; index < 5; index++) {
|
||||
set(level, x0 + 5 + random.nextInt(6), floorY + 1 + random.nextInt(4),
|
||||
z0 + 5 + random.nextInt(6), anomaly.defaultBlockState());
|
||||
}
|
||||
}
|
||||
if (random.nextInt(4_096) == 0
|
||||
&& withinPlayableArea(x0 + 7, z0 + 7) && withinPlayableArea(x0 + 9, z0 + 9)) {
|
||||
for (int dx = -1; dx <= 1; dx++) for (int dy = -1; dy <= 1; dy++) for (int dz = -1; dz <= 1; dz++) {
|
||||
set(level, x0 + 8 + dx, 40 + dy, z0 + 8 + dz, Blocks.SMOOTH_STONE.defaultBlockState());
|
||||
set(level, x0 + 8 + dx, -40 + dy, z0 + 8 + dz, Blocks.SMOOTH_STONE.defaultBlockState());
|
||||
}
|
||||
set(level, x0 + 8, 40, z0 + 8, Blocks.DIAMOND_BLOCK.defaultBlockState());
|
||||
set(level, x0 + 8, -40, z0 + 8, Blocks.DIAMOND_BLOCK.defaultBlockState());
|
||||
}
|
||||
}
|
||||
|
||||
private static void placeBed(WorldGenLevel level, int x, int y, int z) {
|
||||
if (!withinPlayableArea(x, z) || !withinPlayableArea(x + 1, z)) return;
|
||||
Block bed = Blocks.BED.pick(DyeColor.WHITE);
|
||||
BlockState foot = bed.defaultBlockState().setValue(BedBlock.FACING, Direction.EAST).setValue(BedBlock.PART, BedPart.FOOT);
|
||||
BlockState foot = bed.defaultBlockState().setValue(BedBlock.FACING, Direction.EAST)
|
||||
.setValue(BedBlock.PART, BedPart.FOOT);
|
||||
BlockState head = foot.setValue(BedBlock.PART, BedPart.HEAD);
|
||||
set(level, x, y, z, foot);
|
||||
set(level, x + 1, y, z, head);
|
||||
}
|
||||
|
||||
private static void platform(WorldGenLevel level, int x0, int z0, int y, BlockState state) {
|
||||
for (int x = x0; x < x0 + 16; x++) for (int z = z0; z < z0 + 16; z++) set(level, x, y, z, state);
|
||||
}
|
||||
|
||||
private static void boxRoom(WorldGenLevel level, int minX, int minY, int minZ,
|
||||
int maxX, int maxY, int maxZ, BlockState shell, BlockState floor) {
|
||||
for (int x = minX; x <= maxX; x++) for (int y = minY; y <= maxY; y++) for (int z = minZ; z <= maxZ; z++) {
|
||||
boolean boundary = x == minX || x == maxX || y == minY || y == maxY || z == minZ || z == maxZ;
|
||||
if (boundary) set(level, x, y, z, y == minY ? floor : shell);
|
||||
}
|
||||
}
|
||||
|
||||
private static void tree(WorldGenLevel level, int x, int y, int z) {
|
||||
for (int dy = 0; dy < 5; dy++) set(level, x, y + dy, z, Blocks.OAK_LOG.defaultBlockState());
|
||||
for (int dx = -2; dx <= 2; dx++) for (int dz = -2; dz <= 2; dz++) for (int dy = 3; dy <= 6; dy++) {
|
||||
if (Math.abs(dx) + Math.abs(dz) + Math.abs(dy - 5) <= 5) set(level, x + dx, y + dy, z + dz, LEAVES);
|
||||
for (int dy = 0; dy < 6; dy++) set(level, x, y + dy, z, Blocks.OAK_LOG.defaultBlockState());
|
||||
for (int dx = -2; dx <= 2; dx++) for (int dz = -2; dz <= 2; dz++) for (int dy = 4; dy <= 7; dy++) {
|
||||
if (Math.abs(dx) + Math.abs(dz) + Math.abs(dy - 6) <= 5) set(level, x + dx, y + dy, z + dz, LEAVES);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +485,4 @@ public final class BackroomArchitectureFeature extends Feature<NoneFeatureConfig
|
||||
value *= 0x94D049BB133111EBL;
|
||||
return value ^ value >>> 31;
|
||||
}
|
||||
|
||||
public enum Architecture { CITY, OFFICES, PLAINS, AGGREGATES, ROTATED }
|
||||
}
|
||||
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.worldgen.feature;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Pure deterministic layout rules shared by Backroom generation, arrivals and executable tests.
|
||||
* Level numbers run from the top down so a fall always moves toward the next numbered level.
|
||||
*/
|
||||
public final class BackroomLayout {
|
||||
public static final int WORLD_MIN_Y = -64;
|
||||
public static final int WORLD_MAX_Y_EXCLUSIVE = 384;
|
||||
public static final int ENTRY_FLOOR_Y = 367;
|
||||
public static final int ROOM_SIZE = 16;
|
||||
public static final int MAZE_FLOOR_Y = 8;
|
||||
public static final int MAZE_ROOF_Y = 18;
|
||||
private static final int HOLE_CELL_SIZE = 64;
|
||||
|
||||
private BackroomLayout() {}
|
||||
|
||||
public static BackroomLevel levelAt(int y) {
|
||||
for (BackroomLevel level : BackroomLevel.values()) {
|
||||
if (y >= level.floorY && y <= level.ceilingY) return level;
|
||||
}
|
||||
throw new IllegalArgumentException("Y outside the Backroom: " + y);
|
||||
}
|
||||
|
||||
/** A matching opening through an upper floor and the lower level's ceiling. */
|
||||
public static boolean transitionHole(long seed, int transition, int x, int z) {
|
||||
if (transition < 0 || transition >= BackroomLevel.values().length - 1) return false;
|
||||
return authoredHole(seed ^ 0x2f53a71d4e6c890bL, transition, x, z, 1);
|
||||
}
|
||||
|
||||
/** Sparse holes between decks create vertical routes without turning every room into a shaft. */
|
||||
public static boolean deckHole(long seed, int floorY, int x, int z) {
|
||||
return authoredHole(seed ^ 0x74c8e6219b0d35afL, floorY, x, z, 1);
|
||||
}
|
||||
|
||||
/** The last floor deliberately has occasional exits into the actual void. */
|
||||
public static boolean voidHole(long seed, int x, int z) {
|
||||
return authoredHole(seed ^ 0x56de9c183af247b1L, 5, x, z, 2);
|
||||
}
|
||||
|
||||
/** Openings in the maze roof let a player who landed on it enter the generated rooms. */
|
||||
public static boolean mazeRoofHole(long seed, int x, int z) {
|
||||
return authoredHole(seed ^ 0x6b8f1de425a390c7L, 6, x, z, 1);
|
||||
}
|
||||
|
||||
public static int roomPattern(long seed, BackroomLevel level, int cellX, int cellZ, int deck) {
|
||||
return Math.floorMod((int) mix(seed ^ 0x3c79ac492ba7b653L,
|
||||
cellX + level.ordinal() * 1_009, cellZ + deck * 4_099), 6);
|
||||
}
|
||||
|
||||
/** Each of the four authored corner directions independently reaches five material variants. */
|
||||
public static int cornerVariant(long seed, BackroomLevel level, int cellX, int cellZ,
|
||||
RoomCorner corner) {
|
||||
Objects.requireNonNull(level, "level");
|
||||
Objects.requireNonNull(corner, "corner");
|
||||
return Math.floorMod((int) mix(seed ^ 0x1c69b3f74ac4ae35L,
|
||||
cellX + corner.ordinal() * 8_191, cellZ + level.ordinal() * 16_381), 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Symmetric room-edge rule. Every maze cell has a parent one step nearer the origin, which makes
|
||||
* the complete graph connected; extra deterministic edges introduce loops and glitches.
|
||||
*/
|
||||
public static boolean mazeConnected(long seed, int ax, int az, int bx, int bz) {
|
||||
if (Math.abs(ax - bx) + Math.abs(az - bz) != 1) return false;
|
||||
if (isParent(ax, az, bx, bz) || isParent(bx, bz, ax, az)) return true;
|
||||
int minX = Math.min(ax, bx), minZ = Math.min(az, bz);
|
||||
int edgeAxis = ax == bx ? 1 : 0;
|
||||
return Math.floorMod((int) mix(seed ^ 0x5d42c713e9a86bf0L,
|
||||
minX * 2 + edgeAxis, minZ * 2 + 1 - edgeAxis), 5) == 0;
|
||||
}
|
||||
|
||||
/** Some connected cells completely lose their partition and become rooms at a larger scale. */
|
||||
public static boolean mergedMazeRooms(long seed, int ax, int az, int bx, int bz) {
|
||||
if (!mazeConnected(seed, ax, az, bx, bz)) return false;
|
||||
int minX = Math.min(ax, bx), minZ = Math.min(az, bz);
|
||||
int edgeAxis = ax == bx ? 1 : 0;
|
||||
return Math.floorMod((int) mix(seed ^ 0x0b4e8f619c32d75aL,
|
||||
minX * 2 + edgeAxis, minZ * 2 + 1 - edgeAxis), 11) == 0;
|
||||
}
|
||||
|
||||
/** Infinite orthogonal highways, nine blocks wide and 96 blocks apart. */
|
||||
public static boolean cityHighway(int x, int z) {
|
||||
return centeredModulo(x - 8, 96) <= 4 || centeredModulo(z - 8, 96) <= 4;
|
||||
}
|
||||
|
||||
public static boolean cityPark(long seed, int chunkX, int chunkZ) {
|
||||
return Math.floorMod((int) mix(seed ^ 0x43c2a1769f0e85bdL, chunkX, chunkZ), 11) == 0;
|
||||
}
|
||||
|
||||
private static boolean isParent(int childX, int childZ, int parentX, int parentZ) {
|
||||
if (childX == 0 && childZ == 0) return false;
|
||||
int expectedX = childX, expectedZ = childZ;
|
||||
if (Math.abs(childX) >= Math.abs(childZ) && childX != 0) expectedX -= Integer.signum(childX);
|
||||
else expectedZ -= Integer.signum(childZ);
|
||||
return expectedX == parentX && expectedZ == parentZ;
|
||||
}
|
||||
|
||||
private static boolean authoredHole(long seed, int salt, int x, int z, int radius) {
|
||||
int cellX = Math.floorDiv(x, HOLE_CELL_SIZE);
|
||||
int cellZ = Math.floorDiv(z, HOLE_CELL_SIZE);
|
||||
long hash = mix(seed ^ (long) salt * 0x9e3779b97f4a7c15L, cellX, cellZ);
|
||||
int centerX = cellX * HOLE_CELL_SIZE + 8 + Math.floorMod((int) hash, HOLE_CELL_SIZE - 16);
|
||||
int centerZ = cellZ * HOLE_CELL_SIZE + 8
|
||||
+ Math.floorMod((int) (hash >>> 32), HOLE_CELL_SIZE - 16);
|
||||
return Math.abs(x - centerX) <= radius && Math.abs(z - centerZ) <= radius;
|
||||
}
|
||||
|
||||
private static int centeredModulo(int value, int period) {
|
||||
int wrapped = Math.floorMod(value + period / 2, period) - period / 2;
|
||||
return Math.abs(wrapped);
|
||||
}
|
||||
|
||||
private static long mix(long seed, int x, int z) {
|
||||
long value = seed ^ (long) x * 0x9E3779B97F4A7C15L ^ (long) z * 0xC2B2AE3D27D4EB4FL;
|
||||
value ^= value >>> 30;
|
||||
value *= 0xBF58476D1CE4E5B9L;
|
||||
value ^= value >>> 27;
|
||||
value *= 0x94D049BB133111EBL;
|
||||
return value ^ value >>> 31;
|
||||
}
|
||||
|
||||
public enum BackroomLevel {
|
||||
LEVEL_0(295, 383),
|
||||
LEVEL_1(206, 294),
|
||||
LEVEL_2(117, 205),
|
||||
LEVEL_3(28, 116),
|
||||
LEVEL_4(-64, 27);
|
||||
|
||||
private final int floorY;
|
||||
private final int ceilingY;
|
||||
|
||||
BackroomLevel(int floorY, int ceilingY) {
|
||||
this.floorY = floorY;
|
||||
this.ceilingY = ceilingY;
|
||||
}
|
||||
|
||||
public int floorY() {
|
||||
return this.floorY;
|
||||
}
|
||||
|
||||
public int ceilingY() {
|
||||
return this.ceilingY;
|
||||
}
|
||||
}
|
||||
|
||||
public enum RoomCorner {
|
||||
NORTH_WEST,
|
||||
NORTH_EAST,
|
||||
SOUTH_WEST,
|
||||
SOUTH_EAST
|
||||
}
|
||||
}
|
||||
@@ -522,7 +522,7 @@
|
||||
"message.sanctuary.backroom.registry_busy": "The Backroom registry is settling too many interrupted transfers. Try again later.",
|
||||
"message.sanctuary.backroom.mailbox_missing": "Load and register your Mailbox before entering the Backroom.",
|
||||
"message.sanctuary.backroom.mailbox_full": "Your Mailbox has no room for all your gear, companion egg and cape.",
|
||||
"message.sanctuary.backroom.entered": "You slip through the bed with no equipment…",
|
||||
"message.sanctuary.backroom.entered": "You slip through the bed with no equipment… A torch marks your arrival point.",
|
||||
"message.sanctuary.backroom.bed_entry_only": "Only a bed can take you into the Backroom.",
|
||||
"message.sanctuary.backroom.exited": "The bed returns you to your spawn point with your findings.",
|
||||
"message.sanctuary.backroom.exited_with_losses": "The bed returns you with your findings and settles the recovered lost items.",
|
||||
|
||||
@@ -522,7 +522,7 @@
|
||||
"message.sanctuary.backroom.registry_busy": "Le registre Backroom termine trop de transferts interrompus. Réessaie plus tard.",
|
||||
"message.sanctuary.backroom.mailbox_missing": "Charge et enregistre ta Mailbox avant d’entrer dans la Backroom.",
|
||||
"message.sanctuary.backroom.mailbox_full": "Ta Mailbox n’a pas assez de place pour tout ton équipement, l’œuf et la cape.",
|
||||
"message.sanctuary.backroom.entered": "Tu traverses le lit sans aucun équipement…",
|
||||
"message.sanctuary.backroom.entered": "Tu traverses le lit sans aucun équipement… Une torche marque ton point d’arrivée.",
|
||||
"message.sanctuary.backroom.bed_entry_only": "Seul un lit permet d’entrer dans la Backroom.",
|
||||
"message.sanctuary.backroom.exited": "Le lit te ramène à ton point d’apparition avec tes trouvailles.",
|
||||
"message.sanctuary.backroom.exited_with_losses": "Le lit te ramène avec tes trouvailles et valide les objets perdus récupérés.",
|
||||
|
||||
@@ -478,7 +478,7 @@
|
||||
"message.sanctuary.backroom.registry_busy": "Реестр Бэкрума завершает слишком много прерванных переносов. Повторите попытку позже.",
|
||||
"message.sanctuary.backroom.mailbox_missing": "Загрузите и зарегистрируйте почтовый ящик перед входом в Бэкрум.",
|
||||
"message.sanctuary.backroom.mailbox_full": "В почтовом ящике недостаточно места для снаряжения, яйца спутника и плаща.",
|
||||
"message.sanctuary.backroom.entered": "Вы проваливаетесь сквозь кровать без снаряжения…",
|
||||
"message.sanctuary.backroom.entered": "Вы проваливаетесь сквозь кровать без снаряжения… Факел отмечает место прибытия.",
|
||||
"message.sanctuary.backroom.bed_entry_only": "В Бэкрум можно попасть только через кровать.",
|
||||
"message.sanctuary.backroom.exited": "Кровать возвращает вас к точке возрождения со всеми находками.",
|
||||
"message.sanctuary.backroom.exited_with_losses": "Кровать возвращает вас с находками и закрепляет найденные потерянные предметы.",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"ambient_light": 0.35,
|
||||
"ambient_light": 0.0,
|
||||
"attributes": {
|
||||
"minecraft:audio/ambient_sounds": {
|
||||
"mood": {
|
||||
@@ -17,25 +17,25 @@
|
||||
"minecraft:gameplay/respawn_anchor_works": false,
|
||||
"minecraft:gameplay/sky_light_level": 0.0,
|
||||
"minecraft:gameplay/water_evaporates": false,
|
||||
"minecraft:visual/ambient_light_color": "#d5d8ce",
|
||||
"minecraft:visual/fog_color": "#b7b9ad",
|
||||
"minecraft:visual/fog_end_distance": 112.0,
|
||||
"minecraft:visual/fog_start_distance": 20.0,
|
||||
"minecraft:visual/sky_light_color": "#d9dccf",
|
||||
"minecraft:visual/ambient_light_color": "#000000",
|
||||
"minecraft:visual/fog_color": "#000000",
|
||||
"minecraft:visual/fog_end_distance": 96.0,
|
||||
"minecraft:visual/fog_start_distance": 12.0,
|
||||
"minecraft:visual/sky_light_color": "#000000",
|
||||
"minecraft:visual/sky_light_factor": 0.0
|
||||
},
|
||||
"cardinal_light": "nether",
|
||||
"coordinate_scale": 1.0,
|
||||
"has_ceiling": false,
|
||||
"has_ceiling": true,
|
||||
"has_ender_dragon_fight": false,
|
||||
"has_fixed_time": true,
|
||||
"has_skylight": false,
|
||||
"height": 192,
|
||||
"height": 448,
|
||||
"infiniburn": "#minecraft:infiniburn_overworld",
|
||||
"logical_height": 192,
|
||||
"min_y": 0,
|
||||
"monster_spawn_block_light_limit": 15,
|
||||
"monster_spawn_light_level": 7,
|
||||
"logical_height": 448,
|
||||
"min_y": -64,
|
||||
"monster_spawn_block_light_limit": 0,
|
||||
"monster_spawn_light_level": 0,
|
||||
"skybox": "none",
|
||||
"timelines": "#minecraft:in_nether"
|
||||
}
|
||||
|
||||
@@ -6,22 +6,18 @@
|
||||
"effects": {
|
||||
"water_color": "#596f75",
|
||||
"water_fog_color": "#303b3e",
|
||||
"fog_color": "#b7b9ad",
|
||||
"sky_color": "#b7b9ad",
|
||||
"fog_color": "#000000",
|
||||
"sky_color": "#000000",
|
||||
"grass_color": "#70845d",
|
||||
"foliage_color": "#657b58"
|
||||
},
|
||||
"creature_spawn_probability": 0.08,
|
||||
"creature_spawn_probability": 0.0,
|
||||
"spawners": {
|
||||
"ambient": [],
|
||||
"axolotls": [],
|
||||
"creature": [],
|
||||
"misc": [],
|
||||
"monster": [
|
||||
{ "type": "minecraft:zombie", "maxCount": 2, "minCount": 1, "weight": 7 },
|
||||
{ "type": "minecraft:skeleton", "maxCount": 2, "minCount": 1, "weight": 7 },
|
||||
{ "type": "minecraft:enderman", "maxCount": 1, "minCount": 1, "weight": 2 }
|
||||
],
|
||||
"monster": [],
|
||||
"underground_water_creature": [],
|
||||
"water_ambient": [],
|
||||
"water_creature": []
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
"aquifers_enabled": false,
|
||||
"default_block": { "Name": "minecraft:air" },
|
||||
"default_fluid": { "Name": "minecraft:air" },
|
||||
"disable_mob_generation": false,
|
||||
"disable_mob_generation": true,
|
||||
"legacy_random_source": false,
|
||||
"noise": {
|
||||
"height": 192,
|
||||
"min_y": 0,
|
||||
"height": 448,
|
||||
"min_y": -64,
|
||||
"size_horizontal": 1,
|
||||
"size_vertical": 2
|
||||
},
|
||||
|
||||
+64
-17
@@ -1,8 +1,14 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.backroom;
|
||||
|
||||
import fr.koka99cab.sanctuary26.sanctuary.worldgen.feature.BackroomArchitectureFeature;
|
||||
import java.util.EnumSet;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.worldgen.feature.BackroomLayout;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.worldgen.feature.BackroomLayout.BackroomLevel;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.worldgen.feature.BackroomLayout.RoomCorner;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
@@ -18,7 +24,9 @@ public final class BackroomModelSmoke {
|
||||
entryTransactionsAreBounded();
|
||||
personalRecoveryIsPrioritized();
|
||||
recentLossesRemainPersonal();
|
||||
architectureIsDeterministicAndComposed();
|
||||
architectureIsDeterministicAndStacked();
|
||||
roomCornersReachEveryVariant();
|
||||
mazeDoorsStayConnected();
|
||||
}
|
||||
|
||||
private static void entryTransactionsAreBounded() {
|
||||
@@ -74,27 +82,66 @@ public final class BackroomModelSmoke {
|
||||
"An ownerless loss was not immediately eligible for shared loot");
|
||||
}
|
||||
|
||||
private static void architectureIsDeterministicAndComposed() {
|
||||
private static void architectureIsDeterministicAndStacked() {
|
||||
long seed = 26_200_132L;
|
||||
EnumSet<BackroomArchitectureFeature.Architecture> families =
|
||||
EnumSet.noneOf(BackroomArchitectureFeature.Architecture.class);
|
||||
for (int macroX = -12; macroX <= 12; macroX++) for (int macroZ = -12; macroZ <= 12; macroZ++) {
|
||||
int chunkX = macroX * 8, chunkZ = macroZ * 8;
|
||||
BackroomArchitectureFeature.Architecture family =
|
||||
BackroomArchitectureFeature.architecture(seed, chunkX, chunkZ);
|
||||
families.add(family);
|
||||
require(family == BackroomArchitectureFeature.architecture(seed, chunkX + 7, chunkZ + 7),
|
||||
"One macro cell mixed unrelated architecture palettes");
|
||||
require(BackroomArchitectureFeature.fingerprint(seed, chunkX, chunkZ)
|
||||
== BackroomArchitectureFeature.fingerprint(seed, chunkX, chunkZ),
|
||||
"Architecture fingerprint is not deterministic");
|
||||
require(BackroomLayout.WORLD_MIN_Y == -64 && BackroomLayout.WORLD_MAX_Y_EXCLUSIVE == 384,
|
||||
"Backroom no longer occupies the complete requested vertical range");
|
||||
for (int y = BackroomLayout.WORLD_MIN_Y; y < BackroomLayout.WORLD_MAX_Y_EXCLUSIVE; y++) {
|
||||
require(BackroomLayout.levelAt(y) != null, "A Y layer is not assigned to one of the five levels");
|
||||
}
|
||||
require(families.size() == BackroomArchitectureFeature.Architecture.values().length,
|
||||
"Not all authored architecture families are reachable");
|
||||
require(BackroomLevel.values().length == 5, "Backroom no longer has exactly five stacked levels");
|
||||
require(BackroomLayout.levelAt(BackroomLayout.ENTRY_FLOOR_Y) == BackroomLevel.LEVEL_0,
|
||||
"Arrival deck left the top level");
|
||||
for (int transition = 0; transition < 4; transition++) {
|
||||
boolean found = false;
|
||||
for (int x = -64; x < 64 && !found; x++) for (int z = -64; z < 64; z++) {
|
||||
if (BackroomLayout.transitionHole(seed, transition, x, z)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
require(found, "One level transition has no reachable authored opening");
|
||||
}
|
||||
require(BackroomArchitectureFeature.fingerprint(seed, 12, -7)
|
||||
== BackroomArchitectureFeature.fingerprint(seed, 12, -7),
|
||||
"Architecture fingerprint is not deterministic");
|
||||
require(BackroomLayout.cityHighway(8, 1_423) && !BackroomLayout.cityHighway(48, 48),
|
||||
"City highways are no longer long continuous axes separated from building cells");
|
||||
require(BackroomArchitectureFeature.withinPlayableArea(2_000, 0), "Playable boundary moved inward");
|
||||
require(!BackroomArchitectureFeature.withinPlayableArea(2_001, 0), "Architecture escaped the 2,000-block zone");
|
||||
}
|
||||
|
||||
private static void roomCornersReachEveryVariant() {
|
||||
long seed = 26_200_132L;
|
||||
Map<RoomCorner, Set<Integer>> variants = new EnumMap<>(RoomCorner.class);
|
||||
for (RoomCorner corner : RoomCorner.values()) variants.put(corner, new HashSet<>());
|
||||
for (int x = -32; x <= 32; x++) for (int z = -32; z <= 32; z++) {
|
||||
for (RoomCorner corner : RoomCorner.values()) {
|
||||
variants.get(corner).add(BackroomLayout.cornerVariant(seed, BackroomLevel.LEVEL_0, x, z, corner));
|
||||
}
|
||||
}
|
||||
for (RoomCorner corner : RoomCorner.values()) {
|
||||
require(variants.get(corner).equals(Set.of(0, 1, 2, 3, 4)),
|
||||
"A room corner lost one of its five authored material variants: " + corner);
|
||||
}
|
||||
}
|
||||
|
||||
private static void mazeDoorsStayConnected() {
|
||||
long seed = 26_200_132L;
|
||||
for (int x = -16; x <= 16; x++) for (int z = -16; z <= 16; z++) {
|
||||
if (x == 0 && z == 0) continue;
|
||||
boolean descends = false;
|
||||
for (int[] direction : new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}) {
|
||||
int neighborX = x + direction[0], neighborZ = z + direction[1];
|
||||
boolean forward = BackroomLayout.mazeConnected(seed, x, z, neighborX, neighborZ);
|
||||
boolean reverse = BackroomLayout.mazeConnected(seed, neighborX, neighborZ, x, z);
|
||||
require(forward == reverse, "Maze doorway is not symmetric across a room boundary");
|
||||
if (forward && Math.abs(neighborX) + Math.abs(neighborZ) < Math.abs(x) + Math.abs(z)) descends = true;
|
||||
}
|
||||
require(descends, "A generated maze room has no open route toward the connected root");
|
||||
}
|
||||
}
|
||||
|
||||
private static void require(boolean condition, String message) {
|
||||
if (!condition) throw new AssertionError(message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user