Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edc7fc3dfb | ||
|
|
9b0104d27a | ||
|
|
d9b3dd6bed | ||
|
|
e732da8800 |
@@ -20,12 +20,11 @@ La [vision complète](docs/vision.md) conserve les intentions ; le
|
||||
L’économie, les claims et les dimensions décrits dans la vision restent
|
||||
à implémenter.
|
||||
|
||||
La version **beta.120** étend la clé dorée aux états des blocs : clic gauche
|
||||
pour choisir une propriété, clic droit pour changer sa valeur, Maj pour inverser.
|
||||
La terre et les modèles de blocs peuvent aussi tourner leurs textures.
|
||||
Le [ticket de livraison](docs/golden-wrench-beta120.md) détaille les contrôles,
|
||||
la compatibilité et la distribution. Tests natifs, build complet et deux packs
|
||||
vérifiés localement.
|
||||
La version **beta.122** adapte la sélection et les collisions des sculptures
|
||||
à leurs voxels réels et supprime leurs ombres de blocs pleins. Les modèles
|
||||
restent alignés au centre ou au bord choisi, dans les quatre orientations.
|
||||
Le [ticket de livraison](docs/sculpture-shape-light-beta122.md) détaille la
|
||||
forme physique, les règles de lumière et les vérifications réussies.
|
||||
La [livraison précédente](docs/clay-workshop-integration-beta110.md) réunit déjà
|
||||
l’atelier, les œufs et la neige saisonnière dans l’instance **Sanctuary Beta**.
|
||||
Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées.
|
||||
@@ -36,10 +35,25 @@ La [carte native beta.005](docs/atlas-beta005.md) prend le relais après
|
||||
l’étude de Xaero et des alternatives libres.
|
||||
|
||||
|
||||
## beta.122 — Sculptures : forme et lumière
|
||||
|
||||
Collisions et sélection sur les voxels réels, creux traversables, lumière sans
|
||||
occlusion cubique. [Contrat et vérifications](docs/sculpture-shape-light-beta122.md).
|
||||
|
||||
## beta.121 — Texture de clé et pack personnel
|
||||
|
||||
Copie complète des ressources, création depuis le jeu et guide FR/EN inclus.
|
||||
[Contrat et vérifications](docs/resourcepack-template-beta121.md) ·
|
||||
[Template personnel](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.121/Sanctuary-Template-beta.121.zip) ·
|
||||
[Pack normal](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.121/Sanctuary-beta.121.mrpack) ·
|
||||
[Pack de test](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.121/Sanctuary-Test-beta.121.mrpack).
|
||||
|
||||
## beta.120 — Clé dorée et états de blocs
|
||||
|
||||
Gestes du debug stick, rotation de textures et assemblage des machines conservé.
|
||||
[Contrat et vérifications](docs/golden-wrench-beta120.md).
|
||||
[Contrat et vérifications](docs/golden-wrench-beta120.md) ·
|
||||
[Pack normal](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.120/Sanctuary-beta.120.mrpack) ·
|
||||
[Pack de test](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.120/Sanctuary-Test-beta.120.mrpack).
|
||||
|
||||
## beta.119 — Sculptures au centre ou au bord
|
||||
|
||||
@@ -2132,7 +2146,7 @@ Depuis beta.090, la cible est **Minecraft 26.3 finale**, publiée le 15 septembr
|
||||
| Fabric API | 0.160.5+26.3 |
|
||||
| Fabric Loom | 1.17.20 |
|
||||
| Gradle Wrapper | 9.5.1, distribution vérifiée par SHA-256 |
|
||||
| Sanctuary / pack | beta.112 |
|
||||
| Sanctuary / pack | beta.122 |
|
||||
|
||||
Java 25 et Python 3.11 ou plus récent sont nécessaires. Le script pack utilise
|
||||
uniquement la bibliothèque standard et repère aussi une installation Python
|
||||
@@ -2161,7 +2175,7 @@ décrits dans [Validation](docs/testing.md).
|
||||
|
||||
Résultats :
|
||||
|
||||
- `mods/sanctuary/build/libs/sanctuary-beta.112.jar` : mod à installer avec
|
||||
- `mods/sanctuary/build/libs/sanctuary-beta.122.jar` : mod à installer avec
|
||||
Fabric API sur la version Minecraft indiquée.
|
||||
- `build/packwiz/` : pack de développement complet, avec le mod construit et
|
||||
l'index vérifié. Voir [Installation packwiz](packwiz/README.md).
|
||||
|
||||
@@ -127,3 +127,12 @@ de modèles, butin et états reprennent leurs équivalents natifs 26.3.
|
||||
`tools/generate-colored-bricks.py` documente cette transformation ;
|
||||
`tools/colored-bricks-palette.json` conserve couleurs et empreintes des PNG fournis.
|
||||
Ces ressources dérivées ne sont pas présentées comme des créations originales.
|
||||
|
||||
## Clé dorée beta.121
|
||||
|
||||
Le PNG 16 × 16 `golden_wrench.png` a été fourni par le créateur de Sanctuary
|
||||
le 17 septembre 2026. Il est conservé octet pour octet dans le mod, le pack
|
||||
intégré et le template personnel (SHA-256
|
||||
`a97570908db75caf8e6fc2f5ecabf12c54fd99bfea7be634df66c4310dca22af`).
|
||||
Le template regroupe les ressources client déjà distribuées par Sanctuary ;
|
||||
leurs crédits et conditions respectives restent applicables.
|
||||
|
||||
+19
-1
@@ -15,7 +15,7 @@ tasks.register('verifyPack', Exec) {
|
||||
tasks.register('assemblePack', Exec) {
|
||||
group = 'distribution'
|
||||
description = 'Assemble a local packwiz pack including the built Sanctuary mod.'
|
||||
dependsOn(':sanctuary:build', 'verifyPack', 'assembleResourcePack')
|
||||
dependsOn(':sanctuary:build', 'verifyPack', 'assembleResourcePack', 'assembleResourceTemplate')
|
||||
commandLine('python3', 'scripts/pack.py', 'assemble')
|
||||
}
|
||||
|
||||
@@ -46,3 +46,21 @@ tasks.register('assembleTestPack', Exec) {
|
||||
dependsOn('assemblePack', ':sanctuary-test:build')
|
||||
commandLine('python3', 'scripts/test_pack.py')
|
||||
}
|
||||
|
||||
tasks.register('assembleResourceTemplate', Zip) {
|
||||
group = 'distribution'
|
||||
description = 'Export all Sanctuary client assets as an editable personal resource pack.'
|
||||
dependsOn(':sanctuary:processResources', 'verifyResourcePack')
|
||||
def resources = project(':sanctuary').layout.buildDirectory.dir('resources/main')
|
||||
// Same precedence as the native exporter: built-in textures override mod defaults.
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
from(resources.map { it.dir('resourcepacks/textures/assets') }) { into 'assets' }
|
||||
from(resources.map { it.dir('assets') }) { into 'assets' }
|
||||
from(resources.map { it.dir('resourcepacks/template') })
|
||||
from(resources.map { it.file('resourcepacks/textures/pack.png') })
|
||||
exclude '**/.DS_Store'
|
||||
destinationDirectory = layout.buildDirectory
|
||||
archiveFileName = "Sanctuary-Template-${mod_version}.zip"
|
||||
preserveFileTimestamps = false
|
||||
reproducibleFileOrder = true
|
||||
}
|
||||
|
||||
@@ -92,5 +92,18 @@ ni fixture de test.
|
||||
- JAR Sanctuary : SHA-256
|
||||
`8e35ef0f134a46b7760604175ad768264946d31fdd0694982e049e7a928fde2a`.
|
||||
|
||||
Reçu local : `build/wrench120-artifact.json`. Publication et synchronisation
|
||||
Prism à consigner après leur réussite.
|
||||
Reçu local : `build/wrench120-artifact.json`.
|
||||
|
||||
## Publication et installation
|
||||
|
||||
La [release beta.120](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.120)
|
||||
est publiée depuis `248dff47b7a0ebd11026230581fe69641dbde76d`. Le tag exact et les
|
||||
artefacts sont immuables ; leurs téléchargements publics sont vérifiés.
|
||||
Le canal packwiz avance à `e8060764a444a58b0a17318a3821dfeb1a0c33c6`.
|
||||
|
||||
Deux synchronisations isolées puis deux dans **Sanctuary Beta** réussissent.
|
||||
Un seul JAR Sanctuary beta.120 est actif ; le second passage est identique.
|
||||
Les **923 fichiers personnels et réglages suivis** restent inchangés, sans
|
||||
ouvrir de monde personnel. Copie préalable dans
|
||||
`sanctuary-backups/before-beta.120/` de l'instance existante. Reçus locaux :
|
||||
`build/wrench120-isolated.json` et `build/wrench120-prism.json`.
|
||||
|
||||
@@ -14,6 +14,29 @@ JAR Sanctuary sont des pièces jointes de releases Gitea. Aucun JAR de mod ou
|
||||
dépendance téléchargée n'entre dans l'historique Git. Le canal Beta est distinct
|
||||
de l'ancien pack 26.2.
|
||||
|
||||
## beta.121 — texture de clé et pack personnel
|
||||
|
||||
PNG du créateur et copie complète modifiable depuis l'écran des packs.
|
||||
[Contrat et vérifications](resourcepack-template-beta121.md).
|
||||
[Release beta.121](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.121)
|
||||
publiée ; canal `e3e6572da9288fbbcfa36bf6b7dd3aa0d1c54f5e`. Deux
|
||||
synchronisations isolées puis deux dans la même instance **Sanctuary Beta**
|
||||
réussies. Les 923 fichiers personnels et réglages suivis sont inchangés ;
|
||||
sauvegardes restées fermées. Copie préalable des fichiers remplacés dans
|
||||
`sanctuary-backups/before-beta.121/`. Le template personnel est créé par le
|
||||
joueur, jamais géré ou réécrit par packwiz.
|
||||
|
||||
## beta.120 — clé dorée et états de blocs
|
||||
|
||||
Gestes du debug stick, rotation des textures et assemblages prioritaires.
|
||||
[Contrat et vérifications](golden-wrench-beta120.md).
|
||||
[Release beta.120](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.120)
|
||||
publiée ; canal `e8060764a444a58b0a17318a3821dfeb1a0c33c6`. Deux
|
||||
synchronisations isolées puis deux dans la même instance **Sanctuary Beta**
|
||||
réussies. Les 923 fichiers personnels et réglages suivis sont inchangés ;
|
||||
sauvegardes restées fermées. Copie préalable des fichiers remplacés dans
|
||||
`sanctuary-backups/before-beta.120/`.
|
||||
|
||||
## beta.119 — pose centrée des sculptures
|
||||
|
||||
Centre accessible au point visé, bords conservés et grille entière de 1/16.
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# RP-02 — Clé dorée et pack personnel — beta.121
|
||||
|
||||
Contrat du 17 septembre 2026, branche `codex/resourcepack-template-beta121`.
|
||||
Le créateur fournit `golden_wrench.png` et valide un bouton qui crée une copie
|
||||
personnelle complète, modifiable et jamais écrasée par les mises à jour.
|
||||
|
||||
## Résultat
|
||||
|
||||
La clé dorée reprend exactement le PNG fourni, 16 × 16, sans conversion ni
|
||||
redessin. Le modèle reste un item plat `minecraft:item/handheld`. Empreinte :
|
||||
`a97570908db75caf8e6fc2f5ecabf12c54fd99bfea7be634df66c4310dca22af`.
|
||||
Le même fichier est fourni par le mod et par « Textures Sanctuary » ; désactiver
|
||||
le pack intégré conserve donc aussi la nouvelle clé.
|
||||
|
||||
Dans **Options → Packs de ressources**, **Personnaliser Sanctuary** crée
|
||||
`resourcepacks/Sanctuary-Custom/`. Le bouton devient **Ouvrir ma copie**.
|
||||
La copie contient toutes les ressources client de Sanctuary : textures, modèles,
|
||||
étiquettes d'items, blockstates, sons, shaders, langues et remplacements Minecraft
|
||||
présents dans le pack intégré. Celui-ci prend priorité sur les assets de base
|
||||
lors de l'export. Un guide FR/EN, la licence et les crédits accompagnent la copie.
|
||||
|
||||
Le joueur active sa copie avec les commandes natives et la place au-dessus des
|
||||
packs qu'il veut remplacer. Créer la copie ne change ni l'ordre ni la sélection.
|
||||
Une modification se recharge avec F3 + T ; désactiver le pack retrouve les
|
||||
ressources fournies par le mod. L'écran de sélection des datapacks n'est pas modifié.
|
||||
|
||||
La copie est un **instantané**. Elle ne se synchronise pas avec les mises à jour :
|
||||
les fichiers personnalisés restent toujours intacts. Le guide recommande de
|
||||
supprimer les fichiers non personnalisés pour laisser les prochaines ressources
|
||||
du mod s'appliquer. Pour repartir d'un instantané récent, renommer le dossier
|
||||
existant puis créer une nouvelle copie. Aucun fichier utilisateur n'est écrasé.
|
||||
|
||||
La copie s'effectue hors du fil de rendu, dans un dossier temporaire voisin,
|
||||
puis le résultat complet rejoint la liste native. Une erreur de copie est
|
||||
signalée par le bouton et le journal du jeu ; un chemin occupé par un fichier
|
||||
n'est pas remplacé. Aucune extraction au démarrage ni pendant les mises à jour.
|
||||
|
||||
## Distribution
|
||||
|
||||
`./gradlew assembleResourceTemplate` produit
|
||||
`build/Sanctuary-Template-beta.121.zip`, un pack personnel complet à décompresser
|
||||
dans `resourcepacks/Sanctuary-Custom/`. `assemblePack` le produit aussi.
|
||||
`build/Sanctuary-Resource-Pack-beta.121.zip` reste l'export du pack graphique
|
||||
intégré ; son manifeste versionné contient 151 fichiers et les images historiques
|
||||
sont conservées. L'identifiant intégré `sanctuary:textures` reste stable et son
|
||||
activation reste contrôlée par le joueur.
|
||||
|
||||
Le template ne contient pas tous les assets vanilla ni ceux des autres mods.
|
||||
Il ne contient pas les recettes, règles serveur ou sauvegardes. Il conserve
|
||||
les crédits et conditions des ressources d'origine. Aucune génération, aucun
|
||||
identifiant de bloc et aucun format de sauvegarde ne changent.
|
||||
|
||||
## Vérifications
|
||||
|
||||
Le parcours `ResourceTemplate121ClientChecks` passe en **35 s** sur le client
|
||||
Minecraft 26.3 macOS, sans ouvrir de monde :
|
||||
|
||||
- Bouton natif et disposition FR/EN aux échelles 2 et 3, captures relues.
|
||||
- Export complet de **1 202 fichiers**, empreintes identiques aux ressources
|
||||
sources après application des priorités du pack intégré.
|
||||
- PNG fourni vérifié par SHA-256 et affiché avec sa transparence.
|
||||
- Création sans changer la sélection ni l'ordre des packs.
|
||||
- Modification d'une texture et ajout d'un fichier personnel : aucun octet
|
||||
réécrit lors d'un second export ni au rechargement des ressources.
|
||||
- Priorité réelle de la texture personnalisée, puis retour à la clé fournie
|
||||
après désactivation. Activation et désactivation conservées à la redécouverte.
|
||||
- Chemin occupé par un fichier refusé sans le modifier ; aucun bouton ajouté
|
||||
à l'écran des datapacks.
|
||||
|
||||
```sh
|
||||
./gradlew :sanctuary:runClientGameTest \
|
||||
-PsanctuaryClientTests=true -PsanctuaryResourceTemplate121ClientTests=true \
|
||||
-PsanctuaryClientNoVsync=true -PsanctuaryQuickTests=true
|
||||
```
|
||||
|
||||
Marqueur `TEMPLATE121_PASS` dans `build/template121-client.log` ; captures et
|
||||
empreintes de la copie dans `build/template121-evidence/`. Le premier essai
|
||||
avait échoué dans l'aperçu du test qui créait un ItemStack avant connexion à
|
||||
un monde ; cet aperçu affiche maintenant directement la texture résolue.
|
||||
Le modèle plat existant n'est pas modifié. Pas d'essai Windows ni d'ouverture
|
||||
automatisée du gestionnaire de fichiers. Le GameTest serveur dédié reste exclu
|
||||
conformément au refus antérieur de son EULA.
|
||||
|
||||
`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest`
|
||||
passe en **2 min 10 s**, 126 tâches. Les sources JAR correspondent aux sources
|
||||
Java du dépôt. La comparaison avec beta.120 limite les différences de production
|
||||
aux deux nouvelles classes, au hook d'interface, aux libellés et aux ressources
|
||||
prévues (12 entrées). Aucun changement de gameplay, de données serveur ou de
|
||||
monde. Les packs normal/Test contiennent le même JAR Sanctuary, sans fixture
|
||||
ni sauvegarde. Le ZIP du template et l'export natif ont exactement les mêmes
|
||||
1 202 fichiers et empreintes. Le pack intégré conserve ses 151 fichiers vérifiés.
|
||||
|
||||
Reçu : `build/template121-artifact.json`. Artefacts locaux validés :
|
||||
|
||||
- `Sanctuary-beta.121.mrpack` : 10261791 octets, SHA-256
|
||||
`040d68f342155120fa21e0ffcdf34aa38e3225c78862960536622de53ed9fd9f`.
|
||||
- `Sanctuary-Test-beta.121.mrpack` : 10280716 octets, SHA-256
|
||||
`7f5bbd4e13aea4c245d072725c094ca64826ee8debc8607623783c114d437685`.
|
||||
- `Sanctuary-Resource-Pack-beta.121.zip` : 3757952 octets, SHA-256
|
||||
`bbf3c18ea6abc64d951911215975cb6c48c7fc5e0246f6e5f5192a3d061c211d`.
|
||||
- `Sanctuary-Template-beta.121.zip` : 4315131 octets, SHA-256
|
||||
`6fad376c53474fa8bcb0a2e6ae4c13ea61ab91bdd78735be443032d485ed541a`.
|
||||
|
||||
JAR Sanctuary : SHA-256
|
||||
`4276f4fc9834805e3e6d12cd19adba7837e3007656b77f31d525c7b976f9925d`.
|
||||
|
||||
## Publication et installation
|
||||
|
||||
La [release beta.121](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.121)
|
||||
est publiée depuis `d9b3dd6bedf32448f8c6e5bfa4da10cb69968bc7`. Tag et artefacts immuables,
|
||||
téléchargements publics vérifiés. Canal packwiz :
|
||||
`e3e6572da9288fbbcfa36bf6b7dd3aa0d1c54f5e`.
|
||||
|
||||
Deux synchronisations isolées puis deux dans **Sanctuary Beta** réussissent.
|
||||
Un seul JAR Sanctuary beta.121 est actif ; le second passage est identique.
|
||||
Les **923 fichiers personnels et réglages suivis** restent inchangés,
|
||||
y compris les packs de ressources. Aucune copie personnelle n'est créée par
|
||||
la mise à jour : chaque joueur utilise le bouton quand il le souhaite.
|
||||
Aucun monde personnel ouvert. Copie préalable dans
|
||||
`sanctuary-backups/before-beta.121/` de l'instance existante ; reçus
|
||||
`build/template121-isolated.json` et `build/template121-prism.json`.
|
||||
|
||||
- [Template personnel complet](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.121/Sanctuary-Template-beta.121.zip).
|
||||
- [Pack normal](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.121/Sanctuary-beta.121.mrpack).
|
||||
- [Pack de test](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.121/Sanctuary-Test-beta.121.mrpack).
|
||||
@@ -0,0 +1,90 @@
|
||||
# SCULPT-09 — Forme physique et lumière — beta.122
|
||||
|
||||
Contrat du 17 septembre 2026, branche `codex/sculpture-shape-light-beta122`.
|
||||
Le créateur demande des bounding boxes adaptées aux sculptures et la suppression
|
||||
des ombres cubiques visibles lorsqu'on en pose plusieurs côte à côte.
|
||||
|
||||
La sélection, le contact avec les entités et les collisions suivent les voxels
|
||||
occupés de chaque sculpture, y compris les creux. Le rendu et la forme physique
|
||||
partagent leur placement entier sur la grille 1/16, après orientation et ancrage.
|
||||
Le centre, les quatre bords et les dimensions impaires gardent leur alignement.
|
||||
La pose vérifie aussi les voxels portés avant que l’entité de bloc existe :
|
||||
elle refuse une intersection avec le joueur et autorise les espaces libres.
|
||||
La forme est construite directement sur une grille native 16³ et mise en cache
|
||||
par entité de bloc, modèle et état ; aucun calcul complet à chaque interrogation.
|
||||
|
||||
Les sculptures transmettent la lumière et ne sont plus utilisées comme cubes
|
||||
pleins par l'occlusion ambiante. Leurs faces restent éclairées par le monde,
|
||||
sans émission de lumière et sans rendu à luminosité maximale. Cette règle est
|
||||
commune aux sculptures : l'éclairage natif stocké par bloc ne permet pas une
|
||||
ombre distincte pour chaque petit voxel. L'atelier d'argile reste inchangé.
|
||||
|
||||
Aucun identifiant, blockstate, format d'item ou schéma sauvegardé ne change.
|
||||
Les formes sont recalculées à partir des voxels existants ; aucune migration,
|
||||
aucun parcours de monde ni régénération de chunk. Les données inconnues gardent
|
||||
leur conservation opaque et une petite base sélectionnable pour récupérer l'item.
|
||||
Le travail de documentation déjà présent dans l'autre branche reste intact.
|
||||
|
||||
## Vérifications et livraison
|
||||
|
||||
Le parcours `Sculpture122ClientChecks` passe en **37 s** sur Minecraft 26.3,
|
||||
client macOS et serveur intégré, dans un monde plat jetable de graine 122 :
|
||||
|
||||
- Pose par la commande native d'utilisation ; sélection du pilier et visée
|
||||
à travers le trou d'une arche jusqu'au bloc derrière, captures relues.
|
||||
- Requêtes natives de collision côté serveur et client : voxels occupés solides,
|
||||
espace vide libre, aucune profondeur artificielle d'un bloc entier.
|
||||
- Pose refusée dans le joueur, sans consommer l'objet ; petite sculpture acceptée
|
||||
dans la partie libre du même bloc, sans intersection avec le joueur.
|
||||
- **80 cas** confrontent le volume et les faces de collision au maillage réellement
|
||||
émis : arche creuse, marges vides, cube de 4 096 voxels, voxel unique, quatre
|
||||
orientations et cinq ancrages. Chaque interrogation répétée réutilise la forme.
|
||||
- Régression de **5 120 placements** du rendu existant : dimensions paires/impaires,
|
||||
orientations et ancrages, plus marqueurs de direction et modèles avec marges.
|
||||
- Changement d'orientation/ancrage puis changement des voxels sur la même entité :
|
||||
cache recalculé et forme client synchronisée.
|
||||
- **75 sculptures en trois couches** : skylight natif conservé à 15 partout.
|
||||
Un toit de pierre témoin produit toujours une ombre. La lumière d'un bloc
|
||||
traverse une sculpture avec l'atténuation de distance native (13 à deux blocs).
|
||||
- Le rendu reçoit la lumière réelle du monde : 15 au ciel, **0 dans une pièce
|
||||
fermée et obscure**. Aucun rendu artificiellement lumineux.
|
||||
- Sauvegarde/reconnexion : modèle, collisions et lumière conservés sur les
|
||||
deux côtés, sans fichier 3D source.
|
||||
|
||||
```sh
|
||||
./gradlew :sanctuary:runClientGameTest \
|
||||
-PsanctuaryClientTests=true -PsanctuarySculpture122ClientTests=true \
|
||||
-PsanctuaryClientNoVsync=true -PsanctuaryQuickTests=true
|
||||
```
|
||||
|
||||
Marqueurs `SCULPTURE122_PASS`, `SCULPTURE122_GEOMETRY_PASS`,
|
||||
`SCULPTURE122_PLACEMENT_PASS`, `SCULPTURE122_LIGHT_PASS` et
|
||||
`SCULPTURE113_GEOMETRY_PASS` dans `build/sculpture122-client.log` ; captures
|
||||
conservées dans `build/sculpture122-evidence/`. Le test prépare explicitement
|
||||
son sol à Y=0 : le preset plat nu commence à Y=-1.
|
||||
Aucun monde personnel ouvert ; pas d'essai Windows ni depuis deux ordinateurs,
|
||||
ni de garantie sur les ombres ajoutées par des shaders tiers.
|
||||
Le GameTest serveur dédié reste exclu conformément au refus antérieur de son EULA.
|
||||
|
||||
`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest`
|
||||
passe en **2 min 13 s**, 126 tâches. Les sources JAR correspondent aux sources
|
||||
Java du dépôt. Par rapport à beta.121, les différences de production se limitent
|
||||
aux classes des sculptures et à la nouvelle géométrie partagée (14 entrées,
|
||||
y compris le déplacement du sélecteur synthétique d'orientation). Toutes les
|
||||
ressources client, données serveur et configurations de mixins sont identiques.
|
||||
Les packs normal/Test contiennent le même JAR, sans sauvegarde ni fixture.
|
||||
Le template beta.122 contient les mêmes ressources que beta.121, octet pour octet.
|
||||
|
||||
Reçu local : `build/sculpture122-artifact.json`.
|
||||
|
||||
- `Sanctuary-beta.122.mrpack` : 10267459 octets, SHA-256
|
||||
`b6e24d65797eb7f16670d2f3a591b5f2476820608f72241ecd37fb78a486008e`.
|
||||
- `Sanctuary-Test-beta.122.mrpack` : 10286380 octets, SHA-256
|
||||
`90487ee3ad4298fccb8c24e8781af4cbf043d88dc0379fba5c21d99733dac92d`.
|
||||
|
||||
JAR Sanctuary : SHA-256
|
||||
`ff8ebf89f39a64809fae6912253386793e9b5e66ec0adb9fa7521729893d406e`.
|
||||
|
||||
## Publication et installation
|
||||
|
||||
Artefacts prêts ; publication et synchronisation à effectuer.
|
||||
+3
-3
@@ -9,8 +9,8 @@ loom_version=1.17.20
|
||||
fabric_api_version=0.160.5+26.3
|
||||
|
||||
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
|
||||
mod_version=beta.120
|
||||
pack_version=beta.120
|
||||
resource_pack_version=beta.090
|
||||
mod_version=beta.122
|
||||
pack_version=beta.122
|
||||
resource_pack_version=beta.121
|
||||
maven_group=fr.koka.sanctuary
|
||||
jei_version=30.32.0-sanctuary.3
|
||||
|
||||
@@ -93,6 +93,14 @@ tasks.named('runGameTest') {
|
||||
}
|
||||
|
||||
tasks.named('processGametestResources') {
|
||||
def sculpture122Client = providers.gradleProperty('sanctuarySculpture122ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sanctuarySculpture122ClientTests', sculpture122Client)
|
||||
if (sculpture122Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Sculpture122ClientChecks') }
|
||||
|
||||
def template121Client = providers.gradleProperty('sanctuaryResourceTemplate121ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sanctuaryResourceTemplate121ClientTests', template121Client)
|
||||
if (template121Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.ResourceTemplate121ClientChecks') }
|
||||
|
||||
def wrench120Client = providers.gradleProperty('sanctuaryWrench120ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sanctuaryWrench120ClientTests', wrench120Client)
|
||||
if (wrench120Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Wrench120ClientChecks') }
|
||||
@@ -444,6 +452,15 @@ tasks.withType(JavaCompile).configureEach {
|
||||
}
|
||||
|
||||
processResources {
|
||||
from(rootProject.file('ressources-pack/template')) {
|
||||
exclude '**/.DS_Store'
|
||||
into 'resourcepacks/template'
|
||||
}
|
||||
from(rootProject.file('LICENSE')) {
|
||||
rename { 'LICENSE_sanctuary' }
|
||||
into 'resourcepacks/template'
|
||||
}
|
||||
from(rootProject.file('THIRD_PARTY_NOTICES.md')) { into 'resourcepacks/template' }
|
||||
from(rootProject.file('ressources-pack/sanctuary')) {
|
||||
include 'assets/**', 'pack.mcmeta', 'pack.png'
|
||||
exclude '**/.DS_Store'
|
||||
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.packs.PackSelectionScreen;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.repository.PackCompatibility;
|
||||
|
||||
/** Native screen/export, complete snapshot, real resource precedence and preservation of edits. */
|
||||
public final class ResourceTemplate121ClientChecks implements FabricClientGameTest {
|
||||
private static final String BUILTIN = "sanctuary:textures";
|
||||
private static final String PERSONAL = "file/" + ResourcePackTemplate.FOLDER;
|
||||
private static final String WRENCH = "sanctuary:textures/item/golden_wrench.png";
|
||||
private static final String WRENCH_PATH = "assets/sanctuary/textures/item/golden_wrench.png";
|
||||
private static final String WRENCH_SHA = "a97570908db75caf8e6fc2f5ecabf12c54fd99bfea7be634df66c4310dca22af";
|
||||
|
||||
public void runTest(ClientGameTestContext c) {
|
||||
try { run(c); } catch (IOException error) { throw new AssertionError(error); }
|
||||
}
|
||||
|
||||
private void run(ClientGameTestContext c) throws IOException {
|
||||
var previous = c.computeOnClient(m -> m.gui.screen());
|
||||
var packDirectory = c.computeOnClient(Minecraft::getResourcePackDirectory);
|
||||
var selected = c.computeOnClient(m -> List.copyOf(m.getResourcePackRepository().getSelectedIds()));
|
||||
var target = packDirectory.resolve(ResourcePackTemplate.FOLDER);
|
||||
check(!Files.exists(target), "Fresh disposable client directory");
|
||||
c.runOnClient(m -> {
|
||||
check(source(m).equals(BUILTIN), "Built-in pack active");
|
||||
check(hash(resource(m, WRENCH)).equals(WRENCH_SHA), "Exact creator PNG active");
|
||||
m.getWindow().setWindowed(1280, 800);
|
||||
});
|
||||
for (int scale : new int[]{2, 3}) for (String language : List.of("fr_fr", "en_us")) {
|
||||
c.runOnClient(m -> {
|
||||
m.options.guiScale().set(scale); m.resizeGui();
|
||||
m.getLanguageManager().setSelected(language);
|
||||
m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
|
||||
screen(m, packDirectory);
|
||||
var button = button(m, "customize");
|
||||
check(button.getX() >= 0 && button.getRight() <= m.gui.screen().width, "Template button fits width");
|
||||
check(button.getY() > 60 && button.getBottom() < m.gui.screen().height, "Template button fits height");
|
||||
var done = m.gui.screen().children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.filter(b -> key(b, "gui.done")).findFirst().orElseThrow();
|
||||
check(done.getY() >= button.getBottom() + 4, "Separate native footer row");
|
||||
check(m.font.width(button.getMessage()) < button.getWidth() - 8, "Localized label fits");
|
||||
});
|
||||
c.waitTicks(3); c.takeScreenshot("template121-create-" + language + "-" + scale);
|
||||
}
|
||||
c.runOnClient(m -> button(m, "customize").onPress(new KeyEvent(InputConstants.KEY_RETURN, 0, 0)));
|
||||
c.waitFor(m -> m.gui.screen().children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.anyMatch(b -> b.active && key(b, "sanctuary.resource_pack.open_copy")), 1200);
|
||||
c.waitFor(m -> m.getResourcePackRepository().getAvailableIds().contains(PERSONAL), 1200);
|
||||
c.runOnClient(m -> {
|
||||
check(List.copyOf(m.getResourcePackRepository().getSelectedIds()).equals(selected), "Creating never changes selection");
|
||||
check(m.getResourcePackRepository().getPack(PERSONAL).getCompatibility() == PackCompatibility.COMPATIBLE,
|
||||
"Template format accepted by exact Minecraft version");
|
||||
});
|
||||
var mod = FabricLoader.getInstance().getModContainer("sanctuary").orElseThrow();
|
||||
Map<String, String> expected = new TreeMap<>();
|
||||
merge(expected, mod.findPath("assets").orElseThrow(), "assets/");
|
||||
merge(expected, mod.findPath("resourcepacks/textures/assets").orElseThrow(), "assets/");
|
||||
merge(expected, mod.findPath("resourcepacks/template").orElseThrow(), "");
|
||||
expected.put("pack.png", hash(Files.readAllBytes(mod.findPath("resourcepacks/textures/pack.png").orElseThrow())));
|
||||
check(snapshot(target).equals(expected), "Complete byte-identical snapshot with built-in precedence");
|
||||
Files.writeString(packDirectory.getParent().resolve("template121-export-sha256.json"),
|
||||
new com.google.gson.GsonBuilder().setPrettyPrinting().create().toJson(expected));
|
||||
check(expected.containsKey("README.md") && expected.containsKey("LICENSE_sanctuary")
|
||||
&& expected.containsKey("THIRD_PARTY_NOTICES.md"), "Editable copy includes guide and credits");
|
||||
check(expected.keySet().stream().noneMatch(p -> p.startsWith("data/") || p.endsWith(".class")), "Client assets only");
|
||||
c.waitTicks(4); c.takeScreenshot("template121-copy-ready");
|
||||
c.runOnClient(m -> m.gui.setScreen(new Screen(Component.literal("Golden wrench")) {
|
||||
@Override public void extractRenderState(GuiGraphicsExtractor g, int x, int y, float delta) {
|
||||
g.fill(0, 0, width, height, 0xff292929);
|
||||
g.centeredText(font, Component.translatable("item.sanctuary.golden_wrench"), width / 2, 55, 0xffffffff);
|
||||
g.blit(RenderPipelines.GUI_TEXTURED, Identifier.parse(WRENCH), width / 2 - 48, 85, 0, 0, 96, 96, 16, 16, 16, 16);
|
||||
}
|
||||
}));
|
||||
c.waitTicks(4); c.takeScreenshot("template121-wrench-exact-texture");
|
||||
|
||||
byte[] replacement = c.computeOnClient(m -> resource(m, "minecraft:textures/item/golden_pickaxe.png"));
|
||||
Files.write(target.resolve(WRENCH_PATH), replacement);
|
||||
Files.writeString(target.resolve("my-notes.txt"), "Personal edits must survive all subsequent exports.");
|
||||
var edited = snapshot(target);
|
||||
check(ResourcePackTemplate.create(packDirectory).equals(target.toAbsolutePath().normalize()), "Existing copy returned");
|
||||
check(snapshot(target).equals(edited), "Second export preserves every edited byte and extra file");
|
||||
c.runOnClient(m -> {
|
||||
var repo = m.getResourcePackRepository(); repo.addPack(PERSONAL); m.options.updateResourcePacks(repo);
|
||||
});
|
||||
c.waitFor(m -> source(m).equals(PERSONAL) && m.gui.overlay() == null, 1200);
|
||||
c.runOnClient(m -> {
|
||||
check(Arrays.equals(resource(m, WRENCH), replacement), "Personal edit overrides built-in texture");
|
||||
var repo = m.getResourcePackRepository(); repo.reload(); m.options.loadSelectedResourcePacks(repo);
|
||||
check(repo.getSelectedIds().contains(PERSONAL), "Personal activation persists after rediscovery");
|
||||
screen(m, packDirectory); button(m, "open_copy");
|
||||
});
|
||||
c.waitTicks(3); c.takeScreenshot("template121-personal-active");
|
||||
var reload = c.computeOnClient(Minecraft::reloadResourcePacks);
|
||||
c.waitFor(m -> reload.isDone() && m.gui.overlay() == null, 1200); reload.join();
|
||||
check(snapshot(target).equals(edited), "Reload never writes personal assets");
|
||||
c.runOnClient(m -> {
|
||||
check(Arrays.equals(resource(m, WRENCH), replacement), "Reload still uses personal PNG");
|
||||
var repo = m.getResourcePackRepository(); repo.removePack(PERSONAL); m.options.updateResourcePacks(repo);
|
||||
});
|
||||
c.waitFor(m -> source(m).equals(BUILTIN) && m.gui.overlay() == null, 1200);
|
||||
c.runOnClient(m -> {
|
||||
check(hash(resource(m, WRENCH)).equals(WRENCH_SHA), "Disabling restores creator texture");
|
||||
var repo = m.getResourcePackRepository(); repo.reload(); m.options.loadSelectedResourcePacks(repo);
|
||||
check(!repo.getSelectedIds().contains(PERSONAL), "Explicit opt-out retained");
|
||||
});
|
||||
|
||||
Path collision = Files.createTempDirectory(packDirectory.getParent(), "template121-collision-");
|
||||
Files.writeString(collision.resolve(ResourcePackTemplate.FOLDER), "Do not replace this file");
|
||||
try { ResourcePackTemplate.create(collision); throw new AssertionError("Existing file must be refused"); }
|
||||
catch (IOException expectedError) { check(Files.readString(collision.resolve(ResourcePackTemplate.FOLDER)).equals("Do not replace this file"), "Collision leaves file intact"); }
|
||||
Path datapacks = Files.createTempDirectory(packDirectory.getParent(), "template121-datapacks-");
|
||||
c.runOnClient(m -> {
|
||||
screen(m, datapacks);
|
||||
check(m.gui.screen().children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.noneMatch(b -> key(b, "sanctuary.resource_pack.customize") || key(b, "sanctuary.resource_pack.open_copy")), "No button in datapack selector");
|
||||
m.gui.setScreen(previous);
|
||||
});
|
||||
System.out.println("TEMPLATE121_PASS: " + expected.size() + " exact files; native button FR/EN at scales 2/3; complete copy; edits and selection preserved; actual override, reload and opt-out; collision safe; datapacks untouched");
|
||||
}
|
||||
|
||||
private static void screen(Minecraft m, Path directory) {
|
||||
m.gui.setScreen(new PackSelectionScreen(m.getResourcePackRepository(), repo -> {}, directory, Component.translatable("resourcePack.title")));
|
||||
}
|
||||
private static Button button(Minecraft m, String suffix) {
|
||||
return m.gui.screen().children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.filter(b -> key(b, "sanctuary.resource_pack." + suffix)).findFirst().orElseThrow();
|
||||
}
|
||||
private static boolean key(Button b, String key) { return b.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals(key); }
|
||||
private static String source(Minecraft m) { return m.getResourceManager().getResource(Identifier.parse(WRENCH)).orElseThrow().sourcePackId(); }
|
||||
private static byte[] resource(Minecraft m, String name) {
|
||||
try (var in = m.getResourceManager().getResource(Identifier.parse(name)).orElseThrow().open()) { return in.readAllBytes(); }
|
||||
catch (IOException error) { throw new AssertionError(error); }
|
||||
}
|
||||
private static String hash(byte[] bytes) {
|
||||
try { return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes)); }
|
||||
catch (java.security.NoSuchAlgorithmException error) { throw new AssertionError(error); }
|
||||
}
|
||||
private static Map<String, String> snapshot(Path root) throws IOException { Map<String, String> result = new TreeMap<>(); merge(result, root, ""); return result; }
|
||||
private static void merge(Map<String, String> result, Path root, String prefix) throws IOException {
|
||||
try (var files = Files.walk(root)) { for (Path file : files.filter(Files::isRegularFile).toList()) result.put(prefix + root.relativize(file).toString().replace('\\', '/'), hash(Files.readAllBytes(file))); }
|
||||
}
|
||||
private static void check(boolean value, String message) { if (!value) throw new AssertionError(message); }
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.plans.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.ProblemReporter;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.*;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.TagValueInput;
|
||||
import net.minecraft.world.phys.*;
|
||||
import net.minecraft.world.phys.shapes.*;
|
||||
|
||||
/** Native picking/collision and light propagation, compared with the actual emitted sculpture mesh. */
|
||||
public final class Sculpture122ClientChecks implements FabricClientGameTest {
|
||||
private static final String MODEL="sanctuary:model/"+"1".repeat(64);
|
||||
private static final BlockPos TARGET=new BlockPos(0,1,0),SKY=new BlockPos(10,2,2),CONTROL=new BlockPos(18,2,2);
|
||||
private static final VoxelSculpture ARCH=arch();
|
||||
|
||||
public void runTest(ClientGameTestContext c){
|
||||
var intro=new AtomicBoolean(true);
|
||||
c.runOnClient(m->{m.options.renderDistance().set(6);m.options.simulationDistance().set(5);m.options.pauseOnLostFocus=false;IntroClient.setQuickTestIntro(true);});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){
|
||||
Starter039TestSupport.selectFirst(screen);
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(new KeyEvent(InputConstants.KEY_RETURN,0,0));
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals("sanctuary.progression.enter")).findFirst().orElseThrow().onPress(new KeyEvent(InputConstants.KEY_RETURN,0,0));intro.set(false);
|
||||
}});
|
||||
net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave saved;
|
||||
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sculpture122 "+UUID.randomUUID());s.setSeed("122");s.setAllowCommands(true);s.setGameMode(WorldCreationUiState.SelectedGameMode.CREATIVE);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
|
||||
saved=world.getWorldSave();c.waitFor(m->ProgressionClient.active()&&m.player!=null&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);var server=world.getServer();
|
||||
server.runOnServer(s->{var p=player(s);var l=p.level();for(int x=-14;x<=21;x++)for(int z=-6;z<=9;z++)l.setBlockAndUpdate(new BlockPos(x,0,z),Blocks.STONE.defaultBlockState());p.teleportTo(.5,1,3.2);p.setYRot(0);p.getInventory().setItem(p.getInventory().getSelectedSlot(),StatuaryEntity.item(ARCH));p.inventoryMenu.broadcastFullState();
|
||||
s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set noon");
|
||||
s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary weather vanilla");s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"weather clear");
|
||||
});
|
||||
c.waitFor(m->m.player.distanceToSqr(.5,1,3.2)<.1&&m.player.getMainHandItem().is(Statuary.ITEM)&&m.level.getBlockState(TARGET.below()).is(Blocks.STONE),150);
|
||||
c.runOnClient(m->m.player.lookAt(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES,new Vec3(.5,.999,.5)));c.waitTicks(3);
|
||||
c.runOnClient(m->check(m.hitResult instanceof BlockHitResult hit&&hit.getBlockPos().equals(TARGET.below())&&hit.getDirection()==Direction.UP,"Cursor aims at placement support"));
|
||||
c.getInput().holdKey(o->o.keyUse);c.waitTicks(1);c.getInput().releaseKey(o->o.keyUse);c.waitTicks(3);
|
||||
server.runOnServer(s->System.out.println("SCULPTURE122_PLACED "+player(s).level().getBlockState(TARGET)+" "+player(s).level().getBlockEntity(TARGET)));
|
||||
c.waitFor(m->m.level.getBlockEntity(TARGET) instanceof StatuaryEntity e&&ARCH.equals(e.sculpture()),150);
|
||||
server.runOnServer(s->{physical(player(s).level(),TARGET);placementOverlap(player(s));});
|
||||
c.runOnClient(m->{physical(m.level,TARGET);geometry(m);Sculpture113ClientChecks.geometry(m,MODEL);});
|
||||
c.runOnClient(m->m.player.lookAt(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES,new Vec3(.0625,1.5,.5)));c.waitTicks(5);
|
||||
c.runOnClient(m->check(m.hitResult instanceof BlockHitResult hit&&hit.getBlockPos().equals(TARGET),"Native cursor selects an occupied pillar"));
|
||||
c.takeScreenshot("sculpture122-voxel-outline");
|
||||
server.runOnServer(s->player(s).level().setBlockAndUpdate(TARGET.north(),Blocks.REDSTONE_BLOCK.defaultBlockState()));
|
||||
c.waitFor(m->m.level.getBlockState(TARGET.north()).is(Blocks.REDSTONE_BLOCK),100);
|
||||
c.runOnClient(m->m.player.lookAt(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES,new Vec3(.5,1.5,.5)));c.waitTicks(5);
|
||||
c.runOnClient(m->check(m.hitResult instanceof BlockHitResult hit&&hit.getBlockPos().equals(TARGET.north()),"Native cursor passes through the sculpture hole to the block behind"));
|
||||
c.takeScreenshot("sculpture122-pick-through-hole");
|
||||
// A state edit and fresh entity payload must invalidate geometry on both sides.
|
||||
server.runOnServer(s->{var l=player(s).level();l.setBlockAndUpdate(TARGET,l.getBlockState(TARGET).setValue(Statuary.FACING,Direction.WEST).setValue(Statuary.ANCHOR,SculptureAnchor.EAST));});
|
||||
c.waitFor(m->m.level.getBlockState(TARGET).getValue(Statuary.FACING)==Direction.WEST,100);
|
||||
server.runOnServer(s->{var l=player(s).level();var bounds=l.getBlockState(TARGET).getShape(l,TARGET).bounds();check(bounds.maxX==1&&bounds.getXsize()==.25,"Server shape rotates and moves to aimed edge");});
|
||||
c.runOnClient(m->{var bounds=m.level.getBlockState(TARGET).getShape(m.level,TARGET).bounds();check(bounds.maxX==1&&bounds.getXsize()==.25,"Client shape follows changed blockstate");});
|
||||
var small=new VoxelSculpture(MODEL,"Small",1,1,1,List.of(new VoxelSculpture.Cell(0,0,0,-1)));
|
||||
server.runOnServer(s->place(player(s).level(),TARGET,small,Direction.WEST,SculptureAnchor.EAST));
|
||||
c.waitFor(m->m.level.getBlockEntity(TARGET) instanceof StatuaryEntity e&&small.equals(e.sculpture()),100);
|
||||
c.runOnClient(m->check(m.level.getBlockState(TARGET).getShape(m.level,TARGET).bounds().getYsize()==1/16d,"Changed block entity payload invalidates cached shape"));
|
||||
server.runOnServer(s->{var l=player(s).level();place(l,TARGET,ARCH,Direction.SOUTH,SculptureAnchor.CENTER);l.setBlockAndUpdate(TARGET.north(),Blocks.AIR.defaultBlockState());
|
||||
for(int x=8;x<=12;x++)for(int z=0;z<=4;z++)for(int y=3;y<=5;y++)place(l,new BlockPos(x,y,z),ARCH,Direction.SOUTH,SculptureAnchor.CENTER);
|
||||
for(int x=16;x<=20;x++)for(int z=0;z<=4;z++)l.setBlockAndUpdate(new BlockPos(x,3,z),Blocks.STONE.defaultBlockState());
|
||||
l.setBlockAndUpdate(new BlockPos(-8,2,0),Blocks.GLOWSTONE.defaultBlockState());place(l,new BlockPos(-7,2,0),ARCH,Direction.SOUTH,SculptureAnchor.CENTER);
|
||||
});
|
||||
server.waitFor(s->{var l=player(s).level();return l.getBrightness(LightLayer.SKY,SKY)==15&&l.getBrightness(LightLayer.SKY,CONTROL)<15&&l.getBrightness(LightLayer.BLOCK,new BlockPos(-6,2,0))==13;},200);
|
||||
c.waitFor(m->m.level.getBrightness(LightLayer.SKY,SKY)==15&&m.level.getBrightness(LightLayer.SKY,CONTROL)<15&&m.level.getBrightness(LightLayer.BLOCK,new BlockPos(-6,2,0))==13,200);
|
||||
server.runOnServer(s->{var l=player(s).level();for(int x=8;x<=12;x++)for(int z=0;z<=4;z++)for(int y=3;y<=5;y++)check(l.getBrightness(LightLayer.SKY,new BlockPos(x,y,z))==15,"All 75 sculptures keep full native skylight");});
|
||||
c.runOnClient(m->{var e=(StatuaryEntity)m.level.getBlockEntity(new BlockPos(10,4,2));var renderer=new StatuaryRenderer();var state=renderer.createRenderState();renderer.extractRenderState(e,state,0,Vec3.ZERO,null);check(net.minecraft.util.LightCoordsUtil.sky(state.lightCoords)==15,"Actual renderer receives full skylight inside a group");});
|
||||
// Visual crowd at ground level: no whole-block AO squares between the models.
|
||||
server.runOnServer(s->{var p=player(s);for(int x=-3;x<=3;x++)for(int z=-5;z<=-2;z++)place(p.level(),new BlockPos(x,1,z),ARCH,Direction.from2DDataValue(Math.floorMod(x+z,4)),SculptureAnchor.CENTER);p.teleportTo(.5,3,5.5);p.getAbilities().flying=true;p.onUpdateAbilities();});
|
||||
c.waitFor(m->m.player.distanceToSqr(.5,3,5.5)<.1,100);c.runOnClient(m->m.player.lookAt(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES,new Vec3(.5,1,-3)));c.waitTicks(12);c.takeScreenshot("sculpture122-crowd-light");
|
||||
// Transparent-to-light is not emissive: in a closed room sculpture faces stay dark.
|
||||
server.runOnServer(s->{var l=player(s).level();for(int x=-13;x<=-9;x++)for(int y=1;y<=5;y++)for(int z=5;z<=9;z++)l.setBlockAndUpdate(new BlockPos(x,y,z),x==-13||x==-9||y==1||y==5||z==5||z==9?Blocks.STONE.defaultBlockState():Blocks.AIR.defaultBlockState());place(l,new BlockPos(-11,2,7),ARCH,Direction.SOUTH,SculptureAnchor.CENTER);});
|
||||
c.waitFor(m->m.level.getBlockEntity(new BlockPos(-11,2,7)) instanceof StatuaryEntity&&m.level.getBrightness(LightLayer.SKY,new BlockPos(-11,2,7))==0&&m.level.getBrightness(LightLayer.BLOCK,new BlockPos(-11,2,7))==0,200);
|
||||
c.runOnClient(m->{var renderer=new StatuaryRenderer();var state=renderer.createRenderState();renderer.extractRenderState((StatuaryEntity)m.level.getBlockEntity(new BlockPos(-11,2,7)),state,0,Vec3.ZERO,null);check(state.lightCoords==0,"No fullbright workaround: a dark room produces dark faces");});
|
||||
System.out.println("SCULPTURE122_LIGHT_PASS: 75 stacked models sky=15; opaque control shadow; block light=13 across sculpture; dark room=0; shade brightness=1");
|
||||
}
|
||||
try(var world=saved.open()){
|
||||
c.waitFor(m->m.player!=null&&m.gui.screen()==null&&m.level.getBlockEntity(TARGET) instanceof StatuaryEntity e&&ARCH.equals(e.sculpture()),1200);
|
||||
world.getServer().runOnServer(s->physical(player(s).level(),TARGET));c.runOnClient(m->physical(m.level,TARGET));
|
||||
c.waitFor(m->m.level.getBrightness(LightLayer.SKY,SKY)==15,200);
|
||||
System.out.println("SCULPTURE122_PASS: native placement, voxel outline and picking through holes, authoritative collisions, shared render geometry, cache refresh, light propagation and save/reconnect");
|
||||
}
|
||||
}
|
||||
private static void placementOverlap(ServerPlayer player){
|
||||
var pos=new BlockPos(3,1,0);var level=player.level();player.teleportTo(3.75,1,.5);player.setYRot(0);
|
||||
var filled=new ArrayList<VoxelSculpture.Cell>();for(int x=0;x<16;x++)for(int y=0;y<16;y++)for(int z=0;z<16;z++)filled.add(new VoxelSculpture.Cell(x,y,z,-1));
|
||||
var dense=new VoxelSculpture(MODEL,"Solid",16,16,16,filled);var stack=StatuaryEntity.item(dense);player.getInventory().setItem(player.getInventory().getSelectedSlot(),stack);
|
||||
var hit=new BlockHitResult(new Vec3(3.01,1,.5),Direction.UP,pos.below(),false);
|
||||
var blocked=((BlockItem)Statuary.ITEM).place(new BlockPlaceContext(player,InteractionHand.MAIN_HAND,stack,hit));
|
||||
check(!blocked.consumesAction()&&level.getBlockState(pos).isAir()&&stack.getCount()==1,"Native placement refuses occupied voxels inside player without consuming sculpture");
|
||||
var postCells=new ArrayList<VoxelSculpture.Cell>();for(int y=0;y<16;y++)for(int x=0;x<2;x++)for(int z=0;z<2;z++)postCells.add(new VoxelSculpture.Cell(x,y,z,-1));
|
||||
var post=new VoxelSculpture(MODEL,"Post",2,16,2,postCells);stack=StatuaryEntity.item(post);player.getInventory().setItem(player.getInventory().getSelectedSlot(),stack);
|
||||
var allowed=((BlockItem)Statuary.ITEM).place(new BlockPlaceContext(player,InteractionHand.MAIN_HAND,stack,hit));
|
||||
check(allowed.consumesAction()&&level.getBlockState(pos).is(Statuary.BLOCK),"Native placement allows empty part of block beside player");
|
||||
check(level.noCollision(player,player.getBoundingBox()),"Accepted sculpture does not intersect player");
|
||||
level.setBlockAndUpdate(pos,Blocks.AIR.defaultBlockState());player.teleportTo(.5,1,3.2);player.getInventory().setItem(player.getInventory().getSelectedSlot(),StatuaryEntity.item(ARCH));player.inventoryMenu.broadcastFullState();
|
||||
System.out.println("SCULPTURE122_PLACEMENT_PASS native item rejects occupied voxels, allows empty space, preserves stack on rejection");
|
||||
}
|
||||
private static void physical(Level level,BlockPos pos){
|
||||
var state=level.getBlockState(pos);var shape=state.getShape(level,pos);var collision=state.getCollisionShape(level,pos);
|
||||
check(shape==collision,"Selection and native collision share cached shape");check(shape.bounds().getZsize()==.25,"No invisible full-block depth");
|
||||
check(level.clip(new ClipContext(new Vec3(.5,1.5,2),new Vec3(.5,1.5,-.1),ClipContext.Block.OUTLINE,ClipContext.Fluid.NONE,CollisionContext.empty())).getType()==HitResult.Type.MISS,"Outline ray passes through empty arch center");
|
||||
check(level.clip(new ClipContext(new Vec3(.0625,1.5,2),new Vec3(.0625,1.5,-.1),ClipContext.Block.COLLIDER,ClipContext.Fluid.NONE,CollisionContext.empty())).getType()==HitResult.Type.BLOCK,"Collision ray hits pillar");
|
||||
check(level.noCollision(new AABB(.35,1.2,.35,.65,1.7,.65)),"Native collision query leaves hole empty");
|
||||
check(!level.noCollision(new AABB(.02,1.2,.4,.1,1.7,.6)),"Native collision query blocks occupied pillar");
|
||||
check(!state.isCollisionShapeFullBlock(level,pos)&&!state.isSolidRender()&&!state.canOcclude(),"Sculpture is not a solid occluding cube");
|
||||
check(state.getOcclusionShape().isEmpty()&&state.propagatesSkylightDown()&&state.getLightDampening()==0&&state.getShadeBrightness(level,pos)==1,"Light metadata never reintroduces full-block occlusion");
|
||||
check(!state.isSuffocating(level,pos)&&state.getLightEmission()==0,"Decorative shape neither suffocates nor emits light");
|
||||
}
|
||||
private static void geometry(Minecraft m){
|
||||
var padded=new VoxelSculpture(MODEL,"Padded",13,5,15,List.of(new VoxelSculpture.Cell(2,2,3,-1),new VoxelSculpture.Cell(6,4,10,-1)));
|
||||
var dense=new ArrayList<VoxelSculpture.Cell>();for(int x=0;x<16;x++)for(int y=0;y<16;y++)for(int z=0;z<16;z++)dense.add(new VoxelSculpture.Cell(x,y,z,-1));
|
||||
var models=List.of(ARCH,padded,new VoxelSculpture(MODEL,"Dense",16,16,16,dense),new VoxelSculpture(MODEL,"One voxel",1,1,1,List.of(new VoxelSculpture.Cell(0,0,0,-1))));
|
||||
int cases=0;for(var model:models)for(var direction:Direction.Plane.HORIZONTAL)for(var anchor:SculptureAnchor.values()){
|
||||
var state=Statuary.BLOCK.defaultBlockState().setValue(Statuary.FACING,direction).setValue(Statuary.ANCHOR,anchor);var entity=new StatuaryEntity(BlockPos.ZERO,state);entity.setLevel(m.level);load(entity,model,m.level);
|
||||
var shape=entity.shape(state);check(shape==entity.shape(state),"Repeated geometry queries reuse the same shape");var boxes=shape.toAabbs();double volume=boxes.stream().mapToDouble(b->b.getXsize()*b.getYsize()*b.getZsize()).sum();check(volume==model.cells().size()/4096d,"Shape contains exactly the occupied volume");
|
||||
var renderer=new StatuaryRenderer();var render=renderer.createRenderState();renderer.extractRenderState(entity,render,0,Vec3.ZERO,null);
|
||||
for(var q:render.mesh){var center=new Vec3(q.x()+1/32d,q.y()+1/32d,q.z()+1/32d);check(boxes.stream().anyMatch(b->b.contains(center)),"Every rendered cell is physically present");
|
||||
var normal=switch(q.side()){case 0->Direction.NORTH;case 1->Direction.SOUTH;case 2->Direction.WEST;case 3->Direction.EAST;case 4->Direction.UP;default->Direction.DOWN;};var neighbor=center.add(normal.getStepX()/16d,normal.getStepY()/16d,normal.getStepZ()/16d);check(boxes.stream().noneMatch(b->b.contains(neighbor)),"Rendered exposed face is also exposed physically");}
|
||||
cases++;
|
||||
}
|
||||
System.out.println("SCULPTURE122_GEOMETRY_PASS "+cases+" native voxel shapes agree with emitted meshes, including holes, padding, dense 4096 cells, single voxel, 4 directions and 5 anchors");
|
||||
}
|
||||
private static void place(ServerLevel level,BlockPos pos,VoxelSculpture model,Direction facing,SculptureAnchor anchor){
|
||||
var state=Statuary.BLOCK.defaultBlockState().setValue(Statuary.FACING,facing).setValue(Statuary.ANCHOR,anchor);level.setBlockAndUpdate(pos,state);var entity=(StatuaryEntity)level.getBlockEntity(pos);load(entity,model,level);entity.setChanged();level.sendBlockUpdated(pos,state,state,3);
|
||||
}
|
||||
private static void load(StatuaryEntity entity,VoxelSculpture model,Level level){var tag=new CompoundTag();tag.put("sculpture",StatuaryEntity.encode(model));entity.loadWithComponents(TagValueInput.create(ProblemReporter.DISCARDING,level.registryAccess(),tag));}
|
||||
private static VoxelSculpture arch(){var cells=new ArrayList<VoxelSculpture.Cell>();for(int x=0;x<16;x++)for(int y=0;y<16;y++)for(int z=0;z<4;z++)if(x<2||x>=14||y>=14)cells.add(new VoxelSculpture.Cell(x,y,z,0xffc99679));return new VoxelSculpture(MODEL,"Arch",16,16,4,cells);}
|
||||
private static ServerPlayer player(MinecraftServer server){return server.getPlayerList().getPlayers().getFirst();}
|
||||
private static void check(boolean value,String reason){if(!value)throw new AssertionError(reason);}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.Blaze3D;
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Comparator;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.Util;
|
||||
|
||||
/** Explicit, one-time export. The player's copy is never a managed mod resource. */
|
||||
public final class ResourcePackTemplate {
|
||||
public static final String FOLDER = "Sanctuary-Custom";
|
||||
|
||||
private ResourcePackTemplate() {}
|
||||
|
||||
public static Button button(Path packDirectory) {
|
||||
Path target = packDirectory.resolve(FOLDER);
|
||||
return Button.builder(label(Files.isDirectory(target, LinkOption.NOFOLLOW_LINKS)
|
||||
? "open_copy" : "customize"), button -> {
|
||||
if (Files.isDirectory(target, LinkOption.NOFOLLOW_LINKS)) {
|
||||
Blaze3D.openPath(target);
|
||||
return;
|
||||
}
|
||||
button.active = false;
|
||||
button.setMessage(label("creating"));
|
||||
Util.ioPool().execute(() -> {
|
||||
try {
|
||||
create(packDirectory);
|
||||
Minecraft.getInstance().execute(() -> {
|
||||
button.active = true;
|
||||
button.setMessage(label("open_copy"));
|
||||
button.setTooltip(Tooltip.create(label("copy_ready")));
|
||||
});
|
||||
} catch (IOException | RuntimeException error) {
|
||||
SanctuaryMod.LOGGER.error("Could not create personal Sanctuary resource pack", error);
|
||||
Minecraft.getInstance().execute(() -> {
|
||||
button.active = true;
|
||||
button.setMessage(label("copy_failed"));
|
||||
button.setTooltip(Tooltip.create(label("copy_failed_help")));
|
||||
});
|
||||
}
|
||||
});
|
||||
}).width(308).tooltip(Tooltip.create(label("customize_help"))).build();
|
||||
}
|
||||
|
||||
/** Returns an existing personal directory untouched, including after mod upgrades. */
|
||||
public static synchronized Path create(Path packDirectory) throws IOException {
|
||||
Path directory = packDirectory.toAbsolutePath().normalize();
|
||||
Files.createDirectories(directory);
|
||||
Path target = directory.resolve(FOLDER);
|
||||
if (Files.exists(target, LinkOption.NOFOLLOW_LINKS)) return existing(target);
|
||||
var mod = FabricLoader.getInstance().getModContainer(SanctuaryMod.ID).orElseThrow();
|
||||
// Keep incomplete packs outside the directory watched by the native pack screen.
|
||||
Path staging = Files.createTempDirectory(directory.getParent(), ".sanctuary-template-");
|
||||
try {
|
||||
copyTree(mod.findPath("assets").orElseThrow(), staging.resolve("assets"));
|
||||
copyTree(mod.findPath("resourcepacks/textures/assets").orElseThrow(), staging.resolve("assets"));
|
||||
copyTree(mod.findPath("resourcepacks/template").orElseThrow(), staging);
|
||||
Files.copy(mod.findPath("resourcepacks/textures/pack.png").orElseThrow(), staging.resolve("pack.png"));
|
||||
try {
|
||||
Files.move(staging, target);
|
||||
} catch (FileAlreadyExistsException race) {
|
||||
return existing(target);
|
||||
}
|
||||
return target;
|
||||
} finally {
|
||||
if (Files.exists(staging)) {
|
||||
try (var files = Files.walk(staging)) {
|
||||
for (Path file : files.sorted(Comparator.reverseOrder()).toList()) Files.delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Path existing(Path target) throws IOException {
|
||||
if (!Files.isDirectory(target, LinkOption.NOFOLLOW_LINKS)) {
|
||||
throw new IOException("Personal pack path already exists and is not a directory: " + target);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private static void copyTree(Path source, Path destination) throws IOException {
|
||||
try (var files = Files.walk(source)) {
|
||||
for (Path file : files.toList()) {
|
||||
if (Files.isSymbolicLink(file)) throw new IOException("Unexpected asset symlink: " + file);
|
||||
// String paths also work when the mod is loaded from a ZIP filesystem.
|
||||
Path output = destination.resolve(source.relativize(file).toString()).normalize();
|
||||
if (!output.startsWith(destination)) throw new IOException("Invalid asset path: " + file);
|
||||
if (Files.isDirectory(file)) Files.createDirectories(output);
|
||||
else Files.copy(file, output, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Component label(String key) {
|
||||
return Component.translatable("sanctuary.resource_pack." + key);
|
||||
}
|
||||
}
|
||||
@@ -47,16 +47,9 @@ public final class StatuaryRenderer implements BlockEntityRenderer<StatuaryEntit
|
||||
}
|
||||
private static List<Quad> mesh(VoxelSculpture sculpture,Direction facing,SculptureAnchor anchor){
|
||||
if(sculpture==null)return List.of();var key=new Placement(sculpture,facing,anchor);var cached=MESHES.get(key);if(cached!=null)return cached;
|
||||
// Rotate integer cells before anchoring: odd dimensions never introduce half-pixel offsets.
|
||||
int turns=switch(facing){case WEST -> 1;case NORTH -> 2;case EAST -> 3;default -> 0;};
|
||||
for(int i=0;i<turns;i++)sculpture=sculpture.rotate();
|
||||
var placed=SculptureGeometry.place(sculpture,facing,anchor);sculpture=placed.sculpture();
|
||||
var occupied=new BitSet(4096);for(var cell:sculpture.cells())occupied.set(cell.key());var quads=new ArrayList<Quad>();
|
||||
int minX=16,minZ=16,maxX=0,maxZ=0;
|
||||
for(var cell:sculpture.cells()){
|
||||
minX=Math.min(minX,cell.x());minZ=Math.min(minZ,cell.z());
|
||||
maxX=Math.max(maxX,cell.x()+1);maxZ=Math.max(maxZ,cell.z()+1);
|
||||
}
|
||||
float ox=(anchor.originX(maxX-minX)-minX)/16f,oz=(anchor.originZ(maxZ-minZ)-minZ)/16f;
|
||||
float ox=placed.offsetX()/16f,oz=placed.offsetZ()/16f;
|
||||
for(var cell:sculpture.cells())for(int side=0;side<6;side++){
|
||||
var s=STEP[side];int x=cell.x()+s[0],y=cell.y()+s[1],z=cell.z()+s[2];
|
||||
if(x>=0&&x<16&&y>=0&&y<16&&z>=0&&z<16&&occupied.get(x|y<<4|z<<8))continue;
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package fr.koka.sanctuary.mixin.client;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import fr.koka.sanctuary.client.ResourcePackTemplate;
|
||||
import java.nio.file.Path;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout;
|
||||
import net.minecraft.client.gui.layouts.LayoutElement;
|
||||
import net.minecraft.client.gui.layouts.LinearLayout;
|
||||
import net.minecraft.client.gui.screens.packs.PackSelectionScreen;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
/** Extra footer row only in resource packs, never in the world datapack selector. */
|
||||
@Mixin(PackSelectionScreen.class)
|
||||
public abstract class PackSelectionScreenMixin {
|
||||
@Shadow @Final private Path packDir;
|
||||
|
||||
@WrapOperation(method = "init", at = @At(value = "INVOKE", target =
|
||||
"Lnet/minecraft/client/gui/layouts/HeaderAndFooterLayout;addToFooter(Lnet/minecraft/client/gui/layouts/LayoutElement;)Lnet/minecraft/client/gui/layouts/LayoutElement;"))
|
||||
private LayoutElement sanctuary$personalPack(HeaderAndFooterLayout layout, LayoutElement nativeButtons,
|
||||
Operation<LayoutElement> original) {
|
||||
if (!packDir.toAbsolutePath().normalize().equals(
|
||||
Minecraft.getInstance().getResourcePackDirectory().toAbsolutePath().normalize())) {
|
||||
return original.call(layout, nativeButtons);
|
||||
}
|
||||
layout.setFooterHeight(56);
|
||||
LinearLayout rows = LinearLayout.vertical().spacing(4);
|
||||
rows.addChild(ResourcePackTemplate.button(packDir));
|
||||
rows.addChild(nativeButtons);
|
||||
original.call(layout, rows);
|
||||
// Vanilla still adds its Open folder / Done buttons to the original horizontal row.
|
||||
return nativeButtons;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package fr.koka.sanctuary.plans;
|
||||
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
|
||||
import net.minecraft.world.phys.shapes.CubeVoxelShape;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
/** One integer placement shared by rendering, picking and server-side collisions. */
|
||||
public final class SculptureGeometry {
|
||||
public record Placement(VoxelSculpture sculpture, int offsetX, int offsetZ) {}
|
||||
|
||||
private SculptureGeometry() {}
|
||||
|
||||
public static Placement place(VoxelSculpture sculpture, Direction facing, SculptureAnchor anchor) {
|
||||
int turns = switch (facing) { case WEST -> 1; case NORTH -> 2; case EAST -> 3; default -> 0; };
|
||||
for (int i = 0; i < turns; i++) sculpture = sculpture.rotate();
|
||||
int minX = 16, minZ = 16, maxX = 0, maxZ = 0;
|
||||
for (var cell : sculpture.cells()) {
|
||||
minX = Math.min(minX, cell.x()); minZ = Math.min(minZ, cell.z());
|
||||
maxX = Math.max(maxX, cell.x() + 1); maxZ = Math.max(maxZ, cell.z() + 1);
|
||||
}
|
||||
return new Placement(sculpture, anchor.originX(maxX - minX) - minX,
|
||||
anchor.originZ(maxZ - minZ) - minZ);
|
||||
}
|
||||
|
||||
public static VoxelShape shape(VoxelSculpture sculpture, Direction facing, SculptureAnchor anchor) {
|
||||
var placed = place(sculpture, facing, anchor);
|
||||
var grid = new BitSetDiscreteVoxelShape(16, 16, 16);
|
||||
for (var cell : placed.sculpture().cells()) {
|
||||
grid.fill(cell.x() + placed.offsetX(), cell.y(), cell.z() + placed.offsetZ());
|
||||
}
|
||||
// Build the bounded native voxel grid directly, without thousands of box unions.
|
||||
return new CubeVoxelShape(grid);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import net.minecraft.world.level.material.PushReaction;
|
||||
import net.minecraft.world.level.storage.loot.LootParams;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.phys.*;
|
||||
import net.minecraft.world.phys.shapes.*;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
/** The clay workshop crafts portable sculptures; a placed sculpture is only a decoration. */
|
||||
@@ -57,10 +58,31 @@ public final class Statuary {
|
||||
});
|
||||
}
|
||||
private static Block block(String name,Block block){return Registry.register(BuiltInRegistries.BLOCK,SanctuaryMod.id(name),block);}
|
||||
private static Item blockItem(String name,Block block){return Registry.register(BuiltInRegistries.ITEM,SanctuaryMod.id(name),new BlockItem(block,new Item.Properties().setId(ResourceKey.create(Registries.ITEM,SanctuaryMod.id(name))).useBlockDescriptionPrefix()));}
|
||||
private static Item blockItem(String name,Block block){
|
||||
var properties=new Item.Properties().setId(ResourceKey.create(Registries.ITEM,SanctuaryMod.id(name))).useBlockDescriptionPrefix();
|
||||
return Registry.register(BuiltInRegistries.ITEM,SanctuaryMod.id(name),block==BLOCK?new SculptureItem(block,properties):new BlockItem(block,properties));
|
||||
}
|
||||
private static BlockBehaviour.Properties properties(String id){return BlockBehaviour.Properties.of().strength(1.5f).noOcclusion().noLootTable().pushReaction(PushReaction.IMMOVEABLE).setId(ResourceKey.create(Registries.BLOCK,SanctuaryMod.id(id)));}
|
||||
private static final class SculptureItem extends BlockItem {
|
||||
SculptureItem(Block block,Item.Properties properties){super(block,properties);}
|
||||
@Override protected boolean canPlace(BlockPlaceContext context,BlockState state){
|
||||
if(!super.canPlace(context,state))return false;
|
||||
// The destination has no block entity yet: use the carried model for the native entity-overlap check.
|
||||
var model=StatuaryEntity.sculpture(context.getItemInHand());
|
||||
return model==null||context.getLevel().isUnobstructed(null,SculptureGeometry.shape(model,state.getValue(FACING),state.getValue(ANCHOR)).move(context.getClickedPos()));
|
||||
}
|
||||
}
|
||||
private static final class SculptureBlock extends Block implements EntityBlock {
|
||||
SculptureBlock(){super(Statuary.properties("statuary"));registerDefaultState(stateDefinition.any().setValue(FACING,Direction.SOUTH).setValue(ANCHOR,SculptureAnchor.CENTER));}
|
||||
SculptureBlock(){super(Statuary.properties("statuary").dynamicShape().isSuffocating((s,l,p)->false).isViewBlocking((s,l,p,box)->false));registerDefaultState(stateDefinition.any().setValue(FACING,Direction.SOUTH).setValue(ANCHOR,SculptureAnchor.CENTER));}
|
||||
@Override protected VoxelShape getShape(BlockState state,BlockGetter level,BlockPos pos,CollisionContext context){
|
||||
return level.getBlockEntity(pos) instanceof StatuaryEntity entity?entity.shape(state):Shapes.empty();
|
||||
}
|
||||
// Lighting metadata is cached per blockstate, so it cannot describe each block entity's voxels.
|
||||
// Decorative sculptures transmit light; their faces still receive ordinary world lighting.
|
||||
@Override protected VoxelShape getOcclusionShape(BlockState state){return Shapes.empty();}
|
||||
@Override protected boolean propagatesSkylightDown(BlockState state){return true;}
|
||||
@Override protected int getLightDampening(BlockState state){return 0;}
|
||||
@Override protected float getShadeBrightness(BlockState state,BlockGetter level,BlockPos pos){return 1;}
|
||||
@Override protected void createBlockStateDefinition(StateDefinition.Builder<Block,BlockState> builder){builder.add(FACING,ANCHOR);}
|
||||
@Override public BlockState getStateForPlacement(BlockPlaceContext context){return defaultBlockState().setValue(FACING,context.getHorizontalDirection()).setValue(ANCHOR,SculptureAnchor.placement(context));}
|
||||
@Override protected BlockState rotate(BlockState state,Rotation rotation){return state.setValue(FACING,rotation.rotate(state.getValue(FACING))).setValue(ANCHOR,state.getValue(ANCHOR).rotate(rotation));}
|
||||
|
||||
@@ -9,13 +9,26 @@ import net.minecraft.world.item.component.TypedEntityData;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.*;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
/** New, additive block data only. No ticking or mutation of pre-existing world formats. */
|
||||
public final class StatuaryEntity extends BlockEntity {
|
||||
private VoxelSculpture sculpture;
|
||||
private CompoundTag opaque;
|
||||
private record ShapeCache(VoxelSculpture sculpture,BlockState state,VoxelShape shape){}
|
||||
private volatile ShapeCache shapeCache;
|
||||
// Keep unknown/future data recoverable by picking the small base, without a phantom full cube.
|
||||
private static final VoxelShape MISSING_SHAPE=Block.box(6,0,6,10,4,10);
|
||||
public StatuaryEntity(BlockPos pos,BlockState state){super(Statuary.TYPE,pos,state);}
|
||||
public VoxelSculpture sculpture(){return sculpture;}
|
||||
public VoxelShape shape(BlockState state){
|
||||
var model=sculpture;if(model==null)return MISSING_SHAPE;
|
||||
var cached=shapeCache;
|
||||
if(cached!=null&&cached.sculpture()==model&&cached.state()==state)return cached.shape();
|
||||
var shape=SculptureGeometry.shape(model,state.getValue(Statuary.FACING),state.getValue(Statuary.ANCHOR));
|
||||
shapeCache=new ShapeCache(model,state,shape);return shape;
|
||||
}
|
||||
@Override protected void saveAdditional(ValueOutput out){
|
||||
super.saveAdditional(out);
|
||||
if(opaque!=null){out.store("sculpture",CompoundTag.CODEC,opaque);return;}
|
||||
@@ -23,7 +36,7 @@ public final class StatuaryEntity extends BlockEntity {
|
||||
out.store("sculpture",CompoundTag.CODEC,encode(sculpture));
|
||||
}
|
||||
@Override protected void loadAdditional(ValueInput in){
|
||||
super.loadAdditional(in);sculpture=null;opaque=null;
|
||||
super.loadAdditional(in);sculpture=null;opaque=null;shapeCache=null;
|
||||
var data=in.read("sculpture",CompoundTag.CODEC).orElse(null);if(data==null)return;
|
||||
try{
|
||||
if(data.getIntOr("schema",-1)!=1)throw new IllegalArgumentException("schema");
|
||||
|
||||
@@ -1753,6 +1753,14 @@
|
||||
"sanctuary.preload.cities": "Preparing settlements",
|
||||
"sanctuary.preload.cache_write": "Saving plans",
|
||||
"sanctuary.familiar.dragon.lift": "On your head: hold %s to rise, release to glide.",
|
||||
"sanctuary.resource_pack.customize": "Customize Sanctuary",
|
||||
"sanctuary.resource_pack.open_copy": "Open my copy",
|
||||
"sanctuary.resource_pack.creating": "Creating your copy…",
|
||||
"sanctuary.resource_pack.customize_help": "Create an editable copy of Sanctuary resources. Enable it and move it to the top of the list. Updates preserve your changes.",
|
||||
"sanctuary.resource_pack.copy_ready": "Copy ready! Enable Sanctuary-Custom and move it above other packs. Click to open its folder and guide.",
|
||||
"sanctuary.resource_pack.copy_failed": "Copy failed — retry",
|
||||
"sanctuary.resource_pack.copy_failed_help": "Check write permissions and the resourcepacks/Sanctuary-Custom path. See the game log for details.",
|
||||
"sanctuary.resource_pack.custom_description": "Sanctuary — your editable copy",
|
||||
"sanctuary.resource_pack.name": "Sanctuary textures",
|
||||
"sanctuary.resource_pack.description": "Sanctuary textures — %s",
|
||||
"sanctuary.familiar.personality": "Personality: %s",
|
||||
|
||||
@@ -1753,6 +1753,14 @@
|
||||
"sanctuary.preload.cities": "Préparation des implantations",
|
||||
"sanctuary.preload.cache_write": "Enregistrement des plans",
|
||||
"sanctuary.familiar.dragon.lift": "Sur la tête : maintenir %s pour monter, relâcher pour planer.",
|
||||
"sanctuary.resource_pack.customize": "Personnaliser Sanctuary",
|
||||
"sanctuary.resource_pack.open_copy": "Ouvrir ma copie",
|
||||
"sanctuary.resource_pack.creating": "Création de la copie…",
|
||||
"sanctuary.resource_pack.customize_help": "Crée une copie modifiable des ressources de Sanctuary. Activez-la et placez-la en haut de la liste. Vos modifications sont conservées lors des mises à jour.",
|
||||
"sanctuary.resource_pack.copy_ready": "Copie prête ! Activez Sanctuary-Custom et placez-le au-dessus des autres packs. Cliquez pour ouvrir son dossier et son guide.",
|
||||
"sanctuary.resource_pack.copy_failed": "Copie impossible — réessayer",
|
||||
"sanctuary.resource_pack.copy_failed_help": "Vérifiez les droits d’écriture et le chemin resourcepacks/Sanctuary-Custom. Les détails sont dans le journal du jeu.",
|
||||
"sanctuary.resource_pack.custom_description": "Sanctuary — votre copie modifiable",
|
||||
"sanctuary.resource_pack.name": "Textures Sanctuary",
|
||||
"sanctuary.resource_pack.description": "Textures Sanctuary — %s",
|
||||
"sanctuary.familiar.personality": "Personnalité : %s",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 196 B |
@@ -184,6 +184,7 @@
|
||||
"NaturalSnowEditMixin"
|
||||
],
|
||||
"client": [
|
||||
"client.PackSelectionScreenMixin",
|
||||
"client.FourneauFireMixin",
|
||||
"client.BoatPassengerPickMixin",
|
||||
"client.PreloadScreenMixin",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name = "Sanctuary"
|
||||
author = "KOKA99CAB"
|
||||
version = "beta.120"
|
||||
version = "beta.122"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
|
||||
@@ -3,11 +3,18 @@
|
||||
Sources des resource packs propres à Sanctuary. Le nom de ce dossier existant
|
||||
est conservé ; le dossier installé dans Minecraft s'appelle `resourcepacks/`.
|
||||
|
||||
Le bouton **Personnaliser Sanctuary**, dans Options → Packs de ressources,
|
||||
crée maintenant une copie personnelle complète `resourcepacks/Sanctuary-Custom/`.
|
||||
Elle réunit les assets du mod et ceux du pack intégré ; les mises à jour ne
|
||||
l'écrasent jamais. Le [guide du template](template/README.md) détaille les chemins
|
||||
à modifier. `./gradlew assembleResourceTemplate` produit le ZIP complet.
|
||||
Voir le [ticket beta.121](../docs/resourcepack-template-beta121.md).
|
||||
|
||||
Les exports ZIP à la racine restent locaux et ignorés par Git. Les dossiers
|
||||
décompressés contiennent les sources versionnées.
|
||||
|
||||
[`sanctuary/`](sanctuary/README.md) est la base modifiable pour Minecraft
|
||||
26.3 : pack beta.090 versionné, embarqué et activé par défaut dans le mod.
|
||||
26.3 : pack beta.121 versionné, embarqué et activé par défaut dans le mod.
|
||||
Il comprend les menus, 47 cœurs vanilla et les nouveaux dessins de faim et
|
||||
de saturation fournis par le créateur. Les manifests `sanctuary/releases/`
|
||||
fixent les empreintes de chaque révision.
|
||||
|
||||
@@ -5,7 +5,15 @@ Le manifeste déclare le format de ressources **97.1**, relevé dans le client
|
||||
exact utilisé par Sanctuary. Les menus, le panorama et les textures Sanctuary
|
||||
déjà présents sont conservés.
|
||||
|
||||
## Version beta.090
|
||||
## Version beta.121
|
||||
|
||||
La révision [beta.121](releases/beta.121.json) ajoute la clé dorée fournie par
|
||||
le créateur, octet pour octet. Les autres textures et le format 97.1 restent
|
||||
identiques. Le [template personnel](../template/README.md) exporte également
|
||||
les ressources récentes du mod : blocs, items, modèles, sons et traductions.
|
||||
Il se crée depuis l'écran des packs ou avec `assembleResourceTemplate`.
|
||||
|
||||
## Version beta.090 (historique)
|
||||
|
||||
La révision [beta.090](releases/beta.090.json) déclare Minecraft 26.3 finale,
|
||||
sans changer le format 97.1 ni les images. Le manifeste beta.070 et son
|
||||
@@ -26,7 +34,7 @@ bulles de respiration proviennent du jeu.
|
||||
|
||||
La source unique est ce dossier. Gradle le copie dans le mod sous
|
||||
`resourcepacks/textures/` et produit le même contenu dans
|
||||
`build/Sanctuary-Resource-Pack-beta.090.zip`. L’identifiant stable du pack
|
||||
`build/Sanctuary-Resource-Pack-beta.121.zip`. L’identifiant stable du pack
|
||||
est `sanctuary:textures`. Son numéro graphique est indépendant des correctifs
|
||||
de code : `resource_pack_version` dans `gradle.properties` ne change qu’avec
|
||||
une nouvelle révision du pack.
|
||||
@@ -41,7 +49,7 @@ de `pack.mcmeta` et `resource_pack_version`. Ne pas réécrire un ancien manifes
|
||||
Le contrôle refuse un fichier ajouté, oublié ou modifié sans mise à jour de
|
||||
révision. Ne pas éditer les copies générées dans `build/`.
|
||||
|
||||
**English:** This source is the beta.090 built-in pack for Minecraft 26.3, enabled by default and
|
||||
**English:** This source is the beta.121 built-in pack for Minecraft 26.3, enabled by default and
|
||||
still user-disableable. Incoming PNGs are preserved exactly. `releases/` tracks
|
||||
immutable file hashes; the mod and standalone ZIP consume this same source.
|
||||
Keep a new release manifest whenever changing the graphics.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 196 B |
@@ -3,7 +3,7 @@
|
||||
"description": {
|
||||
"translate": "sanctuary.resource_pack.description",
|
||||
"with": [
|
||||
"beta.090"
|
||||
"beta.121"
|
||||
],
|
||||
"fallback": "Sanctuary textures — %s"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
{
|
||||
"version": "beta.121",
|
||||
"minecraft": "26.3",
|
||||
"format": [
|
||||
97,
|
||||
1
|
||||
],
|
||||
"builtinId": "sanctuary:textures",
|
||||
"activation": "DEFAULT_ENABLED",
|
||||
"files": {
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_empty.png": "80ea14a291330f3253fa4ba3c7aa5eeea73ac3df71c85eec649ae75762574cd2",
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_empty_hunger.png": "f9f725d5f76f09c9284ec8ef3eab3bab714b04306ea5b88d2774c6d96c65e525",
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_full.png": "29f5cddd23a6383973690101b3d1d7ab10e421b8bff8c5359e8d2623576e2f14",
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_full_hunger.png": "1c1969632df22e5c3be196ace904174e7797222b4d6514f442fcce0cc9fb2361",
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_half.png": "e5eda03de917caca665dc5ac5210fdf90af745cad0ee17ddccddc4365a7ee55a",
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_half_hunger.png": "51be68df64c4d01c2dff3c300f9e1833bccbc19007d7a18b567a42160740fa8e",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/absorbing_full.png": "4afb2035139c659ff7ed2fbf16bb4c4cc12158c664e422cbb964d4c926f40844",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/absorbing_full_blinking.png": "4afb2035139c659ff7ed2fbf16bb4c4cc12158c664e422cbb964d4c926f40844",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/absorbing_half.png": "ab37c9a7b3ded346b5ab125a6a87045ff6b599a8e1f1796290aae344d8f6189b",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/absorbing_half_blinking.png": "ab37c9a7b3ded346b5ab125a6a87045ff6b599a8e1f1796290aae344d8f6189b",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/absorbing_hardcore_full.png": "aa4a51a65cf6fd6b76dbc040f68b7a90ee70b8b23298d2628a8e0a2ea54d924d",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/absorbing_hardcore_full_blinking.png": "aa4a51a65cf6fd6b76dbc040f68b7a90ee70b8b23298d2628a8e0a2ea54d924d",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/absorbing_hardcore_half.png": "f88b24663f7c14e58490e1a8a60f44339f05c284fb89dbbf6da6bd9890291e06",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/absorbing_hardcore_half_blinking.png": "f88b24663f7c14e58490e1a8a60f44339f05c284fb89dbbf6da6bd9890291e06",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/container.png": "b92355b32f566dd5ec209dcf6f25ddd239fdf967a70af42308ef9607aab3b194",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/container_blinking.png": "1b1094514a73d8673110595050003fff6a7da1d33e6457ceeae004634c2c0963",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/container_hardcore.png": "b92355b32f566dd5ec209dcf6f25ddd239fdf967a70af42308ef9607aab3b194",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/container_hardcore_blinking.png": "1b1094514a73d8673110595050003fff6a7da1d33e6457ceeae004634c2c0963",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/frozen_full.png": "66a22e5a9c1be724078bf6392970917ce74548a75242cb9a8694a8cc788893ed",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/frozen_full_blinking.png": "66a22e5a9c1be724078bf6392970917ce74548a75242cb9a8694a8cc788893ed",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/frozen_half.png": "dda4ed21c8f12f4c5f28fea52d19fda8e09451adbb568bdc5aa4b4243cad9795",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/frozen_half_blinking.png": "dda4ed21c8f12f4c5f28fea52d19fda8e09451adbb568bdc5aa4b4243cad9795",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/frozen_hardcore_full.png": "8b6d31ee17b3eacbbfe69a6c8cf165d5efee9af12aae763a073818bfcc47d2fd",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/frozen_hardcore_full_blinking.png": "8b6d31ee17b3eacbbfe69a6c8cf165d5efee9af12aae763a073818bfcc47d2fd",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/frozen_hardcore_half.png": "55448c040468dab90f48e03392e90b7f4f635109349c1524619babeb875177ac",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/frozen_hardcore_half_blinking.png": "55448c040468dab90f48e03392e90b7f4f635109349c1524619babeb875177ac",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/full.png": "acb11da7611dc8239d4da2fe500cca2fe88a2c4facccf42fa7207c0e9dba4a16",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/full_blinking.png": "d43889d4c676a6e2498b0510bf4f77c0050b792884952c67475f4bff097a498b",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/half.png": "98d1fca665a309879c19c06b0658ba5407520f13a02d4bf7321b060534491efc",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/half_blinking.png": "f63cf9331c3087e5e403b96e2456721cbbbc4be05b86c3443b863efda40b6c88",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/hardcore_full.png": "0a7788d62fb70523880ca26120add4fb397a35ea70e7deca036c8fc2afcbada7",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/hardcore_full_blinking.png": "d66cf76e587d7bbd435d05d4c8a4f3835a90dbfdd63a5e825066e0cf2de53d52",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/hardcore_half.png": "92dd5fe2e4b355b627cf1340a569923d3cd5d6c3c7963e60e6abee207a1abed5",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/hardcore_half_blinking.png": "380da417eeefbaccf0f9f9bf03efd6d2ae713567ddcd47dfbf2c7298d0c2203b",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/poisoned_full.png": "5c47760c8770cbed12392e65fdd2a3acd33836a6a8275f6abd4ce9f56ac01e9f",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/poisoned_full_blinking.png": "bb427c5811d0eaa255f124076e4609333f8301cd5c7094fb344c5480cbb4a995",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/poisoned_half.png": "7e0d0118a3e3a3d4a11dc1b65a4c66af0abdded95d227e3e2fbd97364b4c7ca6",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/poisoned_half_blinking.png": "0b90d19c270047be3b1d502c3f2def99f5899ef430ed6bfff45cc35911e75076",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/poisoned_hardcore_full.png": "8900928082f3ca73c312b8bc78357f283d4101f7dc147f563a09a3ad4c3b8937",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/poisoned_hardcore_full_blinking.png": "354b9975971caf7f3ac28162b4ced0adc9edf9c11e82797bc13e4d9c5bc566bb",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/poisoned_hardcore_half.png": "a3462b71265ef37c0bacff741d106c23f341696ab922049933aaa7751e24c33d",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/poisoned_hardcore_half_blinking.png": "b705fe0b9811ded037b08d4a639710e8829a03d373b5e86087323c811f9654e3",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/vehicle_container.png": "b92355b32f566dd5ec209dcf6f25ddd239fdf967a70af42308ef9607aab3b194",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/vehicle_full.png": "7d676dd277663d3d87bdbe52632235946e484301fb47b6f8b27633d2d4efe1e3",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/vehicle_half.png": "e2171726715fba71f86b6fee9714ec8e3e3c08e75beda62bf411b1d363363ae9",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/withered_full.png": "5c5475bdb3a917b6414c1a1ea6400c1f00bdc1e5906d6781d6abbb9411747fa6",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/withered_full_blinking.png": "8d8f5c897fdd6e3f6e9ca1b7c4e55abbf3af9437ea24d094b80ce9e134a1b27d",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/withered_half.png": "a368bc604caa0aab258d1f9d4313db6031c5462361c41578dd718fae3f677a76",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/withered_half_blinking.png": "758ee154d7879a64c6903e0a0ecb9dd068ca16aef225a9ca8055f68d79ff8e17",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/withered_hardcore_full.png": "a6b4b53b39e85e5758b3ed99e4303f4f7f24d530e0f27510daa5b6c456ab725d",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/withered_hardcore_full_blinking.png": "0fd9f717cbad4111e10c0f848af41e7e66c92d3225fd2ef0b0df48fdf397306c",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/withered_hardcore_half.png": "3e069dcbca3a61492c6480a60c985f21ce39bf851ad870f69f8612689e2efd6b",
|
||||
"assets/minecraft/textures/gui/sprites/hud/heart/withered_hardcore_half_blinking.png": "c9bf1f18f28aad32777dacd1b38ff93263592648bd0a7b9cc2e6633597c625a7",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/join.png": "cdf278b928ac6ecf32b4c28304dcace1ef9d1df017d6bf22610896a41b71f39e",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/join_highlighted.png": "e92353fbd181db587f3c5c4e688638709e69487e9c9eae076918489ef8d2f99e",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/move_down.png": "5443d2f341d7f6ecf00d24759d21b9f11d3c00b6b3464cf3209aad1893fbec35",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/move_down_highlighted.png": "8bc3722212402866e0b83e30893e5681e0173843140d51d54d0381d81fda0923",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/move_up.png": "12a353f27c85943b9e6f78b0534ad2f0315f8fc76af8fbdf25f6ddad674566e1",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/move_up_highlighted.png": "6e0632d7b5cde7be54035ef65a2976f38a960b834ee36e0c7c5450d6f9ffaa4b",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/move_down.png": "829a14cbe49c70284572ba3f6a7fc812b401ed4bac910d46a4b37fa048dc4a0e",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/move_down_highlighted.png": "f0621fda65ad07e989776b554458e941adff9ca7b9f48a64f18840a04e104567",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/move_up.png": "63ffe9417b1ca97d27e6da8bc1d0817b8b282f1489c47a64cba90e9cd6601e81",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/move_up_highlighted.png": "6b7f7ea182728d8ddacd9930f97aa4b6ab69897008eaf655cb4ab00f1b094498",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/select.png": "77979b167e05e92423c35cc3ee72458710e5573833cbdc943ba11d8d50b1b294",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/select_highlighted.png": "2e7fefa4299a08ea3b21d08c23e0fdaa669b25ae24cdce519206abf59e0d68b3",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/unselect.png": "a0b5817ea41b15f72f725101d25581cf1ed5f78069d791d8f679f451e992e840",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/unselect_highlighted.png": "f49ff96621c3e997ef3582a23f8bd727d65d2483e7854545ee87c1ed4d3c3232",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button.png": "79fbfe504e72598ba5f61a8c8e5fe6dbc3ec6c8a530fd6089efee17f89fb7b10",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button.png.mcmeta": "ebdf3a9e4f6dfd37d72ad6af4213d9f61fe8f8342e8f12f00bb6888f3ae28acb",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button_disabled.png": "55f92a6efea542d73ea19ed26147b4ce3aabe405b56e37db0d3cc75dc18a1f47",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button_disabled.png.mcmeta": "979a3e114ffb0213932b71873db149c32a8c1c8ba65a4cbe5d30a1f677564fa2",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button_highlighted.png": "b658d1c9b38057aee2c63f49e19a734763eba720bd88be703ef9835c7f5f7f84",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button_highlighted.png.mcmeta": "ebdf3a9e4f6dfd37d72ad6af4213d9f61fe8f8342e8f12f00bb6888f3ae28acb",
|
||||
"assets/minecraft/textures/gui/sprites/widget/checkbox.png": "af426f3d1e5b32f74efc0aa522db6472a18ee4f2d87608acea04d3960168b5d9",
|
||||
"assets/minecraft/textures/gui/sprites/widget/checkbox_highlighted.png": "57ec86b031c3a480140a60799bde4ca1ba7ab9c14d8a7a37eb48ff64f01e29c6",
|
||||
"assets/minecraft/textures/gui/sprites/widget/checkbox_selected.png": "6d70776447e4ad66d6606b9f7d670d4aa18238429f9c68db51c7f10be01c12ec",
|
||||
"assets/minecraft/textures/gui/sprites/widget/checkbox_selected_highlighted.png": "96875351b998a1e7f730c6231ff4638c7df7a80986d7be94511878ec89c8fbfe",
|
||||
"assets/minecraft/textures/gui/sprites/widget/cross_button.png": "922c64fb3c504e39cbcdf874a0a91ce20b84041724dd8a5ddf318af7d24ca539",
|
||||
"assets/minecraft/textures/gui/sprites/widget/cross_button_highlighted.png": "016e049e726b24fad10fc9d40c85873af92ef02eec5b4ec9793665b80347e02d",
|
||||
"assets/minecraft/textures/gui/sprites/widget/locked_button.png": "9bf2c84eed89da70f7757aee27973834ab421d782ee60ea017e833eeceb46f93",
|
||||
"assets/minecraft/textures/gui/sprites/widget/locked_button_disabled.png": "e77079180fc95b0e75adc280ee0c916f2d315276103a8b88b7440b81e3ea3772",
|
||||
"assets/minecraft/textures/gui/sprites/widget/locked_button_highlighted.png": "9163318a63b1f5c31b115d644020fbeae5fc3e6fd231f877f4c35a82c4a3c414",
|
||||
"assets/minecraft/textures/gui/sprites/widget/page_backward.png": "af6220ed0297e43365a402b2e524ca4ce92f3e8f1b7e746e7f23a4ddf7a7b738",
|
||||
"assets/minecraft/textures/gui/sprites/widget/page_backward_highlighted.png": "ff927d1fd93dbbb226c1693cebd6510ca6a0a1cea526dbf67a7b59c60313af0c",
|
||||
"assets/minecraft/textures/gui/sprites/widget/page_forward.png": "8a2c12ad9dd5877a764e968aa3015f5a69e31bc55585edd788eac9c15bbaf4b9",
|
||||
"assets/minecraft/textures/gui/sprites/widget/page_forward_highlighted.png": "eb983beae120839fbb3c8c96c862beb24f97ccd172d1f31b7e44f3a9fa483fe2",
|
||||
"assets/minecraft/textures/gui/sprites/widget/scroller.png": "066af6679ba68b0e67335cb6ce48ca3e1a058c9b074411573d7ec0418bdea600",
|
||||
"assets/minecraft/textures/gui/sprites/widget/scroller.png.mcmeta": "f6a77d3537ff679b691e3d4b7bab0a1e076f71d1dec602bd65fa16a505092c53",
|
||||
"assets/minecraft/textures/gui/sprites/widget/scroller_background.png": "be03c9e0fcabe74d352539157353497497845669491fb5b2ac07ad27116df7f9",
|
||||
"assets/minecraft/textures/gui/sprites/widget/scroller_background.png.mcmeta": "f6a77d3537ff679b691e3d4b7bab0a1e076f71d1dec602bd65fa16a505092c53",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider.png": "55f92a6efea542d73ea19ed26147b4ce3aabe405b56e37db0d3cc75dc18a1f47",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider.png.mcmeta": "979a3e114ffb0213932b71873db149c32a8c1c8ba65a4cbe5d30a1f677564fa2",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_handle.png": "3b22d9afcea59fd265a75ef8ba547b8f56ae8d53ca7675ccb9bc963abfa3cccb",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_handle.png.mcmeta": "9ecf324f340ac117631939372d93ec59043f22d6caec7b7ea7c140b4d8d77c22",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_handle_highlighted.png": "a89e733c03068dad39c642738508b1e41f7c263e56b32939ff26f72db2fedcd6",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_handle_highlighted.png.mcmeta": "9ecf324f340ac117631939372d93ec59043f22d6caec7b7ea7c140b4d8d77c22",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_highlighted.png": "ba0d5d1e9b3bbb1af5311457a45372d2c89b5dac32a74de40815db945b45d2a5",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_highlighted.png.mcmeta": "979a3e114ffb0213932b71873db149c32a8c1c8ba65a4cbe5d30a1f677564fa2",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slot_frame.png": "8428676f5a55c8fb1f68d693b8b4e8809f03dfa00b062fd1005a307cff9c0a75",
|
||||
"assets/minecraft/textures/gui/sprites/widget/unlocked_button.png": "c4b7b0d01cc458e8d623af1b7cab971f98f5fc5f239cb0d1b0e90b7428406688",
|
||||
"assets/minecraft/textures/gui/sprites/widget/unlocked_button_disabled.png": "0e4e936c191d125ad2abc4d5e253fb9bab7a36d290abd998d752224a70cbff13",
|
||||
"assets/minecraft/textures/gui/sprites/widget/unlocked_button_highlighted.png": "36c81bc881f23d3d377128022c47e43403fe9848690c430ae8427198be0d6e1d",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/error.png": "e4e219517e9b8db2b018efc532f08de50b65019eb094aea0a8814f7d5358433d",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/error_highlighted.png": "37ac98fc99850c78f65ad3dc1b759777db030ca859c8047ffeb453491107b65e",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/join.png": "cb437430e8122c0f791d9834491e7cee11b3c514b25e809ae7f9578879be1e84",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/join_highlighted.png": "e92353fbd181db587f3c5c4e688638709e69487e9c9eae076918489ef8d2f99e",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/marked_join.png": "8732a1c1419bd4046ca8eec12d6c9fdc6ddbd764cce373e12e17a334aed039ed",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/marked_join_highlighted.png": "23c4e5b123be8b555ba8f130a03acde63e2a6fb48f1c1180a031fb077687b631",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/warning.png": "e4e219517e9b8db2b018efc532f08de50b65019eb094aea0a8814f7d5358433d",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/warning_highlighted.png": "49fee0502fab6ea38031664916ba328a1e4d68d8507761ba260efb67e640adde",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_0.png": "af5f25cd7db1b878806cacfc6eabb6a896e37f41340787db0de79d222ec1dbea",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_0.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_1.png": "3323296b81ce46d3dff93abc11289657cacaf9183b0f0663a8fa56d38ae436c8",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_1.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_2.png": "751f2aacfcb382dff3c57be00ece8e3a3cdfe0479374451ecbf70aa95d915b4d",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_2.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_3.png": "bffc916a16c3709d01e10870c329e8b915807177166f271e91ed72aa591d4f70",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_3.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_4.png": "d750f000ff1df1680e30c3c2c4e6921bdd5b69911e1d4e8aec527ef58841edce",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_4.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_5.png": "26f967071cde84299ba747dba7aa2db73784e3d89b158fb9092d0cddd45d215c",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_5.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/edition.png": "44d032976ac08bb01847de99d62cfea98298825fd75a36dbec319eba76081cbb",
|
||||
"assets/minecraft/textures/gui/title/minecraft.png": "efee73a6c812c89b2900d41f6446c0f906ccf85136b6c226daf42870a41a5157",
|
||||
"assets/sanctuary/items/zero_cape.json": "dcdcb599fe8f645ea27429da1c45ac5925b4672578f01cb6b93b2cd03f1f8b7f",
|
||||
"assets/sanctuary/models/item/zero_cape.json": "2e1c1b1030fd054d60cbc2544041bd80a44ac3f2ea89d098958666b97251b56a",
|
||||
"assets/sanctuary/textures/entity/cape/zero_cape.png": "f9a76537647989f9a0b6d001e320dac591c359e9e61a31f4ce11c88f207f0ad4",
|
||||
"assets/sanctuary/textures/gui/aptitudes/familiar.png": "c855f9cddb68cdb4a7110e70af60afbe068bc0176b9b6c69f38e052fcdbfda6c",
|
||||
"assets/sanctuary/textures/gui/aptitudes/familiarpower.png": "9ee75fd66f68bb2ce7b4158d2511ef3a9cd7a20f2b105520c4a4fcec2fee378b",
|
||||
"assets/sanctuary/textures/gui/aptitudes/hungerknown.png": "1e5cd57d12592f03dc2d2071a8c2873a30939f07b0db021fcb7fc34468709acc",
|
||||
"assets/sanctuary/textures/gui/aptitudes/names.png": "77dcea1c72970f87043b551c3506bfec64290b478a4d2981f280079d98158628",
|
||||
"assets/sanctuary/textures/gui/aptitudes/recipes.png": "88ec437776844c15104e0856af8fd768f511b951c79802a151d6ce4ffbbb4357",
|
||||
"assets/sanctuary/textures/gui/aptitudes/rest.png": "f343fd835ed20b9feb5195cd3727c0a3882090b33caf08c3f43120b250c8786e",
|
||||
"assets/sanctuary/textures/gui/aptitudes/sorting.png": "fa6689f4ffdac7ffa58900afc8b264e8106cde7a992167ada7e8debda4d09618",
|
||||
"assets/sanctuary/textures/gui/aptitudes/swim.png": "1e98150e08f2f47208ac0b14517760bddb55be4c013bfc3693dc474d951a9f9a",
|
||||
"assets/sanctuary/textures/gui/aptitudes/worldmap.png": "a413da0a34110d4b7d4422ca2f59defc1c6394184186068974a5ff6c68a4e4b0",
|
||||
"assets/sanctuary/textures/gui/aptitudes/zoom.png": "07e1c2dc51ff86e5f6903a5fc058603a886876600b01d124770336deefffa19f",
|
||||
"assets/sanctuary/textures/gui/food_details.png": "1ebc30ae7394393b5b7b8a0383102bcd035e5e92a602b89db05eeab66d2ef134",
|
||||
"assets/sanctuary/textures/gui/inventory/cape.png": "cd32c5f61d088740a11da8dd2767fda11f8db6b1fe65587a7c5b043177befb17",
|
||||
"assets/sanctuary/textures/gui/inventory/cosmetics.png": "fcb2bca5f01a6fa6028cf6748d06320acb02011d7a7a93103d5ee24be13dca4b",
|
||||
"assets/sanctuary/textures/gui/inventory/egg.png": "344a8624bd03b45472acb045fa607172d00dae603594bfa5e411d5f126fc0975",
|
||||
"assets/sanctuary/textures/gui/progression/breathing.png": "2b1bfe8e0a33ae957f87dc4086b848ac26e209a4b90998fce153b2f394201157",
|
||||
"assets/sanctuary/textures/gui/progression/building.png": "2d143ca35c5eac3d2a07943fe440b6893e189ff2389fd1b300d4f08bd98309ec",
|
||||
"assets/sanctuary/textures/gui/progression/heart.png": "224b4162d500a632fe6861613026941a7dcd0eee97987e0c92483733f2fa0a1e",
|
||||
"assets/sanctuary/textures/gui/progression/hunger.png": "9b3cb1e8206e7d17306fd461660b8ba154d729325f17181f6a045c175674ea97",
|
||||
"assets/sanctuary/textures/gui/progression/inventory.png": "d66b68dda35538639f956d670b3ff25e064b00ab022a6292ce8c0d3e3ab473a3",
|
||||
"assets/sanctuary/textures/gui/progression/mining.png": "c0134e182d63031ba2b53ab4477f8e1c96352375998c1c0bc54fc452816d2599",
|
||||
"assets/sanctuary/textures/item/cape/zero_cape.png": "d3386907d5cc90cdf1911c911faa26dbcb6ea16ce0dc7321abb482f3322255fb",
|
||||
"assets/sanctuary/textures/item/golden_wrench.png": "a97570908db75caf8e6fc2f5ecabf12c54fd99bfea7be634df66c4310dca22af",
|
||||
"pack.mcmeta": "70189fadacaa715704105dd1adea84bd4b4bd2ec650cf048644ab379707afeaf",
|
||||
"pack.png": "33adb8f689d55b7bf8d0824b02942ea3823f128b27fab458713ab9582061a1a0"
|
||||
},
|
||||
"import": {
|
||||
"folder": "Sanctuary-Resource-Pack-base-26-3",
|
||||
"files": {
|
||||
"FOOD.md": "e271c13d8044ee54b7bd9f0e34627480dab14df3f91a17e2a6fcb87e8cba0424",
|
||||
"HUD-SOURCES.json": "40b74fe75a581abdf7c621fbccd44a2acf1a8d2526266bb0b36ff703c404dd3c",
|
||||
"HUD.md": "300eb32ece21171818d97078cc16e9c341fbf19d84d70d33edc898c8479d7b7b",
|
||||
"README.md": "8ce801201583de8ac330e64973d61a4eaed4ecb89d58e31318c5d76883f1cc98",
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_empty.png": "80ea14a291330f3253fa4ba3c7aa5eeea73ac3df71c85eec649ae75762574cd2",
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_empty_hunger.png": "f9f725d5f76f09c9284ec8ef3eab3bab714b04306ea5b88d2774c6d96c65e525",
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_full.png": "29f5cddd23a6383973690101b3d1d7ab10e421b8bff8c5359e8d2623576e2f14",
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_full_hunger.png": "1c1969632df22e5c3be196ace904174e7797222b4d6514f442fcce0cc9fb2361",
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_half.png": "e5eda03de917caca665dc5ac5210fdf90af745cad0ee17ddccddc4365a7ee55a",
|
||||
"assets/minecraft/textures/gui/sprites/hud/food_half_hunger.png": "51be68df64c4d01c2dff3c300f9e1833bccbc19007d7a18b567a42160740fa8e",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/join.png": "cdf278b928ac6ecf32b4c28304dcace1ef9d1df017d6bf22610896a41b71f39e",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/join_highlighted.png": "e92353fbd181db587f3c5c4e688638709e69487e9c9eae076918489ef8d2f99e",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/move_down.png": "5443d2f341d7f6ecf00d24759d21b9f11d3c00b6b3464cf3209aad1893fbec35",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/move_down_highlighted.png": "8bc3722212402866e0b83e30893e5681e0173843140d51d54d0381d81fda0923",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/move_up.png": "12a353f27c85943b9e6f78b0534ad2f0315f8fc76af8fbdf25f6ddad674566e1",
|
||||
"assets/minecraft/textures/gui/sprites/server_list/move_up_highlighted.png": "6e0632d7b5cde7be54035ef65a2976f38a960b834ee36e0c7c5450d6f9ffaa4b",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/move_down.png": "829a14cbe49c70284572ba3f6a7fc812b401ed4bac910d46a4b37fa048dc4a0e",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/move_down_highlighted.png": "f0621fda65ad07e989776b554458e941adff9ca7b9f48a64f18840a04e104567",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/move_up.png": "63ffe9417b1ca97d27e6da8bc1d0817b8b282f1489c47a64cba90e9cd6601e81",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/move_up_highlighted.png": "6b7f7ea182728d8ddacd9930f97aa4b6ab69897008eaf655cb4ab00f1b094498",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/select.png": "77979b167e05e92423c35cc3ee72458710e5573833cbdc943ba11d8d50b1b294",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/select_highlighted.png": "2e7fefa4299a08ea3b21d08c23e0fdaa669b25ae24cdce519206abf59e0d68b3",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/unselect.png": "a0b5817ea41b15f72f725101d25581cf1ed5f78069d791d8f679f451e992e840",
|
||||
"assets/minecraft/textures/gui/sprites/transferable_list/unselect_highlighted.png": "f49ff96621c3e997ef3582a23f8bd727d65d2483e7854545ee87c1ed4d3c3232",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button.png": "79fbfe504e72598ba5f61a8c8e5fe6dbc3ec6c8a530fd6089efee17f89fb7b10",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button.png.mcmeta": "ebdf3a9e4f6dfd37d72ad6af4213d9f61fe8f8342e8f12f00bb6888f3ae28acb",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button_disabled.png": "55f92a6efea542d73ea19ed26147b4ce3aabe405b56e37db0d3cc75dc18a1f47",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button_disabled.png.mcmeta": "979a3e114ffb0213932b71873db149c32a8c1c8ba65a4cbe5d30a1f677564fa2",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button_highlighted.png": "b658d1c9b38057aee2c63f49e19a734763eba720bd88be703ef9835c7f5f7f84",
|
||||
"assets/minecraft/textures/gui/sprites/widget/button_highlighted.png.mcmeta": "ebdf3a9e4f6dfd37d72ad6af4213d9f61fe8f8342e8f12f00bb6888f3ae28acb",
|
||||
"assets/minecraft/textures/gui/sprites/widget/checkbox.png": "af426f3d1e5b32f74efc0aa522db6472a18ee4f2d87608acea04d3960168b5d9",
|
||||
"assets/minecraft/textures/gui/sprites/widget/checkbox_highlighted.png": "57ec86b031c3a480140a60799bde4ca1ba7ab9c14d8a7a37eb48ff64f01e29c6",
|
||||
"assets/minecraft/textures/gui/sprites/widget/checkbox_selected.png": "6d70776447e4ad66d6606b9f7d670d4aa18238429f9c68db51c7f10be01c12ec",
|
||||
"assets/minecraft/textures/gui/sprites/widget/checkbox_selected_highlighted.png": "96875351b998a1e7f730c6231ff4638c7df7a80986d7be94511878ec89c8fbfe",
|
||||
"assets/minecraft/textures/gui/sprites/widget/cross_button.png": "922c64fb3c504e39cbcdf874a0a91ce20b84041724dd8a5ddf318af7d24ca539",
|
||||
"assets/minecraft/textures/gui/sprites/widget/cross_button_highlighted.png": "016e049e726b24fad10fc9d40c85873af92ef02eec5b4ec9793665b80347e02d",
|
||||
"assets/minecraft/textures/gui/sprites/widget/locked_button.png": "9bf2c84eed89da70f7757aee27973834ab421d782ee60ea017e833eeceb46f93",
|
||||
"assets/minecraft/textures/gui/sprites/widget/locked_button_disabled.png": "e77079180fc95b0e75adc280ee0c916f2d315276103a8b88b7440b81e3ea3772",
|
||||
"assets/minecraft/textures/gui/sprites/widget/locked_button_highlighted.png": "9163318a63b1f5c31b115d644020fbeae5fc3e6fd231f877f4c35a82c4a3c414",
|
||||
"assets/minecraft/textures/gui/sprites/widget/page_backward.png": "af6220ed0297e43365a402b2e524ca4ce92f3e8f1b7e746e7f23a4ddf7a7b738",
|
||||
"assets/minecraft/textures/gui/sprites/widget/page_backward_highlighted.png": "ff927d1fd93dbbb226c1693cebd6510ca6a0a1cea526dbf67a7b59c60313af0c",
|
||||
"assets/minecraft/textures/gui/sprites/widget/page_forward.png": "8a2c12ad9dd5877a764e968aa3015f5a69e31bc55585edd788eac9c15bbaf4b9",
|
||||
"assets/minecraft/textures/gui/sprites/widget/page_forward_highlighted.png": "eb983beae120839fbb3c8c96c862beb24f97ccd172d1f31b7e44f3a9fa483fe2",
|
||||
"assets/minecraft/textures/gui/sprites/widget/scroller.png": "066af6679ba68b0e67335cb6ce48ca3e1a058c9b074411573d7ec0418bdea600",
|
||||
"assets/minecraft/textures/gui/sprites/widget/scroller.png.mcmeta": "f6a77d3537ff679b691e3d4b7bab0a1e076f71d1dec602bd65fa16a505092c53",
|
||||
"assets/minecraft/textures/gui/sprites/widget/scroller_background.png": "be03c9e0fcabe74d352539157353497497845669491fb5b2ac07ad27116df7f9",
|
||||
"assets/minecraft/textures/gui/sprites/widget/scroller_background.png.mcmeta": "f6a77d3537ff679b691e3d4b7bab0a1e076f71d1dec602bd65fa16a505092c53",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider.png": "55f92a6efea542d73ea19ed26147b4ce3aabe405b56e37db0d3cc75dc18a1f47",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider.png.mcmeta": "979a3e114ffb0213932b71873db149c32a8c1c8ba65a4cbe5d30a1f677564fa2",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_handle.png": "3b22d9afcea59fd265a75ef8ba547b8f56ae8d53ca7675ccb9bc963abfa3cccb",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_handle.png.mcmeta": "9ecf324f340ac117631939372d93ec59043f22d6caec7b7ea7c140b4d8d77c22",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_handle_highlighted.png": "a89e733c03068dad39c642738508b1e41f7c263e56b32939ff26f72db2fedcd6",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_handle_highlighted.png.mcmeta": "9ecf324f340ac117631939372d93ec59043f22d6caec7b7ea7c140b4d8d77c22",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_highlighted.png": "ba0d5d1e9b3bbb1af5311457a45372d2c89b5dac32a74de40815db945b45d2a5",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slider_highlighted.png.mcmeta": "979a3e114ffb0213932b71873db149c32a8c1c8ba65a4cbe5d30a1f677564fa2",
|
||||
"assets/minecraft/textures/gui/sprites/widget/slot_frame.png": "8428676f5a55c8fb1f68d693b8b4e8809f03dfa00b062fd1005a307cff9c0a75",
|
||||
"assets/minecraft/textures/gui/sprites/widget/unlocked_button.png": "c4b7b0d01cc458e8d623af1b7cab971f98f5fc5f239cb0d1b0e90b7428406688",
|
||||
"assets/minecraft/textures/gui/sprites/widget/unlocked_button_disabled.png": "0e4e936c191d125ad2abc4d5e253fb9bab7a36d290abd998d752224a70cbff13",
|
||||
"assets/minecraft/textures/gui/sprites/widget/unlocked_button_highlighted.png": "36c81bc881f23d3d377128022c47e43403fe9848690c430ae8427198be0d6e1d",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/error.png": "e4e219517e9b8db2b018efc532f08de50b65019eb094aea0a8814f7d5358433d",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/error_highlighted.png": "37ac98fc99850c78f65ad3dc1b759777db030ca859c8047ffeb453491107b65e",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/join.png": "cb437430e8122c0f791d9834491e7cee11b3c514b25e809ae7f9578879be1e84",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/join_highlighted.png": "e92353fbd181db587f3c5c4e688638709e69487e9c9eae076918489ef8d2f99e",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/marked_join.png": "8732a1c1419bd4046ca8eec12d6c9fdc6ddbd764cce373e12e17a334aed039ed",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/marked_join_highlighted.png": "23c4e5b123be8b555ba8f130a03acde63e2a6fb48f1c1180a031fb077687b631",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/warning.png": "e4e219517e9b8db2b018efc532f08de50b65019eb094aea0a8814f7d5358433d",
|
||||
"assets/minecraft/textures/gui/sprites/world_list/warning_highlighted.png": "49fee0502fab6ea38031664916ba328a1e4d68d8507761ba260efb67e640adde",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_0.png": "af5f25cd7db1b878806cacfc6eabb6a896e37f41340787db0de79d222ec1dbea",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_0.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_1.png": "3323296b81ce46d3dff93abc11289657cacaf9183b0f0663a8fa56d38ae436c8",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_1.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_2.png": "751f2aacfcb382dff3c57be00ece8e3a3cdfe0479374451ecbf70aa95d915b4d",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_2.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_3.png": "bffc916a16c3709d01e10870c329e8b915807177166f271e91ed72aa591d4f70",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_3.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_4.png": "d750f000ff1df1680e30c3c2c4e6921bdd5b69911e1d4e8aec527ef58841edce",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_4.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_5.png": "26f967071cde84299ba747dba7aa2db73784e3d89b158fb9092d0cddd45d215c",
|
||||
"assets/minecraft/textures/gui/title/background/panorama_5.png.mcmeta": "6de2cb00c45ccc7edc47a8ebac73b003af81877efa31c755e2ee89adb97e0f0f",
|
||||
"assets/minecraft/textures/gui/title/edition.png": "44d032976ac08bb01847de99d62cfea98298825fd75a36dbec319eba76081cbb",
|
||||
"assets/minecraft/textures/gui/title/minecraft.png": "efee73a6c812c89b2900d41f6446c0f906ccf85136b6c226daf42870a41a5157",
|
||||
"assets/sanctuary/items/zero_cape.json": "dcdcb599fe8f645ea27429da1c45ac5925b4672578f01cb6b93b2cd03f1f8b7f",
|
||||
"assets/sanctuary/models/item/zero_cape.json": "2e1c1b1030fd054d60cbc2544041bd80a44ac3f2ea89d098958666b97251b56a",
|
||||
"assets/sanctuary/textures/entity/cape/zero_cape.png": "f9a76537647989f9a0b6d001e320dac591c359e9e61a31f4ce11c88f207f0ad4",
|
||||
"assets/sanctuary/textures/gui/aptitudes/familiar.png": "c855f9cddb68cdb4a7110e70af60afbe068bc0176b9b6c69f38e052fcdbfda6c",
|
||||
"assets/sanctuary/textures/gui/aptitudes/familiarpower.png": "9ee75fd66f68bb2ce7b4158d2511ef3a9cd7a20f2b105520c4a4fcec2fee378b",
|
||||
"assets/sanctuary/textures/gui/aptitudes/hungerknown.png": "1e5cd57d12592f03dc2d2071a8c2873a30939f07b0db021fcb7fc34468709acc",
|
||||
"assets/sanctuary/textures/gui/aptitudes/names.png": "77dcea1c72970f87043b551c3506bfec64290b478a4d2981f280079d98158628",
|
||||
"assets/sanctuary/textures/gui/aptitudes/recipes.png": "88ec437776844c15104e0856af8fd768f511b951c79802a151d6ce4ffbbb4357",
|
||||
"assets/sanctuary/textures/gui/aptitudes/rest.png": "f343fd835ed20b9feb5195cd3727c0a3882090b33caf08c3f43120b250c8786e",
|
||||
"assets/sanctuary/textures/gui/aptitudes/sorting.png": "fa6689f4ffdac7ffa58900afc8b264e8106cde7a992167ada7e8debda4d09618",
|
||||
"assets/sanctuary/textures/gui/aptitudes/swim.png": "1e98150e08f2f47208ac0b14517760bddb55be4c013bfc3693dc474d951a9f9a",
|
||||
"assets/sanctuary/textures/gui/aptitudes/worldmap.png": "a413da0a34110d4b7d4422ca2f59defc1c6394184186068974a5ff6c68a4e4b0",
|
||||
"assets/sanctuary/textures/gui/aptitudes/zoom.png": "07e1c2dc51ff86e5f6903a5fc058603a886876600b01d124770336deefffa19f",
|
||||
"assets/sanctuary/textures/gui/food_details.png": "1ebc30ae7394393b5b7b8a0383102bcd035e5e92a602b89db05eeab66d2ef134",
|
||||
"assets/sanctuary/textures/gui/inventory/cape.png": "cd32c5f61d088740a11da8dd2767fda11f8db6b1fe65587a7c5b043177befb17",
|
||||
"assets/sanctuary/textures/gui/inventory/cosmetics.png": "fcb2bca5f01a6fa6028cf6748d06320acb02011d7a7a93103d5ee24be13dca4b",
|
||||
"assets/sanctuary/textures/gui/inventory/egg.png": "344a8624bd03b45472acb045fa607172d00dae603594bfa5e411d5f126fc0975",
|
||||
"assets/sanctuary/textures/gui/progression/breathing.png": "2b1bfe8e0a33ae957f87dc4086b848ac26e209a4b90998fce153b2f394201157",
|
||||
"assets/sanctuary/textures/gui/progression/building.png": "2d143ca35c5eac3d2a07943fe440b6893e189ff2389fd1b300d4f08bd98309ec",
|
||||
"assets/sanctuary/textures/gui/progression/heart.png": "224b4162d500a632fe6861613026941a7dcd0eee97987e0c92483733f2fa0a1e",
|
||||
"assets/sanctuary/textures/gui/progression/hunger.png": "9b3cb1e8206e7d17306fd461660b8ba154d729325f17181f6a045c175674ea97",
|
||||
"assets/sanctuary/textures/gui/progression/inventory.png": "d66b68dda35538639f956d670b3ff25e064b00ab022a6292ce8c0d3e3ab473a3",
|
||||
"assets/sanctuary/textures/gui/progression/mining.png": "c0134e182d63031ba2b53ab4477f8e1c96352375998c1c0bc54fc452816d2599",
|
||||
"assets/sanctuary/textures/item/cape/zero_cape.png": "d3386907d5cc90cdf1911c911faa26dbcb6ea16ce0dc7321abb482f3322255fb",
|
||||
"pack.mcmeta": "62d437b8b38a48162b3602b37d4492e239575099bdd22998ff6930afa41e5f1a",
|
||||
"pack.png": "33adb8f689d55b7bf8d0824b02942ea3823f128b27fab458713ab9582061a1a0"
|
||||
}
|
||||
},
|
||||
"changes": [
|
||||
"Golden wrench: original creator PNG, unchanged bytes.",
|
||||
"Editable personal template exports current Sanctuary assets overlaid by the built-in pack."
|
||||
],
|
||||
"golden_wrench_source": {
|
||||
"file": "golden_wrench.png",
|
||||
"sha256": "a97570908db75caf8e6fc2f5ecabf12c54fd99bfea7be634df66c4310dca22af",
|
||||
"size": [
|
||||
16,
|
||||
16
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
# Sanctuary — votre pack personnel / your personal pack
|
||||
|
||||
Cette copie est à vous : Sanctuary ne la réécrit jamais lors d'une mise à jour.
|
||||
Dans Options → Packs de ressources, activez **Sanctuary-Custom** et placez-le
|
||||
au-dessus de « Textures Sanctuary » et des autres packs à remplacer.
|
||||
|
||||
1. Modifiez les fichiers dans `assets/`, en gardant leurs noms et chemins.
|
||||
2. Rechargez avec F3 + T (ou désactivez puis réactivez le pack).
|
||||
3. Pour revenir à l'apparence fournie par Sanctuary, désactivez votre pack.
|
||||
|
||||
Quelques points de départ :
|
||||
|
||||
| Ressource | Chemin |
|
||||
| --- | --- |
|
||||
| Clé dorée | `assets/sanctuary/textures/item/golden_wrench.png` |
|
||||
| Briques et argiles colorées | `assets/sanctuary/textures/block/` |
|
||||
| Menus et icônes Sanctuary | `assets/sanctuary/textures/gui/` |
|
||||
| Vie et faim | `assets/minecraft/textures/gui/sprites/hud/` |
|
||||
| Modèles des objets | `assets/sanctuary/models/item/` et `assets/sanctuary/items/` |
|
||||
| Traductions | `assets/sanctuary/lang/fr_fr.json` et `en_us.json` |
|
||||
|
||||
Les PNG peuvent garder la transparence. Préservez les dimensions des atlas de
|
||||
menus et les découpes utilisées par le jeu. Vous pouvez également ajouter
|
||||
des remplacements Minecraft dans `assets/minecraft/` ; cette base ne contient
|
||||
pas toutes les ressources du jeu ni celles des autres mods.
|
||||
|
||||
Cette copie est un instantané complet des ressources Sanctuary à sa création.
|
||||
**Supprimez de votre copie les fichiers que vous ne personnalisez pas** pour
|
||||
laisser les prochaines mises à jour du mod s'appliquer à ces ressources.
|
||||
Pour repartir d'une base récente, renommez votre dossier personnel puis
|
||||
cliquez de nouveau sur « Personnaliser Sanctuary » : l'ancien reste intact.
|
||||
Le bouton devient « Ouvrir ma copie » tant que `Sanctuary-Custom` existe.
|
||||
N'éditez pas directement le JAR du mod.
|
||||
|
||||
Le pack modifie l'apparence, les modèles, les sons et les textes disponibles
|
||||
dans `assets/` ; les règles de jeu, sauvegardes et recettes côté serveur restent
|
||||
dans le mod. Les crédits figurent dans `THIRD_PARTY_NOTICES.md`.
|
||||
|
||||
## English
|
||||
|
||||
This is your editable copy. Sanctuary updates never overwrite it. Enable
|
||||
**Sanctuary-Custom** in Options → Resource Packs and move it above Sanctuary
|
||||
textures and any other pack you want to override. Edit files under `assets/`
|
||||
without renaming their resource paths, then reload with F3 + T or toggle the
|
||||
pack off and on. Disable this pack to return to Sanctuary's supplied appearance.
|
||||
|
||||
The table above lists useful starting paths. Keep GUI atlas dimensions and
|
||||
sprite layouts intact. You can add Minecraft overrides under `assets/minecraft/`;
|
||||
this template does not include every vanilla asset or other mods' resources.
|
||||
|
||||
This is a full snapshot of Sanctuary's assets when created. **Delete any files
|
||||
you do not customize** so future mod updates can supply them. To start a fresh
|
||||
copy later, rename your existing folder and click “Customize Sanctuary” again.
|
||||
“Open my copy” opens the existing folder without changing it. Do not edit the
|
||||
mod JAR. This pack changes client resources, not server gameplay or save data.
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"pack": {
|
||||
"description": {
|
||||
"translate": "sanctuary.resource_pack.custom_description",
|
||||
"fallback": "Sanctuary — your editable copy"
|
||||
},
|
||||
"min_format": [97, 1],
|
||||
"max_format": [97, 1]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user