Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de6e11848a | ||
|
|
303210124e | ||
|
|
980544e279 | ||
|
|
7ef4d51a59 | ||
|
|
7b56c3cd78 | ||
|
|
bb61547b07 | ||
|
|
ebd35184b7 | ||
|
|
359f49792b | ||
|
|
4cbf350e01 | ||
|
|
03935e9c89 | ||
|
|
72c709ebc7 | ||
|
|
addc4bb4db | ||
|
|
228a953475 | ||
|
|
91db17450a | ||
|
|
36941f611f | ||
|
|
9dd63f683a | ||
|
|
60bc773b80 | ||
|
|
9fd380b782 | ||
|
|
ca5e350e4a | ||
|
|
feaf9ca62c | ||
|
|
a60937f77d | ||
|
|
930719d75e | ||
|
|
394e22d805 | ||
|
|
ed11cd77a5 | ||
|
|
d78d39cd1b | ||
|
|
77fdabfb4d | ||
|
|
fb8705369e | ||
|
|
c3d1443732 | ||
|
|
1967342247 |
@@ -1,7 +1,7 @@
|
||||
# Sanctuary
|
||||
|
||||
[Dernière release : beta.297](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.297) ·
|
||||
[Reçu de livraison](docs/release-beta297.md) ·
|
||||
[Dernière release : beta.315](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.315) ·
|
||||
[Reçu de livraison](docs/release-beta315.md) ·
|
||||
[Installation et mises à jour](docs/packwiz.md).
|
||||
|
||||
Les **quatre expansions validées sont disponibles dans Sanctuary normal** :
|
||||
@@ -10,6 +10,235 @@ Créer un monde **Sanctuary** en Petit, Moyen ou Grand, puis activer les ancres
|
||||
correspondantes. Le module Sanctuary Test n’est pas requis.
|
||||
[Intégration et vérification du pack beta.224](docs/four-islands-base224.md).
|
||||
|
||||
## beta.318 — cuisiner les produits séchés (préparation locale)
|
||||
|
||||
[Les 18 nouvelles recettes et leur fonctionnement](docs/dried-cuisine-beta318.md) :
|
||||
pains, biscuits, sandwichs, plats au chaudron, salade, dessert, provisions et épices.
|
||||
Le saucisson pimenté se prépare, sèche puis se pose pour partager quatre tranches.
|
||||
Manger épicé produit deux petits coups visuels et sonores sans dégât.
|
||||
[Ingrédients, quantités, appareils et durées](docs/wiki/alimentation/sechoir.md).
|
||||
Même branche locale. [Installée dans Prism pour les essais](docs/local-beta318.md),
|
||||
sans publication. Build et packs vérifiés, **189 tests serveur** et parcours
|
||||
**Vulkan avec Sodium** réussis.
|
||||
|
||||
## beta.317 — produits séchés (préparation locale)
|
||||
|
||||
[Viande, poissons, fruits, herbes et vanille au séchoir](docs/drying-foods-beta317.md) :
|
||||
15 recettes avec leurs propres durées, 12 objets supplémentaires, quatre crochets
|
||||
indépendants. Les herbes et piments séchés rejoignent la cuisine existante.
|
||||
[Recettes et 18 propositions de combinaisons](docs/wiki/alimentation/sechoir.md).
|
||||
Même branche locale, sans publication ni installation. Build et packs vérifiés,
|
||||
**179 tests serveur** et parcours **Vulkan avec Sodium** réussis.
|
||||
|
||||
## beta.316 — découverte alimentaire en survie (préparation locale)
|
||||
|
||||
[IA-09 : récolter et rapporter les premières semences](docs/foraging-beta316.md).
|
||||
Plantes sauvages, fruitiers, café et vanille répartis par habitat sur les neuf îles ;
|
||||
solutions de découverte dans les terrains déjà explorés, sans repeuplement.
|
||||
[Guide et pages des îles](docs/wiki/alimentation/README.md) ·
|
||||
[Planche illustrée](docs/wiki/alimentation/planche.html). Même branche locale ;
|
||||
aucune publication ou installation par ce ticket.
|
||||
Build et packs vérifiés, **85 tests serveur** et parcours **Vulkan avec Sodium**
|
||||
réussis ; premières récoltes et sauvegarde sur le preset Sanctuary actuel.
|
||||
|
||||
## beta.315 — cuisine et agriculture livrées
|
||||
|
||||
[Livraison complète vérifiée](docs/release-beta315.md) : les incréments locaux
|
||||
beta.299 à beta.315 sont publiés et installés sur la Beta dédiée, le site et
|
||||
la même instance Prism. **187 tests serveur** et artefacts vérifiés ;
|
||||
sauvegardes et réglages conservés. [Textures modifiables](docs/itsalive-textures-export-beta315.md).
|
||||
|
||||
### Saucisson posé en tranches
|
||||
|
||||
[Poser le saucisson séché et partager quatre tranches](docs/sausage-slices-beta315.md).
|
||||
Même objet après le séchoir, quatre orientations, modèle qui raccourcit et
|
||||
chair visible sur la coupe. Deux points de nourriture par tranche, sons natifs
|
||||
et parts restantes conservées après sauvegarde. Préparation locale vérifiée avant cette publication. Build et packwiz, **134 tests serveur** et deux
|
||||
parcours **Vulkan avec Sodium** réussis : séchoir, pose payée, quatre prises,
|
||||
sauvegarde orientée, seize états de rendu et sons natifs.
|
||||
|
||||
## beta.314 — fromage à partager et vins de garde (préparation locale)
|
||||
|
||||
[Poser et partager le fromage, choisir la qualité du vin](docs/cheese-wine-beta314.md).
|
||||
Le fromage garde son objet historique et se mange en sept parts posées,
|
||||
comme un gâteau. Le vin jeune garde ses trois minutes de fermentation ;
|
||||
le laisser dans le tonneau donne du vin de table après **un jour réel**,
|
||||
de réserve après **trois jours** et un grand cru après **sept jours**,
|
||||
même jeu fermé. Quatre objets/qualités, HUD FR/EN, recettes JEI et sortie
|
||||
compatible avec les entonnoirs. Même branche locale, sans publication ou
|
||||
installation. Build et packwiz, **126 tests serveur** et parcours **Vulkan
|
||||
avec Sodium** réussis : sept parts, face de coupe, sauvegarde, jours réels,
|
||||
qualités, collecte et consommation.
|
||||
|
||||
## beta.313 — saucisson au séchoir (préparation locale)
|
||||
|
||||
[Préparer le saucisson, puis le suspendre cinq minutes](docs/drying-rack-beta313.md).
|
||||
Séchoir en bois sans combustible ni menu, quatre crochets indépendants,
|
||||
pluie et immersion qui suspendent le séchage, toit protecteur et reprise
|
||||
après sauvegarde. Recettes à l’établi, modèles cubiques aux textures vanilla,
|
||||
HUD FR/EN et découverte dans JEI. Même branche locale, sans publication ou
|
||||
installation. Build et packwiz, **112 tests serveur** et parcours
|
||||
**Vulkan avec Sodium** réussis : fabrication, quatre crochets, humidité,
|
||||
sauvegarde, collecte et consommation.
|
||||
|
||||
## beta.312 — recettes répétées au même chaudron (préparation locale)
|
||||
|
||||
[Un premier coup de bâton mémorise la recette](docs/cauldron-repeat-beta312.md).
|
||||
Réapprovisionner avec les mêmes ingrédients et servir les plats ne demandent
|
||||
plus de recommencer. Les ajouts en cours attendent leur lot complet ; la
|
||||
sortie s’accumule et une sortie pleine attend de la place. Maj-clic à main
|
||||
vide arrête la série. Même branche locale, sans publication ou installation.
|
||||
Build et packwiz, **100 tests serveur** et parcours **Vulkan avec Sodium**
|
||||
réussis : quatre lots après un geste, réassort, service, rechargement et arrêt.
|
||||
|
||||
## beta.311 — chaudron préparé puis remué (préparation locale)
|
||||
|
||||
[Ajouter les ingrédients, puis remuer avec un bâton](docs/cauldron-beta311.md).
|
||||
Le chaudron attend le geste, conserve les mélanges incomplets et rend le seau
|
||||
du lait seulement à la cuisson terminée. Toute modification demande de
|
||||
remuer à nouveau ; les restes ne lancent pas une nouvelle recette. Sons
|
||||
natifs discrets à l’ajout, au geste, pendant la cuisson et au service.
|
||||
Même branche locale, sans publication ou installation. Build et packwiz,
|
||||
**89 tests serveur** et parcours **Vulkan avec Sodium et contrôle audio**
|
||||
réussis.
|
||||
|
||||
## beta.310 — poêles en ligne et piles de quatre (préparation locale)
|
||||
|
||||
[Une recette commune par pile de une à quatre poêles](docs/pan-stacks-beta310.md).
|
||||
Chaque portion paie ses ingrédients, y compris les seaux de lait conservés
|
||||
dans des réserves natives. Des piles voisines peuvent cuire des plats
|
||||
différents, chacune avec sa chaleur par-dessous. Contenu visible, HUD FR/EN,
|
||||
casse et continuité des anciennes cases documentés. Même branche locale,
|
||||
sans publication ou installation. Build et packwiz, **76 tests serveur**
|
||||
et parcours **Vulkan avec Sodium** réussis.
|
||||
|
||||
## beta.309 — une seule sortie de cuisinière (préparation locale)
|
||||
|
||||
[La cuisinière garde une seule sortie, comme un four](docs/oven-single-output-beta309.md).
|
||||
La case d'aperçu disparaît. La grille se remplit à la main ; un entonnoir
|
||||
dessous peut récupérer les plats cuits. Consommation en fin de cuisson,
|
||||
annulation des modifications et poursuite avec le menu fermé conservées.
|
||||
Même branche locale, sans publication ou installation.
|
||||
Build et packwiz, **63 tests serveur** et parcours **Vulkan avec Sodium**
|
||||
réussis ; sortie unique, entonnoirs et décalage du feu vérifiés.
|
||||
|
||||
## beta.308 — cuisinière automatique (préparation locale)
|
||||
|
||||
[Grille de préparation et cuisson comme un four](docs/oven-beta308.md) :
|
||||
aperçu séparé, ingrédients consommés uniquement en fin de cuisson, annulation
|
||||
si la grille change et lots successifs avec le menu fermé. Treize recettes de
|
||||
cuisine, combustible natif et sortie réservée aux plats terminés.
|
||||
Même branche locale, sans publication ou installation.
|
||||
Build et packwiz, **63 tests serveur** et parcours **Vulkan avec Sodium**
|
||||
réussis : grille modifiable, annulation, lots avec le menu fermé, menus FR/EN,
|
||||
sortie et rechargement réel.
|
||||
|
||||
## beta.307 — recette du moulin manuel (préparation locale)
|
||||
|
||||
[La recette remplace la redstone par un bâton](docs/mill-recipe-beta307.md)
|
||||
pour représenter la poignée, avec les trois dalles de pierre lisse, deux
|
||||
lingots de cuivre et un tailleur de pierre. Elle rejoint la catégorie Divers
|
||||
du livre de fabrication natif. Même branche locale, sans publication ou
|
||||
installation.
|
||||
Build et packwiz, **52 tests serveur** et comparaison des archives réussis.
|
||||
|
||||
## beta.306 — moulin manuel (préparation locale)
|
||||
|
||||
[Le moulin fonctionne comme le tailleur de pierre](docs/mill-beta306.md) :
|
||||
entrée et résultat immédiat, sans combustible. Prendre le résultat consomme
|
||||
l'ingrédient ; Maj-clic traite les lots disponibles. Le feu disparaît du menu,
|
||||
avec un bref mouvement mécanique à chaque utilisation. Nom **Moulin / Mill**,
|
||||
neuf recettes conservées et anciens objets stockés récupérables.
|
||||
Même branche locale, sans publication ou installation.
|
||||
Build et packwiz, **52 tests serveur** et parcours **Vulkan avec Sodium**
|
||||
réussis : prises natives, clic droit, lots, menus FR/EN et rechargement.
|
||||
|
||||
## beta.305 — écrasement direct des fruits (préparation locale)
|
||||
|
||||
[Les blocs de tomates, raisins et olives s'écrasent en sautant dessus](docs/crushing-beta305.md) :
|
||||
huit paliers descendants, résidus progressifs et neuf portions au total.
|
||||
Un piston libère immédiatement le reste ; le super piston traite jusqu'à
|
||||
27 blocs sur sa course complète. La presse quitte fabrication, créatif et JEI.
|
||||
Même branche locale, sans publication ou installation.
|
||||
Build et packwiz, **78 tests serveur**, parcours d'écrasement et régression
|
||||
complète du super piston sous **Vulkan avec Sodium** réussis.
|
||||
|
||||
## beta.304 — appareils culinaires (préparation locale)
|
||||
|
||||
[Six appareils et 80 recettes physiques portés en 26.3](docs/kitchen-beta304.md) :
|
||||
poêle, chaudron vanilla qui devient une marmite, cuisinière 3 × 3, tonneau de
|
||||
fermentation, moulin et pressoir à piston. Récipients conservés, lots limités,
|
||||
contenus visibles et progression sauvegardée. La fermentation instantanée de
|
||||
beta.303 cède la place au tonneau. Même branche locale, sans publication.
|
||||
Les dix nouvelles entrées rejoignent Nourriture et boissons, sans nouvel onglet.
|
||||
Build et packwiz, **72 tests serveur** et parcours **Vulkan avec Sodium**
|
||||
réussis : appareils, JEI, menus FR/EN GUI 2/3/4, sauvegardes et régressions
|
||||
cuisine/agriculture.
|
||||
|
||||
## beta.303 — recettes de cuisine (préparation locale)
|
||||
|
||||
[Les 92 recettes alimentaires de la 26.2 utilisent les nouvelles récoltes](docs/cuisine-beta303.md).
|
||||
Salades, soupes, plats, pains, gâteaux, boissons et ingrédients intermédiaires.
|
||||
Préparation à la table, cuisson au four/fumoir ; transformations de fermentation
|
||||
adaptées en fabrication manuelle immédiate. 129 objets dans Nourriture et
|
||||
boissons, sans nouvel onglet. Même branche locale, sans publication.
|
||||
Les appareils culinaires restent dans leurs tickets.
|
||||
Build, packwiz et **55 tests serveur** réussis. Parcours **Vulkan avec Sodium**
|
||||
validé : fabrication, fumoir, 196 étapes dans JEI, 129 modèles, onglet natif
|
||||
FR/EN GUI 2/3/4 et rechargement ; régression agricole réussie.
|
||||
|
||||
## beta.302 — vanille en 16 × 16 (préparation locale)
|
||||
|
||||
[Les quatre stades de vanille passent en 16 × 16 pixels](docs/vanilla-16px-beta302.md).
|
||||
Dessin simplifié pour cette résolution, feuillage, fleurs et gousses sur un
|
||||
seul plan contre le tronc. Même branche `codex/itsalive-26-3`, sans publication.
|
||||
Dimensions vérifiées dans le JAR et dans l'atlas chargé par Minecraft.
|
||||
Build et packwiz, 46 tests serveur et parcours Vulkan avec Sodium réussis.
|
||||
|
||||
## beta.301 — vanille grimpante (préparation locale)
|
||||
|
||||
[La vanille s'attache aux troncs comme les lianes et pousse vers le haut](docs/vanilla-beta301.md).
|
||||
Faces fines contre l'écorce, nouvelle texture réunissant feuilles et gousses
|
||||
sur un seul plan, appui requis pour chaque segment, maturité et poudre d'os.
|
||||
Elle s'arrête aux obstacles et à la fin du tronc.
|
||||
Bouture dans Naturel, gousses dans Nourriture et boissons, sans nouvel onglet.
|
||||
Même branche locale `codex/itsalive-26-3`, sans publication ou installation.
|
||||
Build et packwiz, **46 tests serveur** et parcours **Vulkan avec Sodium**
|
||||
réussis, avec contrôle du plan unique et rechargement réel de 24 segments.
|
||||
|
||||
## beta.300 — cultures, arbres fruitiers et caféier (préparation locale)
|
||||
|
||||
[Les 20 cultures d'It's Alive ! deviennent jouables en 26.3](docs/agriculture-beta300.md),
|
||||
avec le citronnier, l'olivier et le caféier. Les 45 objets rejoignent les
|
||||
onglets vanilla Naturel et Nourriture et boissons, sans nouvelle page créative.
|
||||
Semences, croissance, récoltes pérennes, plants hauts, riz immergé, fruits et
|
||||
grains de café. Les textures fournies sont conservées, les libellés sont FR/EN.
|
||||
Quinze cultures, dont l'oignon et l'ail, utilisent deux plans en croix pour
|
||||
mieux montrer le pied ; les PNG restent intacts.
|
||||
Même branche locale `codex/itsalive-26-3` ; apparition naturelle, vanille
|
||||
grimpante et appareils culinaires restent dans leurs prochains tickets.
|
||||
Aucune publication ni mise à jour d'installation.
|
||||
Build, **34 tests serveur**, packwiz et parcours **Vulkan avec Sodium**
|
||||
réussis : modèles, onglets vanilla FR/EN GUI 2/3/4 et rechargement réel.
|
||||
|
||||
## beta.299 — textures d'It's Alive ! (préparation locale)
|
||||
|
||||
[Premier lot de la refonte cuisine et agriculture pour 26.3](docs/itsalive-26-3.md) :
|
||||
343 textures importées, dont les 38 PNG fournis prioritaires et conservés
|
||||
sans retouche. Cultures, citronnier, olivier, caféier, ingrédients et appareils
|
||||
disposent de leurs sources graphiques ; leur fonctionnement reste à porter.
|
||||
Le suivi conserve les tickets chaudron, plats ratés, séchoir, poêle, vanille
|
||||
grimpante et cuisinière. Branche locale `codex/itsalive-26-3`, sans publication.
|
||||
Build, 13 tests serveur ciblés, contenu du JAR et assemblage packwiz vérifiés.
|
||||
|
||||
## beta.298 — normales de l'ancre corrigées
|
||||
|
||||
[Retrait du petit cadre résiduel dans la passe PBR](docs/media-anchor-normals-beta298.md).
|
||||
Le modèle invisible de l'ancre ne laisse plus de reflet sur la vidéo
|
||||
avec les shaders Sanctuary activés.
|
||||
[Livraison vérifiée](docs/release-beta298.md) : serveur Beta, site et même
|
||||
instance Prism à jour ; écrans existants et réglages conservés.
|
||||
|
||||
## beta.297 — correctifs des cadres disponibles
|
||||
|
||||
[Menu de taille à la pose, lecteur au Maj + clic droit et cadre en volume](docs/media-frame-fixes-beta297.md).
|
||||
@@ -3870,8 +4099,9 @@ sauvegarde 26.2 : aucun outil de migration de ses chunks n'est livré ici.
|
||||
| `.gitea/ISSUE_TEMPLATE/` | Modèles de tickets fonctionnalités et bugs |
|
||||
| `.gitea/workflows/` | Vérification sur un runner Gitea compatible |
|
||||
|
||||
It's Alive !, Only Fun et Master Key restent des modules autonomes prévus par la
|
||||
vision. Leurs sources 26.2 ne sont pas copiées dans ce socle. Fabric API est
|
||||
La [refonte d'It's Alive !](docs/itsalive-26-3.md) reprend les textures et
|
||||
l'agriculture dans Sanctuary ; ses appareils et sa cuisine restent à porter. Only Fun
|
||||
et Master Key restent prévus par la vision. Fabric API est
|
||||
requise ; Demeure appartient au code interne de Sanctuary. Depuis beta.280,
|
||||
le port source de JEI pour 26.3 est embarqué dans le JAR Sanctuary pour le
|
||||
client et le serveur. Sound Physics et Sodium restent facultatifs côté client.
|
||||
|
||||
@@ -1,5 +1,182 @@
|
||||
# Provenance et crédits
|
||||
|
||||
## Saucisson posé en tranches, beta.315 locale
|
||||
|
||||
Le bloc de saucisson, ses quatre longueurs et orientations sont développés
|
||||
dans Sanctuary 26.3. Les modèles cubiques originaux reprennent les références
|
||||
natives du modèle d’objet beta.313 : terre cuite rouge, laine blanche et bloc
|
||||
de champignon rouge pour la coupe. Aucun PNG ni fichier audio Minecraft
|
||||
n’est copié ; le son natif de mastication est référencé. Les modèles d’objets
|
||||
cru/séché et leur affichage au séchoir sont conservés. Voir
|
||||
[comportement et continuité](docs/sausage-slices-beta315.md).
|
||||
|
||||
## Fromage à partager et élevage du vin, beta.314 locale
|
||||
|
||||
Le nouveau bloc de fromage réutilise les règles du gâteau de Minecraft 26.3
|
||||
par héritage, avec des modèles cubiques originaux Sanctuary pour ses sept
|
||||
états et des références aux textures natives de terre cuite jaune et pierre
|
||||
de l’End. Les icônes historiques de fromage et de vin sont conservées ; les
|
||||
trois qualités ajoutées héritent du modèle d’objet du vin et distinguent
|
||||
leurs noms, raretés et saturation. Aucun PNG, son ou source Minecraft n’est
|
||||
copié. Le calcul des jours réels et les recettes d’élevage sont développés
|
||||
dans Sanctuary. Voir [comportement et continuité](docs/cheese-wine-beta314.md).
|
||||
|
||||
## Séchoir et saucisson, beta.313 locale
|
||||
|
||||
Le séchoir, ses modèles cubiques, les deux états du saucisson, la préparation
|
||||
à l’établi et le séchage sont développés dans Sanctuary 26.3. Ils réutilisent
|
||||
les ingrédients du catalogue culinaire historique et des textures Minecraft
|
||||
référencées : chêne écorcé, terre cuite rose/rouge, laine blanche et bloc de
|
||||
champignon rouge. Aucun fichier de texture ni son Minecraft n’est copié.
|
||||
Les événements sonores natifs d’ajout/retrait de cadre et de récupération
|
||||
d’objet sont référencés sans nouveau fichier audio. Voir
|
||||
[comportement, provenance et continuité](docs/drying-rack-beta313.md).
|
||||
|
||||
## Répétition au chaudron, beta.312 locale
|
||||
|
||||
Sanctuary mémorise la recette autorisée au bâton, enchaîne ses lots et conserve
|
||||
les ingrédients réapprovisionnés pour leurs cycles complets. Le chaudron, ses
|
||||
19 recettes et ses ressources restent issus du port de `26.2/itsalive`
|
||||
(commit `26f2c2a0061619df1f146f9081d6740c560a7497`, GPL-3.0-or-later).
|
||||
Aucune recette, texture ou piste audio n’est modifiée ; les sons natifs de
|
||||
beta.311 restent utilisés une fois par événement. Voir
|
||||
[comportement et continuité](docs/cauldron-repeat-beta312.md).
|
||||
|
||||
## Chaudron remué, beta.311 locale
|
||||
|
||||
Le chaudron et ses 19 recettes proviennent du port de `26.2/itsalive`
|
||||
(commit `26f2c2a0061619df1f146f9081d6740c560a7497`, GPL-3.0-or-later).
|
||||
Sanctuary remplace le délai d’échec par une préparation lancée au bâton,
|
||||
conserve les récipients réels jusqu’au paiement final et précise la capture
|
||||
dans l’ouverture. Les sons référencent les événements natifs de Minecraft
|
||||
26.3, sans copie de fichier audio ni modification de texture. Voir
|
||||
[fonctionnement et continuité](docs/cauldron-beta311.md).
|
||||
|
||||
## Piles de poêles, beta.310 locale
|
||||
|
||||
La poêle, sa texture et ses 26 recettes restent issues du port de
|
||||
`26.2/itsalive` (commit `26f2c2a0061619df1f146f9081d6740c560a7497`,
|
||||
GPL-3.0-or-later). Sanctuary ajoute une pose native par couches de quatre
|
||||
pixels, une préparation commune et des réserves réelles pour payer chaque
|
||||
portion. Les trois modèles supérieurs translatent la géométrie historique
|
||||
avec ses UV conservés ; aucun PNG ne change. Voir
|
||||
[fonctionnement et contrat de continuité](docs/pan-stacks-beta310.md).
|
||||
|
||||
## Sortie unique de cuisinière, beta.309 locale
|
||||
|
||||
La correction Sanctuary retire la case d'aperçu et l'alimentation de la grille
|
||||
par entonnoir, tout en conservant l'extraction des plats et le cycle culinaire
|
||||
porté de `26.2/itsalive` (GPL-3.0-or-later, même reçu beta.304).
|
||||
L'interface utilise les panneaux et sprites vanilla existants, sans retouche
|
||||
de PNG. Voir [comportement et continuité](docs/oven-single-output-beta309.md).
|
||||
|
||||
## Cycle de cuisinière et aperçu, beta.308 locale
|
||||
|
||||
La cuisinière et ses treize recettes restent issues du port de `26.2/itsalive`
|
||||
(commit `26f2c2a0061619df1f146f9081d6740c560a7497`, GPL-3.0-or-later).
|
||||
Sanctuary renforce l'annulation sur modification de grille, sépare l'aperçu
|
||||
virtuel des plats cuits et conserve l'inventaire natif et ses compteurs.
|
||||
L'interface réutilise les panneaux et sprites Minecraft de l'établi et du four ;
|
||||
aucun PNG n'est retouché. Voir [cycle et continuité](docs/oven-beta308.md).
|
||||
|
||||
## Moulin manuel, beta.306 locale
|
||||
|
||||
Le moulin et ses neuf recettes proviennent du port culinaire de
|
||||
`26.2/itsalive`, commit `26f2c2a0061619df1f146f9081d6740c560a7497`,
|
||||
GPL-3.0-or-later. Sanctuary remplace le traitement au combustible par un poste
|
||||
manuel inspiré du tailleur de pierre vanilla 26.3. L'interface réutilise le
|
||||
panneau natif Minecraft et les deux sprites mécaniques historiques importés ;
|
||||
aucun PNG n'est modifié. Les anciennes cases de stockage restent lisibles.
|
||||
Voir [comportement et continuité](docs/mill-beta306.md).
|
||||
La [recette adaptée en beta.307](docs/mill-recipe-beta307.md) remplace la
|
||||
redstone historique par un bâton et utilise la catégorie native Divers.
|
||||
|
||||
## Écrasement direct des fruits, beta.305 locale
|
||||
|
||||
Le comportement de piétinement à huit hauteurs et son intégration aux pistons
|
||||
sont développés dans Sanctuary 26.3. Ils utilisent les trois textures de blocs
|
||||
et les trois recettes de transformation déjà importées de `26.2/itsalive`
|
||||
(commit `26f2c2a0061619df1f146f9081d6740c560a7497`, GPL-3.0-or-later).
|
||||
Aucun PNG n'est retouché ; les modèles partiels réutilisent les mêmes textures.
|
||||
La presse beta.304 devient une compatibilité de lecture d'inventaire inactive.
|
||||
Voir [comportements, continuité et vérifications](docs/crushing-beta305.md).
|
||||
|
||||
## Appareils culinaires, beta.304 locale
|
||||
|
||||
Les six appareils, leurs 36 sources Java, leurs modèles et recettes adaptent
|
||||
`26.2/itsalive` au commit `26f2c2a0061619df1f146f9081d6740c560a7497`,
|
||||
GPL-3.0-or-later. Le [reçu source](docs/kitchen-beta304-sources.json) conserve
|
||||
les empreintes historiques. Les recettes d'équipement adaptées sont dans
|
||||
`scripts/data/kitchen-crafting.json` ; celles des procédés restent dans le
|
||||
reçu de cuisine beta.303. Le port conserve les PNG déjà importés, adapte
|
||||
explicitement les API Minecraft 26.3 et ajoute les catégories JEI, les retours
|
||||
de récipients et le suivi exact de l'eau vanilla. La fermentation instantanée
|
||||
de l'étape beta.303 est remplacée par le tonneau et ses délais historiques.
|
||||
Voir [comportements, limites et vérifications](docs/kitchen-beta304.md).
|
||||
|
||||
## Cuisine, beta.303 locale
|
||||
|
||||
Les 92 recettes alimentaires, noms FR/EN et profils alimentaires adaptent
|
||||
`26.2/itsalive` au commit `26f2c2a0061619df1f146f9081d6740c560a7497`,
|
||||
GPL-3.0-or-later. Le reçu source `scripts/data/cuisine.json` conserve les
|
||||
recettes originales et leurs SHA-256. Les mécanismes sont adaptés à la table,
|
||||
au four et au fumoir de Minecraft 26.3 ; les transformations de fermentation
|
||||
deviennent des fabrications manuelles immédiates. Les appareils historiques
|
||||
ne sont pas portés dans ce lot. Les modèles de préparations réutilisent les
|
||||
sprites importés, avec une teinte native, sans modifier les PNG. Voir
|
||||
[le contrat, les adaptations et contrôles](docs/cuisine-beta303.md).
|
||||
|
||||
## Vanille 16 × 16, beta.302 locale
|
||||
|
||||
Les quatre sprites grimpants de beta.301 sont repris avec l'outil intégré
|
||||
imagegen pour leur lecture en 16 × 16 pixels. L'atlas de production contient
|
||||
quatre cellules de cette taille ; les PNG de jeu sont exportés à cette
|
||||
résolution exacte, sans interpolation. Les sources, prompt et empreintes
|
||||
restent dans `ressources-pack/sources/vanilla-beta301/`. Les 343 PNG de l'import
|
||||
historique beta.299 restent intacts. Voir [le correctif](docs/vanilla-16px-beta302.md).
|
||||
|
||||
## Vanille grimpante, beta.301 locale
|
||||
|
||||
La maturité et la récolte adaptent le `VanillaVineBlock` de Sanctuary 26.2
|
||||
(`26f2c2a0061619df1f146f9081d6740c560a7497`, GPL-3.0-or-later). L'attachement
|
||||
à plusieurs faces, la croissance ascendante et les modèles plats sont adaptés
|
||||
pour Sanctuary 26.3. Les quatre PNG historiques restent intacts. Quatre
|
||||
nouveaux sprites intégrant feuilles, fleurs et gousses sont créés avec l'outil
|
||||
imagegen, à partir de la liane Minecraft comme référence de structure et de
|
||||
style et des anciens PNG comme références de vanille. Un seul plan est rendu
|
||||
par face ; aucune texture native n'est copiée dans le pack. L'atlas final,
|
||||
les prompts et la préparation des sprites sont conservés dans
|
||||
`ressources-pack/sources/vanilla-beta301/`.
|
||||
Voir [le contrat et les contrôles](docs/vanilla-beta301.md).
|
||||
|
||||
## Agriculture d'It's Alive ! pour 26.3, beta.300 locale
|
||||
|
||||
Les classes `fr.koka.sanctuary.agriculture` adaptent le code agricole et les
|
||||
registres du module `26.2/itsalive`, commit
|
||||
`26f2c2a0061619df1f146f9081d6740c560a7497`, GPL-3.0-or-later. Leur portée
|
||||
exclut créatures, Canaplia et appareils culinaires. Les définitions des deux
|
||||
arbres plantés adaptent les paramètres `oak` et `acacia` de Minecraft 26.3
|
||||
finale ; les butins utilisent les règles natives de cette version.
|
||||
Les textures du [reçu beta.299](docs/itsalive-textures-beta299.json) sont
|
||||
conservées sans retouche. Voir le [contrat et les vérifications](docs/agriculture-beta300.md).
|
||||
|
||||
## Textures cuisine et agriculture d'It's Alive !, beta.299 locale
|
||||
|
||||
Le premier lot de la refonte reprend 343 PNG du module `itsalive` du projet
|
||||
Sanctuary 26.2 (GPL-3.0-or-later), commit
|
||||
`26f2c2a0061619df1f146f9081d6740c560a7497`, sous
|
||||
`itsalive/src/main/resources/assets/itsalive/textures/`. Les 38 images du dossier
|
||||
`food` fournies par le créateur le 28 octobre 2026 remplacent leurs homologues
|
||||
dans la nouvelle copie ; elles sont conservées octet pour octet. Les 305 autres
|
||||
PNG restent strictement identiques aux sources historiques. Les textures de
|
||||
créatures et les œufs ne font pas partie de cet import.
|
||||
|
||||
Le [reçu par fichier](docs/itsalive-textures-beta299.json) conserve origine,
|
||||
empreintes anciennes/nouvelles et dimensions. Le
|
||||
[ticket de portage](docs/itsalive-26-3.md) distingue la présence des textures
|
||||
des mécaniques encore à développer. Aucun nouveau code 26.2 ni modèle tiers
|
||||
n'est inclus par ce lot ; les notices des portages antérieurs restent valables.
|
||||
|
||||
## Mods facultatifs beta.292
|
||||
|
||||
Les binaires officiels de [Simple Voice Chat 2.6.24+26.3](https://modrinth.com/plugin/simple-voice-chat/version/OLnMVWXy)
|
||||
|
||||
@@ -75,6 +75,13 @@ tasks.register('verifyResourcePack', Exec) {
|
||||
}
|
||||
tasks.named('check') { dependsOn('verifyResourcePack') }
|
||||
|
||||
tasks.register('verifyItsAliveTextures', Exec) {
|
||||
group = 'verification'
|
||||
description = 'Verify the first agriculture and cooking texture import and its source hashes.'
|
||||
commandLine('python3', 'scripts/verify_itsalive_textures.py')
|
||||
}
|
||||
tasks.named('check') { dependsOn('verifyItsAliveTextures') }
|
||||
|
||||
tasks.register('assembleResourcePack', Zip) {
|
||||
group = 'distribution'
|
||||
description = 'Export the same textures as the built-in resource pack for standalone use.'
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
# IA-02 — Cultures, arbres fruitiers et caféier en 26.3
|
||||
|
||||
Préparation **locale** du 28 octobre 2026, `beta.300`, sur la même branche
|
||||
`codex/itsalive-26-3`. Socle : import graphique beta.299, commit `d78d39cd`.
|
||||
Minecraft 26.3, Fabric Loader 0.19.5, Fabric API 0.160.5+26.3 et Java 25
|
||||
restent inchangés. Aucun push, tag, publication, serveur, site ou Prism modifié.
|
||||
|
||||
## Contenu jouable
|
||||
|
||||
Le créatif utilise exclusivement les onglets vanilla existants :
|
||||
|
||||
| Onglet | Ajouts |
|
||||
| --- | --- |
|
||||
| **Naturel / Natural Blocks** | 23 semences, propagules et pousses, plus les deux feuillages fruitiers |
|
||||
| **Nourriture et boissons / Food & Drinks** | 26 récoltes et ingrédients, plus la gousse d'ail |
|
||||
|
||||
Ces **45 objets distincts** comprennent sept récoltes replantables ou grains
|
||||
présents dans les deux catégories selon leur usage. Aucun onglet ou page
|
||||
créative supplémentaire n'est enregistré.
|
||||
Tous portent des identifiants `sanctuary:*` et des noms FR/EN. Les blocs et
|
||||
objets sont ceux de Sanctuary ; aucun deuxième JAR It's Alive n'est nécessaire.
|
||||
|
||||
Les **20 cultures** sont : fraise, tomate, ananas, maïs, concombre, poivron,
|
||||
aubergine, laitue, oignon, ail, chou, raisin, riz, soja, haricot, pois chiche,
|
||||
piment, basilic, menthe et coriandre. Elles ont huit âges, croissent par les
|
||||
ticks aléatoires et acceptent la poudre d'os. La croissance garde les règles
|
||||
vanilla de lumière et de sol ; aucun verrou climatique nouveau n'est ajouté.
|
||||
|
||||
Les modèles suivent la silhouette des plantes : **15 cultures en croix**
|
||||
(fraise, ananas, poivron, aubergine, laitue, oignon, ail, chou, raisin, soja,
|
||||
pois chiche, piment, basilic, menthe et coriandre). Le riz, le maïs, la tomate,
|
||||
le concombre et le haricot gardent les plans de culture existants, avec les
|
||||
parties hautes pour les quatre derniers. La croix comprend deux plans
|
||||
diagonaux au lieu des quatre plans formant un cadre. Son pied est abaissé
|
||||
d'un pixel, comme le modèle de culture vanilla, pour toucher la terre
|
||||
labourée. Ce choix s'applique aux huit âges, sans retoucher les PNG.
|
||||
|
||||
- Les semences se plantent sur la terre labourée. L'oignon, l'ail, le riz,
|
||||
le soja, les haricots et les pois chiches sont directement replantables.
|
||||
- Le **riz** se plante exclusivement dans une vraie source d'eau au-dessus
|
||||
de boue. L'eau courante et la plantation à sec sont refusées sans consommer
|
||||
de riz. Le plant contient sa source ; le casser restitue l'eau.
|
||||
- Fraise, tomate, concombre, poivron, aubergine, raisin, piment, basilic,
|
||||
menthe et coriandre se récoltent au clic droit à maturité. Ils donnent
|
||||
une à quatre unités selon la plante et reviennent à l'âge 3.
|
||||
- Le **haricot** donne des haricots verts aux âges 5–6 et des haricots secs
|
||||
à l'âge 7, puis revient à l'âge 3. Les autres cultures se récoltent par casse.
|
||||
- Tomate, maïs, concombre et haricot occupent **deux blocs dès l'âge 4**.
|
||||
La partie haute suit l'âge du pied ; son clic transmet la récolte ou la
|
||||
poudre d'os au pied. La casse ne produit qu'un seul butin. Un plafond bloque
|
||||
le passage au stade haut et la poudre d'os n'est pas consommée en vain.
|
||||
- Manger un **ananas** rend exactement une couronne en survie, pour replanter.
|
||||
Les fruits, légumes et herbes comestibles conservent leurs valeurs 26.2.
|
||||
Les grains secs et ingrédients de transformation ne deviennent pas tous
|
||||
des aliments crus par cet import.
|
||||
|
||||
Le **caféier** se plante avec un grain vert sur un sol admis par les buissons
|
||||
vanilla. Il croît sur quatre âges, donne des cerises aux âges 2–3 et revient
|
||||
à l'âge 1 après récolte. Il n'a ni épines, ni ralentissement du buisson à baies.
|
||||
Les cerises donnent deux grains ; le fumoir torréfie les grains verts.
|
||||
Les boissons et les appareils culinaires restent dans les tickets suivants.
|
||||
|
||||
Le **citronnier** et l'**olivier** poussent depuis leurs propres pousses :
|
||||
forme de chêne pour le premier, forme d'acacia pour le second, avec bois
|
||||
vanilla. Les feuilles fruitières sont créées directement par les arbres,
|
||||
sans scanner ou convertir les feuilles d'un arbre voisin. Une feuille sur
|
||||
douze environ porte un fruit à la croissance ; les fruits reviennent ensuite
|
||||
rarement par tick aléatoire, comme dans l'intention historique. Le clic droit
|
||||
récolte un fruit sans arracher les feuilles ; cisailles et Toucher de soie
|
||||
récupèrent les feuilles, sinon elles peuvent donner une pousse.
|
||||
|
||||
Les recettes natives de récupération des semences et de torréfaction sont
|
||||
disponibles pour la fabrication manuelle. Le compostage utilise le composant
|
||||
`minecraft:compostable` et les probabilités natives 26.3, au lieu de l'ancienne
|
||||
table statique Java. Les tags de cultures, maintien de terre labourée,
|
||||
feuilles, pousses et nourriture des poules sont additifs.
|
||||
|
||||
## Accès pour les essais
|
||||
|
||||
Créer un **nouveau monde de développement** en créatif, prendre les semences
|
||||
dans **Naturel** et les récoltes dans **Nourriture et boissons**. Sur un monde de test où les
|
||||
commandes sont autorisées :
|
||||
|
||||
```mcfunction
|
||||
/give @s sanctuary:tomato_seeds 16
|
||||
/give @s sanctuary:rice 16
|
||||
/give @s sanctuary:coffee_beans 16
|
||||
/give @s sanctuary:lemon_sapling 4
|
||||
/give @s sanctuary:olive_sapling 4
|
||||
```
|
||||
|
||||
Les infobulles FR/EN expliquent la plantation du riz et du caféier.
|
||||
Le bloc d'ananas sauvage est porté pour le futur peuplement, sans apparition
|
||||
naturelle activée dans ce lot.
|
||||
|
||||
## Génération et sauvegardes
|
||||
|
||||
Les deux nouvelles définitions `sanctuary:lemon_tree` et
|
||||
`sanctuary:olive_tree` servent aux **pousses plantées**, avec leurs fournisseurs
|
||||
de feuillage `sanctuary:lemon_foliage` et `sanctuary:olive_foliage`. Leurs formes
|
||||
reprennent les paramètres exacts des arbres `oak` et `acacia` de Minecraft
|
||||
26.3 finale, adaptés au feuillage Sanctuary. Ce premier contrat de pousses
|
||||
fruitières est **IA-02 / beta.300** ; graine de validation : **300**.
|
||||
|
||||
Aucune nouvelle feature n'est ajoutée aux biomes ou aux étapes de décoration.
|
||||
L'apparition naturelle et la découverte des premières semences en survie
|
||||
restent un lot séparé. Aucun ancien chunk n'est repeuplé ou régénéré. Le client
|
||||
de validation utilise un monde plat Sanctuary Test neuf, jamais une sauvegarde
|
||||
personnelle. Les 30 nouveaux blocs utilisent les états natifs de Minecraft :
|
||||
âge, partie haute, distance au tronc, persistance, eau et fructification.
|
||||
Aucun format de sauvegarde existant n'est réécrit ; aucun monde 26.2 n'est migré.
|
||||
|
||||
La **vanille grimpante** reste dans IA-03. Le chaudron, les plats ratés,
|
||||
le séchoir/saucisson, la poêle et la cuisinière restent dans IA-04 à IA-08.
|
||||
Leurs textures déjà importées ne signifient pas que leurs comportements
|
||||
sont activés par ce lot.
|
||||
|
||||
## Sources et reproductibilité
|
||||
|
||||
Le port adapte les classes agricoles de `26.2/itsalive`, commit
|
||||
`26f2c2a0061619df1f146f9081d6740c560a7497`, GPL-3.0-or-later, en lecture seule.
|
||||
Les registres sont séparés des monstres, de Canaplia et des appareils culinaires.
|
||||
Les changements d'API 26.3 sont vérifiés dans le JAR exact de Minecraft utilisé
|
||||
par Loom : végétation, prédiction d'inventaire, compostage, butin, arbres et
|
||||
fournisseurs d'états. Aucun mod agricole tiers n'est ajouté.
|
||||
|
||||
`scripts/data/agriculture.json` décrit le catalogue et les formes des arbres.
|
||||
`scripts/agriculture_resources.py` produit modèles, objets, états de blocs,
|
||||
butins, recettes, tags et définitions des arbres dans le répertoire de build,
|
||||
par `generateAgricultureResources`. Les 343 PNG et leurs empreintes beta.299
|
||||
restent inchangés ; les 38 fichiers du créateur gardent la priorité.
|
||||
|
||||
## Validation
|
||||
|
||||
Le parcours graphique utilise **Vulkan / Apple M1 / Sodium 0.9.2+mc26.3**,
|
||||
un monde de développement neuf de graine **300** et un client de heap 4 Gio :
|
||||
|
||||
- plantation en survie par les vrais paquets client/serveur, consommation
|
||||
d'une semence, poudre d'os et casse de la partie haute ;
|
||||
- **313 états de blocs** avec géométrie et textures réellement chargées,
|
||||
**45 modèles d'objets** avec leurs sprites fournis ;
|
||||
- vérification des deux plans croisés de l'oignon et de l'ail et des quatre
|
||||
plans conservés pour le maïs et le riz ;
|
||||
- onglets vanilla Naturel et Nourriture et boissons : présence effective des
|
||||
ajouts, absence de nouvel onglet, captures **FR/EN et GUI 2/3/4** ;
|
||||
- croissance des deux arbres et fermeture/réouverture réelle du monde :
|
||||
**173 états agricoles** conservés, dont les âges et la fructification.
|
||||
|
||||
La construction finale `check build assemblePack` réussit en **3 min 6 s**,
|
||||
159 tâches, avec **34 GameTests serveur requis réussis** : 17 scénarios
|
||||
agricoles et 17 régressions des recettes, de la progression et de la
|
||||
connaissance alimentaire. Le parcours client final réussit en **53 s**.
|
||||
|
||||
Le JAR contient les modèles croisés attendus pour les huit âges des 15
|
||||
cultures et les classes agricoles identiques aux classes vérifiées. Les
|
||||
343 textures correspondent au reçu beta.299. Les JAR des profils packwiz
|
||||
client et serveur sont strictement identiques au binaire contrôlé ; le
|
||||
template personnel contient les mêmes PNG. Version et index des packs vérifiés.
|
||||
|
||||
SHA-256 de `sanctuary-beta.300.jar` :
|
||||
`d432015abbb52154af8ec2a2b9e31a73374133e6909b954b0fc54a15397661d7`.
|
||||
Reçu local complémentaire : `build/agriculture300-validation.json`.
|
||||
|
||||
Commandes de validation :
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=agriculture300,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryAgriculture300ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=agriculture300 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
Les journaux restent ignorés dans `build/agriculture300-*.log` et les captures
|
||||
dans `mods/sanctuary/build/run/clientGameTest/screenshots/`. Aucun essai
|
||||
OpenGL n'est lancé ou revendiqué. Les autres moteurs graphiques, un serveur
|
||||
multijoueur distant et une partie de survie depuis zéro sans commandes ne
|
||||
sont pas validés par ce lot ; la découverte naturelle des semences est différée.
|
||||
@@ -1,5 +1,60 @@
|
||||
# Backlog Sanctuary
|
||||
|
||||
## IA-01 à IA-10 — Refonte cuisine et agriculture d'It's Alive !
|
||||
|
||||
**IA-10, préparation locale beta.315 :** [saucisson séché posable et mangeable
|
||||
en quatre tranches](sausage-slices-beta315.md), huit points de nourriture au
|
||||
total, quatre orientations et sauvegarde native. Même branche locale,
|
||||
sans publication.
|
||||
|
||||
**IA-09, préparation locale beta.314 :** [fromage en sept parts posées et quatre
|
||||
qualités de vin, après 3 minutes de fermentation puis 1/3/7 jours réels
|
||||
au tonneau](cheese-wine-beta314.md). Le vieillissement continue jeu fermé.
|
||||
Même branche locale, sans publication.
|
||||
|
||||
**IA-06, préparation locale beta.313 :** [saucisson préparé puis séché cinq
|
||||
minutes sur quatre crochets indépendants, protection de la pluie et sauvegarde](drying-rack-beta313.md).
|
||||
Même branche locale, sans publication.
|
||||
|
||||
**CUI-305, préparation locale beta.305 :** [écrasement direct des fruits](crushing-beta305.md).
|
||||
La presse est retirée des recettes, du créatif et de JEI. Huit paliers à piétiner,
|
||||
neuf portions par bloc, écrasement au piston et course complète de 27 blocs
|
||||
pour le super piston. Les autres appareils restent en place.
|
||||
|
||||
**IA-04/05/07/08, préparation locale beta.304 :** [six appareils culinaires et
|
||||
80 procédés physiques](kitchen-beta304.md). Chaudron depuis l'eau vanilla,
|
||||
poêle native avec contenu visible, plats ratés, cuisinière 3 × 3, fermentation
|
||||
temporisée, moulin et pressoir à piston. À cette étape, les six appareils étaient portés ;
|
||||
la refonte de pose/orientation de la poêle, le séchoir et les nouveaux plats
|
||||
restaient ouverts. Même branche locale, sans publication.
|
||||
|
||||
**IA-05, préparation locale beta.303 :** [92 recettes alimentaires historiques,
|
||||
ingrédients et plats jouables](cuisine-beta303.md), avec table et four/fumoir.
|
||||
La suite beta.304 remplace la fermentation instantanée et porte les appareils
|
||||
et les plats ratés. Même branche locale, sans publication.
|
||||
|
||||
**Premier lot graphique importé en beta.299 locale**, branche
|
||||
`codex/itsalive-26-3`, à la demande du 28 octobre 2026 :
|
||||
343 textures, dont les 38 fichiers `food/` prioritaires, avec reçu de provenance
|
||||
et vérification des empreintes. Aucun bloc, objet ou système agricole ajouté
|
||||
au registre par ce lot. [Périmètre, catalogue et tickets](itsalive-26-3.md) :
|
||||
cultures/arbres/caféier, vanille grimpante, chaudron, plats ratés et recettes,
|
||||
saucisson au séchoir, poêle et cuisinière four + table de fabrication.
|
||||
Suite par incréments locaux ; aucune publication ou mise à jour d'installation.
|
||||
|
||||
**IA-02, préparation locale beta.300 :** [20 cultures, deux arbres fruitiers
|
||||
et caféier jouables](agriculture-beta300.md), 45 objets créatifs, modèles,
|
||||
butins et recettes natifs 26.3. Apparition naturelle, vanille grimpante et
|
||||
cuisine restent dans les prochains lots. Même branche `codex/itsalive-26-3`.
|
||||
|
||||
**IA-03, préparation locale beta.301 :** [vanille attachée aux bûches et
|
||||
croissance vers le haut](vanilla-beta301.md), sans propagation descendante,
|
||||
avec gousses et pose à plusieurs faces. Les appareils culinaires et le
|
||||
peuplement naturel restent à traiter. Même branche, sans publication.
|
||||
|
||||
**Correction locale beta.302 :** [les quatre textures de vanille sont
|
||||
reprises en 16 × 16 pixels](vanilla-16px-beta302.md), sur le même plan unique.
|
||||
|
||||
## HELLO-264 — Menu central et sol commun — livré localement
|
||||
|
||||
Sur `codex/hello-world-menu`, socle beta.263. Colonne centrale de 182 pixels,
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"version": "beta.311",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.311.jar",
|
||||
"sha256": "3fa69ebc452ad2bbc3e7d6992305e9349d2f34f04f666a3688453fbd2fe74409",
|
||||
"previous_verified_sha256": "4a2278d987b031b343a6d66694da50db0aeb408bb77785ce8c02ae0ed9174b26",
|
||||
"changed_jar_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json",
|
||||
"fabric.mod.json",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/CookingCauldronBlock.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/CookingCauldronBlockEntity.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/CookingCauldronHud.class",
|
||||
"resourcepacks/template/THIRD_PARTY_NOTICES.md"
|
||||
],
|
||||
"added_jar_entries": [],
|
||||
"removed_jar_entries": [],
|
||||
"generated_json_unchanged": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 195,
|
||||
"agriculture": 376
|
||||
},
|
||||
"culinary_compiled_classes_verified": 61,
|
||||
"cauldron_recipes_unchanged": 19,
|
||||
"manual_start": "stick",
|
||||
"failed_dish_from_cauldron": false,
|
||||
"milk_bucket_paid_at_completion": true,
|
||||
"unchanged_native_inventory_indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6
|
||||
],
|
||||
"preparation_storage_marker": 311,
|
||||
"unchanged_pngs": 1108,
|
||||
"server_required": 89,
|
||||
"cauldron_scenarios": 13,
|
||||
"client_vulkan": {
|
||||
"log": "build/cauldron311-client.log",
|
||||
"screenshots": "build/cauldron311-screenshots",
|
||||
"screenshot_count": 11,
|
||||
"world_seed": 311,
|
||||
"sodium": "0.9.2+mc26.3",
|
||||
"audio_engine_listener": true
|
||||
},
|
||||
"client_server_copies_identical": true,
|
||||
"template_changed_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json"
|
||||
],
|
||||
"build_log": "build/cauldron311-build.log"
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
# CUI-311 / IA-04 — chaudron préparé puis remué
|
||||
|
||||
Suite locale beta.312 : [la recette se répète après son premier geste et
|
||||
reste mémorisée après la collecte des plats](cauldron-repeat-beta312.md).
|
||||
Le comportement à lot unique décrit ci-dessous est celui de beta.311.
|
||||
|
||||
Choix du créateur le 29 octobre 2026 : préparer les ingrédients, puis **remuer
|
||||
avec un bâton** pour lancer la cuisson. Les mélanges incomplets ne deviennent
|
||||
plus des plats ratés. Le lait reste un ingrédient non payé jusqu'à la fin ;
|
||||
le seau vide est éjecté à la préparation terminée. Des sons Minecraft discrets
|
||||
accompagnent les gestes et la cuisson. Même branche `codex/itsalive-26-3`,
|
||||
préparation locale **beta.311**, sans publication ou installation.
|
||||
|
||||
## Contrat de continuité avant modification
|
||||
|
||||
L'identifiant `sanctuary:cooking_cauldron`, les six cases d'ingrédients 0–5,
|
||||
la sortie 6 et `WaterUnits` restent inchangés. Les objets natifs gardent leurs
|
||||
composants et tailles maximales ; les récipients non empilables sont stockés
|
||||
séparément. Une vue agrégée sert uniquement à reconnaître la recette et ses
|
||||
quantités, sans déplacer les objets stockés.
|
||||
|
||||
Le champ `AssemblyComplete` devient l'autorisation donnée par le geste de
|
||||
remuer. `CauldronPreparationVersion=311` distingue ce sens de l'ancien départ
|
||||
automatique. À la lecture d'une sauvegarde antérieure sans ce marqueur, seuls
|
||||
l'autorisation et l'avancement de cuisson non payé sont remis à zéro ; les
|
||||
six cases, la sortie déjà terminée et l'eau sont conservées. La préparation
|
||||
attend alors un nouveau geste. Les anciennes dates restent lisibles mais ne
|
||||
créent plus de délai d'échec. Une sauvegarde beta.311 conserve une cuisson
|
||||
explicitement lancée, qui reprend sur ses ticks restants avec de la chaleur.
|
||||
|
||||
Changer un ingrédient ou l'eau annule la cuisson non payée et demande de
|
||||
remuer à nouveau. Une cuisson produit un lot de la recette choisie ; elle ne
|
||||
relance pas automatiquement une recette depuis les restes. Le bâton n'est
|
||||
pas consommé. Le chaudron ordinaire vide ou rempli d'eau devient la variante
|
||||
culinaire seulement lorsqu'un véritable ingrédient tombe dans son ouverture.
|
||||
Les annulations rendent le lait plein, la casse rend les objets encore
|
||||
stockés et la collecte finale restaure le chaudron vanilla avec son eau exacte.
|
||||
|
||||
Revenir à beta.310 exige d'abord de récupérer les préparations non terminées :
|
||||
l'ancien code reprend son départ automatique. Aucun monde personnel n'est
|
||||
modifié ; les validations utilisent des fixtures et mondes neufs ignorés.
|
||||
|
||||
## Gestes et recettes
|
||||
|
||||
Jeter le premier ingrédient dans l'ouverture d'un chaudron vanilla vide ou
|
||||
rempli d'eau. Ajouter les autres ingrédients par clic, jet dans l'ouverture
|
||||
ou entonnoir. Un objet posé à côté ou sur l'extérieur du rebord n'est pas
|
||||
capturé. La recette reste modifiable sans compte à rebours d'échec.
|
||||
|
||||
Un clic avec un bâton démarre une recette complète, avec une source de
|
||||
chaleur dessous. Le bâton reste entier. Un mélange incomplet, un chaudron
|
||||
froid ou une préparation déjà en cours donnent une indication FR/EN, sans
|
||||
consommer d'ingrédient. Une modification annule l'avancement, sans paiement,
|
||||
et demande un nouveau geste. La perte de chaleur suspend simplement les ticks
|
||||
restants d'une recette déjà lancée.
|
||||
|
||||
Les **19 recettes historiques** et leurs durées restent inchangées. Les lots
|
||||
regroupent les ingrédients identiques à la reconnaissance, tout en conservant
|
||||
les vrais seaux non empilables dans les six cases natives. Trois seaux de lait
|
||||
produisent six crèmes et rendent trois seaux, après la durée entière. Une
|
||||
préparation ne poursuit pas une autre recette depuis ses restes. La sortie
|
||||
et les ingrédients peuvent se récupérer, et l'eau restante retrouve son
|
||||
niveau vanilla exact lorsque la variante culinaire se vide.
|
||||
|
||||
Les sons sont envoyés côté serveur, une fois par événement : ajout (volume
|
||||
0,18 ; 0,35 pour le lait, limité à un son tous les quatre ticks), geste (0,30),
|
||||
mijotage (0,12 tous les 60 ticks réellement cuits), fin (0,35), service (0,25).
|
||||
Événements Minecraft : `entity.generic.splash`, `item.bucket.empty`,
|
||||
`item.bottle.empty`, `block.bubble_column.bubble_pop`,
|
||||
`block.brewing_stand.brew`, `entity.item.pickup`. Aucun fichier audio n'est
|
||||
importé. Un chaudron froid ou non lancé ne joue pas le son de mijotage.
|
||||
|
||||
## Vérifications
|
||||
|
||||
Treize cas serveur natifs couvrent le jet de lait dans un chaudron vide,
|
||||
le vrai geste au bâton, l'absence d'échec et de paiement prématuré, les
|
||||
**19 recettes complètes**, les lots de seaux séparés, l'absence de recette
|
||||
suivante automatique, les changements de préparation/eau, le manque de chaleur,
|
||||
la pause, la sauvegarde et l'ancien avancement désarmé, les objets hors du
|
||||
bol, les récipients jamais réabsorbés, la casse et l'entonnoir natif.
|
||||
Les vérifications culinaires historiques ont été adaptées au geste manuel ;
|
||||
les fixtures successives éliminent leurs anciens objets avant de préparer
|
||||
la recette suivante, pour ne pas introduire d'ingrédients parasites.
|
||||
|
||||
Le parcours client réussi sous **Vulkan / Apple M1**, avec
|
||||
**Sodium 0.9.2+mc26.3**, dure **1 min 15 s** sur un monde plat neuf de graine
|
||||
**311**. Il couvre une vraie touche Q de jet de lait, la capture par les ticks
|
||||
natifs, **650 ticks d'attente sans recette ni échec**, les HUD FR/EN aux
|
||||
échelles 2, 3 et 4, les vrais paquets de geste et de service, la cuisson réelle,
|
||||
le seau éjecté après le paiement, la fermeture/réouverture du monde et un
|
||||
mélange invalide qui conserve le lait. Les 19 recettes sont visibles dans JEI.
|
||||
Le jet Q est replacé au-dessus de l'ouverture par la fixture ; la chute,
|
||||
la capture et la suite n'utilisent aucun raccourci de cuisson.
|
||||
|
||||
Un écouteur du moteur audio client a observé `item.bucket.empty`,
|
||||
`item.bottle.empty`, deux `block.bubble_column.bubble_pop`,
|
||||
`block.brewing_stand.brew` et `entity.item.pickup`. Ce contrôle vérifie l'envoi
|
||||
et la réception des événements ; les volumes restent réglables dans la
|
||||
catégorie native Blocs. Onze captures sont archivées dans
|
||||
`build/cauldron311-screenshots/`, avec les journaux ignorés
|
||||
`build/cauldron311-client.log` et `build/cauldron311-build.log`.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=cauldron311,pan310,oven308,mill306,kitchen304,cuisine303,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryCauldron311ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=cauldron311 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
`check build assemblePack` a réussi en **2 min 52 s**, avec **89 tests serveur
|
||||
obligatoires**. L’audit strict à beta.310 ne trouve aucun fichier ajouté ou
|
||||
retiré du JAR : seuls les trois fichiers de classes du chaudron, les cinq
|
||||
libellés FR/EN, la version et les crédits changent. Les 19 recettes, les
|
||||
432 JSON de cuisine, 195 d’appareils et 376 d’agriculture ainsi que les
|
||||
**1 108 PNG** restent identiques. Les classes compilées sont comparées au
|
||||
JAR et les copies client/serveur sont identiques. Le modèle de pack de
|
||||
ressources ne change que ses deux fichiers de langue et les crédits.
|
||||
|
||||
[Reçu de vérification](cauldron-beta311-verification.json). JAR local :
|
||||
`mods/sanctuary/build/libs/sanctuary-beta.311.jar`
|
||||
|
||||
SHA-256 :
|
||||
`3fa69ebc452ad2bbc3e7d6992305e9349d2f34f04f666a3688453fbd2fe74409`
|
||||
|
||||
Aucun push, tag, publication, avancement du canal packwiz, installation
|
||||
Prism ou déploiement sur un serveur personnel n’a été effectué.
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"version": "beta.312",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.312.jar",
|
||||
"sha256": "90349a998970030ec0bfd880b307b9162e76fd0f460d70f1c9ac8ad2c115c8bd",
|
||||
"previous_verified_sha256": "3fa69ebc452ad2bbc3e7d6992305e9349d2f34f04f666a3688453fbd2fe74409",
|
||||
"changed_jar_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json",
|
||||
"fabric.mod.json",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/CookingCauldronBlock.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/AbstractTimedCulinaryBlockEntity.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/CookingCauldronBlockEntity.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/CookingCauldronHud.class",
|
||||
"resourcepacks/template/THIRD_PARTY_NOTICES.md"
|
||||
],
|
||||
"added_jar_entries": [],
|
||||
"removed_jar_entries": [],
|
||||
"generated_json_unchanged": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 195,
|
||||
"agriculture": 376
|
||||
},
|
||||
"culinary_compiled_classes_verified": 61,
|
||||
"cauldron_recipes_unchanged": 19,
|
||||
"manual_start": "stick",
|
||||
"failed_dish_from_cauldron": false,
|
||||
"milk_bucket_paid_at_completion": true,
|
||||
"unchanged_native_inventory_indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6
|
||||
],
|
||||
"preparation_storage_marker": 311,
|
||||
"optional_storage_fields": [
|
||||
"CauldronRepeatRecipe",
|
||||
"CauldronCycleBatches"
|
||||
],
|
||||
"unchanged_pngs": 1108,
|
||||
"server_required": 100,
|
||||
"cauldron_scenarios": 11,
|
||||
"repeat_recipe": true,
|
||||
"cycle_quantity_fixed": true,
|
||||
"completed_client_batches": 4,
|
||||
"client_paid_milk_buckets": 5,
|
||||
"client_returned_buckets": 5,
|
||||
"client_total_cream": 10,
|
||||
"client_vulkan": {
|
||||
"log": "build/cauldron312-client.log",
|
||||
"screenshots": "build/cauldron312-screenshots",
|
||||
"screenshot_count": 10,
|
||||
"world_seed": 312,
|
||||
"sodium": "0.9.2+mc26.3",
|
||||
"audio_engine_listener": true
|
||||
},
|
||||
"client_server_copies_identical": true,
|
||||
"template_changed_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json"
|
||||
],
|
||||
"build_log": "build/cauldron312-build.log"
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
# CUI-312 / IA-04 — répéter une recette dans le même chaudron
|
||||
|
||||
Demande du créateur du 29 octobre 2026 : refaire la même recette plusieurs
|
||||
fois sans recommencer toute la préparation. Après le premier geste au bâton,
|
||||
le chaudron garde la recette choisie et peut reprendre ses lots avec les mêmes
|
||||
ingrédients, y compris après avoir servi les plats. Préparation locale
|
||||
**beta.312**, même branche `codex/itsalive-26-3`, sans publication ni installation.
|
||||
|
||||
## Contrat de continuité avant modification
|
||||
|
||||
Les identifiants, les six entrées 0–5, la sortie 6, l'eau, les composants
|
||||
natifs et les quantités maximales restent inchangés. La cuisson garde ses
|
||||
lots historiques et ne paie les ingrédients et récipients qu'en fin de lot.
|
||||
|
||||
Le champ optionnel `CauldronCycleBatches` conserve la quantité fixée au début
|
||||
du lot non payé. Les ajouts en cours de cuisson restent pour le lot suivant ;
|
||||
ils ne terminent pas leur cuisson sur le dernier tick d’un lot déjà lancé.
|
||||
Une ancienne cuisson sans ce champ garde sa quantité disponible à la reprise.
|
||||
|
||||
Le champ optionnel `CauldronRepeatRecipe` enregistre l'identifiant exact de la
|
||||
recette autorisée au bâton. `AssemblyComplete` garde l'autorisation manuelle,
|
||||
y compris en attente d'ingrédients ou de place. Le marqueur de préparation
|
||||
manuelle **311** reste valide ; il ne désigne pas la version du mod.
|
||||
|
||||
Une ancienne préparation beta.311 lancée sans ce nouveau champ retrouve sa
|
||||
recette au premier tick chargé. Une préparation ancienne non lancée ne
|
||||
s'autorise pas toute seule. Les sauvegardes antérieures au démarrage manuel
|
||||
restent désarmées, avec ingrédients, eau et sortie conservés. Une recette
|
||||
mémorisée disparue du datapack arrête l'autorisation sans perte d'objets.
|
||||
Une sauvegarde beta.312 garde la recette exacte, les ticks non payés et les
|
||||
objets ; le rechargement ne termine pas de cuisson hors ligne.
|
||||
|
||||
Ajouter un ingrédient de la recette mémorisée ou remettre son eau alimente
|
||||
la même série sans annuler le travail en cours. Retirer un ingrédient,
|
||||
diminuer l'eau ou ajouter un ingrédient d'une autre recette annule le travail
|
||||
non payé et demande un nouveau geste. Servir la sortie laisse la série en
|
||||
place ; le chaudron attend la même recette et ne transforme pas ses restes
|
||||
en un autre produit. Une sortie pleine ou la perte de chaleur suspendent la
|
||||
série ; récupérer les plats ou remettre de la chaleur permet de reprendre.
|
||||
|
||||
Maj-clic à main vide arrête explicitement la série en conservant les objets.
|
||||
Un chaudron vide retrouve alors sa variante vanilla et son eau exacte.
|
||||
Le retour à beta.311 conserve les sept cases et les plats, mais perd la
|
||||
répétition : récupérer les préparations en cours avant un tel retour.
|
||||
Aucun monde personnel n'est ouvert ou modifié par cette préparation locale.
|
||||
|
||||
## Utilisation
|
||||
|
||||
1. Préparer les ingrédients et remuer une première fois avec un bâton.
|
||||
2. Ajouter les mêmes ingrédients quand nécessaire, par clic, jet dans
|
||||
l'ouverture ou entonnoir. Les quantités ajoutées pendant un lot attendent
|
||||
le lot suivant, avec toute sa durée de cuisson.
|
||||
3. Prendre les plats à main vide laisse la série en place, même si un lot
|
||||
suivant est déjà en train de cuire. Une sortie pleine conserve ses objets
|
||||
et attend leur collecte ; un manque d'ingrédients attend le réassort.
|
||||
4. Maj-clic à main vide arrête la série. Les objets restent récupérables et
|
||||
le chaudron vide retrouve sa variante vanilla. Un nouveau plat différent
|
||||
demande de préparer ses ingrédients et de remuer à nouveau.
|
||||
|
||||
Les quantités historiques par lot sont conservées : plusieurs recettes
|
||||
complètes déjà présentes au début d'un lot peuvent cuire ensemble. Un apport
|
||||
pendant ce lot n'augmente pas rétroactivement sa quantité. Un lot paie toujours
|
||||
ses vrais ingrédients en fin de cuisson, avec ses récipients. Les sons de fin
|
||||
restent limités à un par lot terminé, même lorsqu'une ancienne sortie est
|
||||
encore stockée. Les 19 recettes et leurs durées restent inchangées.
|
||||
|
||||
Le HUD FR/EN montre les plats stockés avec l'état de la série : cuisson,
|
||||
ingrédients manquants, chaleur manquante ou sortie pleine.
|
||||
|
||||
## Vérifications locales
|
||||
|
||||
Onze nouveaux cas serveur vérifient : lots qui s'accumulent, service natif en
|
||||
cours de cuisson, réapprovisionnement compatible sans annulation, ingrédients
|
||||
nouveaux conservés pour leur cycle entier, changement de préparation,
|
||||
sortie pleine puis reprise, recette exacte conservée malgré l'eau restante,
|
||||
réassort d'eau et pause sans chaleur, entonnoir natif, sauvegarde d'un lot avec
|
||||
sa quantité fixée et un autre lot en attente, reprise beta.311 autorisée,
|
||||
recette supprimée sans remplacement implicite et arrêt explicite.
|
||||
|
||||
Le parcours client **Vulkan / Apple M1**, avec **Sodium 0.9.2+mc26.3**, a
|
||||
réussi en **1 min 58 s**, monde plat neuf de graine **312** :
|
||||
|
||||
- une vraie touche Q pour jeter le premier lait, puis un seul coup de bâton ;
|
||||
- des ajouts natifs pendant le premier lot, avec leur propre lot suivant ;
|
||||
- quatre lots réels donnant **dix crèmes pour cinq seaux de lait**, avec
|
||||
**cinq seaux vides** rendus et un seul son de fin par lot ;
|
||||
- ajout d'un lait alors que la sortie contient encore les plats précédents ;
|
||||
- service natif de six crèmes pendant la cuisson suivante, sans annulation ;
|
||||
- sauvegarde/réouverture conservant recette, lot non payé et plats servis,
|
||||
puis reprise avec la chaleur remise, sans nouveau geste ;
|
||||
- HUD FR/EN aux échelles 2, 3 et 4, avec contrôle de largeur et captures ;
|
||||
- vrai Maj-clic à main vide pour arrêter, puis collecte des quatre crèmes
|
||||
restantes et retour au chaudron vanilla.
|
||||
|
||||
La fixture replace le jet Q au-dessus de l'ouverture et retire la chaleur
|
||||
avant la fermeture du monde pour observer le lot non payé au rechargement.
|
||||
Les ingrédients, chutes, paiements, ticks de cuisson, interactions et quatre
|
||||
sons de fin sont réels. Dix captures sont archivées dans
|
||||
`build/cauldron312-screenshots/` ; les journaux de contrôle restent ignorés.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=cauldron312,cauldron311,pan310,oven308,mill306,kitchen304,cuisine303,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryCauldron312ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=cauldron312 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
`check build assemblePack` a réussi en **2 min 51 s**, avec **100 tests serveur
|
||||
obligatoires**. L’audit strict à beta.311 ne trouve aucun fichier ajouté ou
|
||||
retiré du JAR : seuls les quatre fichiers de classes prévus (chaudron, HUD et
|
||||
point de sélection de recette partagé), les quatre libellés FR/EN, la version
|
||||
et les crédits changent. Les 19 recettes, les 432 JSON de cuisine, 195
|
||||
d’appareils et 376 d’agriculture, ainsi que les **1 108 PNG**, sont identiques.
|
||||
Les classes culinaires compilées sont comparées au JAR ; les copies
|
||||
client/serveur sont identiques. Le modèle de pack de ressources ne change
|
||||
que ses deux fichiers de langue et les crédits.
|
||||
|
||||
[Reçu de vérification](cauldron-repeat-beta312-verification.json). JAR local :
|
||||
`mods/sanctuary/build/libs/sanctuary-beta.312.jar`
|
||||
|
||||
SHA-256 :
|
||||
`90349a998970030ec0bfd880b307b9162e76fd0f460d70f1c9ac8ad2c115c8bd`
|
||||
|
||||
Aucun push, tag, publication, canal packwiz public, installation Prism ou
|
||||
serveur personnel n’a été modifié.
|
||||
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"version": "beta.314",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.314.jar",
|
||||
"sha256": "82a2a6d8d4e2ad9d0aab754037995609722bbebac28db89a6e641c851ccb3ceb",
|
||||
"previous_verified_sha256": "54ea931d2f54eec8dac2d03e5ad1df8fd5b389db235a536bdd98d90a531fbe55",
|
||||
"changed_jar_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json",
|
||||
"data/sanctuary/recipe/ferment_vinegar.json",
|
||||
"fabric.mod.json",
|
||||
"fr/koka/sanctuary/cuisine/Cuisine.class",
|
||||
"fr/koka/sanctuary/cuisine/KitchenAppliances.class",
|
||||
"fr/koka/sanctuary/cuisine/KitchenClient$1.class",
|
||||
"fr/koka/sanctuary/cuisine/KitchenClient.class",
|
||||
"fr/koka/sanctuary/cuisine/KitchenJei$Category.class",
|
||||
"fr/koka/sanctuary/cuisine/KitchenJei.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/FermentationBarrelBlock.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/FermentationBarrelBlockEntity.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/FermentationBarrelHud.class",
|
||||
"resourcepacks/template/THIRD_PARTY_NOTICES.md"
|
||||
],
|
||||
"added_jar_entries": [
|
||||
"assets/sanctuary/blockstates/cheese.json",
|
||||
"assets/sanctuary/items/grand_cru_wine.json",
|
||||
"assets/sanctuary/items/reserve_wine.json",
|
||||
"assets/sanctuary/items/table_wine.json",
|
||||
"assets/sanctuary/models/block/cheese.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_1.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_2.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_3.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_4.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_5.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_6.json",
|
||||
"assets/sanctuary/models/item/grand_cru_wine.json",
|
||||
"assets/sanctuary/models/item/reserve_wine.json",
|
||||
"assets/sanctuary/models/item/table_wine.json",
|
||||
"data/sanctuary/loot_table/blocks/cheese.json",
|
||||
"data/sanctuary/recipe/age_grand_cru_wine.json",
|
||||
"data/sanctuary/recipe/age_reserve_wine.json",
|
||||
"data/sanctuary/recipe/age_table_wine.json",
|
||||
"data/sanctuary/sanctuary_recipe_collections/wine_maturation.json",
|
||||
"data/sanctuary/tags/item/wines.json",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/CheeseBlock.class",
|
||||
"fr/koka/sanctuary/cuisine/recipe/WineMaturationRecipe$1.class",
|
||||
"fr/koka/sanctuary/cuisine/recipe/WineMaturationRecipe.class"
|
||||
],
|
||||
"removed_jar_entries": [],
|
||||
"generated_json": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 228,
|
||||
"agriculture": 376
|
||||
},
|
||||
"additive_cheese_wine_json": 20,
|
||||
"culinary_source_classes": 52,
|
||||
"culinary_compiled_classes_verified": 72,
|
||||
"cheese_slices": 7,
|
||||
"cheese_top_rind_yellow": true,
|
||||
"cheese_eaten_cut_holes_only": true,
|
||||
"young_wine_game_ticks": 3600,
|
||||
"wine_age_clock": "server UTC epoch milliseconds, including server shutdown",
|
||||
"wine_quality_threshold_real_days": [
|
||||
1,
|
||||
3,
|
||||
7
|
||||
],
|
||||
"wine_quality_ids": [
|
||||
"sanctuary:wine",
|
||||
"sanctuary:table_wine",
|
||||
"sanctuary:reserve_wine",
|
||||
"sanctuary:grand_cru_wine"
|
||||
],
|
||||
"old_pngs_unchanged": 1108,
|
||||
"new_pngs": 0,
|
||||
"server_required": 126,
|
||||
"cheese_wine_scenarios": 14,
|
||||
"client_vulkan": {
|
||||
"log": "build/cheese-wine314-client.log",
|
||||
"screenshots": "build/cheese-wine314-screenshots",
|
||||
"screenshot_count": 15,
|
||||
"world_seed": 314,
|
||||
"sodium": "0.9.2+mc26.3",
|
||||
"native_paid_fermentation": true,
|
||||
"native_paid_cheese_placement": true,
|
||||
"native_seven_slices": true,
|
||||
"actual_disk_partial_cheese_and_wine_reopen": true,
|
||||
"native_grand_cru_collection_and_drinking": true,
|
||||
"fixture_simulates_fermentation_and_real_age": true
|
||||
},
|
||||
"client_server_copies_identical": true,
|
||||
"template_changed_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json"
|
||||
],
|
||||
"template_added_entries": [
|
||||
"assets/sanctuary/blockstates/cheese.json",
|
||||
"assets/sanctuary/items/grand_cru_wine.json",
|
||||
"assets/sanctuary/items/reserve_wine.json",
|
||||
"assets/sanctuary/items/table_wine.json",
|
||||
"assets/sanctuary/models/block/cheese.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_1.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_2.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_3.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_4.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_5.json",
|
||||
"assets/sanctuary/models/block/cheese_slice_6.json",
|
||||
"assets/sanctuary/models/item/grand_cru_wine.json",
|
||||
"assets/sanctuary/models/item/reserve_wine.json",
|
||||
"assets/sanctuary/models/item/table_wine.json"
|
||||
],
|
||||
"build_log": "build/cheese-wine314-build.log"
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
# IA-09 — fromage à partager et quatre qualités de vin — beta.314 locale
|
||||
|
||||
Demande du créateur : poser le fromage comme un gâteau, en manger les parts,
|
||||
et distinguer quatre qualités de vin. Choix confirmé le 29 octobre 2026 :
|
||||
vin jeune après les trois minutes de fermentation existantes, puis vin de table
|
||||
après un jour réel, réserve après trois jours et grand cru après sept jours.
|
||||
Un jour réel vaut 24 heures ; le serveur arrêté compte dans ce vieillissement.
|
||||
Même branche `codex/itsalive-26-3`, sans publication ni installation.
|
||||
|
||||
## Contrat de continuité avant modification
|
||||
|
||||
L'objet `sanctuary:cheese`, ses recettes et son icône conservent leur identifiant.
|
||||
Il devient un BlockItem du nouveau bloc `sanctuary:cheese`, avec la propriété
|
||||
native de gâteau `bites=0..6`. Les objets des inventaires existants deviennent
|
||||
posables sans conversion. Le fromage posé n'a pas de block entity. Les mondes
|
||||
et la génération ne sont pas modifiés. Comme le gâteau, casser une meule ne
|
||||
rend pas une meule neuve et perdre son support la détruit. Aucune part mangée
|
||||
ne peut être récupérée par le minage. Les gâteaux et bougies vanilla restent
|
||||
inchangés ; une bougie ne transforme pas le fromage en gâteau.
|
||||
|
||||
Le tonneau garde son identifiant, ses cinq cases, ses entrées, sa sortie et
|
||||
les champs historiques de fermentation. Vin jeune, bière, fromage, vinaigre
|
||||
et les autres procédés gardent leurs durées de fermentation en temps de jeu.
|
||||
Les nouveaux objets sont `sanctuary:table_wine`, `sanctuary:reserve_wine` et
|
||||
`sanctuary:grand_cru_wine` ; `sanctuary:wine` reste le vin jeune.
|
||||
|
||||
Seule une bouteille terminée qui reste dans la sortie du tonneau vieillit.
|
||||
L'horodatage UTC côté serveur commence à sa transformation effective en vin
|
||||
jeune. De nouveaux champs optionnels de version 1 mémorisent cet instant ;
|
||||
un ancien vin déjà en sortie commence son vieillissement lors du premier
|
||||
tick sous beta.314, sans âge inventé. Une fermentation ancienne en cours
|
||||
continue avec ses champs historiques, puis commence son vieillissement.
|
||||
Retirer le produit libère la sortie et efface uniquement son horodatage.
|
||||
Le vin dans un inventaire garde la qualité obtenue, sans vieillissement caché.
|
||||
|
||||
Le vieillissement se calcule avec l'heure réelle du serveur, même après une
|
||||
fermeture ou un déchargement. Dormir ou modifier l'heure Minecraft ne l'accélère
|
||||
pas. Un recul de l'horloge ne dégrade jamais un vin déjà amélioré. La qualité
|
||||
est matérialisée par l'objet lui-même, ce qui empêche les lots de qualités
|
||||
différentes de fusionner. La collecte et la casse rendent seulement ce produit.
|
||||
Un entonnoir actif récupère le jeune vin dès qu'il est prêt ; pour le laisser
|
||||
vieillir, verrouiller cet entonnoir avec de la redstone ou le retirer.
|
||||
|
||||
Avant un éventuel retour à beta.313, consommer les nouvelles bouteilles ou les
|
||||
transformer en vinaigre et manger les meules posées ; les identifiants ajoutés
|
||||
n'existent pas dans l'ancienne version.
|
||||
Aucun monde personnel n'est ouvert ou migré pour les essais.
|
||||
|
||||
## Aspect du fromage
|
||||
|
||||
Ajustement demandé pendant cette préparation : le dessus et la croûte extérieure
|
||||
restent jaunes. Dès la première part, seule la face de coupe expose la texture
|
||||
claire à trous, auparavant visible sur le dessus. La forme continue de diminuer
|
||||
sur les sept états natifs du gâteau. Les icônes historiques de fromage et de vin
|
||||
restent intactes ; les trois vins ajoutés reprennent le modèle de la bouteille
|
||||
avec des noms, raretés et saturation croissants. Les quatre vins restent
|
||||
compatibles avec la recette de vinaigre.
|
||||
|
||||
## Vérifications locales
|
||||
|
||||
Quatorze cas serveur natifs couvrent :
|
||||
|
||||
- paiement de la pose depuis l'objet historique, sept vraies prises de parts,
|
||||
forme qui diminue, faim pleine, absence de conversion par bougie, casse et
|
||||
perte de support sans restitution d'une meule neuve ;
|
||||
- recette de moût toujours prête à 3 600 ticks, origine UTC créée seulement
|
||||
lors de la production effective du vin jeune ;
|
||||
- frontière à une milliseconde près pour 1, 3 et 7 jours, une bouteille unique,
|
||||
qualité maximale stable, rechargement NBT, rattrapage d'un arrêt et recul de
|
||||
l'horloge sans déclassement ;
|
||||
- ancien vin sans date initialisé sans âge inventé, temps Minecraft indépendant,
|
||||
retrait puis nouveau lot sans âge hérité, casse avec la bouteille réellement
|
||||
obtenue, quatre qualités buvables et utilisables pour le vinaigre ;
|
||||
- trois recettes en secondes réelles, découverte depuis le moût et extraction
|
||||
par un vrai entonnoir qui libère correctement l'horodatage.
|
||||
|
||||
Le parcours graphique **Vulkan / Apple M1 / Sodium 0.9.2+mc26.3** utilise un
|
||||
monde plat neuf de graine **314**. Il vérifie les vrais paquets de dépôt de
|
||||
crème et sel, l'obtention du fromage, sa pose payée puis les sept prises de
|
||||
parts nourrissantes. Une fermeture/réouverture du monde conserve la meule avec
|
||||
trois parts déjà mangées et le vin de table avec son horodatage exact.
|
||||
Les quatre prises restantes épuisent cette même meule après rechargement.
|
||||
Les sept modèles vérifient leurs six faces : dessus et croûte jaunes,
|
||||
face ouest de coupe trouée seulement après la première part. Une capture est
|
||||
prise depuis cette face. Les cinq objets fromage/vins sont dans Nourriture et
|
||||
boissons, avec des modèles valides et les libellés FR/EN aux échelles GUI 2/3/4.
|
||||
Le HUD affiche les jours réels. Le grand cru issu du même moût se récupère et
|
||||
se boit par les interactions natives, et trois étapes de garde sont synchronisées
|
||||
dans leur catégorie JEI. Quinze captures sont conservées dans
|
||||
`build/cheese-wine314-screenshots/` (ignoré).
|
||||
|
||||
Les tests **n'attendent pas réellement sept jours** et ne changent pas l'horloge
|
||||
du poste : les cas serveur fournissent explicitement des instants de référence,
|
||||
et la fixture graphique antidate seulement le champ UTC des bouteilles dans
|
||||
son monde jetable. La fermentation initiale est rapprochée de son échéance
|
||||
historique, puis se termine par les ticks natifs. Le retrait, la pose,
|
||||
la sauvegarde/relecture, les sept parts et la consommation du vin restent réels.
|
||||
Le parcours graphique final a réussi en **1 min 28 s**.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=cheesewine314,drying313,cauldron312,cauldron311,pan310,oven308,mill306,kitchen304,cuisine303,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryCheeseWine314ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=cheesewine314 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
`check build assemblePack` a réussi en **3 min 24 s**, avec **126 tests serveur
|
||||
obligatoires**. L'audit strict à beta.313 autorise seulement les deux nouvelles
|
||||
classes de production, les 20 JSON ajoutés, la compatibilité vinaigre et les
|
||||
modifications de catalogue d'objets, registre, interaction du tonneau,
|
||||
horodatage, HUD, JEI, langues et crédits prévues pour ce ticket. Les
|
||||
**1 108 PNG** historiques sont identiques et aucun PNG n'est ajouté.
|
||||
Les 432 JSON de cuisine et 376 JSON d'agriculture restent identiques ; les
|
||||
228 JSON d'appareils sont comparés aux générateurs. Les modèles de coupe
|
||||
sont vérifiés sur les sept états. Les 52 classes sources culinaires et leurs
|
||||
72 classes compilées sont contrôlées dans le JAR. Les copies de ce JAR dans
|
||||
les assemblages client et serveur sont identiques. Le modèle du pack de
|
||||
ressources contient les quatorze nouveaux JSON visuels.
|
||||
|
||||
[Reçu d'audit](cheese-wine-beta314-verification.json).
|
||||
JAR local : `mods/sanctuary/build/libs/sanctuary-beta.314.jar`.
|
||||
|
||||
SHA-256 :
|
||||
`82a2a6d8d4e2ad9d0aab754037995609722bbebac28db89a6e641c851ccb3ceb`
|
||||
|
||||
Aucun push, tag, publication, canal packwiz public, installation Prism ou
|
||||
serveur personnel n'a été modifié.
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"version": "beta.305",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.305.jar",
|
||||
"sha256": "8c9b358c5c3caac3b9aa9a94230c7f6c6761d32f26002522bd8eba59c7f1cc2b",
|
||||
"generated_json": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 192,
|
||||
"agriculture": 376
|
||||
},
|
||||
"culinary_classes": 44,
|
||||
"server_required": 78,
|
||||
"crushing_scenarios": 8,
|
||||
"physical_recipes": 80,
|
||||
"device_blueprints": 90,
|
||||
"client_vulkan": {
|
||||
"crushing": "build/crushing305-client.log",
|
||||
"super_piston_regression": "build/crushing305-piston-client.log"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
# CUI-305 — écraser les fruits sans presse
|
||||
|
||||
Demande du créateur du 28 octobre 2026, précisée pour le super piston et
|
||||
confirmée : **huit paliers et neuf portions**. Préparation **beta.305** sur
|
||||
`codex/itsalive-26-3`, après beta.304. Travail local, sans publication,
|
||||
installation dans Prism ou déploiement serveur.
|
||||
|
||||
## Comportement retenu
|
||||
|
||||
La presse alimentaire quitte les recettes, les onglets créatifs et JEI.
|
||||
Les blocs de tomates, raisins et olives s'écrasent directement dans le monde.
|
||||
Leurs identifiants restent `sanctuary:tomato_block`, `grape_block` et `olive_block`.
|
||||
Les textures fournies restent intactes.
|
||||
|
||||
Chaque bloc possède **huit hauteurs**, de 16 à 2 pixels. Un véritable saut
|
||||
suivi d'un atterrissage retire un palier et fait apparaître les résidus en
|
||||
objets dans le monde : pulpe de tomate, moût de raisin ou huile d'olive.
|
||||
Les sept premiers sauts libèrent une portion chacun ; le huitième libère les
|
||||
deux dernières et supprime le bloc. Le joueur descend sur la nouvelle surface.
|
||||
Marcher, rester immobile et faire tomber un objet ne déclenchent rien.
|
||||
La petite chute de deux pixels provoquée par l'écrasement n'est pas un
|
||||
nouveau saut. Seuls les joueurs piétinent ces blocs.
|
||||
|
||||
Un **piston normal ou collant**, dans les six directions, écrase immédiatement
|
||||
le bloc rencontré lors de sa sortie. Il libère uniquement le rendement restant.
|
||||
Aucune presse, aucun réservoir ou récipient n'est requis : les portions sont
|
||||
libres et peuvent être ramassées ou collectées par les moyens vanilla.
|
||||
Aucune bouteille ou aucun bol n'est créé comme retour d'une portion libre.
|
||||
Les portions déjà conditionnées de beta.304 gardent leurs composants de retour.
|
||||
|
||||
Le **super piston** conserve sa face 3 × 3, sa course de trois blocs et sa
|
||||
capacité habituelle de déplacement. Une activation complète peut écraser les
|
||||
**27 blocs** situés sur ses trois profondeurs, soit **243 portions** pour un
|
||||
lot intact. Les neuf blocs rencontrés à chaque étape sont traités par le plan
|
||||
commun du super piston ; un obstacle qui empêche une étape ne consomme aucun
|
||||
fruit de cette étape. Les matériaux ordinaires conservent leurs règles.
|
||||
|
||||
Les blocs de fruits gardent leur gravité historique. Leur palier est porté par
|
||||
l'état de bloc, y compris pendant une chute et après sauvegarde.
|
||||
|
||||
## Rendement, casse et recettes
|
||||
|
||||
Un bloc intact coûte neuf fruits et peut encore se décompacter en neuf fruits.
|
||||
Après un ou plusieurs sauts, le casser rend **uniquement les fruits restants**,
|
||||
sans redonner un bloc entier. Cela empêche de récupérer neuf fruits en plus
|
||||
des résidus déjà obtenus. Par exemple :
|
||||
|
||||
| Situation | Déjà obtenu | Ce qui reste dans le bloc |
|
||||
| --- | ---: | ---: |
|
||||
| Bloc intact, 8 paliers | 0 | 9 |
|
||||
| Après trois sauts, 5 paliers | 3 | 6 |
|
||||
| Après six sauts, 2 paliers | 6 | 3 |
|
||||
| Dernier palier | 7 | 2 |
|
||||
|
||||
Un piston appliqué à un bloc de deux paliers donne donc trois portions,
|
||||
y compris après fermeture et réouverture du monde. La casse ordinaire utilise
|
||||
les tables de butin natives 26.3 avec leurs conditions d'état et quantités ;
|
||||
la destruction par piston remplace seulement ces butins par le produit écrasé.
|
||||
|
||||
Les trois identifiants de recettes `sanctuary:press_*` et le sérialiseur
|
||||
`sanctuary:food_pressing` sont conservés pour les données et la progression.
|
||||
Dans JEI, leur catégorie devient **Écrasement des fruits / Fruit Crushing**,
|
||||
avec les deux gestes expliqués en FR/EN. Le total des recettes physiques reste
|
||||
80 ; la recette de la presse disparaît, laissant dix fabrications d'équipement
|
||||
et de stockage. Les recettes des autres appareils restent en place.
|
||||
|
||||
## Contrat de continuité locale
|
||||
|
||||
Aucun monde existant n'est modifié ou converti par ce lot. Un ancien état de
|
||||
fruit sans propriété `layers` se charge comme un bloc plein de huit paliers.
|
||||
Les nouveaux paliers utilisent le format natif des états de blocs ; les
|
||||
identifiants et les recettes de compactage restent stables.
|
||||
|
||||
L'identifiant de presse introduit dans la préparation locale beta.304 reste
|
||||
**inactif**, uniquement pour lire ses anciens inventaires : aucun traitement,
|
||||
aucune recette, aucun catalogue créatif ou JEI. Un inventaire d'essai peut être
|
||||
récupéré à main vide ou à la casse. Ses quatre anciennes cases utilisent le
|
||||
même encodage natif. Il n'y a pas de conversion automatique en appareil ou en
|
||||
fruits, ni de suppression silencieuse d'une sauvegarde. Aucun nouveau monde,
|
||||
serveur ou chargement d'instance personnelle n'est engagé pour cette continuité.
|
||||
|
||||
## Implémentation et validation
|
||||
|
||||
`CrushableProduceBlock` gère la hauteur, les atterrissages et le rendement.
|
||||
Le dernier palier de deux pixels nécessite de prendre le support réel du
|
||||
joueur : le repère historique de chute de Minecraft regarde 0,2 bloc plus bas
|
||||
et désigne sinon le sol sous le fruit. L'adaptation reste limitée aux
|
||||
atterrissages des joueurs sur ces blocs.
|
||||
|
||||
Les pistons vanilla gardent leur résolution native des mouvements, avec la
|
||||
réaction `POPPED` et un remplacement ciblé du butin lors de l'écrasement.
|
||||
Le super piston utilise ce même rendement dans son plan commun déjà validé,
|
||||
avant la création des têtes mobiles. Les 21 modèles de hauteur partielle sont
|
||||
générés à partir des trois textures existantes, sans nouveaux PNG.
|
||||
|
||||
`check build assemblePack` passe en **2 min 20 s**, avec **78 tests serveur
|
||||
requis réussis**, dont huit nouveaux scénarios pour l'écrasement. Ils couvrent
|
||||
les trois fruits, les huit paliers, le rendement, la casse, la lecture d'anciens
|
||||
états sans `layers`, les six directions du piston normal, la disparition de la
|
||||
presse active, la course de 27 blocs du super piston et le refus atomique d'une
|
||||
face bloquée. Le scénario vertical du super piston est isolé des autres tests
|
||||
et compte aussi les résidus projetés en hauteur.
|
||||
|
||||
Le parcours **Vulkan avec Sodium 0.9.2** passe en **55 s** : huit vrais sauts
|
||||
au clavier, neuf portions obtenues, disparition du dernier palier, 24 modèles
|
||||
avec hauteur et collisions correspondantes, 27 blocs / 243 portions sur une
|
||||
activation réelle du super piston. La presse est absente du créatif et des
|
||||
ingrédients JEI, y compris du catalogue opérateur. La catégorie d'écrasement
|
||||
et ses trois recettes s'affichent en FR/EN aux échelles GUI 2/3/4. Une
|
||||
fermeture/réouverture réelle conserve les 24 états partiels ; le piston
|
||||
récupère ensuite exactement les trois portions d'un bloc resté à deux paliers.
|
||||
|
||||
La régression **Piston087 sous Vulkan avec Sodium** passe en **1 min 8 s** :
|
||||
six directions × deux variantes, charges ordinaires de 108/109 blocs, obstacles,
|
||||
inventaires, adhérence, collisions, interruption, sérialisation, rechargement
|
||||
de chunks et démontage. Son ancien appel de clé hors de portée est corrigé
|
||||
dans la fixture de test, sans modifier les règles de portée du jeu.
|
||||
|
||||
Le JAR contient les **192 JSON d'appareils/écrasement**, 432 JSON culinaires et
|
||||
376 JSON agricoles générés, identiques aux sources, les 44 classes culinaires
|
||||
et les deux adaptations de pistons/atterrissages. La recette de presse est
|
||||
absente de l'archive. Les 343 PNG importés restent identiques aux originaux,
|
||||
dont les 38 fournis, et les 13 PNG de vanille sont toujours en 16 × 16.
|
||||
Les copies packwiz client/serveur sont identiques au binaire contrôlé ; le
|
||||
template conserve les mêmes textures culinaires, agricoles et de vanille.
|
||||
|
||||
SHA-256 de `sanctuary-beta.305.jar` :
|
||||
|
||||
```text
|
||||
8c9b358c5c3caac3b9aa9a94230c7f6c6761d32f26002522bd8eba59c7f1cc2b
|
||||
```
|
||||
|
||||
[Reçu de validation](crushing-beta305-verification.json). Journaux dans
|
||||
`build/crushing305-*`, captures archivées dans
|
||||
`build/crushing305-crushing-screenshots/`, monde client neuf de graine **305**.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=crushing305,kitchen304,cuisine303,vanilla301,agriculture300,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryCrushing305ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=crushing305 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
Minecraft 26.3, Fabric API 0.160.5+26.3, Java 25. Les parcours graphiques
|
||||
ciblent exclusivement Vulkan et leurs mondes sont neufs et ignorés par Git.
|
||||
@@ -0,0 +1,241 @@
|
||||
# IA-05 — Recettes de la 26.2 jouables, beta.303 locale
|
||||
|
||||
> Cette page décrit l'étape beta.303. [La préparation beta.304](kitchen-beta304.md)
|
||||
> porte ensuite les six appareils : fermentation temporisée et pressage physique
|
||||
> remplacent leurs adaptations manuelles. Les préparations crues restent valides.
|
||||
|
||||
|
||||
Préparation du 28 octobre 2026 sur **la même branche `codex/itsalive-26-3`**,
|
||||
à partir de beta.302 (`930719d7`). Minecraft 26.3, Java 25 et dépendances
|
||||
inchangés. Aucun push, tag, publication, site, serveur ou Prism modifié.
|
||||
Les essais utilisent seulement des mondes de développement neufs.
|
||||
|
||||
## Catalogue importé et accès
|
||||
|
||||
Les **92 recettes alimentaires de la 26.2** sont reprises : 12 fabrications
|
||||
à froid, 19 préparations de chaudron, 26 de poêle, 13 de four, 10 recettes
|
||||
de fermentation, 9 de moulin et 3 de presse. Les ingrédients intermédiaires
|
||||
complètent les récoltes : farines, huile, pulpe, beurre, crème, fromage,+bouillon, sauces, tofu, riz cuit, nori, café moulu et vanille en poudre.
|
||||
Le catalogue apporte **83 ingrédients et plats**, dont 70 comestibles, et
|
||||
**46 préparations à cuire**, soit **129 objets**, tous dans l'onglet natif
|
||||
**Nourriture et boissons**. Les semences gardent l'onglet Naturel ; aucune
|
||||
nouvelle page créative n'est créée.
|
||||
|
||||
Les recettes utilisent la table de fabrication, le four et le fumoir
|
||||
existants. Une salade se fabrique directement ; les plats chauds composés
|
||||
passent par une préparation non comestible, puis une cuisson avec combustible.
|
||||
Les préparations ont le dessin du plat, teinté, un nom distinct et une aide
|
||||
FR/EN. Les recettes à un ingrédient sans récipient passent directement en
|
||||
cuisson, notamment le maïs grillé et l'ananas grillé.
|
||||
|
||||
Le parcours utilise **196 recettes natives** : **180 recettes ajoutées**
|
||||
(fabrications des préparations et variantes four/fumoir) et **16 recettes
|
||||
vanilla réutilisées** pour les viandes, poissons et pommes de terre.
|
||||
La découverte d'une récolte enseigne
|
||||
la collection Cuisine au catalogue existant ; sa consultation conserve les
|
||||
règles Sanctuary. La fabrication manuelle reste disponible sans cet achat.
|
||||
JEI utilise les catégories natives de fabrication et de cuisson.
|
||||
|
||||
## Adaptations explicites
|
||||
|
||||
Ce lot porte le catalogue alimentaire sur les outils déjà jouables. Il
|
||||
n'implémente pas les appareils historiques, leurs inventaires ou leurs gestes.
|
||||
Les 10 transformations issues de la fermentation sont **des recettes manuelles
|
||||
immédiates**, sans maturation en tonneau ni reprise du délai historique.
|
||||
Le travail du chaudron, de la poêle, de la cuisinière et du séchoir reste
|
||||
dans ses tickets ; aucun séchage ni mécanisme de plat raté n'est ajouté.
|
||||
|
||||
- Le pressage devient une fabrication avec **neuf fruits pour neuf portions**
|
||||
d'huile, de pulpe ou de moût, conservant le bilan de l'ancien bloc pressé.
|
||||
Les cinq recettes d'appareils et six recettes de compression/décompression
|
||||
ne sont pas importées ; elles ne sont pas nécessaires à cette chaîne.
|
||||
- Le moulin est adapté en fabrication manuelle. **Deux maïs donnent quatre
|
||||
semoules**, pour conserver le rendement sans entrer en conflit avec la
|
||||
récupération des grains depuis un maïs.
|
||||
Deux fèves de cacao donnent quatre poudres, pour éviter le conflit avec
|
||||
la teinture marron vanilla obtenue depuis une seule fève.
|
||||
- L'ancien curry et le ragoût de pois chiches avaient les mêmes ingrédients
|
||||
et se distinguaient par l'appareil. Le ragoût demande désormais une fiole
|
||||
d'eau supplémentaire ; le curry garde ses ingrédients. La table donne un
|
||||
résultat unique, sans dépendre de l'ordre de chargement des recettes.
|
||||
- `minecraft:potion` signifie exclusivement **une fiole d'eau sans effet**.
|
||||
Les potions de soin, poison, maladroites et eaux avec effets personnalisés
|
||||
ne sont pas des ingrédients. La cuisson ne consomme jamais une bouteille
|
||||
ou un seau : le récipient est récupéré dès la préparation à la table.
|
||||
- La fabrication rend exactement les bouteilles d'eau/miel, les seaux de
|
||||
lait et les couronnes d'ananas concernés. Les portions finies conservent
|
||||
le modèle d'objet historique, sans ajout de bols ou bouteilles gratuits.
|
||||
- Le tag historique manquant `herbs` est défini avec basilic, menthe et
|
||||
coriandre. Les tags de baies, poissons, champignons et viandes cuites sont
|
||||
repris. Les quantités finales et les profils alimentaires historiques
|
||||
sont conservés, y compris les effets des plats et du café ; les boissons
|
||||
utilisent l'animation native de boisson.
|
||||
- Les huit cuissons simples déjà disponibles dans le jeu réutilisent leurs
|
||||
recettes four/fumoir exactes de Minecraft 26.3, afin de conserver leurs
|
||||
durées, combustible et expérience, sans ajouter de recettes concurrentes.
|
||||
|
||||
## Provenance et limites
|
||||
|
||||
`scripts/data/cuisine.json` conserve les 92 recettes sources et leurs
|
||||
empreintes SHA-256, le catalogue alimentaire, les tags et les 11 exclusions.
|
||||
Source historique en lecture seule : `26.2/itsalive`, commit
|
||||
`26f2c2a0061619df1f146f9081d6740c560a7497`, GPL-3.0-or-later.
|
||||
`scripts/cuisine_resources.py` produit les données, modèles et collection.
|
||||
Les identifiants de destination utilisent `sanctuary:*` ; aucun ancien
|
||||
identifiant agricole n'est renommé. Les 343 PNG importés, les images fournies
|
||||
et les textures 16 × 16 de vanille sont conservés sans retouche.
|
||||
|
||||
`CulinaryCraftingRecipe` reste une recette sans forme native, avec contrôle
|
||||
de l'eau et retours de récipients. Ses codecs 26.3 conservent les templates
|
||||
sans matérialiser d'objets avant la liaison des composants du registre.
|
||||
Les inventaires et sauvegardes utilisent les formats natifs ; aucune
|
||||
migration, génération naturelle ou conversion d'appareil 26.2 n'est effectuée.
|
||||
|
||||
## Validation
|
||||
|
||||
`check build assemblePack` passe en **2 min 49 s**, avec **55 tests serveur
|
||||
requis réussis**, dont neuf scénarios culinaires. Ils couvrent les 92 recettes
|
||||
et leurs rendements, les chemins de production des 83 ingrédients/plats,
|
||||
l'absence de résultats concurrents, l'eau et les potions refusées, les
|
||||
récipients/couronnes, la fabrication en série, une vraie cuisson au fumoir,
|
||||
les propriétés alimentaires, les effets du café et la découverte des recettes.
|
||||
|
||||
Le parcours **Vulkan avec Sodium 0.9.2** passe en **53 s** : paquets natifs
|
||||
de fabrication d'une salade et d'une préparation de café, consommation exacte,
|
||||
bouteille récupérée et cuisson par les ticks du fumoir. Les **196 étapes sont
|
||||
présentes dans JEI** ; les ingrédients d'eau s'y affichent comme de l'eau.
|
||||
Les 129 modèles d'objets, le seul onglet natif Nourriture et boissons, les
|
||||
noms FR/EN et les échelles GUI 2/3/4 sont contrôlés. Un plat, une préparation
|
||||
et un ingrédient gardent leurs quantités après fermeture/réouverture du monde.
|
||||
|
||||
La régression agricole Vulkan passe en **36 s** : 313 états de cultures,
|
||||
48 objets, arbres/caféier, croix de l'oignon et de l'ail, onglets natifs FR/EN
|
||||
GUI 2/3/4 et rechargement des 173 états agricoles. Les ajouts culinaires
|
||||
n'empêchent pas l'accès aux récoltes dans le même onglet.
|
||||
|
||||
Les **445 ressources culinaires générées** et 376 JSON agricoles sont
|
||||
vérifiés dans le JAR, avec les classes compilées et notices. Les 92 sources
|
||||
de recette correspondent aux SHA-256 du reçu ; les 343 PNG importés restent
|
||||
identiques, dont les 38 fournis. Les 13 textures de vanille sont toujours en
|
||||
16 × 16. Les copies JAR packwiz client/serveur sont identiques au binaire
|
||||
contrôlé ; le template contient les mêmes textures culinaires et notices.
|
||||
|
||||
SHA-256 de `sanctuary-beta.303.jar` :
|
||||
|
||||
```text
|
||||
91809157a1f720192d5558682219840e6f70d13007a384973fa526ec4e9b2f66
|
||||
```
|
||||
|
||||
Profils de vérification :
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=cuisine303,vanilla301,agriculture300,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryCuisine303ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=cuisine303 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
Journaux et reçu dans `build/cuisine303-*`, captures et monde neuf de graine
|
||||
**303** dans le dossier de tests client ignoré. Validation graphique Vulkan
|
||||
exclusivement.
|
||||
|
||||
## Catalogue vérifiable
|
||||
|
||||
| Recette historique | Ingrédients à la table ou en cuisson directe | Résultat | Usage 26.3 |
|
||||
| --- | --- | --- | --- |
|
||||
| `cauldron_bean_chili` | Haricots secs + Sauce tomate + Piment | Chili de haricots | Préparation → four/fumoir |
|
||||
| `cauldron_broth` | Fiole d'eau + Carotte + Pomme de terre | 2 × Bouillon | Préparation → four/fumoir |
|
||||
| `cauldron_cabbage_soup` | Chou + Carotte + Bouillon | Soupe au chou | Préparation → four/fumoir |
|
||||
| `cauldron_chicken_rice_soup` | Poulet cru + Bouillon + Riz | Soupe poulet-riz | Préparation → four/fumoir |
|
||||
| `cauldron_chickpea_stew` | Pois chiches + Sauce tomate + Coriandre + Piment + Fiole d'eau | Ragoût de pois chiches | Préparation → four/fumoir |
|
||||
| `cauldron_coffee` | Café moulu + Fiole d'eau | Café | Préparation → four/fumoir |
|
||||
| `cauldron_cooked_rice` | Riz + Fiole d'eau | 2 × Riz cuit | Préparation → four/fumoir |
|
||||
| `cauldron_corn_chowder` | Maïs + Crème + Pomme de terre | Soupe de maïs | Préparation → four/fumoir |
|
||||
| `cauldron_cream` | Seau de lait | 2 × Crème | Préparation → four/fumoir |
|
||||
| `cauldron_lemon_honey_tea` | Citron + Fiole d'eau + Fiole de miel | Infusion citron-miel | Préparation → four/fumoir |
|
||||
| `cauldron_mint_tea` | Menthe + Fiole d'eau | Infusion de menthe | Préparation → four/fumoir |
|
||||
| `cauldron_mushroom_veloute` | Champignons de cuisine + Bouillon + Crème | Velouté de champignons | Préparation → four/fumoir |
|
||||
| `cauldron_potato_soup` | Pomme de terre + Crème + Oignon | Soupe de pommes de terre | Préparation → four/fumoir |
|
||||
| `cauldron_pumpkin_veloute` | Citrouille + Crème + Sel | Velouté de citrouille | Préparation → four/fumoir |
|
||||
| `cauldron_salt` | Fiole d'eau | 2 × Sel | Préparation → four/fumoir |
|
||||
| `cauldron_soy_milk` | Graines de soja + Fiole d'eau | 2 × Lait de soja | Préparation → four/fumoir |
|
||||
| `cauldron_tofu` | Lait de soja + Sel | 2 × Tofu | Préparation → four/fumoir |
|
||||
| `cauldron_tomato_sauce` | Pulpe de tomate + Sel + Basilic | 2 × Sauce tomate | Préparation → four/fumoir |
|
||||
| `cauldron_vegetable_soup` | Carotte + Bouillon + Oignon | Soupe de légumes | Préparation → four/fumoir |
|
||||
| `cold_burger` | Pain + Bœuf cuit + Fromage + Laitue + Tomate | Burger | Table |
|
||||
| `cold_cucumber_maki` | Riz cuit + Nori + Concombre | Maki concombre | Table |
|
||||
| `cold_fruit_salad` | Ananas + Fraise + Raisin + Pomme | Salade de fruits | Table |
|
||||
| `cold_green_salad` | Laitue + Concombre + Tomate | Salade verte | Table |
|
||||
| `cold_hummus` | Pois chiches + Huile d'olive + Citron + Ail | Houmous | Table |
|
||||
| `cold_kebab` | Pain + Mouton cuit + Oignon + Tomate | Kebab | Table |
|
||||
| `cold_mediterranean_salad` | Laitue + Concombre + Tomate + Olives | Salade méditerranéenne | Table |
|
||||
| `cold_sandwich` | Pain + Viandes cuites + Laitue + Tomate | Sandwich | Table |
|
||||
| `cold_sushi` | Riz cuit + Nori + Poissons de cuisine | Sushi | Table |
|
||||
| `cold_tofu_maki` | Riz cuit + Nori + Tofu | Maki tofu | Table |
|
||||
| `cold_vegetarian_sandwich` | Pain + Fromage + Tomate + Laitue | Sandwich végétarien | Table |
|
||||
| `ferment_beer` | Blé + Fiole d'eau + Levure | Bière | Table |
|
||||
| `ferment_cheese` | Crème + Sel | Fromage | Table |
|
||||
| `ferment_hot_sauce` | Pulpe de tomate + Piment + Vinaigre | Sauce piquante | Table |
|
||||
| `ferment_kimchi` | Chou + Sel + Piment | Kimchi | Table |
|
||||
| `ferment_pickles` | Concombre + Sel + Vinaigre | Cornichons | Table |
|
||||
| `ferment_soy_sauce` | Graines de soja + Sel | Sauce soja | Table |
|
||||
| `ferment_vinegar` | Vin | Vinaigre | Table |
|
||||
| `ferment_wine` | Moût de raisin | Vin | Table |
|
||||
| `ferment_yeast` | Blé + Sucre | 2 × Levure | Table |
|
||||
| `ferment_yogurt` | Seau de lait + Sucre | Yaourt | Table |
|
||||
| `mill_chickpeas` | Pois chiches | 2 × Farine de pois chiche | Table |
|
||||
| `mill_cocoa_powder` | 2 × Fèves de cacao | 4 × Poudre de cacao | Table |
|
||||
| `mill_cornmeal` | 2 × Maïs | 4 × Semoule de maïs | Table |
|
||||
| `mill_cream` | Crème | Beurre | Table |
|
||||
| `mill_dried_kelp` | Algue séchée | Nori | Table |
|
||||
| `mill_ground_coffee` | Grains de café torréfiés | 2 × Café moulu | Table |
|
||||
| `mill_rice` | Riz | 2 × Farine de riz | Table |
|
||||
| `mill_vanilla_powder` | Gousse de vanille séchée | 2 × Poudre de vanille | Table |
|
||||
| `mill_wheat` | Blé | 2 × Farine de blé | Table |
|
||||
| `oven_apple_pie` | Pâte crue + Pomme + Sucre + Beurre | Tarte aux pommes | Préparation → four/fumoir |
|
||||
| `oven_berry_pie` | Pâte crue + Baies de cuisine + Sucre + Beurre | Tarte aux baies | Préparation → four/fumoir |
|
||||
| `oven_bread` | Pâte crue | Pain | Four/fumoir |
|
||||
| `oven_carrot_cake` | Pâte crue + Carotte + Sucre + Œuf | Gâteau aux carottes | Préparation → four/fumoir |
|
||||
| `oven_chickpea_flatbread` | Farine de pois chiche + Fiole d'eau + Huile d'olive | Galette de pois chiche | Préparation → four/fumoir |
|
||||
| `oven_chocolate_cake` | Pâte crue + Poudre de cacao + Sucre + Œuf | Gâteau au chocolat | Préparation → four/fumoir |
|
||||
| `oven_cornbread` | Semoule de maïs + Levure + Seau de lait | Pain de maïs | Préparation → four/fumoir |
|
||||
| `oven_flatbread` | Farine de blé + Fiole d'eau + Sel | Pain plat | Préparation → four/fumoir |
|
||||
| `oven_margherita_pizza` | Pâte crue + Sauce tomate + Fromage + Basilic | Pizza margherita | Préparation → four/fumoir |
|
||||
| `oven_pumpkin_pie` | Pâte crue + Citrouille + Sucre + Œuf | Tarte à la citrouille | Préparation → four/fumoir |
|
||||
| `oven_rice_bread` | Farine de riz + Levure + Fiole d'eau | Pain de riz | Préparation → four/fumoir |
|
||||
| `oven_stuffed_pepper` | Poivron + Riz cuit + Sauce tomate + Fromage | Poivron farci | Préparation → four/fumoir |
|
||||
| `oven_vanilla_cake` | Pâte crue + Œuf + Sucre + Poudre de vanille | Gâteau à la vanille | Préparation → four/fumoir |
|
||||
| `pan_beef` | Bœuf cru | Bœuf cuit | Four/fumoir |
|
||||
| `pan_beef_stir_fry` | Bœuf cru + Poivron + Oignon + Sauce soja | Bœuf sauté | Préparation → four/fumoir |
|
||||
| `pan_chicken` | Poulet cru | cooked_chicken | Four/fumoir |
|
||||
| `pan_chickpea_curry` | Pois chiches + Sauce tomate + Piment + Coriandre | Curry de pois chiches | Préparation → four/fumoir |
|
||||
| `pan_cod` | Morue crue | cooked_cod | Four/fumoir |
|
||||
| `pan_fish_meuniere` | Poissons de cuisine + Citron + Beurre | Poisson meunière | Préparation → four/fumoir |
|
||||
| `pan_fried_cabbage` | Chou + Beurre + Oignon | Chou poêlé | Préparation → four/fumoir |
|
||||
| `pan_fried_egg` | Œuf | Œuf au plat | Four/fumoir |
|
||||
| `pan_fried_rice` | Riz cuit + Œuf + Graines de soja + Sauce soja | Riz sauté | Préparation → four/fumoir |
|
||||
| `pan_garlic_green_beans` | Haricots verts + Ail + Beurre | Haricots verts sautés | Préparation → four/fumoir |
|
||||
| `pan_grilled_corn` | Maïs | Maïs grillé | Four/fumoir |
|
||||
| `pan_grilled_eggplant` | Aubergine + Huile d'olive | Aubergine grillée | Préparation → four/fumoir |
|
||||
| `pan_grilled_pineapple` | Ananas | Ananas grillé | Four/fumoir |
|
||||
| `pan_herb_omelette` | Œuf + Seau de lait + Herbes aromatiques | Omelette aux herbes | Préparation → four/fumoir |
|
||||
| `pan_mushroom_saute` | Champignons de cuisine + Beurre + Ail | Poêlée de champignons | Préparation → four/fumoir |
|
||||
| `pan_mutton` | Mouton cru | Mouton cuit | Four/fumoir |
|
||||
| `pan_omelette` | Œuf + Seau de lait | Omelette | Préparation → four/fumoir |
|
||||
| `pan_porkchop` | Porc cru | cooked_porkchop | Four/fumoir |
|
||||
| `pan_potato` | Pomme de terre | baked_potato | Four/fumoir |
|
||||
| `pan_rabbit` | Lapin cru | cooked_rabbit | Four/fumoir |
|
||||
| `pan_ratatouille` | Aubergine + Tomate + Poivron + Oignon | Ratatouille | Préparation → four/fumoir |
|
||||
| `pan_salmon` | Saumon cru | cooked_salmon | Four/fumoir |
|
||||
| `pan_sauteed_potatoes` | Pomme de terre + Oignon + Beurre | Pommes de terre sautées | Préparation → four/fumoir |
|
||||
| `pan_spiced_mutton` | Mouton cru + Oignon + Piment + Coriandre | Émincé de mouton épicé | Préparation → four/fumoir |
|
||||
| `pan_tofu_stir_fry` | Tofu + Poivron + Graines de soja + Sauce soja | Wok de tofu | Préparation → four/fumoir |
|
||||
| `pan_tomato_basil_chicken` | Poulet cru + Tomate + Basilic | Poulet tomate-basilic | Préparation → four/fumoir |
|
||||
| `press_grape_block` | 9 × Raisin | 9 × Moût de raisin | Table |
|
||||
| `press_olive_block` | 9 × Olives | 9 × Huile d'olive | Table |
|
||||
| `press_tomato_block` | 9 × Tomate | 9 × Pulpe de tomate | Table |
|
||||
| `raw_dough` | Farine de blé + Levure + Fiole d'eau | 2 × Pâte crue | Table |
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"version": "beta.318",
|
||||
"date": "2026-10-30",
|
||||
"branch": "codex/itsalive-26-3",
|
||||
"base_commit": "7ef4d51a",
|
||||
"local_only": true,
|
||||
"published_or_installed": false,
|
||||
"new_results": 18,
|
||||
"new_items_including_preparation": 19,
|
||||
"new_recipes": 19,
|
||||
"active_drying_recipes": 16,
|
||||
"active_oven_recipes": 17,
|
||||
"active_cauldron_recipes": 24,
|
||||
"active_milling_recipes": 10,
|
||||
"generated_json_verified": 1268,
|
||||
"unchanged_original_pngs": 1108,
|
||||
"jar_sha256": "5b8d6fff72c3128ceac085a9d71b493bbda13a0dbbe42ffdd3e8dc2fda3a632b",
|
||||
"client_server_staged_jar_sha256_match": true,
|
||||
"template_zip_sha256": "a5b6199779c818e5134d6b9eef10f257dc4412f1bcd0dc53589121eff793ada3",
|
||||
"server": {
|
||||
"command": "./gradlew check build assemblePack -PsanctuaryFocusedTests=driedcuisine318,drying317,drying313,foraging316,agriculture300,vanilla301,sausage315,cheesewine314,cauldron312,cauldron311,pan310,oven308,mill306,kitchen304,cuisine303,recipes,progression,foodknowledge -PsanctuaryAtlasOnly=true",
|
||||
"required_tests_passed": 189,
|
||||
"duration": "2m 48s",
|
||||
"log": "build/dried-cuisine318-check-build-pack.log"
|
||||
},
|
||||
"client": {
|
||||
"command": "./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryDriedCuisine318ClientTests=true -PsanctuarySodiumClientTests=true -PsanctuaryFocusedTests=driedcuisine318,drying317 -PsanctuaryQuickTests=true -PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 -PsanctuaryClientNoVsync=true",
|
||||
"backend": "Vulkan / Apple M1",
|
||||
"sodium": true,
|
||||
"duration": "1m 10s",
|
||||
"result": "pass",
|
||||
"models": 19,
|
||||
"screenshots": 16,
|
||||
"screenshots_directory": "build/dried-cuisine318-screenshots",
|
||||
"log": "build/dried-cuisine318-client.log",
|
||||
"spicy_visual_pulses_and_unchanged_health_verified": true,
|
||||
"only_graphical_drying_fixture_accelerates_after_actual_save": true,
|
||||
"final_low_impact_change": "Raw spicy sausage tooltip points to the drying rack; compiled with the full final build."
|
||||
},
|
||||
"spicy_effect": {
|
||||
"visual_packet": "ClientboundHurtAnimationPacket",
|
||||
"pulses": 2,
|
||||
"delay_ticks": 8,
|
||||
"damage_event_sent": false,
|
||||
"health_absorption_velocity_and_server_hurt_state_unchanged": true,
|
||||
"saved_state_added": false
|
||||
},
|
||||
"graphics_limit": "Existing sprites with native tint and reused cuboid sausage geometry."
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
# IA-11 — Les 18 nouvelles recettes aux produits séchés
|
||||
|
||||
Préparation locale **beta.318**, 30 octobre 2026, branche `codex/itsalive-26-3`,
|
||||
socle `7ef4d51a` / beta.317. Demande : rendre jouables les dix-huit combinaisons
|
||||
du guide, puis ajouter de petits coups sans dégâts lorsqu'on mange épicé.
|
||||
Aucune publication, installation Prism, mise à jour de site ou de serveur.
|
||||
|
||||
## Résultat jouable
|
||||
|
||||
[Liste complète : ingrédients, appareils, quantités et durées](wiki/alimentation/sechoir.md) ·
|
||||
[Fiche autonome HTML](wiki/alimentation/sechoir.html).
|
||||
|
||||
- Cuisinière : pain aux raisins, brioche vanillée aux raisins, biscuits aux fruits
|
||||
secs et pain aux tomates et basilic. Cuisson de 12 à 16 secondes, ingrédients
|
||||
payés en fin de cycle, combustible et sortie unique habituels. Quatre biscuits
|
||||
par lot ; un pain ou une brioche pour les autres préparations.
|
||||
- Établi : les deux sandwichs, salade de pois chiches aux tomates séchées,
|
||||
tomates marinées, mélange de fruits secs, dessert ananas-vanille et
|
||||
assaisonnement aux herbes. Un résultat par lot, ou deux portions d'assaisonnement.
|
||||
- Chaudron : riz au saumon séché, ragoût du voyageur, soupe de poisson séché,
|
||||
haricots à la viande séchée et infusion pomme-menthe. Une dose d'eau par portion,
|
||||
chaleur et geste au bâton ; cuisson de 11 à 18 secondes. Le réassort et la recette
|
||||
mémorisée gardent le fonctionnement existant. Ajouter le bol, ou une fiole vide
|
||||
pour l'infusion, avec les ingrédients. Le récipient revient une fois à la consommation.
|
||||
- Moulin manuel : un piment séché donne deux portions de poudre, sans combustible.
|
||||
La poudre remplace aussi le piment dans les anciennes recettes. L'assaisonnement
|
||||
aux herbes fonctionne dans l'omelette existante.
|
||||
- Saucisson pimenté : à l'établi, une préparation de saucisson, un piment séché
|
||||
ou une poudre de piment et une coriandre séchée donnent une préparation pimentée.
|
||||
Elle sèche cinq minutes puis se pose pour manger quatre tranches de deux points
|
||||
de nourriture chacune. Les modèles montrent la coupe et la longueur restante.
|
||||
|
||||
Cela ajoute **18 résultats, 19 objets en comptant la préparation de saucisson,
|
||||
et 19 transformations** : le saucisson possède deux étapes. Le séchoir passe de
|
||||
15 à **16 recettes**. Aucun nouvel onglet créatif ; tout rejoint Nourriture et boissons.
|
||||
Libellés FR/EN, recettes natives synchronisées dans JEI et découverte par les
|
||||
ingrédients séchés ou les préparations correspondantes.
|
||||
|
||||
Les bols et fioles sont des composants natifs `USE_REMAINDER`, après paiement
|
||||
effectif de leur récipient d'origine. L'infusion consomme aussi une fiole de miel :
|
||||
sa bouteille est éjectée à la cuisson, tandis que la fiole de service revient
|
||||
quand on boit. L'eau peut venir de la réserve du chaudron ou d'une vraie fiole
|
||||
d'eau ; les potions à effets sont refusées. L'ananas séché ne rend aucune seconde
|
||||
couronne et les nouveaux plats ne créent aucune semence.
|
||||
|
||||
## Réaction épicée
|
||||
|
||||
Après la consommation effective d'un aliment du tag `sanctuary:spicy_foods`,
|
||||
un paquet natif `ClientboundHurtAnimationPacket` et un petit son de coup reçu
|
||||
produisent la réaction ; un second coup suit huit ticks plus tard, soit 0,4 seconde.
|
||||
Une nouvelle bouchée remplace l'attente précédente, sans empiler une file sans limite.
|
||||
|
||||
Le piment cru et les anciens chilis, ragoût de pois chiches, curry, mouton épicé
|
||||
et kimchi sont concernés, ainsi que les nouveaux haricots à la viande séchée et
|
||||
le saucisson pimenté posé. Les ingrédients non comestibles, dont la sauce piquante,
|
||||
ne déclenchent aucune consommation artificielle. Les plats doux ne réagissent pas.
|
||||
|
||||
Il n'y a aucun appel de dégâts, aucune modification de santé, absorption, armure,
|
||||
invulnérabilité ou vitesse. Aucun événement de dégâts n'est envoyé. La réaction
|
||||
reste transitoire, ne crée ni effet de statut ni donnée de sauvegarde, et s'arrête
|
||||
à la mort, à la déconnexion ou à l'arrêt du serveur. Une tranche refusée parce
|
||||
que le joueur est rassasié ne déclenche pas la réaction.
|
||||
|
||||
## Compatibilité et graphismes
|
||||
|
||||
Ajouts uniquement pour les objets, bloc de saucisson pimenté et recettes. Les
|
||||
identifiants existants et le stockage des appareils, dont `DryingVersion=1`,
|
||||
restent inchangés. Aucun changement de génération, aucun monde personnel ouvert,
|
||||
aucune conversion de sauvegarde. Les anciennes préparations et le séchage nature
|
||||
gardent leurs recettes et leurs durées.
|
||||
|
||||
Les nouvelles recettes réutilisent les types natifs de fabrication culinaire,
|
||||
cuisson, moulin et séchoir déjà synchronisés. Le catalogue historique 26.2
|
||||
n'est pas modifié. Le nouveau catalogue `scripts/data/dried-cuisine.json` contient
|
||||
les valeurs alimentaires, quantités, récipients, durées et sources des icônes.
|
||||
Les propositions du catalogue précédent sont marquées comme implémentées en beta.318.
|
||||
|
||||
Les icônes utilisent des sprites existants avec des teintes natives. Le saucisson
|
||||
pimenté réutilise les géométries cubiques du saucisson avec une teinte orange,
|
||||
y compris les seize orientations et découpes. Les PNG d'origine restent intacts ;
|
||||
il ne s'agit pas de dix-neuf nouvelles textures peintes sur mesure.
|
||||
|
||||
## Vérification
|
||||
|
||||
`./gradlew check build assemblePack` a réussi en **2 min 48 s**. Les **189 tests
|
||||
serveur natifs** passent : dix nouveaux cas couvrent les dix-huit résultats et
|
||||
leurs dix-neuf transformations, les fabrications payées, toutes les échéances
|
||||
complètes, la cuisson répétée, les bols/fioles, la découverte, le saucisson
|
||||
sauvegardé puis partagé et les paquets épicés sans dégâts. Les régressions couvrent
|
||||
les 24 recettes du chaudron, les autres appareils, l'agriculture, les récoltes,
|
||||
la vanille, les recettes et la progression.
|
||||
|
||||
Le parcours **Vulkan / Apple M1 avec Sodium** a réussi en **1 min 10 s** :
|
||||
les dix-neuf modèles et leurs noms FR/EN, nom du bloc placé, onglet Nourriture et
|
||||
boissons, menus aux échelles 2/3/4, vrais paquets de fabrication d'un sandwich,
|
||||
cuisson du pain aux raisins, riz au saumon au chaudron après remuage, mouture de
|
||||
quatre piments en huit poudres puis paiement d'une poudre pour le saucisson,
|
||||
accrochage et récupération, sauvegarde réelle, quatre tranches, deux animations
|
||||
épicées par tranche avec santé inchangée, retour d'un bol et recettes JEI.
|
||||
Seuls les derniers ticks du séchage graphique sont rapprochés de leur échéance,
|
||||
après la sauvegarde ; les 6 000 ticks complets passent côté serveur. Seize
|
||||
captures sont conservées dans `build/dried-cuisine318-screenshots/`.
|
||||
|
||||
L'infobulle finale de la préparation pimentée indique le séchoir et ses 300 secondes,
|
||||
puis est compilée avec le build complet. Les contrôles du JAR confirment
|
||||
**1 268 ressources JSON**, les **19 recettes ajoutées** sans suppression d'ancien
|
||||
identifiant et **1 108 PNG strictement identiques à beta.317**. Les JAR du mod et
|
||||
des packs client/serveur préparés ont le même SHA-256 ; le ZIP du template est valide.
|
||||
Aucun monde personnel ni installation de jeu n'a été touché.
|
||||
[Commandes, empreintes et reçu](dried-cuisine-beta318-verification.json).
|
||||
|
||||
## Installation locale ultérieure
|
||||
|
||||
Le 30 octobre 2026, ce binaire vérifié est installé dans la même instance Prism
|
||||
pour les essais, sans publication ni lancement d’un monde personnel.
|
||||
[Profil local, conservation et reçu](local-beta318.md).
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"version": "beta.317",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.317.jar",
|
||||
"jar_sha256": "a1ddd0b1701d2144a93f1368a4e1f862869e2170ee03c7c842908578c81a60a6",
|
||||
"generated_json_verified": 1182,
|
||||
"unchanged_original_pngs": 1108,
|
||||
"new_items": 12,
|
||||
"active_drying_recipes": 15,
|
||||
"new_drying_recipes": 14,
|
||||
"future_combinations_proposed": 18,
|
||||
"legacy_smoking_xp_recipe_preserved": true,
|
||||
"date": "2026-10-30",
|
||||
"branch": "codex/itsalive-26-3",
|
||||
"base_commit": "7b56c3cd",
|
||||
"local_only": true,
|
||||
"published_or_installed": false,
|
||||
"world_generation_changed": false,
|
||||
"drying_storage_version": 1,
|
||||
"server": {
|
||||
"command": "./gradlew check build assemblePack -PsanctuaryFocusedTests=drying317,drying313,foraging316,agriculture300,vanilla301,sausage315,cheesewine314,cauldron312,cauldron311,pan310,oven308,mill306,kitchen304,cuisine303,recipes,progression,foodknowledge -PsanctuaryAtlasOnly=true",
|
||||
"required_tests_passed": 179,
|
||||
"build_duration": "2m 40s",
|
||||
"log": "build/drying317-check-build-pack.log"
|
||||
},
|
||||
"client": {
|
||||
"command": "./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryDrying317ClientTests=true -PsanctuarySodiumClientTests=true -PsanctuaryFocusedTests=drying317,drying313 -PsanctuaryQuickTests=true -PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 -PsanctuaryClientNoVsync=true",
|
||||
"backend": "Vulkan",
|
||||
"renderer": "Apple M1",
|
||||
"sodium": true,
|
||||
"duration": "51s",
|
||||
"result": "pass",
|
||||
"screenshots": "build/drying317-final-screenshots",
|
||||
"screenshots_count": 9,
|
||||
"log": "build/drying317-client.log",
|
||||
"accelerated_near_completion_only_in_graphical_fixture": true
|
||||
},
|
||||
"template_zip_sha256": "bd8f959ce3d835c1ffff7f7d769cf125700c599bee2f049ed9b04cb1e9412b94",
|
||||
"staged_client_and_server_jar_sha256_match": true,
|
||||
"graphics_limit": "Existing sprites with native constant tint; no twelve separately painted PNGs."
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
# IA-10 — Viandes, poissons, fruits et herbes au séchoir
|
||||
|
||||
Préparation locale **beta.317**, 30 octobre 2026, même branche
|
||||
`codex/itsalive-26-3`, socle `7b56c3cd` / beta.316 locale. Demande : ajouter les
|
||||
produits séchés et proposer des combinaisons culinaires. Aucune publication,
|
||||
installation Prism, modification du site ou du serveur personnel.
|
||||
|
||||
## Résultat jouable
|
||||
|
||||
[Recettes détaillées, usages et 18 propositions de plats](wiki/alimentation/sechoir.md) ·
|
||||
[Copie autonome sous forme de tableaux](wiki/alimentation/sechoir.html).
|
||||
|
||||
Le séchoir possède **15 recettes**, dont le saucisson de beta.313 inchangé :
|
||||
|
||||
| Durée de jeu | Produits |
|
||||
| --- | --- |
|
||||
| 1 minute | Basilic, menthe, coriandre et fraises séchés |
|
||||
| 1 min 30 s | Raisins secs, piment séché, pomme séchée |
|
||||
| 2 minutes | Cabillaud séché, saumon séché, tomates et ananas séchés |
|
||||
| 3 minutes | Gousse de vanille séchée |
|
||||
| 4 minutes | Viande séchée depuis du bœuf cru ou du porc cru |
|
||||
| 5 minutes | Saucisson séché depuis sa préparation |
|
||||
|
||||
**Douze nouveaux objets** dans l'onglet vanilla Nourriture et boissons,
|
||||
avec libellés FR/EN, modèles et recettes JEI. Cabillaud et saumon crus utilisent
|
||||
les objets Minecraft existants. Bœuf et porc donnent le même produit séché ;
|
||||
les deux poissons gardent des produits distincts. Les fruits ajoutés sont
|
||||
pomme, fraise et ananas, plus les raisins secs.
|
||||
|
||||
Les nouvelles provisions sont comestibles. Viande : 6 points et coefficient
|
||||
de saturation 0,8 ; cabillaud : 5/0,6 ; saumon : 6/0,7 ; raisins et fraises :
|
||||
3/0,5 ; tomate : 2/0,6 ; pomme : 4/0,5 ; ananas : 4/0,7. Piment et herbes
|
||||
séchés restent des ingrédients. La couronne d'ananas est rendue exactement
|
||||
une fois **à la fin du séchage**, par le mécanisme de restes déjà présent.
|
||||
Le produit séché n'en rend pas à sa consommation.
|
||||
|
||||
Les quatre crochets restent indépendants : aliments différents, départs
|
||||
différents et durées différentes sur un même cadre. Aucun combustible.
|
||||
Ticks chargés à 20 ticks/s ; pluie et immersion suspendent le travail,
|
||||
un toit protège. Alimentation par entonnoir et extraction des produits prêts
|
||||
seulement conservées. Le retrait d'un aliment annule uniquement son propre
|
||||
travail. Les ingrédients prêts ne sont pas séchés à nouveau.
|
||||
|
||||
## Recettes existantes et pistes futures
|
||||
|
||||
Le piment et chaque herbe séchée remplacent leur équivalent frais, un pour
|
||||
un, dans les recettes de cuisine portées, aussi bien à la préparation manuelle
|
||||
que dans les appareils. Les tags sont additifs : `cooking_chilies`,
|
||||
`cooking_basil`, `cooking_mint`, `cooking_coriander`, et le tag `herbs` inclut
|
||||
aussi les trois herbes sèches. Cela rend immédiatement utilisables les
|
||||
omelettes, infusions de menthe, sauces tomate, pizzas, chilis, currys, mouton
|
||||
épicé, kimchi et sauce piquante concernés. Les récoltes sèches ne donnent
|
||||
pas de nouvelles graines via les recettes agricoles.
|
||||
|
||||
Les tags `dried_fish` et `dried_fruits` décrivent les produits disponibles.
|
||||
Les **18 autres combinaisons du guide sont des propositions**, avec des
|
||||
ingrédients présents dans le jeu ; aucun nouveau pain, sandwich, ragoût,
|
||||
mélange de fruits ou assaisonnement n'est annoncé comme implémenté.
|
||||
Les quantités, eau, récipients et valeurs nutritionnelles de ces futurs plats
|
||||
restent à décider lors d'un prochain lot.
|
||||
|
||||
## Contrat de compatibilité et graphismes
|
||||
|
||||
Les identifiants existants, le format `DryingVersion=1`, les quatre cases
|
||||
et leurs compteurs sont conservés. Aucun changement de génération ni de
|
||||
sauvegarde n'est ajouté. Pas de repeuplement ou de conversion d'un monde.
|
||||
Les préparations de saucisson gardent leur recette et leurs 6 000 ticks.
|
||||
|
||||
La production active de vanille utilise désormais `sanctuary:dry_vanilla_pod`,
|
||||
`sanctuary:food_drying`, 3 600 ticks, et garde le résultat `sanctuary:vanilla_pod`.
|
||||
L'ancien identifiant `sanctuary:dried_vanilla_pod` reste enregistré comme recette
|
||||
native de fumoir avec sa valeur d'XP historique, mais son tag d'ingrédients de
|
||||
compatibilité `legacy_vanilla_smoking` est vide : aucune gousse ne s'y traite.
|
||||
Cette conservation évite le cast natif vers `AbstractCookingRecipe` lors du
|
||||
paiement d'une ancienne réserve d'XP de fumoir. Le test recharge cette histoire
|
||||
native, récupère les recettes/XP et vérifie que gousses vertes et charbon restent
|
||||
intacts. Aucun inventaire ou format de sauvegarde n'est réécrit.
|
||||
La poudre de vanille au moulin et les recettes utilisant la gousse restent
|
||||
compatibles. Aucune modification de monde existant n'est réalisée pour tester
|
||||
ce changement.
|
||||
|
||||
Les nouvelles icônes utilisent le modèle natif `item/generated`, les sprites
|
||||
existants et une teinte constante, comme les préparations déjà portées.
|
||||
Tous les PNG d'origine restent intacts. Ce sont des icônes réutilisant les
|
||||
dessins actuels, pas douze nouvelles textures peintes sur mesure.
|
||||
|
||||
## Sources et vérifications
|
||||
|
||||
`scripts/data/drying-foods.json` pilote les objets, 14 nouvelles transformations,
|
||||
durées et 18 propositions. `scripts/dried_food_resources.py` génère les
|
||||
modèles, recettes, tags et fiches de découverte ; `DriedFoods` enregistre les
|
||||
objets. `scripts/build_drying_guide.py` régénère le guide Markdown et sa copie
|
||||
autonome HTML. Le catalogue historique 26.2 n'est pas réécrit.
|
||||
|
||||
`./gradlew check build assemblePack` a réussi en **2 min 40 s**, avec
|
||||
**179 tests serveur natifs réussis** : nouvelles transformations, séchoir historique,
|
||||
récoltes, agriculture et vanille, fromage/vin/saucisson, appareils, cuisine,
|
||||
recettes et progression. Les 14 nouvelles transformations exécutent chaque
|
||||
échéance complète, avec contrôle au tick précédent puis au tick exact.
|
||||
|
||||
Le parcours **Vulkan / Apple M1 avec Sodium** a réussi en **51 s** : pose et
|
||||
accrochage payés de quatre aliments différents, douze modèles sans texture
|
||||
manquante, onglet Nourriture et boissons, FR/EN aux échelles 2/3/4, immersion
|
||||
et vidage avec de vrais seaux, fermeture/réouverture du monde de test, reprise
|
||||
des compteurs, quatre récupérations, consommation native et **15 recettes JEI**.
|
||||
Les compteurs sont rapprochés de leur fin uniquement dans la fixture graphique,
|
||||
après sauvegarde et vidage ; les durées complètes sont testées côté serveur.
|
||||
Neuf captures sont conservées dans `build/drying317-final-screenshots/`.
|
||||
|
||||
L'audit du JAR vérifie **1 182 ressources JSON** contre les générateurs,
|
||||
**1 108 PNG strictement identiques à beta.316**, et les modèles/libellés FR/EN.
|
||||
Les JAR du mod et des packs client/serveur préparés ont le même SHA-256 ;
|
||||
le ZIP du template beta.317 est valide. Aucune installation ni publication.
|
||||
[Commandes, empreintes et reçu](drying-foods-beta317-verification.json).
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"version": "beta.313",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.313.jar",
|
||||
"sha256": "54ea931d2f54eec8dac2d03e5ad1df8fd5b389db235a536bdd98d90a531fbe55",
|
||||
"previous_verified_sha256": "90349a998970030ec0bfd880b307b9162e76fd0f460d70f1c9ac8ad2c115c8bd",
|
||||
"changed_jar_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json",
|
||||
"data/minecraft/tags/block/mineable/axe.json",
|
||||
"fabric.mod.json",
|
||||
"fr/koka/sanctuary/cuisine/KitchenAppliances.class",
|
||||
"fr/koka/sanctuary/cuisine/KitchenClient$1.class",
|
||||
"fr/koka/sanctuary/cuisine/KitchenClient.class",
|
||||
"fr/koka/sanctuary/cuisine/KitchenJei$Category.class",
|
||||
"fr/koka/sanctuary/cuisine/KitchenJei.class",
|
||||
"resourcepacks/template/THIRD_PARTY_NOTICES.md"
|
||||
],
|
||||
"added_jar_entries": [
|
||||
"assets/sanctuary/blockstates/drying_rack.json",
|
||||
"assets/sanctuary/items/cured_sausage.json",
|
||||
"assets/sanctuary/items/drying_rack.json",
|
||||
"assets/sanctuary/items/raw_sausage.json",
|
||||
"assets/sanctuary/models/block/drying_rack.json",
|
||||
"assets/sanctuary/models/item/cured_sausage.json",
|
||||
"assets/sanctuary/models/item/drying_rack.json",
|
||||
"assets/sanctuary/models/item/raw_sausage.json",
|
||||
"data/sanctuary/loot_table/blocks/drying_rack.json",
|
||||
"data/sanctuary/recipe/dry_sausage.json",
|
||||
"data/sanctuary/recipe/drying_rack.json",
|
||||
"data/sanctuary/recipe/prepare_sausage.json",
|
||||
"data/sanctuary/sanctuary_recipe_collections/drying.json",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/DryingRackBlock.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/DryingRackBlockEntity.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/DryingRackHud.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/renderer/DryingRackBlockEntityRenderer$1.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/renderer/DryingRackBlockEntityRenderer.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/renderer/state/DryingRackRenderState.class",
|
||||
"fr/koka/sanctuary/cuisine/recipe/FoodDryingRecipe$1.class",
|
||||
"fr/koka/sanctuary/cuisine/recipe/FoodDryingRecipe.class"
|
||||
],
|
||||
"removed_jar_entries": [],
|
||||
"generated_json": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 208,
|
||||
"agriculture": 376
|
||||
},
|
||||
"additive_drying_json": 13,
|
||||
"culinary_source_classes": 50,
|
||||
"culinary_compiled_classes_verified": 69,
|
||||
"drying_slots": 4,
|
||||
"items_per_slot": 1,
|
||||
"drying_ticks": 6000,
|
||||
"native_minecraft_textures_only": true,
|
||||
"old_pngs_unchanged": 1108,
|
||||
"new_pngs": 0,
|
||||
"server_required": 112,
|
||||
"drying_scenarios": 12,
|
||||
"client_vulkan": {
|
||||
"log": "build/drying313-client.log",
|
||||
"screenshots": "build/drying313-screenshots",
|
||||
"screenshot_count": 11,
|
||||
"world_seed": 313,
|
||||
"sodium": "0.9.2+mc26.3",
|
||||
"native_crafting": true,
|
||||
"native_eating": true,
|
||||
"actual_disk_reopen": true,
|
||||
"actual_bucket_pause_drain": true
|
||||
},
|
||||
"client_server_copies_identical": true,
|
||||
"template_changed_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json"
|
||||
],
|
||||
"template_added_entries": [
|
||||
"assets/sanctuary/blockstates/drying_rack.json",
|
||||
"assets/sanctuary/items/cured_sausage.json",
|
||||
"assets/sanctuary/items/drying_rack.json",
|
||||
"assets/sanctuary/items/raw_sausage.json",
|
||||
"assets/sanctuary/models/block/drying_rack.json",
|
||||
"assets/sanctuary/models/item/cured_sausage.json",
|
||||
"assets/sanctuary/models/item/drying_rack.json",
|
||||
"assets/sanctuary/models/item/raw_sausage.json"
|
||||
],
|
||||
"build_log": "build/drying313-build.log"
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
# IA-06 — préparation de saucisson et séchoir, beta.313 locale
|
||||
|
||||
Demande du créateur du 29 octobre 2026 : préparer le saucisson puis le sécher
|
||||
avec des gestes vanilla. Durée confirmée : **5 minutes de jeu**. Un séchoir
|
||||
en bois porte quatre préparations visibles, sans combustible ni menu. La
|
||||
pluie suspend le séchage exposé ; un toit le protège. Même branche locale
|
||||
`codex/itsalive-26-3`, sans publication, installation ou déploiement.
|
||||
|
||||
## Contrat de stockage avant développement
|
||||
|
||||
Ajouts uniquement : bloc/objet/bloc entité `sanctuary:drying_rack`, objets
|
||||
`sanctuary:raw_sausage` et `sanctuary:cured_sausage`, type et sérialiseur de
|
||||
recette `sanctuary:food_drying`. Aucun identifiant existant ni format des
|
||||
anciens appareils ne change. Aucune génération de monde n'est ajoutée.
|
||||
|
||||
Le séchoir possède quatre cases natives 0–3, **un objet par crochet**. La
|
||||
préparation et le produit séché utilisent la même case : une conversion
|
||||
remplace exactement un objet par un autre. Chaque crochet conserve ses
|
||||
propres ticks, durée, identifiant de recette et état terminé, sous marqueur
|
||||
`DryingVersion=1`. Les composants des objets restent natifs.
|
||||
|
||||
La progression est autoritaire côté serveur, uniquement sur les ticks du
|
||||
bloc chargé et sec. Pluie et immersion suspendent sans payer ni perdre le
|
||||
travail ; un toit protège de la pluie. Le rechargement reprend les ticks
|
||||
stockés, sans rattrapage hors ligne. Remplacer/retirer une préparation annule
|
||||
son avancement ; les autres crochets continuent. La casse rend le séchoir et
|
||||
chaque objet présent une seule fois, cru ou séché selon son état réel.
|
||||
Les pistons ne déplacent pas le bloc entité.
|
||||
|
||||
Une ancienne version ne connaît pas ces nouveaux identifiants : récupérer
|
||||
les objets et retirer les séchoirs avant un retour à une version antérieure.
|
||||
Aucun monde personnel n'est converti ; les tests ciblent des mondes neufs
|
||||
et des fixtures de développement ignorés.
|
||||
|
||||
## Préparation et gestes retenus
|
||||
|
||||
À l'établi : deux côtelettes de porc crues, du sel, une gousse d'ail et une
|
||||
ficelle donnent **deux préparations de saucisson**. La préparation crue ne se
|
||||
mange pas. Le séchoir se fabrique avec trois bâtons, deux ficelles et deux
|
||||
planches ; son modèle et ceux des saucissons utilisent des cuboïdes et des
|
||||
textures vanilla référencées, sans nouveaux PNG.
|
||||
|
||||
Un clic place une préparation sur un crochet libre. À main vide, récupérer
|
||||
un saucisson prêt ; sinon reprendre une préparation en cours. L'eau se gère
|
||||
avec le comportement vanilla des blocs pouvant contenir de l'eau. L'orientation
|
||||
est horizontale et les contenus tournent avec le cadre. Les entonnoirs peuvent
|
||||
alimenter les crochets libres et ne retirer que les produits prêts dessous.
|
||||
Les trois nouveaux objets rejoignent Nourriture et boissons, sans nouvelle
|
||||
page créative. Les recettes sont intégrées au système de découverte et à JEI.
|
||||
|
||||
## Vérifications locales
|
||||
|
||||
Douze cas serveur natifs vérifient le vrai paiement à l'établi, l'absence de
|
||||
recette concurrente, les **6 000 ticks complets** sur quatre crochets, les
|
||||
interruptions indépendantes, la pluie et le toit, l'immersion et le seau,
|
||||
les quatre progressions sauvegardées sans rattrapage hors ligne, la collecte
|
||||
sélective, la casse sans duplication, l'alimentation/extraction par de vrais
|
||||
entonnoirs, la sauvegarde d'un résultat fini et la découverte des trois recettes.
|
||||
La fixture de pluie est placée dans un biome de plaine dans le monde de test.
|
||||
|
||||
Le parcours **Vulkan / Apple M1**, avec **Sodium 0.9.2+mc26.3**, a réussi en
|
||||
**1 min 16 s**, monde plat neuf de graine **313** :
|
||||
|
||||
- vrais paquets de l'établi, deux fabrications payées donnant quatre
|
||||
préparations depuis quatre porcs crus, deux sels, deux ails et deux ficelles ;
|
||||
- pose du séchoir avec un objet réellement consommé, puis quatre accrochages ;
|
||||
- retrait/raccrochage d'une préparation, sans annuler les trois autres ;
|
||||
- huit états de modèle chargés, cinq cuboïdes de bois par état, trois modèles
|
||||
d'objets sans texture manquante, objets dans Nourriture et boissons ;
|
||||
- HUD français/anglais aux échelles 2, 3 et 4 ;
|
||||
- vrai seau d'eau puis seau vide pour suspendre/reprendre le séchage ;
|
||||
- fermeture/réouverture conservant les quatre préparations et leur progression
|
||||
exacte pendant l'immersion ;
|
||||
- transformation, quatre récupérations natives puis consommation réelle d'un
|
||||
saucisson qui rend de la nourriture ;
|
||||
- recette de cinq minutes synchronisée dans sa catégorie JEI.
|
||||
|
||||
Les 6 000 ticks sont exécutés intégralement dans les cas serveur. Pour ne pas
|
||||
attendre cinq minutes dans le parcours graphique, la fixture rapproche les
|
||||
quatre compteurs de leur fin **après** le rechargement et le vidage au seau ;
|
||||
les 40 à 100 derniers ticks, les transformations, clics et paiements restent
|
||||
réels. Onze captures sont archivées dans `build/drying313-screenshots/` ;
|
||||
les journaux de build, serveur et client sont ignorés.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=drying313,cauldron312,cauldron311,pan310,oven308,mill306,kitchen304,cuisine303,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryDrying313ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=drying313 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
`check build assemblePack` a réussi en **3 min 9 s**, avec **112 tests serveur
|
||||
obligatoires**. L'audit strict à beta.312 vérifie les ajouts du séchoir et les
|
||||
modifications prévues de registre, rendu, JEI, langues et crédits. Les
|
||||
**1 108 PNG** et les 195 JSON culinaires d'appareils existants sont inchangés ;
|
||||
13 JSON de modèles/recettes/découverte sont ajoutés. Les 432 JSON de cuisine,
|
||||
208 d'appareils et 376 d'agriculture sont comparés à leurs générateurs.
|
||||
Le tag de minage à la hache garde ses entrées historiques. Les classes
|
||||
culinaires compilées et les copies client/serveur du JAR sont vérifiées.
|
||||
Le modèle de pack de ressources emporte les huit nouveaux JSON visuels.
|
||||
|
||||
[Reçu de vérification](drying-rack-beta313-verification.json). JAR local :
|
||||
`mods/sanctuary/build/libs/sanctuary-beta.313.jar`
|
||||
|
||||
SHA-256 :
|
||||
`54ea931d2f54eec8dac2d03e5ad1df8fd5b389db235a536bdd98d90a531fbe55`
|
||||
|
||||
Aucun push, tag, publication, canal packwiz public, installation Prism ou
|
||||
serveur personnel n'a été modifié.
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"version": "beta.316",
|
||||
"status": "local-only",
|
||||
"date": "2026-10-30",
|
||||
"jar": {
|
||||
"path": "mods/sanctuary/build/libs/sanctuary-beta.316.jar",
|
||||
"sha256": "27ec0a8f492517131766a8c026cd0406189db3e78595a59ab605bbacd4303889",
|
||||
"bytes": 19070080
|
||||
},
|
||||
"textures_unchanged_from_beta315": 1108,
|
||||
"server_required_tests": 85,
|
||||
"build": "check build assemblePack",
|
||||
"build_seconds": 300,
|
||||
"client": {
|
||||
"backend": "Vulkan",
|
||||
"device": "Apple M1",
|
||||
"sodium": "0.9.2+mc26.3",
|
||||
"preset": "sanctuary:sanctuary",
|
||||
"profile": "231 Medium",
|
||||
"seed": 316,
|
||||
"natural_plants": 66,
|
||||
"types": 16,
|
||||
"models": 24,
|
||||
"locales": [
|
||||
"fr_fr",
|
||||
"en_us"
|
||||
],
|
||||
"reload_preserved": 65,
|
||||
"harvested_stays_empty": true,
|
||||
"seconds": 140,
|
||||
"scope": "starter island native generation, native survival packets, models and disk reload; wild fruit tree plans validated separately by server tests on seeds 0/42/316"
|
||||
},
|
||||
"wiki": {
|
||||
"islands": 9,
|
||||
"crops_and_products": 28,
|
||||
"biome_aliases": 111,
|
||||
"missing_images": 0
|
||||
},
|
||||
"deployment": "none; server and Prism remain beta.315"
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
# IA-09 — Découverte alimentaire en survie, beta.316
|
||||
|
||||
Demande du 30 octobre 2026. Même branche `codex/itsalive-26-3`, socle public
|
||||
`beta.315` / branche `bb61547b`. Préparation locale : pas de publication,
|
||||
d'installation personnelle ou de modification de la Beta dédiée par ce ticket.
|
||||
|
||||
## Résultat
|
||||
|
||||
La première semence ne nécessite plus le créatif. Les 20 cultures importées,
|
||||
le café, la vanille, les citronniers et les oliviers ont une voie de découverte
|
||||
renouvelable en survie. Le blé, la carotte, la pomme de terre et la betterave
|
||||
disposent aussi de plantes sauvages sur l'île tempérée.
|
||||
|
||||
Le [guide des récoltes](wiki/alimentation/README.md), ses neuf pages d'îles
|
||||
et sa [planche illustrée](wiki/alimentation/planche.html) viennent du même
|
||||
catalogue que le jeu : `scripts/data/foraging.json`. Ils décrivent les habitats,
|
||||
les gestes et les semences à rapporter, ainsi que les ressources vanilla.
|
||||
Ils restent des pages locales ; aucun wiki externe n'est modifié.
|
||||
|
||||
## Répartition
|
||||
|
||||
| Habitat | Principales zones | Récoltes |
|
||||
| --- | --- | --- |
|
||||
| Tempéré | Sanctuary ; certaines prairies du Nord-Ouest | Blé, carotte, pomme de terre, betterave, fraise, tomate, maïs, concombre, poivron, laitue, oignon, ail, chou, haricot, basilic, menthe |
|
||||
| Frais | Vallées et clairières du Nord, prairies/taïgas du Nord-Ouest | Blé, carotte, pomme de terre, betterave, fraise, laitue, oignon, ail, chou, menthe |
|
||||
| Frais humide | Marais et jardin pâle du Nord-Est | Concombre, laitue, chou, haricot, menthe |
|
||||
| Chaud sec | Bosquets/berges du Sud, savanes et sols vivants du Sud-Ouest | Maïs, raisin, pois chiche, piment, coriandre, olive |
|
||||
| Doux ensoleillé | Oasis/berges vivantes de l'Ouest | Tomate, maïs, poivron, aubergine, raisin, basilic, citron, olive |
|
||||
| Tropical | Jungles de l'Est et du Sud-Est, mangroves | Ananas, riz, soja, piment, café, vanille |
|
||||
|
||||
La règle lit le **biome réel à la position**, avec des tags explicites pour
|
||||
les biomes vanilla et les alias Sanctuary versionnés. Elle ne déduit pas
|
||||
le climat de coordonnées cardinales : les expansions ne sont pas fixées
|
||||
à des coordonnées universelles. Le bambou tempéré de l'île principale
|
||||
reste dans le groupe tempéré ; les grottes de roche nue, zones de soufre, cratères, pics de glace et
|
||||
biomes de vide ne sont pas des lieux de cueillette agricole.
|
||||
|
||||
La découverte suit le climat. La culture après transplantation conserve les
|
||||
règles de beta.300/301 : lumière et sol vanilla, riz dans une source sur boue,
|
||||
vanille appuyée contre les troncs. Aucun nouveau verrou climatique de croissance.
|
||||
|
||||
## Gestes et renouvellement
|
||||
|
||||
- Casser une plante sauvage donne une unité de récolte et une semence distincte,
|
||||
ou une seule unité si le produit se replante lui-même. Ananas : fruit + couronne.
|
||||
Aucun labourage automatique du terrain naturel.
|
||||
- Caféier sauvage mûr : clic droit pour les cerises ; leur recette donne les
|
||||
grains verts replantables. Le casser donne aussi un grain.
|
||||
- Arbre fruitier : clic droit sur les feuilles portant un fruit. Les feuilles
|
||||
cassées/décomposées peuvent donner une pousse, selon les règles déjà portées.
|
||||
- Vanille mûre sur un tronc : clic droit pour les gousses, casse pour une bouture.
|
||||
- Dans un terrain déjà exploré, les petites herbes et fougères cassées sans
|
||||
cisailles ni Toucher de soie donnent parfois une récolte du climat : **1/8**,
|
||||
puis tirage uniforme dans la liste admissible. La recette de récupération
|
||||
de semences ou la plantation directe permet de la multiplier.
|
||||
- Riz : herbes marines simples de jungle/mangrove, **1/8**. Pas les algues de
|
||||
pleine mer ; le riz sauvage visible se trouve sur les berges proches d'eau.
|
||||
- Bouture de vanille : lianes vanilla cassées en jungle/mangrove, **1/8**.
|
||||
- Pousses fruitières : feuilles de chêne/acacia dans les habitats chauds
|
||||
correspondants, **1/40**, puis choix parmi les pousses autorisées.
|
||||
|
||||
Les tables natives sont **étendues**, sans remplacement de leurs graines,
|
||||
pommes, bâtons ou pousses. Les variantes à deux blocs ne reçoivent aucun butin
|
||||
ajouté, pour éviter le double paiement des moitiés. Cisailles et Toucher de soie
|
||||
conservent la végétation sans créer simultanément une récolte. Les remplacements
|
||||
de tables par un datapack restent prioritaires. Fortune n'augmente pas les
|
||||
quantités de ces nouvelles trouvailles.
|
||||
|
||||
## Contrat de génération et sauvegardes
|
||||
|
||||
**Décoration agricole IA-09 / révision 316**, graine du parcours natif **316**.
|
||||
Le relief, les identifiants des profils, les biomes et les journaux des expansions
|
||||
ne changent pas. La décoration s'ajoute à la fin de `applyBiomeDecoration`, pour
|
||||
le générateur Sanctuary qui possède les neuf îles. Elle ne s'applique pas aux
|
||||
mondes vanilla ou au monde plat de test.
|
||||
|
||||
Huit candidats déterministes par chunk, sel `0x316F00D`, graine du monde et
|
||||
coordonnées du chunk. Lecture de surfaces déjà présentes ; aucun sol, bassin,
|
||||
source d'eau ou couche de neige n'est créé. Petites herbes/fougères remplaçables,
|
||||
autres végétaux, roches, fluides, constructions et réservations conservés.
|
||||
Les petits arbres fruitiers sauvages utilisent les bois vanilla et un volume
|
||||
entièrement contenu dans leur chunk ; leurs pousses plantées conservent les
|
||||
arbres natifs de beta.300. La vanille ne se pose que contre un tronc accepté.
|
||||
|
||||
**Aucun ancien chunk n'est repassé en décoration**. Les chunks sauvegardés
|
||||
gardent leurs blocs. Aucun chargement, tick de repeuplement, changement de
|
||||
format de sauvegarde ou activation d'expansion n'est ajouté. Un monde existant
|
||||
peut découvrir les semences par les nouveaux butins puis les cultiver ; les
|
||||
nouveaux chunks obtiennent la décoration agricole à leur première génération.
|
||||
Cette évolution additive peut donc être visible à une ancienne frontière
|
||||
d'exploration, sans retouche rétroactive.
|
||||
|
||||
## Vérification
|
||||
|
||||
Le contrôle final `check build assemblePack` valide **85 GameTests serveur requis**,
|
||||
dont huit scénarios IA-09 : toutes les cultures, les climats, les terrasses
|
||||
vivantes du preset public, les premières propagules, les outils natifs,
|
||||
la plantation en survie, les obstacles et les deux arbres sauvages (graines
|
||||
0, 42 et 316). Log : `build/foraging316-build.log`.
|
||||
|
||||
Le parcours client final réussit sous **Vulkan / Apple M1 / Sodium 0.9.2+mc26.3**,
|
||||
avec le preset public `sanctuary:sanctuary`, profil **231 Medium**, graine **316**,
|
||||
dans un monde de développement neuf :
|
||||
|
||||
- **66 plantes sauvages**, **16 types**, relevés dans 25 chunks autour du départ ;
|
||||
- vraie casse en survie, attente de l'état serveur et contrôle des objets attendus,
|
||||
au sol ou ramassés dans l'inventaire ;
|
||||
- **24 modèles sauvages** réellement chargés, quatre faces pour deux plans en croix,
|
||||
sans sprite manquant ; noms FR/EN vérifiés ;
|
||||
- fermeture et réouverture sur disque : **65 plantes conservées**, emplacement
|
||||
récolté toujours vide, aucun repeuplement au chargement.
|
||||
|
||||
Log : `build/foraging316-client-final.log`, réussite en **2 min 20 s**.
|
||||
Les captures restent ignorées dans `build/foraging316-screenshots/` ; le contrôle
|
||||
valide le chargement des modèles et les interactions, pas une visite esthétique
|
||||
exhaustive des huit expansions.
|
||||
|
||||
La planche est vérifiée dans le navigateur : **9 îles, 28 lignes de produits,
|
||||
aucune image manquante ni débordement horizontal** au format du panneau.
|
||||
Ses 111 alias de biomes et les 80 ressources ajoutées sont issus du catalogue.
|
||||
Les commandes reproductibles sont :
|
||||
|
||||
```sh
|
||||
python3 scripts/build_foraging_wiki.py
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=foraging316,agriculture300,vanilla301,sausage315,cheesewine314,cuisine303,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryForaging316ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=foraging316 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
Le module Test sert au lanceur de contrôle ; les classes de génération et les
|
||||
ressources agricoles sont dans le JAR Sanctuary de production. Aucun test
|
||||
OpenGL, déploiement, visite d'un monde personnel ou activation d'expansion sur
|
||||
le serveur existant n'est effectué. Le serveur et Prism restent en beta.315.
|
||||
Les durées/quantités ci-dessus décrivent le parcours local, pas une garantie
|
||||
uniforme du nombre de plantes de chaque île.
|
||||
|
||||
Assemblage final réussi en **5 min**, 161 tâches. JAR client/serveur et
|
||||
ressources générées identiques ; les **1108 PNG** restent strictement
|
||||
identiques à beta.315. [Reçu binaire et vérifications](foraging-beta316-verification.json).
|
||||
|
||||
SHA-256 du JAR beta.316 : `27ec0a8f492517131766a8c026cd0406189db3e78595a59ab605bbacd4303889`.
|
||||
@@ -0,0 +1,295 @@
|
||||
# IA — Refonte d'It's Alive ! pour Minecraft 26.3
|
||||
|
||||
**Livraison du 29 octobre 2026 :** [beta.315 publiée, serveur/site/Prism mis à jour](release-beta315.md). Les mentions « locale » ci-dessous conservent l'historique du développement avant cette publication.
|
||||
|
||||
Demande du créateur du **28 octobre 2026**, branche locale
|
||||
`codex/itsalive-26-3`, socle `77fdabfb` (dernière livraison beta.298).
|
||||
Le checkout historique 26.2 et les changements locaux du checkout principal
|
||||
restent intacts. Aucun push, tag, canal, serveur, site ou Prism mis à jour.
|
||||
|
||||
Le premier lot retenu par le créateur est **textures et ressources d'abord**.
|
||||
Le même jour, il demande ensuite le port jouable des cultures, arbres
|
||||
fruitiers et caféier : [IA-02, préparation beta.300](agriculture-beta300.md).
|
||||
Les statuts ci-dessous distinguent le port agricole des prochains tickets ;
|
||||
leur inscription seule ne vaut pas implémentation. Les incréments continueront avec le compteur normal
|
||||
`beta.xxx`, sans réserver ni publier à l'avance une série de versions 300.
|
||||
L'intégration reste dans Sanctuary, conformément au
|
||||
[cadrage de fusion](multiblocs-itsalive-contrat.md).
|
||||
|
||||
## IA-01 — Import graphique, beta.299 locale
|
||||
|
||||
**Import réalisé.** Les textures sont copiées dans
|
||||
`mods/sanctuary/src/main/resources/assets/sanctuary/textures/`, avec les noms
|
||||
de fichiers historiques, sans conversion, redimensionnement ou retouche.
|
||||
Aucune ressource Sanctuary préexistante n'est écrasée. Le namespace de
|
||||
destination est celui de Sanctuary ; aucun identifiant de registre n'est
|
||||
ajouté par la présence d'un PNG.
|
||||
|
||||
| Ressources | Nombre | Contenu |
|
||||
| --- | ---: | --- |
|
||||
| Textures de blocs | 212 | Croissance, feuillages, pousses, appareils, blocs d'ingrédients |
|
||||
| Textures d'objets | 129 | Semences, récoltes, ingrédients et plats |
|
||||
| Sprites d'interface | 2 | Rail et tête du moulin historique |
|
||||
| **Total** | **343** | Cuisine et agriculture uniquement |
|
||||
|
||||
Les **38 PNG fournis dans `food/`** sont prioritaires sur leurs homologues
|
||||
26.2 et conservés octet pour octet, transparence comprise. Ils existent tous
|
||||
déjà sous le même nom dans l'historique : ce sont de nouvelles versions des
|
||||
dessins, pas 38 nouveaux objets. Comparaison avec la 26.2 :
|
||||
|
||||
- **32** modifient les pixels ;
|
||||
- **2**, `corn.png` et `lettuce.png`, modifient seulement l'encodage PNG ;
|
||||
- **4** sont strictement identiques : `bell_pepper.png`, `eggplant.png`,
|
||||
`strawberry_seeds.png` et `tomato.png`.
|
||||
|
||||
Les **305 autres PNG** proviennent sans modification du module historique
|
||||
`26.2/itsalive`, commit `26f2c2a0061619df1f146f9081d6740c560a7497`.
|
||||
Le [reçu complet](itsalive-textures-beta299.json) conserve pour chaque texture
|
||||
son origine, son SHA-256 final, le SHA-256 26.2 et ses dimensions. Les monstres,
|
||||
œufs, archives ZIP, fichiers système et textures Canaplia restent hors de
|
||||
ce lot culinaire. Les portages déjà présents dans Sanctuary sont conservés.
|
||||
|
||||
Les textures comprennent les huit stades des **20 cultures historiques** :
|
||||
fraise, tomate, ananas, maïs, concombre, poivron, aubergine, laitue, oignon,
|
||||
ail, chou, raisin, riz, soja, haricot, pois chiche, piment, basilic, menthe
|
||||
et coriandre. S'ajoutent les parties hautes de la tomate, du maïs, du
|
||||
concombre et du haricot, les quatre stades du caféier et de la vanille,
|
||||
ainsi que les pousses et feuillages avec/sans fruits du citronnier et de
|
||||
l'olivier. Les deux arbres historiques réutilisent les bois vanilla :
|
||||
aucune nouvelle famille de bois n'est sous-entendue.
|
||||
|
||||
Les textures sont présentes dans les ressources du mod, mais **aucun de ces
|
||||
nouveaux objets, blocs, arbres, appareils ou recettes n'est encore enregistré
|
||||
en jeu**. Le Java, les recettes, les modèles et les règles de génération de
|
||||
26.2 ne sont pas copiés automatiquement. Les images seules ne suffisent pas
|
||||
à porter les comportements ni à valider leur compatibilité 26.3.
|
||||
|
||||
## Évolution beta.307 — recette du moulin manuel
|
||||
|
||||
[La redstone de la recette cède la place à un bâton](mill-recipe-beta307.md)
|
||||
pour la poignée. Les dalles de pierre lisse, le cuivre et le tailleur de pierre
|
||||
restent la base de l'appareil. La recette rejoint Divers dans le livre natif ;
|
||||
son identifiant et le fonctionnement manuel de beta.306 restent stables.
|
||||
|
||||
## Évolution beta.306 — moulin manuel
|
||||
|
||||
[Le moulin devient un poste manuel comme le tailleur de pierre](mill-beta306.md),
|
||||
sans combustible ni attente. Le résultat est un aperçu : seule sa récupération
|
||||
paie un ingrédient. Le nom devient Moulin / Mill ; le menu conserve le
|
||||
mouvement mécanique et retire le feu. Les neuf recettes et les identifiants
|
||||
restent stables ; les anciens objets stockés sont récupérables.
|
||||
|
||||
## Évolution beta.305 — écrasement direct
|
||||
|
||||
À la demande du créateur, la presse alimentaire est retirée du jeu courant.
|
||||
[Les blocs de fruits s'écrasent directement en huit paliers](crushing-beta305.md),
|
||||
avec résidus progressifs, piston immédiat et super piston sur 27 blocs.
|
||||
Les identifiants de fruits restent stables ; l'ancien ID de presse n'est gardé
|
||||
que pour récupérer un inventaire d'essai local, sans traitement ni catalogue.
|
||||
|
||||
## IA-09 — Découverte des premières semences en survie
|
||||
|
||||
[Répartition climatique, récoltes sauvages et guide des neuf îles](foraging-beta316.md),
|
||||
préparation locale beta.316 du 30 octobre 2026. Ce lot remplace le report de
|
||||
la découverte naturelle annoncé dans IA-02/IA-03, sans repeupler les chunks sauvegardés.
|
||||
|
||||
## IA-10 — Extension du séchoir, beta.317 locale
|
||||
|
||||
[15 recettes de séchage, 12 nouveaux produits et propositions culinaires](drying-foods-beta317.md).
|
||||
Les quatre crochets et les sauvegardes gardent leur fonctionnement ; la vanille
|
||||
se sèche désormais sur le cadre et les herbes/piments secs servent aussi en cuisine.
|
||||
|
||||
## IA-11 — Les 18 recettes aux produits séchés, beta.318 locale
|
||||
|
||||
[Implémentation et vérifications](dried-cuisine-beta318.md). Les propositions de
|
||||
beta.317 deviennent des recettes jouables avec les appareils prévus ; le saucisson
|
||||
pimenté garde les gestes de préparation, séchage et partage en tranches. Les plats
|
||||
épicés déclenchent de petits coups cosmétiques sans perte de vie.
|
||||
|
||||
## Suivi des mécaniques
|
||||
|
||||
### IA-02 — Cultures, arbres fruitiers et caféier jouables
|
||||
|
||||
**Port implémenté dans la préparation locale beta.300**, sur cette même
|
||||
branche. [Comportements, accès et vérifications](agriculture-beta300.md).
|
||||
Le texte ci-dessous conserve le périmètre du ticket.
|
||||
|
||||
Porter les 20 cultures, leurs propagules, les arbres et le caféier vers les
|
||||
API 26.3. Reprendre explicitement les récoltes pérennes et annuelles, les
|
||||
parties hautes et la plantation particulière du riz, après examen des règles
|
||||
historiques. Ajouter les noms et aides FR/EN. Tester plantation, croissance,
|
||||
récolte, casse, rechargement et absence de duplication côté serveur.
|
||||
|
||||
La répartition climatique et l'apparition naturelle feront l'objet d'un
|
||||
contrat de génération séparé, documenté avec graine et version. Aucune
|
||||
injection rétrospective dans les chunks existants n'est autorisée par ce lot.
|
||||
|
||||
### IA-03 — Vanille grimpante
|
||||
|
||||
**Implémentée dans la préparation locale beta.301**, sur la même branche :
|
||||
[attachement aux troncs, croissance ascendante et vérifications](vanilla-beta301.md).
|
||||
[Correction graphique beta.302 : quatre stades en 16 × 16](vanilla-16px-beta302.md).
|
||||
Le texte ci-dessous conserve le périmètre initial du ticket.
|
||||
|
||||
Obtenir une plante plaquée contre son support, visuellement comparable aux
|
||||
lianes, qui **progresse vers le haut** plutôt que de former un grand totem.
|
||||
L'ancien `VanillaVineBlock` possède déjà une méthode `growUpward` et exige
|
||||
une bûche de jungle ; il faut donc examiner aussi le modèle, les faces et les
|
||||
supports, sans présenter un simple changement de direction comme une refonte.
|
||||
|
||||
Vérifier la pose, le support de chaque segment, la croissance supérieure,
|
||||
les obstacles, la récolte, la casse du support et la poudre d'os. Choisir les
|
||||
supports admis et la forme exacte dans ce ticket. Les quatre anciens PNG
|
||||
sont conservés comme références, sans promettre qu'ils suffiront au modèle final.
|
||||
|
||||
### IA-04 — Cuisiner avec le chaudron
|
||||
|
||||
**Suite locale beta.312 :** [le premier geste mémorise la recette, les lots
|
||||
se répètent et le réapprovisionnement garde le travail en cours](cauldron-repeat-beta312.md).
|
||||
La sortie s’accumule, sa collecte conserve la série et Maj-clic à main vide
|
||||
permet de l’arrêter.
|
||||
|
||||
**Refonte locale beta.311 :** [préparation libre, démarrage au bâton, aucun
|
||||
plat raté au chaudron, récipients payés en fin de cuisson et sons natifs](cauldron-beta311.md).
|
||||
Le geste manuel est confirmé par le créateur ; les 19 recettes restent les
|
||||
recettes portées, avec une seule préparation lancée par geste.
|
||||
|
||||
**Port jouable en beta.304 locale :** [chaudron vanilla, eau exacte, chauffe, lots et récipients](kitchen-beta304.md).
|
||||
|
||||
Repartir des usages du chaudron vanilla pour l'eau, la chauffe, les ingrédients
|
||||
et les récipients, puis définir comment préparer et servir un plat. Examiner
|
||||
l'ancien `CookingCauldronBlock` avant de choisir l'extension du bloc vanilla
|
||||
ou un appareil spécialisé. Les gestes, niveaux d'eau, portions et retours de
|
||||
bols/seaux doivent former une boucle vérifiable, sans pertes ni duplication.
|
||||
|
||||
### IA-05 — Plats ratés et nouvelles recettes
|
||||
|
||||
**Suite beta.304 locale :** [appareils et plats ratés historiques portés](kitchen-beta304.md). La fermentation est désormais temporisée dans le tonneau et le pressage exige le pressoir.
|
||||
|
||||
**Recettes portées en beta.303 locale à la demande du créateur :**
|
||||
[92 recettes alimentaires sur table et four/fumoir](cuisine-beta303.md),
|
||||
avec les ingrédients intermédiaires. La fabrication manuelle instantanée de
|
||||
fermentation de cette étape est remplacée par le tonneau en beta.304. Les plats
|
||||
ratés historiques sont également portés dans cette suite.
|
||||
Le texte ci-dessous conserve le cadrage initial ; la demande d'import du
|
||||
28 octobre autorise le port du catalogue avec les adaptations documentées.
|
||||
|
||||
Revoir les « fake dishes » cités dans la demande. L'historique contient
|
||||
`failed_dish`, produit par la poêle et le chaudron : c'est la correspondance
|
||||
probable, à confirmer au cadrage de ce ticket. Définir ce qui constitue une
|
||||
recette, un mélange incomplet et un échec, puis les ingrédients consommés et
|
||||
le résultat obtenu. Ne pas importer en bloc l'ancien catalogue de recettes
|
||||
avant cette décision. Conserver les idées de nouveaux plats dans ce même suivi.
|
||||
|
||||
### IA-06 — Saucisson et séchoir
|
||||
|
||||
**Implémentation locale beta.313 :** [préparation à l’établi, quatre crochets
|
||||
visibles, cinq minutes de jeu, protection de la pluie et conservation des
|
||||
objets/progressions](drying-rack-beta313.md). Les modèles utilisent les
|
||||
textures natives ; le fonctionnement ci-dessous décrit le cadrage initial.
|
||||
|
||||
Créer une préparation qui devient du saucisson après séchage dans un séchoir.
|
||||
Définir la recette initiale, la durée, les conditions et les gestes de pose
|
||||
et de récupération. Vérifier interruption, reprise et rechargement, avec
|
||||
progression autoritaire côté serveur et résultat unique. Le séchoir et le
|
||||
saucisson n'ont pas de nouvelle texture fournie dans `food/` ; leur dessin
|
||||
et leur fonctionnement restent à réaliser.
|
||||
|
||||
### IA-07 — Poêle native et pose souple
|
||||
|
||||
**Refonte locale beta.310 :** [une recette commune par pile de une à quatre
|
||||
poêles, pose en ligne, paiement réel, réserves natives et continuité](pan-stacks-beta310.md).
|
||||
Le choix confirmé concerne les piles ; les orientations supplémentaires et
|
||||
les ancrages particuliers restent ouverts.
|
||||
|
||||
**Port de la poêle en beta.304 locale :** [bloc entité, contenu visible, cuisson, retraits et sauvegarde](kitchen-beta304.md). La forme historique est conservée ; la refonte de pose/orientation reste ouverte.
|
||||
|
||||
Revoir les bugs, la pose et l'intégration de la poêle, avec la souplesse de
|
||||
placement des coussins comme référence exprimée par le créateur. L'ancienne
|
||||
`FryingPanBlock` étend déjà `BaseEntityBlock` et possède une
|
||||
`FryingPanBlockEntity` : l'absence de bloc entité n'est donc pas un diagnostic
|
||||
établi. Reproduire les défauts puis choisir le placement, l'orientation et
|
||||
l'ancrage adéquats pour 26.3.
|
||||
|
||||
Vérifier les surfaces prévues, le contenu visible, la source de chaleur,
|
||||
l'ajout/retrait d'ingrédients, la casse et le rechargement. Un ingrédient ou
|
||||
plat doit toujours être récupéré une seule fois. Ne pas reprendre l'ancien
|
||||
modèle et son placement comme s'ils satisfaisaient déjà cette demande.
|
||||
|
||||
### IA-08 — Cuisinière : four et table de fabrication
|
||||
|
||||
**Précision beta.309 :** [une seule sortie, sans aperçu supplémentaire,
|
||||
grille manuelle et extraction des plats par entonnoir](oven-single-output-beta309.md).
|
||||
|
||||
**Refonte locale beta.308 :** [cuisson automatique, aperçu, paiement en fin de
|
||||
cycle, annulation des modifications et poursuite avec le menu fermé](oven-beta308.md).
|
||||
Le créateur a retenu le démarrage comme un four et les recettes de cuisine
|
||||
uniquement. La fabrication de l'appareil garde sa table et son four.
|
||||
|
||||
**Port culinaire en beta.304 locale :** [grille 3 × 3, combustible, résultat temporisé et recette table + four + briques + fer](kitchen-beta304.md). Le Fourneau multibloc existant est conservé.
|
||||
|
||||
Reprendre la cuisinière comme un assemblage cohérent du four et de la table
|
||||
de fabrication. Examiner ses usages historiques et le Fourneau multibloc
|
||||
déjà présent pour éviter deux appareils redondants. Définir ensuite les
|
||||
gestes et une recette plus élaborée, mais lisible dans Minecraft.
|
||||
|
||||
La table de fabrication et les quatre pierres évoquées sont une **piste**,
|
||||
pas une recette arrêtée. Le remplacement par le Fourneau reste à décider
|
||||
dans ce ticket. Aucun inventaire historique ni travail de cuisson en cours
|
||||
n'est converti sans contrat explicite.
|
||||
|
||||
## Vérifications du premier lot
|
||||
|
||||
Le contrôle permanent des 343 empreintes et dimensions est intégré à
|
||||
`./gradlew check` via `verifyItsAliveTextures`. Il peut aussi comparer les
|
||||
fichiers originaux et le JAR construit :
|
||||
|
||||
```sh
|
||||
python3 scripts/verify_itsalive_textures.py
|
||||
python3 scripts/verify_itsalive_textures.py --jar mods/sanctuary/build/libs/sanctuary-beta.299.jar
|
||||
./gradlew check build assemblePack -PsanctuaryFocusedTests=recipes,progression -PsanctuaryAtlasOnly=true
|
||||
```
|
||||
|
||||
Import : les 343 PNG ont été décodés intégralement ; comparaison des pixels
|
||||
des 38 images fournies, contrôle visuel de leur planche avant/après et
|
||||
vérification des fichiers originaux réussis.
|
||||
|
||||
- `check build assemblePack` avec le profil ci-dessus : **réussi en 3 min 2 s**,
|
||||
158 tâches, **13 GameTests serveur requis réussis** et contrôles du dépôt.
|
||||
Le profil cible les recettes et la progression existantes ; il ne teste
|
||||
aucune culture nouvelle, puisqu'elles ne sont pas encore enregistrées.
|
||||
- JAR final : les 343 textures correspondent exactement au reçu. Comparé
|
||||
au JAR beta.298, aucune classe, recette, modèle ou dépendance embarquée
|
||||
ne change. Seuls les 343 PNG, la version Fabric et les crédits (dont leur
|
||||
copie dans le template) diffèrent ; aucune entrée n'est supprimée.
|
||||
- Template de ressources et JAR rafraîchis après finalisation des crédits ;
|
||||
profils packwiz client/serveur assemblés, versions et index vérifiés.
|
||||
Le JAR du pack est strictement identique au JAR contrôlé.
|
||||
- SHA-256 final de `sanctuary-beta.299.jar` :
|
||||
`e219b13b2e211908bfff171615ad7c3f923d4e4c2df1bf031689930f3efd489b`.
|
||||
- Logs et reçu local dans `build/itsalive-beta299-build.log`,
|
||||
`build/itsalive-beta299-refresh.log` et
|
||||
`build/itsalive-beta299-validation.json`, hors de Git.
|
||||
|
||||
Aucun essai graphique en jeu n'est revendiqué pour cet import sans contenu
|
||||
enregistré. Les futurs essais graphiques cibleront Vulkan exclusivement.
|
||||
Aucun monde personnel n'est ouvert ou modifié, aucun format de sauvegarde
|
||||
ne change. Les versions mod/pack sont alignées en **beta.299**, préparée
|
||||
localement ; la dernière release publiée reste beta.298.
|
||||
|
||||
### IA-09 — Fromage à partager et qualité du vin
|
||||
|
||||
Demande du 29 octobre 2026 : fromage posable et mangeable par parts comme un
|
||||
gâteau ; quatre qualités de vin. Confirmation : le jeune vin reste prêt après
|
||||
trois minutes de jeu, puis table/réserve/grand cru après un/trois/sept jours
|
||||
réels de garde dans le tonneau, y compris jeu fermé.
|
||||
[Implémentation et contrat local beta.314](cheese-wine-beta314.md).
|
||||
Même branche locale, aucune publication.
|
||||
|
||||
### IA-10 — Saucisson posé en tranches
|
||||
|
||||
Demande du 29 octobre 2026 : poser le saucisson séché et manger ses tranches,
|
||||
comme le fromage. [Implémentation locale beta.315](sausage-slices-beta315.md) :
|
||||
le même objet de séchage devient posable, quatre tranches rendent huit points
|
||||
de nourriture au total, quatre orientations et parts sauvegardées nativement.
|
||||
Même branche locale, sans publication ou installation.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
# Export modifiable It's Alive — beta.315
|
||||
|
||||
Demande du 29 octobre 2026 : fournir toutes les textures de pousse et tout le
|
||||
contenu graphique ajouté depuis le début de la refonte, puis livrer le pack.
|
||||
|
||||
Le script `scripts/export_itsalive_textures.py` extrait les **347 PNG Sanctuary**
|
||||
ajoutés depuis le socle beta.298, y compris les quatre nouveaux stades de
|
||||
vanille grimpante. Il fournit **368 images modifiables** et **696 JSON visuels**
|
||||
avec les matériaux vanilla réutilisés et les fonds/sprites d'interface.
|
||||
|
||||
L'export local est dans `Downloads/ItsAlive-Textures-beta.315/`, avec son ZIP.
|
||||
`a-modifier/` est un pack de ressources au format 97.1, prêt pour Minecraft
|
||||
26.3. `originaux/`, les notices et les inventaires CSV/JSON conservent les
|
||||
références et SHA-256. Aucun dessin n'est réencodé ou redimensionné.
|
||||
|
||||
Les matériaux partagés par les modèles Minecraft sont recopiés sous
|
||||
`sanctuary:itsalive_editable/<aliment>/...`, et les modèles de cet export sont
|
||||
redirigés vers ces images. Fromage, saucissons cru/séché et séchoir se modifient
|
||||
ainsi séparément, sans recolorer les blocs vanilla correspondants. Toutes les
|
||||
portions d'un même aliment partagent ses matériaux. Chaque qualité de vin a
|
||||
également son propre PNG, même si le jeu emploie l'icône héritée du vin jeune.
|
||||
|
||||
Les quatre images d'interface sous `assets/minecraft/` sont une exception
|
||||
signalée dans l'inventaire : les écrans natifs partagent ces fonds/sprites ;
|
||||
leur modification touche aussi ces écrans vanilla. Les 343 images d'import
|
||||
initial, y compris les anciennes références conservées, restent présentes.
|
||||
|
||||
Le script vérifie les références, dimensions, copies, empreintes et intégrité
|
||||
ZIP, refuse d'écraser un export existant et n'installe/active aucun pack.
|
||||
Pour tester des dessins, copier `a-modifier/` dans les packs de ressources de
|
||||
l'instance choisie, l'activer au-dessus de Textures Sanctuary et recharger.
|
||||
Les règles de jeu et recettes restent celles du mod.
|
||||
|
||||
```sh
|
||||
python3.14 scripts/export_itsalive_textures.py \
|
||||
--output /chemin/neuf/ItsAlive-Textures-beta.315 \
|
||||
--native-jar /chemin/du/client/Minecraft-26.3.jar
|
||||
```
|
||||
@@ -0,0 +1,297 @@
|
||||
{
|
||||
"source_commit": "26f2c2a0061619df1f146f9081d6740c560a7497",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"java": [
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/CulinaryAppliance.java",
|
||||
"sha256": "aabb476edecbe0c0f7accd936853a625681b924c47b7800995dfd5da0035358c",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/CulinaryAppliance.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/CulinaryExperience.java",
|
||||
"sha256": "7f658276c4918af0ffac5f4bb9682b5844f520fa70f301999ad97fa115e32367",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/CulinaryExperience.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/CulinaryHeat.java",
|
||||
"sha256": "b9294332154a1110d94f5110d693109e7c6c35ad6cdb5332c1af833d0c91a2fb",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/CulinaryHeat.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/CookingCauldronBlock.java",
|
||||
"sha256": "334d22224387c7da4ce28bfd0e642bb5e7adb6697ddebd884e21df5d2a84ea27",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/block/CookingCauldronBlock.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/FermentationBarrelBlock.java",
|
||||
"sha256": "770364f134b8c9ee04be674b1dbe38956f5fa17c91a81ca258d7ea6a8d187dc5",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/block/FermentationBarrelBlock.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/FryingPanBlock.java",
|
||||
"sha256": "d6ec6cbf4ab52230bcdd6a64c97ceb8a4df33f2b2d717ce7346fe3c79370cffd",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/block/FryingPanBlock.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/KitchenOvenBlock.java",
|
||||
"sha256": "d9cd55b9e002aab5c2d14d95d59ba46a7731a70d0bbb3d0959f1bf7e3eee1a19",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/block/KitchenOvenBlock.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/AbstractCulinaryContainerBlockEntity.java",
|
||||
"sha256": "25ec974def75ab1891ddcd3eb150bbacf2a2b7e1c8b2ac30081698fe7afa94d7",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/block/entity/AbstractCulinaryContainerBlockEntity.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/AbstractTimedCulinaryBlockEntity.java",
|
||||
"sha256": "99f217bc615aa414f41ffeeccc85d68655da48b794b82f8289fe86f93004c8a3",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/block/entity/AbstractTimedCulinaryBlockEntity.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/CookingCauldronBlockEntity.java",
|
||||
"sha256": "9c7f8145973f570f70e758650f99e174b5625b91e844bbcd1bf417eff1322951",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/block/entity/CookingCauldronBlockEntity.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/FermentationBarrelBlockEntity.java",
|
||||
"sha256": "feb866fca3848c80e9d06aa9fa38e9f49b8a5b22aa9c7bb5b5f4837ad4abee9a",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/block/entity/FermentationBarrelBlockEntity.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/FryingPanBlockEntity.java",
|
||||
"sha256": "af102fc1c80600208778d6d2047781b63fa2ebaae2890b71acfc6dfa2af600fe",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/block/entity/FryingPanBlockEntity.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/KitchenOvenBlockEntity.java",
|
||||
"sha256": "0f7c3f6612dee3dbb1b264be3e8a94c55e4f129155f351ea0eb8c3a1639ac463",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/block/entity/KitchenOvenBlockEntity.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/client/CookingCauldronHud.java",
|
||||
"sha256": "ec67f5180d01e5aa51abf229d1aa35aac325c50e93c3f2386ca194f95cf2e99a",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/client/CookingCauldronHud.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/client/FermentationBarrelHud.java",
|
||||
"sha256": "ada21e2c3a268aa5b6b27bf9a16ffe42aeb1e20fd4305e07900e88a1a376d5e2",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/client/FermentationBarrelHud.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/client/FryingPanHud.java",
|
||||
"sha256": "e37a353fde43eba54e4e60a98aa8a6ed87a06921f4040faca92dbd694d491304",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/client/FryingPanHud.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/client/renderer/FermentationBarrelBlockEntityRenderer.java",
|
||||
"sha256": "adba55ed2154e1f0243bae3bf78247dac24fa79f9cc3de7fbb6e522f700f5109",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/client/renderer/FermentationBarrelBlockEntityRenderer.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/client/renderer/FryingPanBlockEntityRenderer.java",
|
||||
"sha256": "13a30fee522d9b2efda13a763198b285f555bf3d011758780d27c19d9627249d",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/client/renderer/FryingPanBlockEntityRenderer.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/client/renderer/state/FermentationBarrelRenderState.java",
|
||||
"sha256": "1adc65fe528cc9ea1da8e43350f8062c4bec0bb31cf03ee54914776acd9d9647",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/client/renderer/state/FermentationBarrelRenderState.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/client/renderer/state/FryingPanRenderState.java",
|
||||
"sha256": "26eb76e9d6776d34ac9abb0769315d41f5a9574bf3415c2efd16b7b2c33c1b82",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/client/renderer/state/FryingPanRenderState.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/client/screen/KitchenOvenScreen.java",
|
||||
"sha256": "f619f2b95a76193e21db4e2cbed4fa64ac2848aedaed042bee4fcfac54ea5842",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/client/screen/KitchenOvenScreen.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/client/screen/RedstoneMillScreen.java",
|
||||
"sha256": "5ebc44972d34ea18b7982592b47a02ccd01fadd550bbe616e970b80cba1f9086",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/client/screen/RedstoneMillScreen.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryBatching.java",
|
||||
"sha256": "9532bf15306e21e09da4e90f19563284c0d3889f8d4382ad46e23b6daf6274a1",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/recipe/CulinaryBatching.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryPartialMatching.java",
|
||||
"sha256": "8cf3e2d74e24b9d83b8e9bdb36a216ba2745e1ab07bd2cbd55fb442b1a0f2824",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/recipe/CulinaryPartialMatching.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryProcessRecipe.java",
|
||||
"sha256": "57a51a52d4732fd8f7df38b02ea6bafecf3290c88f3146fe8c44892688736c11",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/recipe/CulinaryProcessRecipe.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryRecipeInput.java",
|
||||
"sha256": "d733004754aa377752118d8dd965a9025daafff2174bab76678940c4cf9fd79a",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/recipe/CulinaryRecipeInput.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryRecipeTypes.java",
|
||||
"sha256": "5227e6e3314502298ccba4cad95060b83604041eb0df53aac4769de1fe4fbe3c",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/culinary/recipe/CulinaryRecipeTypes.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/machine/FoodPressBlock.java",
|
||||
"sha256": "aeb6210329b66323de012616fcb8f4e360dcd990a1b930347a6fb20110c6eeda",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/machine/FoodPressBlock.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/machine/FoodPressBlockEntity.java",
|
||||
"sha256": "1d0c93c1982fa1197fe9c56fd2a8e94c2109a29b2efc3bd5d6ab1e5e22a3d34e",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/machine/FoodPressBlockEntity.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/machine/FoodPressContent.java",
|
||||
"sha256": "2aab91dd3b96d1fd2e759cb984bea6593e99a205ee68d0c7c00912329130e9e7",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/machine/FoodPressContent.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/machine/RedstoneMillBlock.java",
|
||||
"sha256": "8dbd9e15609b4c07a27af0997b5c2e15f7bce49e32eedf3cc3e266f9685c09ef",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/machine/RedstoneMillBlock.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/machine/RedstoneMillBlockEntity.java",
|
||||
"sha256": "31c750f9727430d00a80e1a65558ecc0471300bed5f9fbd4716a500d5d2274aa",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/machine/RedstoneMillBlockEntity.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/menu/KitchenOvenMenu.java",
|
||||
"sha256": "4f222eb436ea88785e82c927682588530b44db7b5f86dca341c8cfb88ddd19ba",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/menu/KitchenOvenMenu.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/menu/RedstoneMillMenu.java",
|
||||
"sha256": "7ca7c4d77c48121f9c81fecacf9b09f14964e7ac33a65d0c0688852a73425a2a",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/menu/RedstoneMillMenu.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/recipe/FoodPressingRecipe.java",
|
||||
"sha256": "fbc8cf4b452e54662c4fc469aa16e14880f86110339d9388b5dba27469ba5c5d",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/recipe/FoodPressingRecipe.java"
|
||||
},
|
||||
{
|
||||
"source": "src/main/java/fr/koka99cab/sanctuary26/itsalive/recipe/RedstoneMillingRecipe.java",
|
||||
"sha256": "a0d011d89f46f38e8fa384299f73a7cab44fd3189600e041fb9aa20ea2ff612d",
|
||||
"destination": "mods/sanctuary/src/main/java/fr/koka/sanctuary/cuisine/recipe/RedstoneMillingRecipe.java"
|
||||
}
|
||||
],
|
||||
"assets": {
|
||||
"src/main/resources/assets/itsalive/blockstates/olive_block.json": "998c5fa53bb09b44ce0cff864ca0a2ea7717b79efbd08637d820449a99e7ed69",
|
||||
"src/main/resources/assets/itsalive/blockstates/food_press.json": "1f15baab1f3b678b2e3650b9c6af4bcc3404b9af0a3ade61eaf3a66049469f91",
|
||||
"src/main/resources/assets/itsalive/blockstates/grape_block.json": "ed05b5c4e237b61ca0716a90bd7c57bcfa60aa2e40230481d86345862dd03622",
|
||||
"src/main/resources/assets/itsalive/blockstates/fermentation_barrel.json": "47db1631c9bad0b690f04e7cd78f1c1d219ad1a2c4aff7d535dea8c6ae7e06a1",
|
||||
"src/main/resources/assets/itsalive/blockstates/cooking_cauldron.json": "c79479d63942c3ae6ad360996c0e3e020575565dbc69c6e5c7f110c7225535a5",
|
||||
"src/main/resources/assets/itsalive/blockstates/frying_pan.json": "c2de84cf795042a16faad51e61d4afe900571227cd0585624e8a994d2deaffdf",
|
||||
"src/main/resources/assets/itsalive/blockstates/redstone_mill.json": "9cf9223ea7fb6fa2fa8df8a3fec63a60cc5849425b32ab3aa97c08b5a106eeb0",
|
||||
"src/main/resources/assets/itsalive/blockstates/kitchen_oven.json": "d9ef62ee1e5fa7abec636166528628fc265de2241a0f3234e919b2bea5bbe0a8",
|
||||
"src/main/resources/assets/itsalive/blockstates/tomato_block.json": "2acb0a87767691a0247788bdbfd744d7d06c03bf817e4dcaea48d6f86e3a6f6d",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_grape_7.json": "773e1389d1dfd67e9bfa1eb0dc21052e126aeb3bee29a7ec4be886867b95e17f",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_tomato_3.json": "b7becfd2e2691d6b36d2a3cf2a315c96fa6db87709e0d2741e535da17f10051e",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_olive_7.json": "3eac8754b3426624da4be9f67d201619231491702611a2f037490c8b48a18e6d",
|
||||
"src/main/resources/assets/itsalive/models/block/olive_block.json": "de9fa7bb7246ff9aec219f9ab9de43a219c9d6ee684bfc7fae66f6038c3fb983",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_olive_6.json": "5d864c423dd19ac779634899650c53562a22bb1ab175b93c28c8987329a22660",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press.json": "615b598729b5462a7e4cc654f79ebce901b6d4e82d89b1895b03945f30ca2d35",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_tomato_2.json": "23d6ec4aa8e21eb63561b8132d441fb76a5f6dce152a3ee5ce2289c077d4023f",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_olive.json": "b8df7bb819163a91ab6f7911743126c1583f8aa453b0b396cd6f6ce2abd3b668",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_grape_6.json": "869b096b5ae751bb6020ed3ef42ff998b28f79d8f44fac0a05c40561db57b4a5",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_tomato_5.json": "0b803d06608cfa324bb6e35ac301a59ec86b701e96f2fff9a585835924ddc0e4",
|
||||
"src/main/resources/assets/itsalive/models/block/grape_block.json": "195114f18de907448c9797aa539c4c5792c48efd17fff0f0a2779358eafcee73",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_olive_1.json": "9376fecd6122b37ff4c6aebfe09ed798326fa932a9c28f41b83cc6dd28d0b89a",
|
||||
"src/main/resources/assets/itsalive/models/block/redstone_mill_on.json": "50009556a95a7db5bdce15622208a2931ed4a2a6e1fd3457c0be2754b495b78c",
|
||||
"src/main/resources/assets/itsalive/models/block/fermentation_barrel.json": "0ccb53d3f86eb2b7b4932e29149a5e210b54324c07c517c0aa79d73e6417a8dd",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_grape_1.json": "7c5d3b41782ad57688a1209e83d15e58d145980423acc44d93e29baec0c3a961",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_tomato_9.json": "dfbbc138ee4c0851acc220ab7c1fc63498b12e39f1eb3a75c3c2d2b582b1574d",
|
||||
"src/main/resources/assets/itsalive/models/block/kitchen_oven_on.json": "326567713bae7ed35dad107838b81b1e2b00b9a3a67a644782eb0a1323d2fa1e",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_tomato_8.json": "ee4b305779c34e5743728ef78dd964d3aa8302526a2bbb4ffe05c6efaa29c8f5",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_tomato_4.json": "e7f95e5ab13f1b98c18fbd2563925bc9c9adbd793d129c7752fe5d2b66c2a33c",
|
||||
"src/main/resources/assets/itsalive/models/block/cooking_cauldron.json": "f9fcfdbb09a63ee6437b5494009a3d3f727291cf356f502a1e8a5aa30f972968",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_olive_3.json": "7fd398a8089e5fa5f7d0b12f2799c6e960d03abefd6e186d5ff7f11e7f50a3f6",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_tomato_7.json": "c1042823e4fa94c5635e12f446a6bf7eb48541edce0bfd1661aa441a46d74c23",
|
||||
"src/main/resources/assets/itsalive/models/block/frying_pan.json": "9f9b5b1c7ae19b350e1d4c8f973b9987c8efb0a0f218fcf31d6d035957e1d88c",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_grape_3.json": "eb3189f1fa68a3f9cda9f801c83ec55fc1c7a81c4369c139e1218e88d38b62dd",
|
||||
"src/main/resources/assets/itsalive/models/block/redstone_mill.json": "3d0be0d018982089aa3b9214abd1cc2c1b89ccc6f021f5eef57560feb1cc9a0f",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_grape_2.json": "7cfe14676d665c671cebc443d4d5e17db441526ff76db1a2740abdebe77259a4",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_grape.json": "e5b07ea4d0c7c0303c2a38bae068a1dff16293df894a59d09b3cf57fdbe434a7",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_tomato_6.json": "8d6019eb3a441d91a34e6ceeb6e7d482431e48f6d42536b015fc42281e1f6764",
|
||||
"src/main/resources/assets/itsalive/models/block/kitchen_oven.json": "e9b8327dd6022313935edee4c8daa1af0a0071c7d8436f96fa94c46de9eee14d",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_olive_2.json": "7db27db21ebedce3f5cf5a3801ec9ecb908ca590a3e23e2375243e8195988c95",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_olive_9.json": "a29d412b4e576d37962194aa61b1d676399741500d9e29d171536ef352cc2079",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_grape_5.json": "d2627140a4f52fdc59e7773e0b5f524ed91e141b7026805a78f21009fbff73ed",
|
||||
"src/main/resources/assets/itsalive/models/block/cooking_cauldron_coffee.json": "818a1e10296a651845621cf9c44647fe5fac1b37ba358c88dde445714dc98119",
|
||||
"src/main/resources/assets/itsalive/models/block/tomato_block.json": "bec799c63bd80b6c69a3ddff317d688c717fb89cb25d43783194f9f2426ff2d3",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_olive_5.json": "953f2e19c4997927d524aa5e768fd02b36170baa43ffa52572a889fefe431417",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_tomato_1.json": "3a6f5d0cf5747a3441ed8171ea845240bd272f46a1ffe634a2058699b1d661aa",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_grape_9.json": "767e70ea72263ffbb6f1812cf84ec90bcd2001254e5ae3e88c2280fd9fdef62d",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_grape_8.json": "9d1d33f89591e75353fcbb083164328e0961c2d97f678724813d3e5015334397",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_tomato.json": "a590f64f1042ca751cd81717cc184d53a761c5daf2667004b1a0a6d8e0bc5b21",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_olive_4.json": "5730a951afc7bf294a082908e9cdb6617c48159fed64d2d79f400cf65c9769a9",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_grape_4.json": "140e7b9e9703c56f897a469e70e6c082ce5697e0c1a13210184c2980e8e0d325",
|
||||
"src/main/resources/assets/itsalive/models/block/food_press_fill_olive_8.json": "a751e6738d5a4ae00f2a9c7b9d0035ecf6bb5c989a1733e5fc3471ba742330dc",
|
||||
"src/main/resources/assets/itsalive/models/item/olive_block.json": "84b2fad0a5e2ce33e1798786a79a436246a75836acd98828e52f1bd393193314",
|
||||
"src/main/resources/assets/itsalive/models/item/food_press.json": "a4a57208fd428939b7b3d042335dcec6c45f8e9de190c9cabd8bbc831a011ce1",
|
||||
"src/main/resources/assets/itsalive/models/item/grape_block.json": "92d78de7a02a42fbe015e7aee1338052ff29701d37fe6c7906d6de611c4508f1",
|
||||
"src/main/resources/assets/itsalive/models/item/fermentation_barrel.json": "c9e3db73daee99a2a3efca454541e8dd5f2f6d9e1c40b3d0eb5f222914082533",
|
||||
"src/main/resources/assets/itsalive/models/item/frying_pan.json": "5477410d6dd022b72ea0ebe7306dd0bf86ba6bf6357bec67ec265c654801d3d8",
|
||||
"src/main/resources/assets/itsalive/models/item/redstone_mill.json": "b19fb1874697283774f774ca1cb57c817a55ce55b6ae3ed032d61d2b949e6151",
|
||||
"src/main/resources/assets/itsalive/models/item/kitchen_oven.json": "f9dd37260005e466276b4c9002b8093f8446df854369bb7b0310417ad50881ed",
|
||||
"src/main/resources/assets/itsalive/models/item/tomato_block.json": "5956b5a5a866fade827ad6088dda8cd6db341d702896cea1c425809abe424c9b",
|
||||
"src/main/resources/assets/itsalive/items/olive_block.json": "a88c83d92ff0b115dc6bd8c37fb1a46a93d1722520989c62b39f3809279f9987",
|
||||
"src/main/resources/assets/itsalive/items/food_press.json": "5fcd04addfe5cf076340f639c78cf63c77bc04988357328dd2877695b09f89dc",
|
||||
"src/main/resources/assets/itsalive/items/grape_block.json": "068e0859a395f620d10ae8df4c42871ffe0e70b94d2a383adb61d717843edbb1",
|
||||
"src/main/resources/assets/itsalive/items/fermentation_barrel.json": "dcc8d9be395cb93c4545481e675f8159e48facca2eccdc8f34de6a012e5ff719",
|
||||
"src/main/resources/assets/itsalive/items/frying_pan.json": "de51f0130cb1107c4d57db273ba581519ed35fef1c911f80793746cac2b11717",
|
||||
"src/main/resources/assets/itsalive/items/redstone_mill.json": "3c22f1b4e085d4c0742caae943a8cbf90ce6b5523cb27bcf346ca262a8726f6b",
|
||||
"src/main/resources/assets/itsalive/items/kitchen_oven.json": "a20e4385c41fcc3045fcc4ce11d4749980860a5221458df2ca550e7535169610",
|
||||
"src/main/resources/assets/itsalive/items/tomato_block.json": "bd7e4e2e8cf433d5bf6180134ae81539f1ee72f75f7f60b373cae98e37f9b839",
|
||||
"src/main/resources/assets/itsalive/items/failed_dish.json": "f0331dab307785acc01ce34922ef0e8bc6751245f356c9d19f1442908894ae72",
|
||||
"src/main/resources/assets/itsalive/models/item/failed_dish.json": "9ea5d1fa8a9de8b94fdc14436b0dd33da34a0051672aa42f759f091b3f6075e3"
|
||||
},
|
||||
"recipes": "scripts/data/cuisine.json; scripts/data/kitchen-crafting.json",
|
||||
"crafting": {
|
||||
"src/main/resources/data/itsalive/recipe/fermentation_barrel.json": "bce931d76ef4c465c757f16ad7ee0db71838a77b39d3540cec327b7bfd72cde9",
|
||||
"src/main/resources/data/itsalive/recipe/food_press.json": "431bbd78c482085ebde27a5501db97d4c999bf6b91524fba22793e9f4085caa3",
|
||||
"src/main/resources/data/itsalive/recipe/frying_pan.json": "e99e9a0b514642897fa8e82f12847fca618350d86359fd8786c6f1561c772cac",
|
||||
"src/main/resources/data/itsalive/recipe/grape_block.json": "625b20deab5f4debfa1c08febf5f752e705ee015f2c796accf95e21945b0b89b",
|
||||
"src/main/resources/data/itsalive/recipe/grapes_from_block.json": "870393e4a147f99440cf2742c8cd852b403caac85dcd9ac4bfef1ccbbfddec6c",
|
||||
"src/main/resources/data/itsalive/recipe/kitchen_oven.json": "9f2ccb182f907f2d0932fb53b21226afb9c61faf280089703b6a04b6fe66f3be",
|
||||
"src/main/resources/data/itsalive/recipe/olive_block.json": "35a5c6f87f4483837242598c58367d986c475f8fa44d6bca513383e4b58ee60c",
|
||||
"src/main/resources/data/itsalive/recipe/olives_from_block.json": "23d7c22b40bda59e717b98254cfbf73c32c1cb8e03f9a605235c680b061122d0",
|
||||
"src/main/resources/data/itsalive/recipe/redstone_mill.json": "cedd2025ff9961084f1e0818c2d45b4696e6c0108fda25847b07df8fc62a9425",
|
||||
"src/main/resources/data/itsalive/recipe/tomato_block.json": "994e0f68beb5ea3b8811912d3eef46a6b7f0a7dc48af2e05c2a33e4fb87fe598",
|
||||
"src/main/resources/data/itsalive/recipe/tomatoes_from_block.json": "eb7fa7e229187b4439e88f1e95f143ed999533f00415db06768f537c1f01e5ee"
|
||||
},
|
||||
"adaptations": [
|
||||
"namespace sanctuary; Minecraft 26.3 APIs and exact cooking-fuel components",
|
||||
"native cauldron liquid accounting; ingredient containers; timed fermentation",
|
||||
"JEI six physical categories; native cauldron item icon added",
|
||||
"equipment crafts: iron/copper replace unavailable silver; oven uses iron/bricks/crafting table/furnace"
|
||||
],
|
||||
"verification": {
|
||||
"version": "beta.304",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.304.jar",
|
||||
"sha256": "d24bac38e1177d785cd23a0ebe702c20e6a36dda160a0b4f366d23e9e7f4fa09",
|
||||
"generated_json": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 172,
|
||||
"agriculture": 376
|
||||
},
|
||||
"culinary_classes": 43,
|
||||
"server_required": 72,
|
||||
"kitchen_scenarios": 17,
|
||||
"physical_recipes": 80,
|
||||
"device_blueprints": 91,
|
||||
"client_vulkan": {
|
||||
"kitchen": "build/kitchen304-client.log",
|
||||
"cuisine": "build/kitchen304-cuisine303-client.log",
|
||||
"agriculture": "build/kitchen304-agriculture300-client.log"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
# IA-04 / IA-05 / IA-07 / IA-08 — appareils culinaires, beta.304 locale
|
||||
|
||||
Précision locale beta.311 : le chaudron attend désormais un geste au bâton et
|
||||
ne produit plus de plat raté. Voir [fonctionnement et continuité](cauldron-beta311.md).
|
||||
|
||||
Précision locale beta.310 : la poêle se pose désormais en piles de une à
|
||||
quatre, avec une recette commune et les ingrédients de chaque portion. Voir
|
||||
[comportement actuel et continuité](pan-stacks-beta310.md). Le reçu ci-dessous
|
||||
décrit les vérifications historiques de beta.304.
|
||||
|
||||
> Étape historique : [beta.305 remplace la presse par le piétinement et les
|
||||
> pistons directement sur les fruits](crushing-beta305.md), puis
|
||||
> [beta.306 rend le moulin manuel, sans combustible](mill-beta306.md).
|
||||
> [beta.308 précise le cycle de la cuisinière et sépare aperçu et plats cuits](oven-beta308.md).
|
||||
> Cette page conserve le résultat vérifié de beta.304.
|
||||
|
||||
|
||||
Préparation du 28 octobre 2026 sur **`codex/itsalive-26-3`**, après beta.303.
|
||||
Le créateur a choisi de porter **recettes et appareils culinaires ensemble**.
|
||||
Aucune publication, installation, mise à jour de canal, de Prism ou de serveur.
|
||||
Le checkout historique 26.2 reste une référence en lecture seule.
|
||||
|
||||
## Résultat jouable
|
||||
|
||||
Les six appareils sont enregistrés pour Minecraft **26.3**, Fabric API
|
||||
**0.160.5+26.3**. Ils utilisent les textures déjà importées, des inventaires
|
||||
serveur, les sauvegardes natives, les comparateurs et les entonnoirs.
|
||||
Aucun onglet créatif supplémentaire : les dix nouvelles entrées d'objets
|
||||
rejoignent **Nourriture et boissons**. Les semences restent dans Naturel.
|
||||
|
||||
| Appareil | Geste et fonctionnement | Recettes physiques |
|
||||
| --- | --- | ---: |
|
||||
| Poêle | Poser, ajouter un ingrédient par clic ; Maj + clic pour retirer le plat ou le dernier ingrédient. Contenu visible dans le bloc entité. Chauffe par dessous. | 26 |
|
||||
| Chaudron | Jeter un aliment reconnu dans un chaudron vanilla rempli d'eau pour commencer ; ajouter ensuite par clic ou jet. Clic pour récupérer. | 19 |
|
||||
| Cuisinière / four de cuisine | Ouvrir la grille 3 × 3, disposer les ingrédients sans forme, ajouter du combustible et attendre le résultat. | 13 |
|
||||
| Tonneau de fermentation | Ajouter par clic, retirer à main vide. Délai historique conservé et affiché ; le temps du monde continue de compter lorsque le chunk est déchargé. | 10 |
|
||||
| Moulin à redstone | Interface entrée / combustible / sortie, durées historiques ; le combustible suit les composants natifs du four 26.3. | 9 |
|
||||
| Pressoir | Compacter neuf fruits, placer le bloc contre le pressoir et pousser avec un piston horizontal alimenté, depuis l'un des quatre côtés. Récupérer les portions avec le bon récipient. | 3 |
|
||||
| **Total** | Catégories dédiées dans JEI, avec ingrédients, résultat et durée pour les procédés temporisés. | **80** |
|
||||
|
||||
La poêle est un bloc entité bas qui peut être posé sur les surfaces usuelles,
|
||||
sans condition d'ancrage supplémentaire. Ce port conserve sa forme historique ;
|
||||
il n'ajoute pas une pose murale, une orientation libre ou un nouveau modèle de
|
||||
poignée. Ces améliorations éventuelles de IA-07 restent ouvertes.
|
||||
|
||||
Poêle et chaudron laissent **30 secondes de chauffe** pour assembler un mélange.
|
||||
Une poêle avec quatre positions d'ingrédients distinctes peut commencer après
|
||||
3 secondes de stabilité. Un gros tas d'œufs ne compte pas comme quatre
|
||||
positions : le joueur peut encore ajouter du lait pour une omelette.
|
||||
Sans chaleur, le délai d'assemblage et la cuisson sont suspendus.
|
||||
Un mélange invalide à l'échéance donne un **plat raté**, en consommant une unité
|
||||
de chaque ingrédient présent et en conservant ses récipients.
|
||||
|
||||
Les sources de chaleur sont le feu, la lave, un feu de camp allumé, un four
|
||||
vanilla allumé ou une cuisinière allumée. Une lampe de redstone ou une propriété
|
||||
`lit` quelconque ne suffit pas. Le moulin brûle du combustible ; son nom
|
||||
historique ne signifie pas qu'un signal redstone remplace ce combustible.
|
||||
|
||||
## Eau, portions et récipients
|
||||
|
||||
Le chaudron conserve séparément ses **1 à 3 niveaux d'eau vanilla**. Ils servent
|
||||
aux recettes comme le café, sans créer de bouteilles. Un niveau paie une dose
|
||||
d'eau ; trois niveaux et trois cafés moulus donnent trois cafés. Les recettes
|
||||
sont traitées en lots limités par les ingrédients et la place en sortie.
|
||||
Récupérer le dernier plat ou annuler le mélange rétablit le chaudron vanilla,
|
||||
avec exactement l'eau restante. Les potions à effets et les objets étrangers
|
||||
ne sont pas des ingrédients admis.
|
||||
|
||||
Les bouteilles et seaux réellement utilisés sont rendus par la transformation.
|
||||
L'ananas rend sa couronne. Les mélanges ratés rendent aussi ces récipients.
|
||||
Les retours du chaudron sont éjectés avec une courte protection contre leur
|
||||
réabsorption. Le pressoir donne neuf portions par bloc : bol pour la pulpe de
|
||||
tomate, bouteille pour le moût de raisin et l'huile d'olive. Une portion
|
||||
conditionnée conserve le récipient payé dans son composant natif de retour.
|
||||
Une recette qui consomme cette portion rend ce même récipient.
|
||||
|
||||
Pour automatiser le pressoir, placer un inventaire/entonnoir dessous et fournir
|
||||
les récipients correspondants par dessus. Seules les portions conditionnées
|
||||
sortent par le bas ; un réservoir plein préserve le bloc de fruits devant lui.
|
||||
Poêle, chaudron et tonneau acceptent les ingrédients par dessus/côtés et sortent
|
||||
les résultats par dessous. Cuisinière et moulin prennent leur combustible par
|
||||
les côtés et les ingrédients par dessus. Les récipients restés dans leurs
|
||||
cases sont récupérables dans l'interface ou en vidant l'appareil.
|
||||
|
||||
## Recettes et continuité avec beta.303
|
||||
|
||||
Les **92 recettes historiques** restent couvertes. Les 46 préparations crues
|
||||
et les identifiants d'objets déjà ajoutés restent utilisables au four/fumoir.
|
||||
Les alternatives manuelles de cuisson et de mouture sont conservées.
|
||||
Les dix identifiants `sanctuary:ferment_*` désignent désormais les procédés du
|
||||
tonneau : leur ancienne fabrication instantanée disparaît. Les trois
|
||||
`sanctuary:press_*` deviennent des recettes physiques de pressoir ; neuf fruits
|
||||
se compactent en bloc, et ce bloc peut aussi se décompacter à la table. Il n'y
|
||||
a plus deux résultats concurrents pour la même grille de neuf fruits.
|
||||
Les 67 autres procédés physiques portent le préfixe `sanctuary:physical_*`.
|
||||
|
||||
Onze recettes donnent les cinq appareils fabriqués, les trois blocs de fruits
|
||||
et leurs trois décompactages. Le chaudron de cuisine commence depuis le bloc
|
||||
vanilla et rend un chaudron vanilla à la casse. La cuisinière coûte **trois
|
||||
lingots de fer, quatre blocs de briques, une table de fabrication et un four** :
|
||||
|
||||
```text
|
||||
Fer Fer Fer
|
||||
Briques Table Briques
|
||||
Briques Four Briques
|
||||
```
|
||||
|
||||
Le pressoir utilise du fer à la place de l'ancien argent indisponible ; le
|
||||
moulin utilise du cuivre à sa place. Les noms et aides d'interface sont FR/EN.
|
||||
La pâte d'olive historique sans recette n'est pas ajoutée comme objet orphelin.
|
||||
|
||||
Les cultures enseignent également les recettes d'appareils et les procédés
|
||||
physiques dans la progression et JEI. Les nouvelles entités sont additives ;
|
||||
aucun identifiant agricole n'est renommé, aucun monde existant n'est modifié,
|
||||
aucune génération n'est activée et aucune sauvegarde 26.2 n'est convertie.
|
||||
Le saucisson, le séchoir, les nouvelles recettes inventées et la génération
|
||||
naturelle restent dans leurs tickets. La cuisinière possède une grille de
|
||||
recettes culinaires ; le Fourneau multibloc existant garde ses propres usages.
|
||||
|
||||
## Provenance et vérifications
|
||||
|
||||
Le port adapte **36 fichiers Java** de `26.2/itsalive`, commit
|
||||
`26f2c2a0061619df1f146f9081d6740c560a7497`, GPL-3.0-or-later.
|
||||
[Reçu des sources](kitchen-beta304-sources.json), modèles historiques dans
|
||||
`scripts/data/kitchen-assets.json`, recettes d'équipement adaptées dans
|
||||
`scripts/data/kitchen-crafting.json`. Les 92 recettes originales et leurs
|
||||
empreintes restent dans `scripts/data/cuisine.json`.
|
||||
|
||||
Les adaptations 26.3 concernent notamment les codecs de templates d'objets,
|
||||
les combustibles à composants, les menus, le rendu de blocs entités, la
|
||||
synchronisation Fabric des recettes et les six catégories JEI. Aucun PNG
|
||||
agricole ou culinaire n'est retouché.
|
||||
|
||||
`check build assemblePack` passe en **2 min 51 s**, avec **72 tests serveur
|
||||
requis réussis**, dont **17 scénarios pour les appareils**. Ils chargent les
|
||||
80 recettes, font réellement cuire les 58 procédés chauds et tourner les neuf
|
||||
recettes du moulin, vérifient le délai de fermentation et son rechargement,
|
||||
l'eau vanilla, les récipients, les mélanges ratés, les quatre côtés du pressoir,
|
||||
la capacité de sortie, le conditionnement, les 11 fabrications et la découverte
|
||||
des 91 plans d'appareils/procédés. La casse répétée rend une seule fois les
|
||||
inventaires des six appareils. Les régressions cuisine, cultures, vanille et
|
||||
progression sont incluses.
|
||||
|
||||
Le parcours **Vulkan avec Sodium 0.9.2** passe en **1 min 8 s**. Il vérifie un
|
||||
vrai paquet d'insertion dans la poêle, les ticks de cuisson, les contenus
|
||||
synchronisés, les **233 états de blocs et dix modèles d'objets**, les menus
|
||||
FR/EN aux échelles GUI 2/3/4 et les **six catégories / 80 recettes dans JEI**.
|
||||
Une fermeture/réouverture réelle conserve les six appareils et leurs contenus,
|
||||
y compris le délai du tonneau ; le vin se termine une fois après le temps
|
||||
requis. Les échecs graphiques repérés pendant le port — icône de marmite,
|
||||
libellé et références des sprites de moulin — sont corrigés.
|
||||
|
||||
La régression cuisine Vulkan passe en **37 s** : les 196 étapes du catalogue
|
||||
restent indexées dans JEI, avec l'eau correcte ; 129 objets, table, fumoir et
|
||||
rechargement sont vérifiés. La régression agricole Vulkan passe en **32 s** :
|
||||
313 états, 48 objets, 20 cultures, les arbres et le caféier, onglets FR/EN
|
||||
GUI 2/3/4 et rechargement réel de 173 états agricoles.
|
||||
|
||||
Le JAR contient les **432 JSON de cuisine**, **172 JSON d'appareils** et
|
||||
**376 JSON agricoles** générés, identiques aux générateurs, ainsi que les
|
||||
43 classes culinaires et les notices courantes. Les **343 PNG importés**
|
||||
restent identiques, dont les 38 fichiers fournis ; les 13 PNG de vanille sont
|
||||
toujours en 16 × 16. Les copies du JAR packwiz client et serveur sont
|
||||
identiques au binaire vérifié. Le template conserve les mêmes textures
|
||||
culinaires/agricoles/vanille et notices.
|
||||
|
||||
SHA-256 de `sanctuary-beta.304.jar` :
|
||||
|
||||
```text
|
||||
d24bac38e1177d785cd23a0ebe702c20e6a36dda160a0b4f366d23e9e7f4fa09
|
||||
```
|
||||
|
||||
Journaux et reçu de vérification dans `build/kitchen304-*`. Les captures du
|
||||
dernier parcours restent dans le dossier de test client ignoré.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=kitchen304,cuisine303,vanilla301,agriculture300,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryKitchen304ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=kitchen304,cuisine303 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
Les tests client utilisent un monde de développement neuf, graine **304**.
|
||||
La validation graphique cible uniquement Vulkan. Les tests des longues durées
|
||||
avancent explicitement l'horloge de leur monde de test ; ils ne prétendent pas
|
||||
avoir attendu plusieurs jours de jeu en temps réel.
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"version": "beta.318",
|
||||
"instance": "/Users/koka/Library/Application Support/PrismLauncher/instances/Sanctuary-0.1.0-alpha.1",
|
||||
"profile": "/Users/koka/Library/Application Support/PrismLauncher/instances/Sanctuary-0.1.0-alpha.1/sanctuary-local-packs/beta.318",
|
||||
"source_commit": "980544e27947b490684fb9dd1b0cb9a1eac4e3b1",
|
||||
"jar_sha256": "5b8d6fff72c3128ceac085a9d71b493bbda13a0dbbe42ffdd3e8dc2fda3a632b",
|
||||
"isolated_passes": 2,
|
||||
"personal_passes": 2,
|
||||
"passes_identical": true,
|
||||
"protected_files_unchanged": 1018,
|
||||
"optional_choices_preserved": true,
|
||||
"launcher_changes": "Only the two PreLaunchCommand URL arguments; all other settings preserved.",
|
||||
"installer_unchanged": "9e2912fdd05e5c23721acd58cd5c61bbf13061e453627e8f4df9a4c53f7aa875",
|
||||
"bootstrap_unchanged": "a8fbb24dc604278e97f4688e82d3d91a318b98efc08d5dbfcbcbcab6443d116c",
|
||||
"backup": {
|
||||
"instance": "/Users/koka/Library/Application Support/PrismLauncher/instances/Sanctuary-0.1.0-alpha.1",
|
||||
"backup": "/Users/koka/Library/Application Support/PrismLauncher/instances/Sanctuary-0.1.0-alpha.1/sanctuary-backups/before-local-beta318-20261030T053736Z",
|
||||
"protected_files": 1018,
|
||||
"backup_sha256": "56158b90e8b6b8ec9040129b1ee8077b0764f73aed1e7ba6afe946294b7f9327",
|
||||
"backup_bytes": 272635407
|
||||
},
|
||||
"personal_world_opened": false,
|
||||
"published": false,
|
||||
"public_channel_modified": false,
|
||||
"remote_server_modified": false,
|
||||
"web_modified": false,
|
||||
"date": "2026-10-30",
|
||||
"verification": "Actual packwiz installer, two isolated passes then two personal passes; no game or personal world launched."
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
# beta.318 — installation locale pour les essais
|
||||
|
||||
Installation du **30 octobre 2026**, même branche `codex/itsalive-26-3`.
|
||||
La demande autorise l’instance locale et exclut toute publication.
|
||||
|
||||
## Installation vérifiée
|
||||
|
||||
L’instance Prism existante **Sanctuary Beta**, dossier historique
|
||||
`Sanctuary-0.1.0-alpha.1`, passe de beta.315 à **beta.318**. Aucune nouvelle
|
||||
instance n’est créée ou importée. Un seul JAR Sanctuary parmi les neuf mods ;
|
||||
les huit autres JAR, les choix des mods facultatifs, Minecraft 26.3, Fabric
|
||||
0.19.5, Java, l’installateur au logo et le lien FFmpeg sont conservés.
|
||||
|
||||
Le JAR installé correspond exactement au binaire de
|
||||
[la préparation beta.318](dried-cuisine-beta318.md), source `980544e27947b490684fb9dd1b0cb9a1eac4e3b1` :
|
||||
|
||||
```text
|
||||
5b8d6fff72c3128ceac085a9d71b493bbda13a0dbbe42ffdd3e8dc2fda3a632b
|
||||
```
|
||||
|
||||
Cette préparation avait réussi 189 tests serveur et le parcours Vulkan/Sodium.
|
||||
La présente installation vérifie les fichiers et la commande de pré-lancement ;
|
||||
aucun jeu ni monde personnel n’est ouvert pour la prouver. Pour essayer les
|
||||
nouvelles recettes et les coups épicés, lancer **Sanctuary Beta dans Prism en solo**.
|
||||
Les appareils restent autoritaires côté serveur ; aucun serveur distant n’est mis à jour.
|
||||
|
||||
## Profil de test local
|
||||
|
||||
Un profil autonome est conservé dans le dossier de l’instance :
|
||||
|
||||
```text
|
||||
sanctuary-local-packs/beta.318/pack.toml
|
||||
```
|
||||
|
||||
Il contient le manifeste, l’index vérifié, les métadonnées inchangées des
|
||||
huit dépendances et le JAR Sanctuary local. Le vrai installateur packwiz lit
|
||||
ce profil depuis le disque ; aucun serveur HTTP ni accès au worktree n’est
|
||||
nécessaire au lancement. Le JAR est une entrée directe de l’index local.
|
||||
|
||||
Seuls les deux arguments d’URL `PreLaunchCommand` de `instance.cfg` sont
|
||||
remplacés par le chemin cité `"$INST_DIR/sanctuary-local-packs/beta.318/pack.toml"`.
|
||||
Le bootstrap, `--bootstrap-no-update`, les autres arguments et toutes les
|
||||
lignes de configuration restent inchangés. Les lancements restent donc sur
|
||||
beta.318 pour ces essais, au lieu de reprendre la version du canal public.
|
||||
La commande publique d’origine est sauvegardée ; son retour sera effectué
|
||||
lors d’une mise à jour publique compatible demandée.
|
||||
|
||||
## Sauvegarde et contrôles
|
||||
|
||||
Sauvegarde complète, hors de `mods/`, avant toute modification :
|
||||
|
||||
```text
|
||||
/Users/koka/Library/Application Support/PrismLauncher/instances/Sanctuary-0.1.0-alpha.1/sanctuary-backups/before-local-beta318-20261030T053736Z/instance.tar.gz
|
||||
```
|
||||
|
||||
Archive de **272635407 octets**, SHA-256 :
|
||||
|
||||
```text
|
||||
56158b90e8b6b8ec9040129b1ee8077b0764f73aed1e7ba6afe946294b7f9327
|
||||
```
|
||||
|
||||
Le véritable installateur réussit **deux passages dans une copie isolée**,
|
||||
puis **deux dans l’instance personnelle**. Le second passage est à jour,
|
||||
les neuf JAR gardent leurs hashes et aucun doublon Sanctuary ne reste.
|
||||
**1018 fichiers personnels** sont contrôlés identiques avant/après : sauvegardes,
|
||||
réglages, captures, favoris, packs et dépendances. Les métadonnées Minecraft/Fabric,
|
||||
les préférences des mods facultatifs et les outils de lancement restent identiques.
|
||||
Aucune régénération, conversion ou ouverture de monde.
|
||||
|
||||
Aucun push, tag, release, canal public, site, serveur Beta ou autre instance
|
||||
n’est modifié. Les journaux privés sont dans `build/local318/` ; les scripts
|
||||
ponctuels y sont un historique de cette intervention, pas une procédure à rejouer.
|
||||
[Reçu de vérification](local-beta318-verification.json).
|
||||
@@ -0,0 +1,70 @@
|
||||
# MEDIA-07 — normales résiduelles de l'ancre
|
||||
|
||||
Retour du 27 octobre 2026 : une bordure légèrement brillante reste sur la
|
||||
vidéo à l'emplacement de l'ancre, dans le coin inférieur gauche.
|
||||
Branche `codex/media-anchor-model`, **beta.298**, Media **0.3.2**.
|
||||
[Livraison complète du 27 octobre 2026](release-beta298.md).
|
||||
|
||||
## Cause et correction
|
||||
|
||||
Le bloc est invisible pour le rendu Minecraft, mais conserve un ancien modèle
|
||||
de cadre 1 × 1 avec **30 faces**. La passe PBR de Sanctuary collectait aussi
|
||||
les modèles des blocs invisibles. Elle dessinait donc les normales et le
|
||||
matériau de ce petit cadre, alors que sa couleur n'était pas rendue.
|
||||
Les essais graphiques de MEDIA-06 avaient les shaders Sanctuary désactivés ;
|
||||
ils ne couvraient pas cette passe.
|
||||
|
||||
La collecte PBR des modèles opaques et du verre respecte maintenant
|
||||
`RenderShape.MODEL`. Les fluides gardent leur chemin dédié, y compris l'eau
|
||||
contenue dans un bloc. Le modèle de bloc de l'ancre est également vidé ;
|
||||
sa texture de particules reste disponible. L'objet utilise déjà un modèle
|
||||
indépendant et garde son apparence dans l'inventaire et dans la main.
|
||||
Le grand cadre reste dessiné par son maillage aux dimensions de l'écran.
|
||||
|
||||
Aucun identifiant, paquet, format de données ou règle d'interaction ne change.
|
||||
Les ancres existantes bénéficient du correctif au chargement du client.
|
||||
La sauvegarde et les dimensions ne nécessitent aucune migration.
|
||||
|
||||
## Validation
|
||||
|
||||
Reproduction avec le JAR beta.297 publié : deux clients Vulkan, Sodium,
|
||||
Sound Physics et **shaders Sanctuary/PBR actifs**. Le diagnostic constate
|
||||
`INVISIBLE`, 30 faces et admission de l'ancre dans la passe PBR opaque.
|
||||
Le bois et le verre visibles sont admis dans leurs passes respectives, l'eau
|
||||
dans la passe transparente.
|
||||
|
||||
Après correction, les deux clients constatent **zéro face**, ancre refusée
|
||||
par les deux passes de matériau, PBR réellement appliqué et vidéo en mouvement.
|
||||
Les chemins bois, verre et eau restent admis. Pose, taille 9 × 5 et ouverture
|
||||
du lecteur testées dans le laboratoire ; un clic de pose a dû être rejoué
|
||||
après la réception de l'inventaire du test.
|
||||
|
||||
Captures finales du client A inspectées, en gros plan du coin inférieur gauche,
|
||||
de face et en vue rasante : aucune empreinte du cadre 1 × 1 sur la vidéo.
|
||||
La sortie OpenAL est active sur les deux clients. Le JAR Sanctuary testé,
|
||||
avec un seul Media embarqué et sans sonde, a pour SHA-256
|
||||
`e6fdc36a212dcfcc89d31fbb29bc8eeec9d002cc9223ea6dcbfb31b7df17ec23`.
|
||||
|
||||
`check build assemblePack assembleServerPack :sanctuary-test:exportDuoLaunch`
|
||||
réussis en **3 min 6 s**, 162 tâches, **18 GameTests requis** au profil
|
||||
`property270,claims272,claims277,claims278`, atlas ciblé. Contrats Media inclus.
|
||||
Compilation des vérifications client PBR, y compris les nouveaux contrôles
|
||||
des blocs invisibles et des modèles visibles, réussie en **37 s**. La suite
|
||||
graphique PBR historique complète n'est pas rejouée ; ce parcours ciblé
|
||||
exerce la sélection réelle des matériaux du JAR construit.
|
||||
|
||||
Un lancement de client a automatiquement basculé hors Vulkan après une course
|
||||
d'extraction MoltenVK entre les deux processus : il a été rejeté, arrêté et
|
||||
relancé sous Vulkan. Seuls les relevés Vulkan sont retenus. Après les captures
|
||||
et les relevés valides, le client B s'est arrêté sur l'écriture du journal de
|
||||
la sonde (`No space left on device`). Le disque local ne disposait plus que
|
||||
de 136 Mio au contrôle suivant ; ce parcours ne vaut donc pas essai d'endurance.
|
||||
Ce défaut d'espace appartient au laboratoire, la sonde n'est pas distribuée.
|
||||
|
||||
[Relevé structuré](media-anchor-normals-results.json). Pas de nouvelle mesure
|
||||
de synchronisation vidéo/live ou de source publique. Mur graphique orienté sud.
|
||||
Laboratoires isolés sous `build/media-prototype/anchor-model-*`, captures finales
|
||||
dans `anchor-model-after/client-a/screenshots/`. Aucun monde personnel utilisé.
|
||||
Le correctif est maintenant publié et installé sur le canal, la Beta et la
|
||||
même instance Prism. Le [reçu](release-beta298.md) distingue ces essais
|
||||
graphiques des vérifications effectuées pendant la livraison.
|
||||
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"ticket": "MEDIA-07",
|
||||
"state": "published and installed on Beta server, Web distribution and existing Prism instance",
|
||||
"artifact": {
|
||||
"version": "beta.298",
|
||||
"bytes": 18301295,
|
||||
"sha256": "e6fdc36a212dcfcc89d31fbb29bc8eeec9d002cc9223ea6dcbfb31b7df17ec23",
|
||||
"media_version": "0.3.2",
|
||||
"nested_media_count": 1,
|
||||
"anchor_block_faces": 0,
|
||||
"probe_embedded": false
|
||||
},
|
||||
"backend": "Vulkan",
|
||||
"clients": [
|
||||
{
|
||||
"client": "client-a",
|
||||
"model": {
|
||||
"anchor_model_quads": 0,
|
||||
"anchor_render_shape": "INVISIBLE",
|
||||
"shaders_active": true,
|
||||
"pbr_enabled": true,
|
||||
"pbr_applied": true,
|
||||
"anchor_pbr_opaque": false,
|
||||
"water_pbr_opaque": false,
|
||||
"wood_pbr_opaque": true,
|
||||
"anchor_pbr_transparent": false,
|
||||
"water_pbr_transparent": true,
|
||||
"glass_pbr_transparent": true
|
||||
},
|
||||
"vulkan_video_uploads": 1625
|
||||
},
|
||||
{
|
||||
"client": "client-b",
|
||||
"model": {
|
||||
"anchor_model_quads": 0,
|
||||
"anchor_render_shape": "INVISIBLE",
|
||||
"shaders_active": true,
|
||||
"pbr_enabled": true,
|
||||
"pbr_applied": true,
|
||||
"anchor_pbr_opaque": false,
|
||||
"water_pbr_opaque": false,
|
||||
"wood_pbr_opaque": true,
|
||||
"anchor_pbr_transparent": false,
|
||||
"water_pbr_transparent": true,
|
||||
"glass_pbr_transparent": true
|
||||
},
|
||||
"vulkan_video_uploads": 1448
|
||||
}
|
||||
],
|
||||
"before": {
|
||||
"version": "beta.297",
|
||||
"clients": [
|
||||
{
|
||||
"client": "client-a",
|
||||
"anchor_model_quads": 30,
|
||||
"anchor_render_shape": "INVISIBLE",
|
||||
"shaders_active": true,
|
||||
"pbr_enabled": true,
|
||||
"anchor_pbr_opaque": true,
|
||||
"water_pbr_opaque": false,
|
||||
"wood_pbr_opaque": true,
|
||||
"anchor_pbr_transparent": false,
|
||||
"water_pbr_transparent": true,
|
||||
"glass_pbr_transparent": true
|
||||
},
|
||||
{
|
||||
"client": "client-b",
|
||||
"anchor_model_quads": 30,
|
||||
"anchor_render_shape": "INVISIBLE",
|
||||
"shaders_active": true,
|
||||
"pbr_enabled": true,
|
||||
"anchor_pbr_opaque": true,
|
||||
"water_pbr_opaque": false,
|
||||
"wood_pbr_opaque": true,
|
||||
"anchor_pbr_transparent": false,
|
||||
"water_pbr_transparent": true,
|
||||
"glass_pbr_transparent": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"check_build_assemble": "passed",
|
||||
"duration": "3m06s",
|
||||
"tasks": 162,
|
||||
"required_gametests": 18,
|
||||
"client_checks_compile": "passed, 37s, 6 tasks; historical PBR graphic suite not rerun"
|
||||
},
|
||||
"limits": [
|
||||
"No new live synchronization or public-source validation",
|
||||
"Graphical wall faces south",
|
||||
"Graphical validation used isolated instances; deployment to Beta and existing Prism is recorded in the delivery receipt",
|
||||
"One automatic fallback after concurrent MoltenVK extraction was rejected and the client relaunched in Vulkan",
|
||||
"Client B stopped after valid samples and captures because the probe could not write its log: No space left on device; not an endurance test"
|
||||
],
|
||||
"graphics_checks": {
|
||||
"lower_left_front_video": "inspected, no 1x1 frame imprint",
|
||||
"lower_left_grazing_video": "inspected, no 1x1 frame imprint",
|
||||
"placement_resize_reader": "passed; placement replayed after inventory arrival",
|
||||
"final_screenshots": [
|
||||
"anchor-model-shaders-front-settled.png",
|
||||
"anchor-model-shaders-side-settled.png"
|
||||
]
|
||||
},
|
||||
"delivery": {
|
||||
"date": "2026-10-27",
|
||||
"receipt": "release-beta298.md",
|
||||
"source_tag": "fb8705369e3834c66d965108d609ec704b584ec2",
|
||||
"packwiz_commit": "7fa08625a4fa89391d64b4b4fc0859611ecf0952",
|
||||
"fresh_check_build_assemble": "passed, 2m50s, 158 tasks, 18 required GameTests; identical JAR to final Vulkan validation"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"version": "beta.306",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.306.jar",
|
||||
"sha256": "a58ddb731719b5bcd975ac86acb95941f0f1bfe49cbefab429b01d47e847af60",
|
||||
"generated_json": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 192,
|
||||
"agriculture": 376
|
||||
},
|
||||
"culinary_classes": 44,
|
||||
"server_required": 52,
|
||||
"manual_mill_scenarios": 10,
|
||||
"manual_mill_recipes": 9,
|
||||
"physical_recipes": 80,
|
||||
"device_blueprints": 90,
|
||||
"client_vulkan": {
|
||||
"manual_mill": "build/mill306-client.log",
|
||||
"screenshots": "build/mill306-screenshots",
|
||||
"backend": "Vulkan",
|
||||
"sodium": "0.9.2+mc26.3",
|
||||
"world_seed": 306
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
# CUI-306 — moulin manuel, sans combustible
|
||||
|
||||
> Suite locale : [beta.307 adapte aussi la recette de fabrication, sans
|
||||
> redstone](mill-recipe-beta307.md). Cette page conserve le résultat de beta.306.
|
||||
|
||||
Choix du créateur du 28 octobre 2026 : **manuel, comme le tailleur de pierre**.
|
||||
Préparation **beta.306** sur la même branche `codex/itsalive-26-3`, après
|
||||
beta.305. Travail local, sans publication, installation dans Prism, mise à
|
||||
jour de canal ou déploiement serveur.
|
||||
|
||||
## Résultat jouable
|
||||
|
||||
Le bloc, l'objet, le titre de menu et la catégorie JEI s'appellent désormais
|
||||
**Moulin / Mill**. L'interface propose une entrée et un résultat immédiat.
|
||||
Chaque joueur possède sa propre surface de travail, comme dans un menu de
|
||||
fabrication vanilla.
|
||||
|
||||
L'aperçu ne consomme rien. Prendre le résultat paie **un ingrédient** et donne
|
||||
le rendement prévu par la recette. Par exemple, un blé donne deux farines de
|
||||
blé, y compris au clic droit qui prend le lot entier. Le Maj-clic traite les
|
||||
lots disponibles dans la limite de l'inventaire.
|
||||
Si seul un morceau du dernier lot tient, le surplus payé tombe au sol, selon
|
||||
le comportement natif du tailleur de pierre. Un inventaire plein ne consomme
|
||||
pas d'ingrédient. Les récipients réellement payés sont rendus une seule fois.
|
||||
Fermer le menu ou casser le moulin rend les ingrédients inutilisés ; aucun
|
||||
aperçu n'est sauvegardé ou lâché comme objet.
|
||||
|
||||
Le moulin ne consomme **aucun combustible**, n'attend pas un délai, ne produit
|
||||
pas passivement et n'accorde pas d'XP de cuisson. Il ne demande aucun signal
|
||||
redstone. Les entonnoirs n'ont pas accès à cette fabrication manuelle.
|
||||
|
||||
Le feu et les commandes de combustible disparaissent du menu. Les deux
|
||||
sprites historiques conservent un bref mouvement mécanique descendant puis
|
||||
ascendant, de **12 ticks**, après une prise de résultat. Le son réutilise
|
||||
celui du tailleur de pierre. L'ancien état `lit` sert uniquement à afficher
|
||||
brièvement la face active du bloc, sans chaleur ni combustion.
|
||||
|
||||
Les **neuf recettes**, leurs rendements, les ingrédients reconnus et la
|
||||
recette de fabrication du moulin sont conservés. JEI affiche « Mouture
|
||||
manuelle / Manual milling » au lieu d'une durée. Le champ historique
|
||||
`processing_time` reste accepté dans les données pour garder les recettes
|
||||
compatibles ; il ne pilote plus le moulin.
|
||||
|
||||
## Continuité des objets déjà stockés
|
||||
|
||||
Les identifiants `sanctuary:redstone_mill` du bloc, de l'objet, du menu et du
|
||||
bloc entité restent stables. Les trois anciennes cases gardent leur encodage
|
||||
natif et leur version de stockage 2. La lecture historique de version 1 est
|
||||
conservée. Aucun monde existant n'est ouvert, modifié ou converti par ce lot.
|
||||
|
||||
À la première ouverture d'un ancien moulin, son entrée stockée rejoint la
|
||||
surface de travail du joueur. L'ancien combustible et l'ancien résultat
|
||||
restent visibles dans deux cases **de récupération uniquement**, affichées
|
||||
tant qu'elles contiennent un objet. Ils peuvent être retirés par clic ou
|
||||
Maj-clic. Ils sont aussi conservés à la casse du bloc. Ces cases n'acceptent
|
||||
aucun nouvel apport et disparaissent de l'affichage une fois vidées.
|
||||
|
||||
Les anciens compteurs de chauffe et de progression sont ignorés. Ils ne
|
||||
consomment pas le combustible restant, ne créent pas de résultat et ne
|
||||
remboursent pas un combustible déjà brûlé. Les objets réellement stockés
|
||||
restent récupérables, sans conversion automatique en farine.
|
||||
|
||||
## Vérifications
|
||||
|
||||
Minecraft **26.3**, Fabric API **0.160.5+26.3**, Java 25. Les tests graphiques
|
||||
ciblent uniquement **Vulkan avec Sodium 0.9.2**, dans un monde de développement
|
||||
neuf de graine **306**, ignoré par Git.
|
||||
|
||||
`check build assemblePack` passe en **3 min 11 s**, avec **52 tests serveur
|
||||
requis réussis**, dont les dix scénarios du moulin et les régressions
|
||||
appareils, cuisine, recettes, progression et connaissances alimentaires.
|
||||
|
||||
Les dix nouveaux tests serveur couvrent l'aperçu sans paiement, le clic droit,
|
||||
les prises et Maj-clics natifs, l'inventaire plein ou presque plein, la
|
||||
fermeture/casse, deux joueurs indépendants, les anciens stocks et compteurs, l'absence de
|
||||
combustible et d'accès par entonnoir, ainsi que les récipients payés. La
|
||||
régression des appareils exécute les neuf recettes réelles du moulin.
|
||||
|
||||
Le parcours client Vulkan passe en **1 min 8 s** : vrais paquets
|
||||
d'insertion, prise, Maj-clic et fermeture, mouvement mécanique sans feu,
|
||||
deux cases de travail, noms FR/EN aux échelles GUI 2/3/4 et neuf recettes
|
||||
dans JEI. Une fermeture/réouverture réelle du monde conserve les anciens
|
||||
stocks ; leur extraction rend exactement sept charbons, cinq blés et trois
|
||||
farines, en plus des objets déjà possédés. Les dix captures sont archivées
|
||||
dans `build/mill306-screenshots/` ; journal `build/mill306-client.log`.
|
||||
|
||||
Le JAR contient les **432 JSON culinaires**, **192 JSON d'appareils** et
|
||||
**376 JSON agricoles**, identiques aux générateurs, ainsi que les 44 classes
|
||||
culinaires et les libellés FR/EN. Les trois classes du moulin sont identiques
|
||||
aux fichiers compilés ; les neuf recettes sont présentes. Les **343 PNG
|
||||
importés** restent identiques, dont les 38 fournis ; les 13 PNG de vanille
|
||||
restent en 16 × 16. Les copies packwiz client et serveur sont identiques au
|
||||
JAR contrôlé. Le template conserve les mêmes textures et les notices à jour.
|
||||
|
||||
SHA-256 de `sanctuary-beta.306.jar` :
|
||||
|
||||
```text
|
||||
a58ddb731719b5bcd975ac86acb95941f0f1bfe49cbefab429b01d47e847af60
|
||||
```
|
||||
|
||||
[Reçu de validation](mill-beta306-verification.json), journaux de build et
|
||||
d'archives dans `build/mill306-build.log` et `build/mill306-artifacts.log`.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=mill306,kitchen304,cuisine303,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryMill306ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=mill306 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
La recette de fabrication du moulin et les autres appareils culinaires
|
||||
sont conservés. L'automatisation ne fait pas partie du fonctionnement manuel
|
||||
retenu. Les textures importées, les catégories créatives et les cultures
|
||||
sont conservées.
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"version": "beta.307",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.307.jar",
|
||||
"sha256": "e510871bd515e5b52f2e1b218b593b49ff74ffae320e9b6b665ef1629c53d3d0",
|
||||
"previous_verified_sha256": "a58ddb731719b5bcd975ac86acb95941f0f1bfe49cbefab429b01d47e847af60",
|
||||
"changed_jar_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"data/sanctuary/recipe/redstone_mill.json",
|
||||
"fabric.mod.json",
|
||||
"resourcepacks/template/THIRD_PARTY_NOTICES.md"
|
||||
],
|
||||
"generated_json": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 192,
|
||||
"agriculture": 376
|
||||
},
|
||||
"server_required": 52,
|
||||
"recipe_id": "sanctuary:redstone_mill",
|
||||
"pattern": [
|
||||
"SSS",
|
||||
"GCG",
|
||||
" B "
|
||||
],
|
||||
"ingredients": {
|
||||
"S": "minecraft:smooth_stone_slab",
|
||||
"G": "minecraft:copper_ingot",
|
||||
"C": "minecraft:stonecutter",
|
||||
"B": "minecraft:stick"
|
||||
},
|
||||
"result": {
|
||||
"id": "sanctuary:redstone_mill"
|
||||
},
|
||||
"crafting_category": "misc",
|
||||
"runtime_classes_unchanged": 2745,
|
||||
"pngs_unchanged": 973,
|
||||
"client_server_copies_identical": true,
|
||||
"template_changed_entries": [
|
||||
"THIRD_PARTY_NOTICES.md"
|
||||
],
|
||||
"build_log": "build/mill307-build.log"
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
# CUI-307 — recette du moulin manuel
|
||||
|
||||
Demande du créateur du 28 octobre 2026 : adapter la fabrication au
|
||||
fonctionnement manuel du moulin, **sans redstone**. Préparation **beta.307**
|
||||
sur la même branche `codex/itsalive-26-3`, après beta.306. Travail local,
|
||||
sans publication, installation, mise à jour de canal ou déploiement serveur.
|
||||
|
||||
## Fabrication
|
||||
|
||||
La table de fabrication donne **un moulin** avec :
|
||||
|
||||
| Dalle de pierre lisse | Dalle de pierre lisse | Dalle de pierre lisse |
|
||||
| --- | --- | --- |
|
||||
| Lingot de cuivre | Tailleur de pierre | Lingot de cuivre |
|
||||
| Vide | Bâton | Vide |
|
||||
|
||||
Le bâton représente la poignée du mécanisme manuel. Il remplace la poudre
|
||||
de redstone de la recette précédente ; les autres ingrédients et leurs
|
||||
quantités sont conservés. Aucun de ces composants ne demande de redstone.
|
||||
La catégorie du livre de fabrication devient **Divers / Miscellaneous** au
|
||||
lieu de Redstone ; les objets et leurs noms FR/EN restent ceux du jeu.
|
||||
Les onglets créatifs ne changent pas.
|
||||
|
||||
La recette garde l'identifiant **`sanctuary:redstone_mill`** et sa découverte
|
||||
dans la collection des appareils culinaires. Les moulins déjà placés, les
|
||||
inventaires et les neuf recettes de mouture gardent le comportement manuel
|
||||
de beta.306. Ce lot change uniquement les données de fabrication, sans
|
||||
nouveau Java de jeu, nouveau PNG ou modification de sauvegarde.
|
||||
|
||||
La source adaptée est `scripts/data/kitchen-crafting.json`, distribuée par
|
||||
`scripts/kitchen_resources.py`. Les reçus historiques 26.2 et beta.304 restent
|
||||
inchangés ; cette page documente l'évolution de la copie Sanctuary.
|
||||
|
||||
## Vérifications
|
||||
|
||||
`check build assemblePack` passe en **2 min 53 s**, avec **52 tests serveur
|
||||
requis réussis**. La nouvelle grille est chargée et produit un moulin sans
|
||||
résultat concurrent ; la découverte du plan et les transformations manuelles
|
||||
restent vérifiées.
|
||||
|
||||
Minecraft **26.3**, Fabric API **0.160.5+26.3**, Java 25. Les tests serveur
|
||||
existants chargent les dix fabrications d'appareils/stockage, font correspondre
|
||||
leurs grilles réelles et vérifient l'absence de résultat concurrent. Ils
|
||||
couvrent également la découverte du plan et les neuf recettes du moulin
|
||||
manuel. Le code de menu et les textures sont inchangés ; le parcours Vulkan
|
||||
de beta.306 reste le reçu graphique de référence et n'est pas présenté comme
|
||||
un nouvel essai de beta.307.
|
||||
|
||||
Les **1 000 JSON générés** correspondent aux sources : 432 culinaires,
|
||||
192 d'appareils et 376 agricoles. Comparé au JAR vérifié de beta.306, seuls
|
||||
la recette du moulin, la version du manifeste et les deux copies des notices
|
||||
changent. Les **2 745 classes et 973 PNG** de l'archive sont identiques octet
|
||||
pour octet. Les copies du JAR packwiz client et serveur sont identiques au
|
||||
binaire contrôlé ; le template ne change que par sa notice.
|
||||
|
||||
SHA-256 de `sanctuary-beta.307.jar` :
|
||||
|
||||
```text
|
||||
e510871bd515e5b52f2e1b218b593b49ff74ffae320e9b6b665ef1629c53d3d0
|
||||
```
|
||||
|
||||
[Reçu de validation](mill-recipe-beta307-verification.json). Journaux dans
|
||||
`build/mill307-build.log` et `build/mill307-artifacts.log`.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=mill306,kitchen304,cuisine303,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
```
|
||||
@@ -3,6 +3,12 @@
|
||||
16 septembre 2026. Cadrage corrigé après clarification du créateur.
|
||||
Aucun portage, adaptateur ou changement de sauvegarde livré ici.
|
||||
|
||||
**Suite du 28 octobre 2026 :** le créateur ouvre la
|
||||
[refonte 26.3 d'It's Alive !](itsalive-26-3.md). Le premier lot importe les
|
||||
textures ; les mécaniques restent en tickets. La cuisinière four + table de
|
||||
fabrication et son lien avec le Fourneau seront examinés dans IA-08. Le texte
|
||||
ci-dessous conserve les décisions et constats du cadrage du 16 septembre.
|
||||
|
||||
## Direction retenue
|
||||
|
||||
Le créateur souhaite intégrer **tout It's Alive dans Sanctuary**, mais
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Sanctuary beta.298
|
||||
|
||||
Le petit cadre brillant dans le coin inférieur gauche de la vidéo est corrigé
|
||||
avec les shaders Sanctuary activés. Le modèle invisible de l'ancre ne fournit
|
||||
plus de faces à la passe des normales PBR. Le grand cadre conserve sa taille
|
||||
et son épaisseur ; le lecteur et le redimensionnement gardent leurs gestes.
|
||||
|
||||
Les écrans déjà posés conservent leur source, leurs dimensions et leurs données.
|
||||
Aucun bloc à remplacer ni conversion de sauvegarde. Media passe en 0.3.2 ;
|
||||
Minecraft 26.3, Fabric et Dream Displays 1.10.4 restent inchangés.
|
||||
|
||||
Correctif vérifié à deux clients Vulkan avec PBR actif, vidéo en mouvement et
|
||||
gros plans de face et en vue rasante. Le détail des contrôles et leurs limites
|
||||
est dans `docs/media-anchor-normals-beta298.md` ; pas de nouvelle validation
|
||||
des sources publiques ni de l'endurance du lecteur.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Sanctuary beta.315 — cuisine et agriculture It's Alive
|
||||
|
||||
La refonte locale beta.299 à beta.315 rejoint le pack Sanctuary 26.3.
|
||||
|
||||
- Vingt cultures, citronnier, olivier, caféier et vanille grimpante vers le haut.
|
||||
- Recettes pour les nouvelles récoltes, poêle et piles de quatre, cuisinière
|
||||
automatique, chaudron remué au bâton et recettes répétées, fermentation.
|
||||
- Moulin manuel, sans combustible. Les blocs de tomates, raisins et olives
|
||||
se piétinent en huit paliers ou s'écrasent au piston ; super piston 27 blocs.
|
||||
- Préparation de saucisson à l'établi puis séchage sur quatre crochets,
|
||||
cinq minutes de jeu au sec ; pluie et immersion suspendent la progression.
|
||||
- Fromage posé en sept parts et saucisson posé en quatre tranches.
|
||||
- Vin jeune après trois minutes de fermentation ; vin de table, réserve et
|
||||
grand cru après un, trois et sept jours réels de garde au tonneau, jeu fermé.
|
||||
- Nourriture dans Nourriture et boissons, semences dans Naturel, sans nouvel onglet.
|
||||
|
||||
Les identifiants historiques de cette refonte restent stables. Les nouveaux
|
||||
blocs conservent leur état par les mécanismes natifs ; le vin a un horodatage
|
||||
serveur. Aucune régénération ou expansion de monde n'est effectuée.
|
||||
Les cultures sont jouables ; leur apparition naturelle et la répartition
|
||||
climatique ne sont pas introduites par cette livraison. Chercher les objets
|
||||
dans les onglets vanilla et leurs recettes dans JEI pour les essais.
|
||||
|
||||
Le fromage et le saucisson se mangent après la pose ; casser un aliment entamé
|
||||
ne rend pas un nouvel aliment entier. Une sortie de tonneau extraite par un
|
||||
entonnoir garde sa qualité : le verrouiller pour laisser vieillir le vin.
|
||||
|
||||
Minecraft 26.3, Fabric Loader 0.19.5 et dépendances du pack conservés.
|
||||
Les contrôles graphiques ciblent Vulkan avec Sodium. Les preuves détaillées
|
||||
et limites figurent dans les tickets beta.299–315 et le reçu de livraison.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Sanctuary beta.318
|
||||
|
||||
Minecraft 26.3 · Fabric Loader 0.19.5 · Java 25.
|
||||
|
||||
Cette mise à jour réunit les ajouts beta.316 à beta.318 :
|
||||
|
||||
- Récoltes sauvages et premières semences en survie selon les biomes des neuf
|
||||
îles : cultures, arbres fruitiers, café et vanille. Les terrains déjà explorés
|
||||
gardent leurs blocs et disposent aussi de trouvailles adaptées au climat.
|
||||
- Séchoir : viandes, cabillaud et saumon crus, raisins, tomates, piment, herbes,
|
||||
fruits et vanille. Durées propres à chaque aliment, quatre crochets indépendants,
|
||||
pause sous la pluie et protection par un toit.
|
||||
- Dix-huit nouvelles recettes aux produits séchés : pains, brioche, biscuits,
|
||||
sandwichs, plats au chaudron, salade, dessert, mélange de fruits et épices.
|
||||
Le saucisson pimenté se prépare, sèche cinq minutes et se partage en quatre tranches.
|
||||
- Manger épicé provoque deux petits coups visuels et sonores, sans dégâts
|
||||
ni recul. La réaction fonctionne aussi sur les tranches de saucisson pimenté.
|
||||
|
||||
Les nouveaux objets restent dans Nourriture et boissons. Les herbes et le piment
|
||||
séchés, la poudre de piment et l'assaisonnement servent aussi dans la cuisine
|
||||
existante. Les bols et fioles des nouveaux plats reviennent à la consommation.
|
||||
|
||||
Les anciennes recettes, appareils et sauvegardes restent compatibles. Aucun
|
||||
repeuplement des anciens chunks ni changement de format de sauvegarde : les
|
||||
plantes sauvages apparaissent à la première décoration des nouveaux chunks.
|
||||
Les icônes supplémentaires réutilisent les sprites existants recolorés.
|
||||
|
||||
[Guide des récoltes](https://git.botsu.net/koka/sanctuary-beta/src/tag/beta.318/docs/wiki/alimentation/README.md) ·
|
||||
[Recettes, quantités et durées](https://git.botsu.net/koka/sanctuary-beta/src/tag/beta.318/docs/wiki/alimentation/sechoir.md).
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"version": "beta.308",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.308.jar",
|
||||
"sha256": "103e08039063633829b4f2059f25e32920cdb7e4df6d1e02512f40a35c0390eb",
|
||||
"previous_verified_sha256": "e510871bd515e5b52f2e1b218b593b49ff74ffae320e9b6b665ef1629c53d3d0",
|
||||
"changed_jar_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json",
|
||||
"fabric.mod.json",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/KitchenOvenBlockEntity$1.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/KitchenOvenBlockEntity$2.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/KitchenOvenBlockEntity.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/screen/KitchenOvenScreen.class",
|
||||
"fr/koka/sanctuary/cuisine/menu/KitchenOvenMenu$FuelSlot.class",
|
||||
"fr/koka/sanctuary/cuisine/menu/KitchenOvenMenu$IngredientSlot.class",
|
||||
"fr/koka/sanctuary/cuisine/menu/KitchenOvenMenu$OutputSlot.class",
|
||||
"fr/koka/sanctuary/cuisine/menu/KitchenOvenMenu.class",
|
||||
"resourcepacks/template/THIRD_PARTY_NOTICES.md"
|
||||
],
|
||||
"added_jar_entries": [
|
||||
"fr/koka/sanctuary/cuisine/menu/KitchenOvenMenu$1.class"
|
||||
],
|
||||
"generated_json": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 192,
|
||||
"agriculture": 376
|
||||
},
|
||||
"culinary_source_classes": 44,
|
||||
"culinary_compiled_classes_verified": 62,
|
||||
"oven_recipes": 13,
|
||||
"unchanged_pngs": 1108,
|
||||
"server_required": 63,
|
||||
"oven_scenarios": 11,
|
||||
"client_vulkan": {
|
||||
"log": "build/oven308-client.log",
|
||||
"screenshots": "build/oven308-screenshots",
|
||||
"screenshot_count": 12,
|
||||
"world_seed": 308,
|
||||
"sodium": "0.9.2+mc26.3"
|
||||
},
|
||||
"client_server_copies_identical": true,
|
||||
"template_changed_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json"
|
||||
],
|
||||
"build_log": "build/oven308-build.log"
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
# CUI-308 / IA-08 — cuisinière, grille et cuisson automatique
|
||||
|
||||
> Correction locale suivante : [beta.309 retire l'aperçu, garde une seule sortie
|
||||
> et bloque le remplissage de la grille par entonnoir](oven-single-output-beta309.md).
|
||||
> Cette page conserve le résultat historique de beta.308.
|
||||
|
||||
Demande du créateur du 28 octobre 2026, précisée : **démarrage automatique
|
||||
comme un four**, et **recettes de cuisine uniquement**. Préparation locale
|
||||
**beta.308**, après beta.307, sur la même branche `codex/itsalive-26-3`.
|
||||
Aucune publication, installation dans Prism, mise à jour de canal ou de serveur.
|
||||
|
||||
## Cycle jouable
|
||||
|
||||
La cuisinière réunit une **grille persistante 3 × 3**, un combustible natif et
|
||||
une sortie pour les plats terminés. L'interface montre aussi un **aperçu
|
||||
« À cuire / To cook »**, séparé de cette sortie. Cet aperçu indique la recette
|
||||
reconnue mais ne peut être retiré, déplacé, cloné ou collecté par double-clic.
|
||||
Ses aides FR/EN expliquent le paiement en fin de cuisson.
|
||||
|
||||
La cuisson commence automatiquement lorsqu'une recette valide, de la place
|
||||
en sortie et une source de combustible sont présents. Les ingrédients restent
|
||||
visibles et disponibles dans la grille pendant **toute la durée de cuisson**.
|
||||
À l'échéance seulement, la cuisinière consomme les quantités d'une recette,
|
||||
rend les récipients réellement payés et range le plat dans la sortie.
|
||||
Les ingrédients restants permettent un nouveau cycle complet, sans bouton.
|
||||
Trois pâtes donnent donc trois pains sur trois durées de 240 ticks.
|
||||
|
||||
Fermer le menu laisse la grille dans le bloc et la cuisson continue tant que
|
||||
le chunk est chargé. Retirer, remplacer, déplacer ou modifier la quantité ou
|
||||
les composants d'un ingrédient **annule l'avancement en cours**. Cela couvre
|
||||
les Maj-clics partiels, les autres joueurs, les entonnoirs et les changements
|
||||
de pile effectués directement. Si la nouvelle grille reste une recette valide,
|
||||
un nouveau cycle démarre automatiquement à zéro ; sinon elle attend.
|
||||
Remettre immédiatement le même ingrédient ne rend pas l'ancien avancement.
|
||||
|
||||
Prendre un plat déjà cuit ou ajouter du combustible n'édite pas la grille et
|
||||
ne remet pas sa cuisson à zéro. Une sortie pleine ou incompatible empêche
|
||||
l'allumage d'un nouveau combustible et remet le cycle bloqué à zéro, sans
|
||||
consommer les ingrédients. Libérer la sortie permet un nouveau cycle complet.
|
||||
Le Maj-clic utilise les mêmes règles de combustible côté client et serveur.
|
||||
|
||||
Le combustible suit les composants et durées du four Minecraft **26.3**.
|
||||
Un charbon est payé à l'allumage, comme dans un four ; retirer un ingrédient
|
||||
ne rembourse pas le combustible déjà brûlé. La chaleur restante continue de
|
||||
se dépenser. Les combustibles encore en pile restent disponibles. Une pause
|
||||
par manque de combustible conserve l'avancement tant que la grille est intacte.
|
||||
|
||||
Les **treize recettes de cuisinière**, leurs ingrédients, résultats, identifiants
|
||||
et durées de 240 à 380 ticks restent en place. La cuisinière ne fabrique pas
|
||||
les outils et objets de l'établi ordinaire et ne fond pas les minerais.
|
||||
Les 92 recettes du catalogue culinaire et les autres appareils restent dans
|
||||
leurs circuits existants. La fabrication de la cuisinière conserve trois
|
||||
lingots de fer, quatre blocs de briques, une table de fabrication et un four.
|
||||
|
||||
## Continuité des sauvegardes
|
||||
|
||||
L'identifiant `sanctuary:kitchen_oven` reste stable pour le bloc, l'objet, le
|
||||
menu et le bloc entité. Les **onze cases sauvegardées** gardent leur encodage :
|
||||
0–8 pour la grille, 9 pour le combustible, 10 pour les plats cuits. L'aperçu
|
||||
est une case virtuelle du menu, jamais une douzième case de stockage.
|
||||
|
||||
Les champs natifs existants `CookingProgress`, `CookingDuration`,
|
||||
`ActiveRecipe`, `BurnTime` et `BurnDuration` sont conservés. La surveillance de
|
||||
la grille est reconstruite depuis les objets chargés, sans nouveau champ
|
||||
de sauvegarde ni conversion automatique. Une cuisson en cours reprend avec
|
||||
ses ingrédients et son avancement. Le temps écoulé hors chargement ne produit
|
||||
aucun repas supplémentaire, comme pour une cuisson fondée sur les ticks du bloc.
|
||||
|
||||
La casse rend uniquement le vrai inventaire restant, une seule fois. Elle
|
||||
ne transforme pas l'aperçu en plat. Aucun monde personnel n'est ouvert ou
|
||||
modifié par cette préparation ; les tests utilisent de nouveaux mondes ignorés.
|
||||
|
||||
## Vérifications
|
||||
|
||||
`check build assemblePack` passe en **3 min 6 s**, avec **63 tests serveur
|
||||
requis réussis**, dont onze nouveaux scénarios de cuisinière. Les régressions
|
||||
appareils, cuisine, moulin, recettes, progression et connaissances alimentaires
|
||||
sont incluses.
|
||||
|
||||
Minecraft **26.3**, Fabric API **0.160.5+26.3**, Java 25. Les onze nouveaux tests
|
||||
serveur couvrent les échéances et lots successifs, la fermeture, l'annulation
|
||||
près de la fin, le Maj-clic partiel, les changements directs de pile, la sortie
|
||||
bloquée, l'aperçu non retirable, le rechargement natif d'une cuisson en cours,
|
||||
les récipients, le périmètre culinaire et la casse sans duplication.
|
||||
Les régressions des appareils exécutent également les treize recettes réelles.
|
||||
|
||||
Le parcours **Vulkan avec Sodium 0.9.2** passe en **1 min 41 s**. Il utilise
|
||||
de vrais paquets d'insertion et de retrait, vérifie la prédiction du Maj-clic
|
||||
du combustible, l'aperçu non retirable et les écrans FR/EN aux échelles GUI
|
||||
2/3/4. Retirer la pâte annule une cuisson aux trois quarts sans la consommer.
|
||||
Après remise en place, les trois cycles se terminent avec le menu fermé et
|
||||
produisent exactement trois pains. Une fermeture/réouverture réelle du monde
|
||||
conserve ces trois pains, récupérés ensuite par un vrai Maj-clic. Les treize
|
||||
recettes de cuisinière restent présentes dans JEI. Les douze captures sont
|
||||
archivées dans `build/oven308-screenshots/`, journal `build/oven308-client.log`.
|
||||
|
||||
Les **1 000 JSON générés** sont identiques aux sources et au JAR beta.307 :
|
||||
432 culinaires, 192 d'appareils et 376 agricoles. Les 44 sources culinaires
|
||||
et leurs 62 classes compilées sont présentes et contrôlées. L'archive ne
|
||||
change que les classes de cuisinière, les libellés FR/EN, la version et les
|
||||
notices ; le nouveau slot d'aperçu ajoute une classe interne de menu.
|
||||
Les **1 108 PNG** de l'archive sont identiques à beta.307. Les copies du JAR
|
||||
packwiz client et serveur sont identiques au binaire vérifié ; le template
|
||||
ne change que par les notices et les deux fichiers de libellés.
|
||||
|
||||
SHA-256 de `sanctuary-beta.308.jar` :
|
||||
|
||||
```text
|
||||
103e08039063633829b4f2059f25e32920cdb7e4df6d1e02512f40a35c0390eb
|
||||
```
|
||||
|
||||
[Reçu de validation](oven-beta308-verification.json). Journaux complémentaires
|
||||
dans `build/oven308-build.log` et `build/oven308-artifacts.log`.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=oven308,mill306,kitchen304,cuisine303,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryOven308ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=oven308 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
Le parcours graphique cible uniquement **Vulkan avec Sodium 0.9.2**, dans un
|
||||
monde de développement neuf de graine **308**. Aucun PNG n'est modifié :
|
||||
l'interface assemble les panneaux et sprites vanilla de l'établi et du four.
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"version": "beta.309",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.309.jar",
|
||||
"sha256": "32ca2d855f71ac29af0a62ba9f4e463e3d11f172fefe7ecdbb1afad086b63ff9",
|
||||
"previous_verified_sha256": "103e08039063633829b4f2059f25e32920cdb7e4df6d1e02512f40a35c0390eb",
|
||||
"changed_jar_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json",
|
||||
"fabric.mod.json",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/KitchenOvenBlockEntity$1.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/KitchenOvenBlockEntity$2.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/KitchenOvenBlockEntity.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/screen/KitchenOvenScreen.class",
|
||||
"fr/koka/sanctuary/cuisine/menu/KitchenOvenMenu$FuelSlot.class",
|
||||
"fr/koka/sanctuary/cuisine/menu/KitchenOvenMenu$IngredientSlot.class",
|
||||
"fr/koka/sanctuary/cuisine/menu/KitchenOvenMenu$OutputSlot.class",
|
||||
"fr/koka/sanctuary/cuisine/menu/KitchenOvenMenu.class",
|
||||
"resourcepacks/template/THIRD_PARTY_NOTICES.md"
|
||||
],
|
||||
"added_jar_entries": [],
|
||||
"removed_jar_entries": [
|
||||
"fr/koka/sanctuary/cuisine/menu/KitchenOvenMenu$1.class"
|
||||
],
|
||||
"generated_json": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 192,
|
||||
"agriculture": 376
|
||||
},
|
||||
"culinary_source_classes": 44,
|
||||
"culinary_compiled_classes_verified": 61,
|
||||
"oven_recipes": 13,
|
||||
"machine_slots": 11,
|
||||
"cooked_output_slots": 1,
|
||||
"ingredient_hopper_insertion": false,
|
||||
"cooked_hopper_extraction": true,
|
||||
"unchanged_pngs": 1108,
|
||||
"server_required": 63,
|
||||
"oven_scenarios": 11,
|
||||
"client_vulkan": {
|
||||
"log": "build/oven309-client.log",
|
||||
"screenshots": "build/oven309-screenshots",
|
||||
"screenshot_count": 11,
|
||||
"world_seed": 309,
|
||||
"sodium": "0.9.2+mc26.3"
|
||||
},
|
||||
"client_server_copies_identical": true,
|
||||
"template_changed_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json"
|
||||
],
|
||||
"build_log": "build/oven309-build.log"
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
# CUI-309 — une seule sortie pour la cuisinière
|
||||
|
||||
Correction demandée par le créateur le 28 octobre 2026 : **une seule sortie,
|
||||
comme un four Minecraft**, et abandon du remplissage automatique de la grille.
|
||||
Préparation locale **beta.309**, après beta.308, sur `codex/itsalive-26-3`.
|
||||
Aucune publication, installation dans Prism ou mise à jour de serveur/canal.
|
||||
|
||||
## Résultat
|
||||
|
||||
La cuisinière présente sa grille 3 × 3, le combustible et **une seule case de
|
||||
sortie**, centrée face à la flèche de cuisson. La case d'aperçu « À cuire »
|
||||
ajoutée en beta.308 est retirée, ainsi que ses aides et sa flèche supplémentaire.
|
||||
La sortie reste vide jusqu'à la fin d'une cuisson réelle.
|
||||
À la demande suivante du créateur, l'icône de feu (vide et animée) est décalée
|
||||
de **2 pixels GUI vers la gauche et de 2 pixels vers le haut**, sans déplacer
|
||||
la case de combustible.
|
||||
|
||||
Le cycle confirmé précédemment reste en place : démarrage automatique avec
|
||||
une recette de cuisine et du combustible, ingrédients conservés dans la grille
|
||||
jusqu'à l'échéance, paiement d'un lot en fin de cuisson, puis enchaînement des
|
||||
lots disponibles. Modifier la grille annule l'avancement ; fermer le menu
|
||||
laisse la cuisson continuer tant que le chunk est chargé. Les treize recettes
|
||||
de cuisinière et leurs durées sont conservées.
|
||||
|
||||
La grille se remplit **à la main**. Aucun ingrédient n'est insérable par
|
||||
entonnoir, quelle que soit la face. Un entonnoir placé dessous récupère
|
||||
uniquement les plats déjà cuits dans l'unique sortie ; il ne prélève ni les
|
||||
ingrédients ni le combustible. L'accès latéral au combustible déjà présent
|
||||
reste disponible. Aucune grille d'automatisation spéciale n'est ajoutée.
|
||||
|
||||
Les identifiants et les onze cases sauvegardées restent inchangés : neuf
|
||||
ingrédients, combustible en case 9 et plats cuits en case 10. L'ancienne case
|
||||
d'aperçu n'était pas sauvegardée. Retirer cette case virtuelle ne convertit ni
|
||||
ne supprime aucun objet. Les compteurs de cuisson et de combustion gardent
|
||||
leur encodage. Aucun monde personnel n'est ouvert ou modifié.
|
||||
|
||||
## Vérifications
|
||||
|
||||
`check build assemblePack` passe en **3 min 32 s**, avec **63 tests serveur
|
||||
requis réussis**, dont les onze scénarios de cuisinière adaptés. Le parcours
|
||||
Vulkan repasse après le déplacement du feu ; les contrôles portent sur le
|
||||
binaire final de cette préparation.
|
||||
|
||||
Minecraft **26.3**, Fabric API **0.160.5+26.3**, Java 25. La régression serveur
|
||||
de la cuisinière vérifie maintenant un véritable entonnoir d'alimentation
|
||||
bloqué au-dessus et un véritable entonnoir d'extraction dessous : aucune sortie
|
||||
avant l'échéance, puis exactement un pain extrait et deux pâtes encore en
|
||||
grille après le premier cycle. Les autres scénarios de beta.308 restent actifs.
|
||||
|
||||
Le parcours client est adapté à l'unique sortie, aux onze cases réelles et
|
||||
aux indices natifs d'inventaire retrouvés. Les tests graphiques restent
|
||||
exclusivement **Vulkan avec Sodium 0.9.2**, dans un nouveau monde de graine
|
||||
**309**. Aucun PNG n'est modifié ; l'interface utilise les panneaux vanilla.
|
||||
Les classes de régression et leurs options gardent leur nom `Oven308`.
|
||||
|
||||
Le parcours Vulkan passe en **1 min 47 s**, après le décalage du feu. Les
|
||||
écrans FR/EN aux échelles GUI 2/3/4 présentent une seule sortie et aucune case
|
||||
virtuelle. De vrais paquets d'inventaire vérifient le remplissage manuel, le
|
||||
combustible, l'annulation d'une cuisson aux trois quarts et sa reprise depuis
|
||||
zéro. Trois cycles se terminent avec le menu fermé, donnant exactement trois
|
||||
pains dans l'unique sortie. Une fermeture/réouverture réelle conserve ces
|
||||
trois pains et leur retrait par Maj-clic. Les treize recettes restent dans JEI.
|
||||
Les onze captures sont dans `build/oven309-screenshots/`, avec l'icône vide
|
||||
et la flamme animée décalées ensemble. Journal `build/oven309-client.log`.
|
||||
|
||||
Les **1 000 JSON générés** et les treize recettes de cuisinière sont inchangés.
|
||||
Les 44 sources culinaires et leurs 61 classes compilées sont contrôlées dans
|
||||
le JAR. La classe interne de l'ancien aperçu a disparu ; seules les classes
|
||||
de cuisinière, la version, les notices et le retrait des trois libellés
|
||||
d'aperçu diffèrent de beta.308. Les **1 108 PNG** sont identiques octet pour
|
||||
octet. Les copies packwiz client et serveur sont identiques au JAR vérifié ;
|
||||
le template ne change que par ses notices et fichiers de libellés FR/EN.
|
||||
|
||||
SHA-256 de `sanctuary-beta.309.jar` :
|
||||
|
||||
```text
|
||||
32ca2d855f71ac29af0a62ba9f4e463e3d11f172fefe7ecdbb1afad086b63ff9
|
||||
```
|
||||
|
||||
[Reçu de validation](oven-single-output-beta309-verification.json).
|
||||
Journaux de build et d'archives dans `build/oven309-build.log` et
|
||||
`build/oven309-artifacts.log`.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=oven308,mill306,kitchen304,cuisine303,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryOven308ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=oven308 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
@@ -8,6 +8,14 @@ La [procédure de reprise et de publication complète](updates.md) relie aussi
|
||||
le serveur dédié, le téléchargement du site et l'instance locale. Les reçus
|
||||
des anciennes versions ci-dessous restent historiques.
|
||||
|
||||
## beta.315 — cuisine et agriculture publiées
|
||||
|
||||
[Reçu de livraison](release-beta315.md) : refonte It's Alive beta.299–315,
|
||||
release et canal client/serveur, CT 350, téléchargement Web et même instance
|
||||
Prism mis à jour. 187 tests serveur, neuf JAR clients/cinq serveur, deux passages
|
||||
isolés et personnels identiques. 211 fichiers serveur et 936 fichiers personnels
|
||||
protégés conservés. [Export de textures](itsalive-textures-export-beta315.md).
|
||||
|
||||
## beta.293 — livraison complète publiée
|
||||
|
||||
[Reçu de livraison](release-beta293.md) : Happy Creepers de la branche dédiée,
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"version": "beta.310",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.310.jar",
|
||||
"sha256": "4a2278d987b031b343a6d66694da50db0aeb408bb77785ce8c02ae0ed9174b26",
|
||||
"previous_verified_sha256": "32ca2d855f71ac29af0a62ba9f4e463e3d11f172fefe7ecdbb1afad086b63ff9",
|
||||
"changed_jar_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/blockstates/frying_pan.json",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json",
|
||||
"data/sanctuary/loot_table/blocks/frying_pan.json",
|
||||
"fabric.mod.json",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/FryingPanBlock.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/AbstractTimedCulinaryBlockEntity.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/entity/FryingPanBlockEntity.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/FryingPanHud.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/renderer/FryingPanBlockEntityRenderer.class",
|
||||
"fr/koka/sanctuary/cuisine/culinary/client/renderer/state/FryingPanRenderState.class",
|
||||
"resourcepacks/template/THIRD_PARTY_NOTICES.md"
|
||||
],
|
||||
"added_jar_entries": [
|
||||
"assets/sanctuary/models/block/frying_pan_layer_2.json",
|
||||
"assets/sanctuary/models/block/frying_pan_layer_3.json",
|
||||
"assets/sanctuary/models/block/frying_pan_layer_4.json"
|
||||
],
|
||||
"removed_jar_entries": [],
|
||||
"generated_json": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 195,
|
||||
"agriculture": 376
|
||||
},
|
||||
"culinary_source_classes": 44,
|
||||
"culinary_compiled_classes_verified": 61,
|
||||
"pan_recipes_unchanged": 26,
|
||||
"physical_pan_counts": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"heights_pixels": [
|
||||
4,
|
||||
8,
|
||||
12,
|
||||
16
|
||||
],
|
||||
"pan_output_slots": 1,
|
||||
"shared_recipe_per_stack": true,
|
||||
"native_inventory_size": 17,
|
||||
"original_indices_preserved": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"unchanged_pngs": 1108,
|
||||
"server_required": 76,
|
||||
"pan_scenarios": 13,
|
||||
"client_vulkan": {
|
||||
"log": "build/pan310-client.log",
|
||||
"screenshots": "build/pan310-screenshots",
|
||||
"screenshot_count": 13,
|
||||
"world_seed": 310,
|
||||
"sodium": "0.9.2+mc26.3"
|
||||
},
|
||||
"client_server_copies_identical": true,
|
||||
"template_changed_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/blockstates/frying_pan.json",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json"
|
||||
],
|
||||
"template_added_entries": [
|
||||
"assets/sanctuary/models/block/frying_pan_layer_2.json",
|
||||
"assets/sanctuary/models/block/frying_pan_layer_3.json",
|
||||
"assets/sanctuary/models/block/frying_pan_layer_4.json"
|
||||
],
|
||||
"build_log": "build/pan310-build.log"
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
# CUI-310 / IA-07 — poêles en ligne et piles de quatre
|
||||
|
||||
Choix du créateur le 28 octobre 2026 : **une recette commune par pile**.
|
||||
Le modèle actuel fait quatre pixels de haut ; quatre poêles tiennent donc
|
||||
dans un bloc. Chaque emplacement reste chauffé par-dessous. Des piles côte
|
||||
à côte peuvent préparer des plats différents. Préparation locale **beta.310**
|
||||
sur `codex/itsalive-26-3`, sans publication, installation ou déploiement.
|
||||
|
||||
## Contrat de stockage et de continuité
|
||||
|
||||
Ce lot ajoute une capacité réelle pour les ingrédients non empilables :
|
||||
quatre omelettes doivent réellement payer quatre seaux de lait, et non créer
|
||||
quatre plats depuis un seul seau. Les quatre positions de préparation restent
|
||||
communes à toute la pile, avec une seule recette et une seule sortie.
|
||||
|
||||
L'identifiant `sanctuary:frying_pan` du bloc, de l'objet et du bloc entité
|
||||
reste stable. Le nouvel état natif `pans` vaut 1 à 4 ; une ancienne sauvegarde
|
||||
sans cette propriété se charge avec **une poêle**. Aucune ancienne colonne
|
||||
n'est supprimée ou regroupée automatiquement.
|
||||
|
||||
Les indices d'inventaire **0 à 4 restent inchangés** : ingrédients 0–3,
|
||||
résultat 4. Douze indices de réserve sont ajoutés au même inventaire natif :
|
||||
|
||||
| Position commune | Case historique | Réserves pour les poêles 2–4 |
|
||||
| --- | ---: | --- |
|
||||
| Ingrédient 1 | 0 | 5, 6, 7 |
|
||||
| Ingrédient 2 | 1 | 8, 9, 10 |
|
||||
| Ingrédient 3 | 2 | 11, 12, 13 |
|
||||
| Ingrédient 4 | 3 | 14, 15, 16 |
|
||||
| Plat terminé | 4 | Aucune autre sortie |
|
||||
|
||||
La lecture des anciennes cinq cases conserve chaque objet à son indice,
|
||||
avec douze réserves vides. Les champs de cuisson et d'assemblage sont gardés.
|
||||
Le champ additionnel `CookingPanCount` mémorise le nombre de poêles associé
|
||||
à l'avancement, avec 1 par défaut pour l'ancien format. Un ajout de poêle
|
||||
pendant la cuisson remet l'assemblage et l'avancement à zéro, y compris si
|
||||
la pile est sauvegardée avant le tick suivant. L'indication des récipients à
|
||||
récupérer est dérivée des objets et synchronisée sans nouvelle case de sortie.
|
||||
Les piles physiques respectent toujours la taille maximale native des objets :
|
||||
quatre seaux de lait restent quatre piles d'un seau, sans pile illégale.
|
||||
Les quantités agrégées servent uniquement à la résolution de la recette.
|
||||
La quantité réellement consommée est prélevée dans les cases physiques et
|
||||
les récipients payés sont rendus une seule fois.
|
||||
|
||||
Ce contrat décrit une évolution **vers beta.310**. Pour revenir à une version
|
||||
antérieure, il faut d'abord vider les réserves et récupérer les poêles de la
|
||||
pile : une ancienne version ne connaît ni les nouveaux indices ni leur compte.
|
||||
Aucun monde personnel n'est ouvert ou converti par cette préparation ; la
|
||||
continuité est vérifiée dans des sauvegardes et mondes de développement neufs.
|
||||
|
||||
## Utilisation
|
||||
|
||||
- Poser une poêle, puis utiliser d'autres poêles sur le même bloc pour atteindre
|
||||
deux, trois ou quatre couches. Chaque ajout consomme un objet ; la casse
|
||||
rend toutes les poêles et chaque pile réelle d'ingrédients une seule fois.
|
||||
- Chauffer chaque emplacement par-dessous avec une source reconnue. Une poêle
|
||||
ne transmet pas la chaleur à un autre bloc de poêle au-dessus. Les anciennes
|
||||
colonnes restent présentes ; la pose neuve se prolonge horizontalement.
|
||||
- Ajouter les ingrédients par clic droit, un objet à la fois. Les quatre
|
||||
positions de recette et leurs réserves sont partagées. Quatre seaux de lait
|
||||
pour quatre omelettes occupent quatre cases natives distinctes.
|
||||
- L'assemblage conserve sa fenêtre de 30 secondes, ou son départ après trois
|
||||
secondes de stabilité si les quatre positions d'une recette sont remplies.
|
||||
Les réserves d'un même ingrédient ne comptent pas comme quatre positions.
|
||||
- Une cuisson traite au plus autant de recettes complètes que de poêles,
|
||||
pendant la durée historique du plat. Deux œufs dans quatre poêles donnent
|
||||
deux œufs au plat. Une préparation invalide paie aussi ses plats ratés.
|
||||
- Les ingrédients restent présents jusqu'au paiement final. Modifier une case
|
||||
annule l'avancement. Ajouter une poêle relance aussi l'assemblage.
|
||||
- Maj-clic récupère d'abord la sortie commune, puis les ingrédients ou
|
||||
récipients. Les récipients revenus dans une position vidée doivent être
|
||||
collectés avant de commencer une nouvelle préparation. Si des ingrédients
|
||||
restent dans cette position, les récipients sont déposés dans le monde.
|
||||
- Un entonnoir peut alimenter les réserves par les côtés ou le dessus selon
|
||||
les limites natives. La face inférieure expose seulement la sortie ; une
|
||||
chaleur réelle doit toujours être présente juste sous la poêle pour cuire.
|
||||
|
||||
Les 26 recettes, leurs durées, les textures et les catégories créatives sont
|
||||
conservées. Les piles voisines peuvent cuire des plats différents. Le contenu
|
||||
commun apparaît dans la poêle supérieure, et le HUD FR/EN affiche le nombre
|
||||
de poêles, le temps, la quantité terminée ou les récipients à récupérer.
|
||||
|
||||
## Validation locale
|
||||
|
||||
Le parcours client sur un monde plat de développement neuf, graine **310**,
|
||||
a réussi sous **Vulkan / Apple M1**, avec **Sodium 0.9.2+mc26.3** :
|
||||
|
||||
- vrais paquets de pose en survie, paiement des quatre objets, plafond à
|
||||
quatre et prolongement horizontal ;
|
||||
- vrais clics d'ajout pour quatre œufs et quatre seaux de lait non empilables ;
|
||||
- huit états de modèle chargés, 30 faces par couche, hauteurs visibles exactes
|
||||
et texture historique résolue sur chaque face ;
|
||||
- HUD français/anglais aux échelles 2, 3 et 4, assemblage, cuisson et quantité
|
||||
prête ; les autres piles cuisent chacune leur propre bœuf ;
|
||||
- ticks réels pour la cuisson, quatre omelettes payées, quatre seaux rendus ;
|
||||
- fermeture et réouverture réelle du monde, récupération par Maj-clic des
|
||||
quatre plats puis des quatre seaux, conservés ensemble dans l'inventaire ;
|
||||
- 26 recettes historiques présentes dans JEI.
|
||||
|
||||
La fenêtre d'assemblage est avancée par une fixture après les interactions
|
||||
réelles, pour ne pas attendre 30 secondes avant la cuisson ; le paiement,
|
||||
la progression et les durées de cuisson restent réels. Treize captures sont
|
||||
archivées dans `build/pan310-screenshots/` (ignoré). Le parcours client dure
|
||||
47 secondes et son journal est `build/pan310-client.log` (ignoré).
|
||||
|
||||
Les treize nouveaux cas serveur vérifient la pose native, formes et loot,
|
||||
les quatre capacités, les **26 recettes à quatre portions**, le lait et ses
|
||||
récipients, l'ajout d'une poêle avant un paiement et avant sauvegarde,
|
||||
les recettes voisines et l'absence de chauffe transmise verticalement,
|
||||
la casse, les 17 indices, les anciennes cinq cases sans propriété `pans`,
|
||||
les quantités insuffisantes et préparations ratées, le retrait d'une réserve
|
||||
juste avant la fin et l'insertion par un entonnoir natif.
|
||||
|
||||
Commandes de vérification :
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=pan310,oven308,mill306,kitchen304,cuisine303,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryPan310ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=pan310 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
Les orientations supplémentaires et les ancrages particuliers du ticket IA-07
|
||||
restent ouverts. Ce lot réalise le choix confirmé de piles partageant une
|
||||
recette, avec des ingrédients réellement stockés et payés.
|
||||
|
||||
`check build assemblePack` a réussi en **3 min 27 s**, avec **76 tests serveur
|
||||
obligatoires**. La comparaison stricte à beta.309 confirme les six classes
|
||||
prévues, le modèle par couches, le loot, les trois libellés FR/EN, la version
|
||||
et les crédits. Les 26 recettes de poêle et les **1 108 PNG** de l’archive
|
||||
sont identiques. Les 432 JSON de cuisine, 195 d’appareils et 376 d’agriculture
|
||||
sont comparés à leurs générateurs. Les copies client/serveur du JAR sont
|
||||
identiques ; le modèle de pack de ressources emporte les trois nouveaux
|
||||
modèles et leur état multipart.
|
||||
|
||||
[Reçu de vérification](pan-stacks-beta310-verification.json). JAR local :
|
||||
`mods/sanctuary/build/libs/sanctuary-beta.310.jar`
|
||||
|
||||
SHA-256 :
|
||||
`4a2278d987b031b343a6d66694da50db0aeb408bb77785ce8c02ae0ed9174b26`
|
||||
|
||||
Aucun push, tag, canal packwiz public, installation Prism ou serveur
|
||||
personnel n’a été modifié.
|
||||
@@ -0,0 +1,97 @@
|
||||
# beta.298 — normales de l'ancre corrigées et installées
|
||||
|
||||
Livraison complète du **27 octobre 2026**, branche `codex/media-anchor-model`.
|
||||
[Correctif et preuve graphique](media-anchor-normals-beta298.md),
|
||||
[notes de version](notes-beta298.md).
|
||||
|
||||
Avec les shaders Sanctuary activés, le petit cadre invisible ne laisse plus
|
||||
de normales PBR sur la vidéo dans le coin inférieur gauche. Son modèle de
|
||||
bloc est vidé et la collecte des matériaux ignore les modèles invisibles.
|
||||
Le grand cadre, le lecteur et les interactions sont conservés. Les écrans
|
||||
existants gardent leur source, leurs dimensions et leur identité ; aucun
|
||||
bloc à reposer ni conversion de données.
|
||||
|
||||
## Cibles vérifiées
|
||||
|
||||
| Cible | État |
|
||||
| --- | --- |
|
||||
| Source et tag `beta.298` | `fb8705369e3834c66d965108d609ec704b584ec2` |
|
||||
| Canal client/serveur | `7fa08625a4fa89391d64b4b4fc0859611ecf0952` |
|
||||
| Release | [beta.298](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.298), quatre artefacts publics vérifiés |
|
||||
| CT 350 | Service actif, Sanctuary beta.298, Media 0.3.2 embarqué et Dream Displays 1.10.4 chargé |
|
||||
| CT 320 | Distribution `releases/beta.298/prism`, ancienne beta.297 conservée |
|
||||
| Prism historique | Même instance Sanctuary Beta, neuf JAR, deux synchronisations identiques |
|
||||
|
||||
Minecraft 26.3, Fabric 0.19.5, Java 25 et les dépendances restent inchangés.
|
||||
Un JAR Sanctuary contient JEI et Media ; Dream est distinct et obligatoire
|
||||
des deux côtés. Le serveur compte cinq JAR. Le présent reçu est ajouté après
|
||||
la publication sans déplacer le tag immuable.
|
||||
|
||||
## Vérifications
|
||||
|
||||
- Exécution fraîche `check build assemblePack assembleServerPack` réussie
|
||||
en **2 min 50 s**, 158 tâches, **18 GameTests requis**, profil
|
||||
`property270,claims272,claims277,claims278`, atlas ciblé. Contrats Media inclus.
|
||||
- JAR reconstruit strictement identique au binaire vérifié dans MEDIA-07 :
|
||||
deux clients Vulkan avec PBR actif et vidéo en mouvement, zéro face
|
||||
résiduelle, ancre exclue des matériaux, gros plans de face et en vue rasante.
|
||||
Ces essais précèdent la publication ; aucune nouvelle séance graphique ou
|
||||
mesure de synchronisation n'est revendiquée pendant ce déploiement.
|
||||
- Archives, profils et hashes vérifiés. Deux passages du véritable
|
||||
installateur en instance isolée, puis deux dans Prism personnel : second
|
||||
passage identique, aucun doublon Sanctuary, sélections facultatives conservées.
|
||||
- Démarrage CT confirmé par `Done` et versions chargées dans le journal.
|
||||
Ping DNS/SRV depuis le LAN : Minecraft 26.3, 0/20 lors du contrôle, icône
|
||||
serveur inchangée. Cela ne prouve pas un accès depuis un autre réseau.
|
||||
- ZIP et MRpack publics en HTTP 200, hashes attendus ; nom reçu
|
||||
`Sanctuary-Beta.zip`, version beta.298 et lien direct dans le HTML public.
|
||||
- Faux code d'admission refusé (`invalid`, HTTP 200), aucun redeem.
|
||||
Métriques Web fraîches, serveur en marche, même UUID communautaire
|
||||
`cc6f4f5a-e58d-409a-879f-81daecf59461` côté jeu et site.
|
||||
|
||||
## Artefacts
|
||||
|
||||
| Artefact | Octets | SHA-256 |
|
||||
| --- | ---: | --- |
|
||||
| `sanctuary-beta.298.jar` | 18301295 | `e6fdc36a212dcfcc89d31fbb29bc8eeec9d002cc9223ea6dcbfb31b7df17ec23` |
|
||||
| `Sanctuary-beta.298.mrpack` | 16890840 | `29f88803b0abc88443a32830ca0b8a1e0490de1543f8164bed1ce86c26aff10e` |
|
||||
| `Sanctuary-Prism-beta.298.zip` | 3928525 | `95c57f5e70627a2847d1cc2033c8d9f50f44ef4a302ca892bc727e0c37ece0a9` |
|
||||
| `Sanctuary-Server-beta.298.zip` | 4036721 | `c33ac4c08bb8b99598c71d44e1e5047f0810c4938f68d9d393507b950e865d43` |
|
||||
|
||||
Le ZIP Prism reste identique à beta.297 : mêmes outils et plateforme, canal
|
||||
stable maintenant en beta.298. Le kit serveur ne change que son README pour
|
||||
aligner le nom du kit courant ; ses outils sont inchangés. Cette documentation
|
||||
a été assemblée par `scripts/server_pack.py` après le build complet réussi.
|
||||
|
||||
## Conservation
|
||||
|
||||
Une connexion était active avant l'arrêt de maintenance. Sauvegarde complète
|
||||
privée après arrêt propre :
|
||||
`/root/sanctuary-beta-backups/before-beta298-20261027T045721Z/instance.tar.gz`,
|
||||
SHA-256 `1310c975169f1b33ecb3b3494dbb0a12a8f9815b45e2240d25c509c19c5dc755`.
|
||||
Les **177 fichiers protégés** sont identiques après synchronisation, avant
|
||||
redémarrage. Environnement privé et clés/valeurs de `server.properties`
|
||||
identiques après démarrage. Aucune migration ni régénération de monde.
|
||||
|
||||
Prism : sauvegarde sous la même instance,
|
||||
`sanctuary-backups/before-beta298-20261027T045406Z/instance.tar.gz`, SHA-256
|
||||
`692b1989f5dac8c2bc28a2fb2f7842937b5e28eeefeb4e30d428169ecc175d0e`.
|
||||
Les **933 fichiers personnels contrôlés**, `instance.cfg` et l'installateur
|
||||
habillé Sanctuary sont inchangés. Le lien FFmpeg existant est conservé.
|
||||
Aucun monde personnel ouvert. L'autre instance importée n'est pas modifiée
|
||||
manuellement ; elle suivra le canal au prochain lancement.
|
||||
|
||||
Web : HEAD `eadc30e8c7b6dda50bc810584af057235e7fdba8` et **73 changements
|
||||
préexistants** conservés, empreintes Git avant/après identiques. Distribution
|
||||
basculée atomiquement ; pas de remplacement de code ou de cache. Alpha,
|
||||
réseau/NAT, comptes, permissions et chantier du checkout principal préservés.
|
||||
|
||||
## Limites
|
||||
|
||||
Les limites du parcours graphique MEDIA-07 restent valables : mur orienté sud,
|
||||
pas de nouvelle validation de source publique, arrêt tardif d'un client par
|
||||
manque d'espace pour les journaux de la sonde. Ce n'est pas un essai d'endurance.
|
||||
La sonde n'est pas distribuée. Aucun nouveau problème d'espace n'est survenu
|
||||
pendant cette livraison ; environ 2,5 Gio restaient au contrôle final local.
|
||||
|
||||
Reçus et journaux opérationnels : `build/release298/`, hors Git.
|
||||
@@ -0,0 +1,95 @@
|
||||
# beta.315 — cuisine et agriculture publiées
|
||||
|
||||
Livraison complète du **29 octobre 2026**, branche `codex/itsalive-26-3`.
|
||||
[Notes de version](notes-beta315.md), [export de textures](itsalive-textures-export-beta315.md).
|
||||
Les incréments beta.299 à beta.315, jusque-là locaux, sont réunis : cultures,
|
||||
vanille grimpante, cuisine et appareils, écrasement, séchoir et saucisson,
|
||||
fromage à partager et quatre qualités de vin.
|
||||
|
||||
## Cibles vérifiées
|
||||
|
||||
| Cible | État |
|
||||
| --- | --- |
|
||||
| Source/tag `beta.315` | `359f49792bbe401acb53da121babaca451744b9c` |
|
||||
| Canal client/serveur | `a5df95c2d43a2e80d1215a1bca39528f4ce55957` |
|
||||
| Release publique | [beta.315](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.315), quatre artefacts anonymes vérifiés |
|
||||
| CT 350 | Service actif, Sanctuary beta.315, Media 0.3.2 et Dream Displays 1.10.4 chargés |
|
||||
| CT 320 | Distribution `releases/beta.315/prism`, ancienne beta.298 conservée |
|
||||
| Prism historique | Même instance Sanctuary Beta, neuf JAR, deux passages identiques |
|
||||
|
||||
Minecraft 26.3, Fabric Loader 0.19.5, Java 25 et les dépendances sont conservés.
|
||||
Le serveur a cinq JAR, JEI et Media sont embarqués dans Sanctuary.
|
||||
Ce reçu est committé après publication sans déplacer le tag immuable.
|
||||
|
||||
## Contrôles
|
||||
|
||||
- Build frais `check build assemblePack assembleServerPack`, **2 min 38 s**,
|
||||
162 tâches et **187 GameTests requis** : cuisine complète, agriculture,
|
||||
vanille, écrasement/super piston, progression/recettes et contrôles de
|
||||
propriété/admission. Les contrats smoke du dépôt sont inclus.
|
||||
- Le JAR reste strictement identique au binaire final audité beta.315,
|
||||
[134 tests ciblés et contrôles de la préparation](sausage-slices-beta315.md).
|
||||
Les deux parcours **Vulkan / Sodium** de cette préparation couvrent le
|
||||
séchoir et les parts posées. Aucun jeu personnel n'est ouvert au déploiement.
|
||||
- MRpack : huit dépendances distantes et un override Sanctuary vérifié.
|
||||
ZIP Prism et kit serveur identiques à beta.298 : outils, plateforme et canal
|
||||
stable inchangés ; le canal fournit maintenant le nouveau mod.
|
||||
- Deux vrais passages de l'installateur en instance isolée puis deux dans
|
||||
l'instance personnelle : neuf JAR, aucun doublon, second passage identique.
|
||||
- CT 350 : versions chargées et `Done` dans le journal, propriétés identiques
|
||||
par clés/valeurs, environnement privé et UUID communautaire conservés.
|
||||
Vérification authentifiée d'un code fictif : HTTP 200, `invalid`, aucun redeem.
|
||||
- Ping par DNS/SRV depuis le LAN : Minecraft 26.3, **0/20**, port public 25567,
|
||||
icône inchangée. Cette preuve ne couvre pas un réseau extérieur.
|
||||
- Site public : ZIP et MRpack en HTTP 200, hashes attendus, nom du ZIP reçu
|
||||
`Sanctuary-Beta.zip`, bouton direct, survol et footer beta.315.
|
||||
Métriques `running=true`, `fresh=true`, même UUID communautaire.
|
||||
|
||||
## Artefacts
|
||||
|
||||
| Artefact | Octets | SHA-256 |
|
||||
| --- | ---: | --- |
|
||||
| `sanctuary-beta.315.jar` | 19031856 | `3767fe61832e1193367a09fa9e8c9c1df1a2f5ccf4802bc7280d2cfbe665ce78` |
|
||||
| `Sanctuary-beta.315.mrpack` | 17335224 | `d1811f1e892ab41147b55280976fc8fc24bd20b867ff8b17d94217d98f72b1e1` |
|
||||
| `Sanctuary-Prism-beta.315.zip` | 3928525 | `95c57f5e70627a2847d1cc2033c8d9f50f44ef4a302ca892bc727e0c37ece0a9` |
|
||||
| `Sanctuary-Server-beta.315.zip` | 4036721 | `c33ac4c08bb8b99598c71d44e1e5047f0810c4938f68d9d393507b950e865d43` |
|
||||
|
||||
## Conservation
|
||||
|
||||
CT : arrêt avant avance du canal ; le wrapper retourne 143 après SIGTERM et
|
||||
aucun Java Minecraft ne reste actif avant la sauvegarde.
|
||||
Sauvegarde privée `/root/sanctuary-beta-backups/before-beta315-20261029T085359Z/instance.tar.gz`, SHA-256
|
||||
`58993afceff58adfa8b705a727ca19d5be24269065d8c23547088847daf9c77e` ; environnement privé copié à part en accès root.
|
||||
**211 fichiers protégés** identiques après mise à jour,
|
||||
avant redémarrage. Aucune régénération ni conversion explicite de monde.
|
||||
|
||||
Prism : sauvegarde `/Users/koka/Library/Application Support/PrismLauncher/instances/Sanctuary-0.1.0-alpha.1/sanctuary-backups/before-beta315-20261029T085045Z/instance.tar.gz`, SHA-256
|
||||
`f418c47484ecb67a8301f4ae6010b60f7c5c34980400e8fea44baf6f6b8c69a7`. **936 fichiers personnels**
|
||||
contrôlés inchangés, avec `instance.cfg`, réglages, sauvegardes, captures,
|
||||
packs personnels, installateur au logo et lien FFmpeg. Aucune instance créée
|
||||
ou réimportée dans Prism ; l'instance isolée de vérification reste dans build.
|
||||
|
||||
Web : publication limitée à la distribution, sans pull/reset, déploiement de
|
||||
code, migration DB ou changement de cache. HEAD observé
|
||||
`eadc30e8c7b6dda50bc810584af057235e7fdba8`, **73 changements préexistants**.
|
||||
Le premier relevé Git préalable a été refusé par le contrôle de propriétaire ;
|
||||
le relevé final utilise une exception de confiance limitée à la commande,
|
||||
sans modifier la configuration Git. Aucun fichier applicatif n'est écrit
|
||||
par le script de publication. Alpha, NAT, comptes et permissions hors cible.
|
||||
|
||||
## Textures éditables et limites
|
||||
|
||||
Export dans `Downloads/ItsAlive-Textures-beta.315/`, ZIP de même nom :
|
||||
**347 PNG de la refonte**, **368 images modifiables** avec les matériaux
|
||||
réutilisés et **696 JSON visuels**. Les 347 PNG correspondent exactement au
|
||||
JAR. Copies séparées pour les matériaux du fromage, des saucissons et du
|
||||
séchoir, et un PNG propre à chaque vin de garde. Le pack éditable n'est pas
|
||||
activé automatiquement. [Mode d'emploi](itsalive-textures-export-beta315.md).
|
||||
|
||||
Les cultures sont jouables ; leur apparition naturelle/répartition climatique
|
||||
reste hors de cette livraison. Le créateur prévoit maintenant ses essais de
|
||||
saucisson et de cuisine : les contrôles techniques ne valent pas validation de
|
||||
ses préférences d'équilibrage ou de présentation. Les limites Media/voix et
|
||||
réseau des livraisons précédentes restent inchangées.
|
||||
|
||||
Reçus et journaux opérationnels : `build/release315/`, hors Git.
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"version": "beta.315",
|
||||
"jar": "mods/sanctuary/build/libs/sanctuary-beta.315.jar",
|
||||
"sha256": "3767fe61832e1193367a09fa9e8c9c1df1a2f5ccf4802bc7280d2cfbe665ce78",
|
||||
"previous_verified_sha256": "82a2a6d8d4e2ad9d0aab754037995609722bbebac28db89a6e641c851ccb3ceb",
|
||||
"changed_jar_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json",
|
||||
"fabric.mod.json",
|
||||
"fr/koka/sanctuary/cuisine/KitchenAppliances.class",
|
||||
"fr/koka/sanctuary/cuisine/KitchenClient$1.class",
|
||||
"fr/koka/sanctuary/cuisine/KitchenClient.class",
|
||||
"resourcepacks/template/THIRD_PARTY_NOTICES.md"
|
||||
],
|
||||
"added_jar_entries": [
|
||||
"assets/sanctuary/blockstates/cured_sausage.json",
|
||||
"assets/sanctuary/models/block/cured_sausage.json",
|
||||
"assets/sanctuary/models/block/cured_sausage_slice_1.json",
|
||||
"assets/sanctuary/models/block/cured_sausage_slice_2.json",
|
||||
"assets/sanctuary/models/block/cured_sausage_slice_3.json",
|
||||
"data/sanctuary/loot_table/blocks/cured_sausage.json",
|
||||
"fr/koka/sanctuary/cuisine/culinary/block/CuredSausageBlock.class"
|
||||
],
|
||||
"removed_jar_entries": [],
|
||||
"generated_json": {
|
||||
"cuisine": 432,
|
||||
"kitchen": 234,
|
||||
"agriculture": 376
|
||||
},
|
||||
"additive_sausage_json": 6,
|
||||
"culinary_source_classes": 53,
|
||||
"culinary_compiled_classes_verified": 73,
|
||||
"sausage_slices": 4,
|
||||
"food_per_slice": 2,
|
||||
"total_food": 8,
|
||||
"saturation_modifier": 1.2,
|
||||
"horizontal_orientations": 4,
|
||||
"block_states": 16,
|
||||
"existing_drying_recipe_and_raw_cured_item_models_unchanged": true,
|
||||
"old_pngs_unchanged": 1108,
|
||||
"new_pngs": 0,
|
||||
"server_required": 134,
|
||||
"sausage_scenarios": 8,
|
||||
"client_vulkan": {
|
||||
"log": "build/sausage315-client.log",
|
||||
"screenshots": "build/sausage315-screenshots",
|
||||
"screenshot_count": 12,
|
||||
"world_seed": 315,
|
||||
"sodium": "0.9.2+mc26.3",
|
||||
"native_paid_rack_insertion_and_collection": true,
|
||||
"native_crosshair_key_placement_and_eating": true,
|
||||
"actual_disk_orientation_and_partial_sausage_reopen": true,
|
||||
"four_native_eating_sounds": true,
|
||||
"fixture_advances_only_drying_progress": true
|
||||
},
|
||||
"drying_regression_vulkan": {
|
||||
"log": "build/sausage315-drying-regression.log",
|
||||
"native_four_preparations_and_hooks": true,
|
||||
"actual_bucket_and_disk": true,
|
||||
"placed_slice_tasting": true
|
||||
},
|
||||
"client_server_copies_identical": true,
|
||||
"template_changed_entries": [
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"assets/sanctuary/lang/en_us.json",
|
||||
"assets/sanctuary/lang/fr_fr.json"
|
||||
],
|
||||
"template_added_entries": [
|
||||
"assets/sanctuary/blockstates/cured_sausage.json",
|
||||
"assets/sanctuary/models/block/cured_sausage.json",
|
||||
"assets/sanctuary/models/block/cured_sausage_slice_1.json",
|
||||
"assets/sanctuary/models/block/cured_sausage_slice_2.json",
|
||||
"assets/sanctuary/models/block/cured_sausage_slice_3.json"
|
||||
],
|
||||
"build_log": "build/sausage315-build.log"
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
# IA-10 — saucisson posé à partager — beta.315 locale
|
||||
|
||||
Demande du 29 octobre 2026 : poser le saucisson séché puis en manger des tranches,
|
||||
comme le fromage. Même branche `codex/itsalive-26-3`, sans publication ni
|
||||
installation. Premier réglage : quatre tranches de deux points de nourriture,
|
||||
pour conserver les huit points du saucisson entier de beta.313.
|
||||
|
||||
## Contrat de continuité avant modification
|
||||
|
||||
L'objet `sanctuary:cured_sausage`, sa recette et son modèle restent stables.
|
||||
Il devient le BlockItem du nouveau bloc du même identifiant. Comme le fromage,
|
||||
il se mange désormais après la pose plutôt qu'entier dans la main. Les objets
|
||||
existants deviennent posables sans conversion. Le cru, les quatre crochets,
|
||||
les cinq minutes et les règles d'humidité du séchoir restent inchangés.
|
||||
|
||||
Le bloc n'a pas de block entity. Son état natif conserve `bites=0..3` et les
|
||||
quatre orientations horizontales `facing`. Ces données additives se sauvegardent
|
||||
par les mécanismes standard des blocs. Le modèle et la forme raccourcissent à
|
||||
chaque tranche. Aucune migration de chunk, génération ou monde personnel n'est
|
||||
nécessaire ou exécutée. Comme le gâteau et le fromage, casser le saucisson ou
|
||||
lui enlever son support ne rend pas un saucisson entier ; les tranches mangées
|
||||
ne peuvent pas être reconstituées gratuitement. Avant un retour à beta.314,
|
||||
manger les saucissons posés ; les objets non posés gardent leur identifiant.
|
||||
|
||||
## Geste et apparence
|
||||
|
||||
Poser sur un bloc solide consomme un saucisson séché. Clic droit pour manger
|
||||
une tranche si le joueur a faim ; la quatrième enlève le bloc. Aucun couteau,
|
||||
menu, combustible ou nouvel objet « tranche » n'est ajouté. Chaque tranche
|
||||
conserve le modificateur de saturation de 1,2 ; la faim totale reste de huit.
|
||||
Un comparateur lit 8/6/4/2 selon les tranches restantes.
|
||||
|
||||
Les nouvelles géométries cubiques reprennent les références du modèle d'objet :
|
||||
terre cuite rouge, laine blanche et bloc de champignon rouge pour la coupe.
|
||||
La ficelle du bout coupé disparaît à la première tranche ; la ficelle et les
|
||||
bandes encore présentes restent à leur vraie position sur le morceau restant.
|
||||
Les modèles d'objet cru/séché et leurs rendus sur le séchoir restent intacts.
|
||||
Aucun PNG ou son n'est ajouté. Les sons natifs de mastication sont référencés.
|
||||
|
||||
## Vérifications
|
||||
|
||||
Huit cas serveur natifs vérifient le paiement de la pose, les quatre vraies
|
||||
prises de tranches et leurs huit points de nourriture, la faim pleine,
|
||||
les bougies, les seize états de forme et de comparateur, la rotation et le
|
||||
miroir, le codec standard de sauvegarde d'un saucisson entamé et orienté,
|
||||
la casse de chacun des quatre états, la perte de support et la pose refusée
|
||||
sans consommation. Le dernier cas exécute **6 000 ticks complets** du séchoir
|
||||
sur une vraie préparation, puis pose le résultat payé du même identifiant.
|
||||
|
||||
Le parcours **Vulkan / Apple M1 / Sodium 0.9.2+mc26.3** utilise un monde plat
|
||||
jetable de graine **315**, une préparation crue fournie en fixture et de vraies
|
||||
touches d'utilisation pour l'accrocher, récupérer le résultat, poser le
|
||||
saucisson puis manger ses quatre tranches. Le viseur doit réellement toucher
|
||||
la forme du bloc avant chaque action. Le serveur confirme le paiement de
|
||||
chaque étape et les deux points de nourriture de chaque tranche.
|
||||
|
||||
Les seize modèles sont chargés : huit cuboïdes pour l'entier, cinq pour chaque
|
||||
portion raccourcie, références de textures natives valides et tous les sommets
|
||||
à l'intérieur de la forme orientée correspondante. L'objet reste dans l'onglet
|
||||
Nourriture et boissons et son modèle historique reste valide. FR/EN et GUI
|
||||
2/3/4 sont contrôlés. Une fermeture/réouverture réelle conserve le bloc avec
|
||||
**deux tranches déjà mangées** et la même orientation ; les deux autres se
|
||||
mangent sur ce même morceau. Les quatre sons natifs de mastication sont captés.
|
||||
Douze images sont conservées dans `build/sausage315-screenshots/` (ignoré).
|
||||
|
||||
Ce parcours graphique rapproche seulement la progression du séchage à dix
|
||||
ticks de sa fin pour ne pas attendre cinq minutes ; les derniers ticks et
|
||||
toutes les interactions restent natifs. Les cas serveur exécutent la durée
|
||||
complète. Le parcours graphique a réussi en **1 min 14 s**. Le contrôle
|
||||
historique beta.313 est adapté pour goûter le résultat après sa pose, et son
|
||||
assertion d'objet comestible devient celle d'un BlockItem à partager.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=sausage315,cheesewine314,drying313,cauldron312,cauldron311,pan310,oven308,mill306,kitchen304,cuisine303,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuarySausage315ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=sausage315 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
Le parcours historique complet du séchoir est également rejoué sous Vulkan
|
||||
avec Sodium et réussit en **1 min 16 s** : fabrication payée des quatre
|
||||
préparations, quatre crochets, retrait/raccrochage, seau d'eau, sauvegarde et
|
||||
vidage au seau, quatre saucissons récupérés puis pose et dégustation d'une
|
||||
tranche du même résultat. Sa table de dégustation est surélevée pour rester
|
||||
au sec après l'essai d'écoulement. Les miroirs de structure vérifient aussi
|
||||
la réflexion réelle de la forme restante sur X et Z pour les seize états,
|
||||
et pas seulement la conservation du nombre de tranches.
|
||||
|
||||
`check build assemblePack` a réussi en **3 min 19 s**, avec **134 tests serveur
|
||||
obligatoires**, dont les assertions de réflexion de coupe. L'audit strict à
|
||||
beta.314 vérifie le nouveau bloc, ses six JSON ajoutés et les modifications
|
||||
prévues de registre, libellés et crédits. Les **1 108 PNG** et tous les anciens
|
||||
JSON de recettes/modèles sont inchangés. Les générateurs sont comparés au JAR :
|
||||
432 JSON de cuisine, 234 d'appareils et 376 d'agriculture. Les 53 sources et
|
||||
73 classes compilées culinaires sont vérifiées. Les assemblages client/serveur
|
||||
embarquent exactement le même JAR et le modèle de pack de ressources reprend
|
||||
les cinq nouveaux JSON visuels. Aucun fichier d'objet ou de modèle de séchoir
|
||||
historique n'a changé.
|
||||
|
||||
[Reçu d'audit](sausage-slices-beta315-verification.json).
|
||||
JAR local : `mods/sanctuary/build/libs/sanctuary-beta.315.jar`.
|
||||
|
||||
SHA-256 :
|
||||
`3767fe61832e1193367a09fa9e8c9c1df1a2f5ccf4802bc7280d2cfbe665ce78`
|
||||
|
||||
Aucun push, tag, publication, canal packwiz public, installation Prism ou
|
||||
serveur personnel n'a été modifié.
|
||||
+1
-1
@@ -8,7 +8,7 @@ Plateforme épinglée : Minecraft **26.3**, Fabric Loader **0.19.5**, Fabric API
|
||||
## Préparer le kit
|
||||
|
||||
Dans les sources : `./gradlew check build assemblePack assembleServerPack`.
|
||||
Le kit de la livraison courante est `build/Sanctuary-Server-beta.297.zip` ; les manifestes locaux sont
|
||||
Le kit de la livraison courante est `build/Sanctuary-Server-beta.298.zip` ; les manifestes locaux sont
|
||||
dans `build/packwiz-server/`. Le ZIP est un amorçage : il télécharge les mods
|
||||
depuis le canal au premier démarrage, il n'est pas une copie hors ligne du pack.
|
||||
Il n'est utilisable sur le canal public qu'après publication de cette version.
|
||||
|
||||
@@ -242,6 +242,27 @@ gérés au second passage, mêmes données personnelles avant/après. Conserver
|
||||
`saves/`, captures, favoris, réglages, packs graphiques et configurations tierces.
|
||||
Ne pas ouvrir un monde personnel pour prouver une simple synchronisation.
|
||||
|
||||
### Essai local explicitement demandé sans publication
|
||||
|
||||
Pour ce périmètre, ne pas publier le canal et ne pas remplacer seulement le JAR
|
||||
alors que le pré-lancement suit une ancienne version publique. La beta.318 a
|
||||
validé un profil packwiz autonome copié dans l’instance existante sous
|
||||
`sanctuary-local-packs/beta.318/`. L’installateur v0.5.14 accepte un chemin local
|
||||
pour le pack ; le JAR Sanctuary est une entrée directe de cet index local.
|
||||
Les huit métadonnées des dépendances et leurs choix restent inchangés.
|
||||
|
||||
Après les essais isolés et la sauvegarde, remplacer seulement l’argument du
|
||||
pack dans les deux `PreLaunchCommand` par le chemin cité
|
||||
`"$INST_DIR/sanctuary-local-packs/beta.318/pack.toml"`. Garder le bootstrap au logo,
|
||||
`--bootstrap-no-update` et tous les autres réglages. Vérifier deux passages réels,
|
||||
l’unicité du JAR et les hashes personnels ; ne pas lancer un monde personnel.
|
||||
Le profil doit être hors du worktree pour rester disponible aux lancements suivants.
|
||||
|
||||
Le pré-lancement demeure sur ce profil pendant l’essai. Le rétablissement de la
|
||||
commande publique est une étape explicite d’une future livraison compatible,
|
||||
après lecture de la configuration courante et de sa sauvegarde. Ne pas réutiliser
|
||||
les scripts ponctuels d’une ancienne intervention. [Reçu beta.318](local-beta318.md).
|
||||
|
||||
## Reprise et reçu de livraison
|
||||
|
||||
Pour un échec, conserver l'état/les logs, revenir au dernier artefact immuable
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# IA-03 — Vanille en 16 × 16, beta.302 locale
|
||||
|
||||
Correction du 28 octobre 2026 sur `codex/itsalive-26-3`, après le retour du
|
||||
créateur sur les textures 32 × 32 de beta.301. Les quatre stades de vanille
|
||||
doivent être des **PNG de 16 × 16 pixels réels**, comme les textures vanilla.
|
||||
|
||||
Le dessin est simplifié pour cette grille : jeune tige, feuillage, fleurs,
|
||||
puis gousses vertes. Feuilles et gousses restent dans un même sprite sur le
|
||||
plan unique contre le tronc. Les fichiers conservent leurs identifiants
|
||||
`sanctuary:block/vanilla_climbing_stage0` à `3` ; la pousse ascendante, la
|
||||
récolte, les supports et les onglets créatifs de beta.301 sont conservés.
|
||||
|
||||
L'[atlas de production et le prompt](../ressources-pack/sources/vanilla-beta301/README.md)
|
||||
documentent le travail avec l'outil intégré imagegen. L'atlas contient quatre
|
||||
cellules 16 × 16 ; les PNG de jeu sont extraits de ces cellules. L'ancien
|
||||
atlas conceptuel reste une référence historique. Les 343 PNG du reçu beta.299
|
||||
ne sont pas modifiés. Aucune génération, migration ou installation modifiée.
|
||||
|
||||
Le parcours client vérifie désormais la taille **16 × 16 de chaque sprite
|
||||
effectivement chargé dans l'atlas Minecraft**, pour tous les modèles de
|
||||
vanille, en plus du plan unique, de la culture en survie et du rechargement.
|
||||
|
||||
Le parcours **Vulkan avec Sodium 0.9.2** passe en **1 min 16 s** : les
|
||||
64 états sont contrôlés, avec les quatre sprites chargés en 16 × 16 et un
|
||||
plan recto verso par face. Après fermeture et réouverture du monde de test,
|
||||
les 24 segments conservent exactement leurs faces et leur maturité.
|
||||
|
||||
Commandes de vérification :
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=vanilla301,agriculture300,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryVanilla301ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=vanilla301 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
`check build assemblePack` passe en **3 min 18 s**, avec **46 tests serveur
|
||||
requis** réussis. Le JAR, ses copies packwiz client/serveur et le template
|
||||
contiennent les quatre nouveaux PNG 16 × 16 identiques aux sources. Les
|
||||
**13 PNG de vanille présents dans le mod** sont tous contrôlés en 16 × 16.
|
||||
Les 376 JSON agricoles et les notices sont vérifiés dans les archives ; les
|
||||
343 PNG historiques restent identiques, dont les 38 images fournies.
|
||||
|
||||
SHA-256 de `sanctuary-beta.302.jar` :
|
||||
|
||||
```text
|
||||
5959fa5e2fab808c55e011fc578171bc8cadca9d89d7170795051fc19fa01c0c
|
||||
```
|
||||
|
||||
Journaux et reçu dans `build/vanilla302-*`, captures `vanilla302-16px-*`
|
||||
dans le dossier de test client ignoré. Minecraft 26.3, graine de test 301,
|
||||
Vulkan exclusivement.
|
||||
Même branche en local, aucun push, tag, publication, serveur ou Prism mis à jour.
|
||||
@@ -0,0 +1,146 @@
|
||||
# IA-03 — Vanille grimpante, beta.301 locale
|
||||
|
||||
**Suite en beta.302 :** [les quatre textures sont reprises en 16 × 16](vanilla-16px-beta302.md).
|
||||
Le reçu ci-dessous conserve la préparation initiale beta.301 en 32 × 32.
|
||||
|
||||
Préparation du **28 octobre 2026**, même branche `codex/itsalive-26-3`, sur
|
||||
le port agricole beta.300 (`ed11cd77`). Minecraft 26.3, Java 25 et les
|
||||
dépendances sont inchangés. Aucun push, tag, publication, serveur ou Prism
|
||||
mis à jour ; seuls les mondes de développement neufs servent aux essais.
|
||||
|
||||
## Plante attachée aux arbres
|
||||
|
||||
La bouture se pose **contre une face latérale d'une bûche**, à n'importe
|
||||
quelle hauteur, sans exiger de terre au pied. Les supports par défaut sont
|
||||
les bûches et bois des arbres de l'Overworld, y compris les variantes
|
||||
écorcées : tag `sanctuary:vanilla_supports`, qui reprend
|
||||
`#minecraft:logs_that_burn`. La pierre, le feuillage, le sol sous le plant
|
||||
et les tiges du Nether ne sont pas des supports. La plantation sous l'eau
|
||||
est refusée sans consommer de bouture.
|
||||
|
||||
Chaque segment peut porter plusieurs faces attachées, à la manière des
|
||||
lianes. Poser une autre bouture ajoute une face disposant de sa propre bûche
|
||||
sans réinitialiser l'âge du segment. Aucun plafond ou segment flottant ne
|
||||
sert d'appui. La plante n'a pas de collision centrale et le tag vanilla
|
||||
`minecraft:climbable` permet l'escalade native.
|
||||
|
||||
Le rendu reste **plaqué dans le premier seizième de bloc contre l'écorce**.
|
||||
À la demande du créateur, feuilles, fleurs et gousses sont réunies dans
|
||||
**une seule texture sur un seul plan**, sans couches décalées. Quatre nouveaux
|
||||
sprites RGBA de 32 × 32 pixels suivent la maturité : jeune plant, feuillage,
|
||||
fleurs, gousses vertes. Ils prennent la liane Minecraft comme référence de
|
||||
structure et de style. Leurs couleurs sont intégrées, sans teinte de biome.
|
||||
Aucun modèle en croix, cadre ou colonne au centre de la cellule.
|
||||
L'[atlas source et les prompts](../ressources-pack/sources/vanilla-beta301/README.md)
|
||||
sont conservés ; les quatre anciens PNG de vanille restent intacts.
|
||||
|
||||
## Croissance et récolte
|
||||
|
||||
Les quatre âges sont **0 à 3**. Sous une lumière d'au moins 9, les ticks
|
||||
aléatoires font mûrir le segment. À maturité, la croissance crée uniquement
|
||||
un **nouveau segment au-dessus**, âge 0, sur les faces qui trouvent encore
|
||||
une bûche à cette hauteur. Il n'y a ni propagation vers le bas ni propagation
|
||||
latérale automatique.
|
||||
|
||||
La pousse s'arrête à la fin du tronc, devant un bloc, une autre plante, de
|
||||
l'eau ou à la limite de hauteur du monde. Elle ne remplace que l'air.
|
||||
La règle native `minecraft:spread_vines` contrôle l'extension naturelle ;
|
||||
la maturation reste possible lorsqu'elle est désactivée.
|
||||
|
||||
La poudre d'os ajoute un âge, puis fait pousser le segment supérieur lorsque
|
||||
celui-ci est possible. Une tentative d'extension bloquée ne consomme rien.
|
||||
Cette action volontaire reste disponible lorsque la propagation naturelle
|
||||
des lianes est désactivée.
|
||||
|
||||
Un clic droit à maturité donne **une à trois gousses vertes**, conserve les
|
||||
faces et remet le segment à l'âge 1. Même une interaction obsolète ne peut
|
||||
récompenser deux fois la même récolte. Une bouture en main sert à ajouter
|
||||
une face ; la poudre d'os garde son usage de croissance. La perte d'une
|
||||
bûche retire seulement les faces qu'elle portait. Si plus aucune face n'a
|
||||
d'appui, le bloc disparaît et rend **une bouture par segment**, indépendamment
|
||||
du nombre de faces. Les autres segments soutenus par leur propre tronc restent.
|
||||
|
||||
Le fumoir transforme une gousse verte en une gousse de vanille préparée,
|
||||
comme dans le catalogue historique. Aucun nouvel appareil culinaire n'est
|
||||
introduit. Le séchoir/saucisson et les autres machines restent
|
||||
dans leurs tickets respectifs.
|
||||
|
||||
## Objets et sauvegardes
|
||||
|
||||
- `sanctuary:vanilla_cutting` : bouture, dans **Naturel**, avec aide FR/EN.
|
||||
- `sanctuary:green_vanilla_pod` et `sanctuary:vanilla_pod` : ingrédients dans
|
||||
**Nourriture et boissons**.
|
||||
- `sanctuary:vanilla_vine` : bloc, états `north`, `east`, `south`, `west` et `age`.
|
||||
|
||||
Le catalogue agricole contient désormais **48 objets** distincts, sans
|
||||
nouvel onglet créatif. La vanille n'était pas enregistrée en beta.300 : ces
|
||||
identifiants et états sont additifs, sans conversion de données existantes.
|
||||
Les états sont sauvegardés par Minecraft ; aucune donnée persistante annexe.
|
||||
Le modèle prend en charge les rotations et miroirs des structures natives.
|
||||
|
||||
Les 343 PNG du reçu beta.299 restent inchangés ; les quatre nouveaux sprites
|
||||
`vanilla_climbing_stage0` à `3` s'y ajoutent. Aucune génération de biome,
|
||||
apparition naturelle, expansion, réécriture de chunk ou migration du monde
|
||||
26.2 n'est ajoutée. L'obtention des premières boutures en survie naturelle
|
||||
reste à traiter avec le peuplement agricole.
|
||||
|
||||
```mcfunction
|
||||
/give @s sanctuary:vanilla_cutting 16
|
||||
```
|
||||
|
||||
## Implémentation et vérification
|
||||
|
||||
`VanillaVineBlock` reprend le cycle de maturité et de récolte de l'historique
|
||||
26.2, avec un nouvel attachement à plusieurs faces et une croissance strictement
|
||||
ascendante. Les modèles multipart, le butin, la recette et les tags sont
|
||||
produits par `generateAgricultureResources` pour les formats exacts 26.3.
|
||||
|
||||
La session ciblée passe **13 tests serveur**, dont **12 scénarios vanille**.
|
||||
Ils couvrent la pose sur les quatre côtés,
|
||||
les refus de plantation, plusieurs faces et leur conservation, poudre d'os,
|
||||
obstacles, appuis en hauteur, ticks naturels et règle des lianes, récolte
|
||||
unique, perte du support, formes fines, escalade native, NBT, rotations,
|
||||
miroirs et recette au fumoir.
|
||||
|
||||
Le parcours **Vulkan avec Sodium** vérifie la pose réelle en survie,
|
||||
la consommation de bouture et de poudre d'os, la pousse ascendante et
|
||||
la récolte par les interactions client/serveur natives. Les 64 états de modèle
|
||||
sont compilés et contrôlés, dont 60 avec au moins une face ; leurs sommets
|
||||
restent contre le tronc, avec un seul plan recto verso par face et le nouveau
|
||||
sprite correspondant à l'âge. Les onglets créatifs et noms FR/EN sont vérifiés.
|
||||
Le monde est fermé puis rouvert pour comparer les faces et la maturité des
|
||||
segments réellement cultivés.
|
||||
|
||||
**Validation finale avec les nouvelles textures :** `check build assemblePack`
|
||||
passe en **2 min 43 s**, avec **46 tests serveur requis** réussis : vanille,
|
||||
agriculture beta.300, recettes, progression et connaissances alimentaires.
|
||||
Le parcours Vulkan avec Sodium 0.9.2 passe en **50 s**, avec les 64 états,
|
||||
un seul plan par face et **24 segments identiques après rechargement réel**.
|
||||
Les captures finales montrent la galerie de troncs et un gros plan des gousses.
|
||||
|
||||
Le JAR et les copies packwiz client/serveur sont identiques. Les **376 JSON
|
||||
agricoles générés**, classes agricoles compilées, quatre nouveaux sprites et
|
||||
notices sont vérifiés dans l'archive ; le template contient les mêmes sprites.
|
||||
Les 343 PNG historiques et les 38 sources fournies sont revérifiés octet pour
|
||||
octet, y compris dans le JAR. SHA-256 de `sanctuary-beta.301.jar` :
|
||||
|
||||
```text
|
||||
14057e91d6d0fa776ffc67543f8c9cd137e71d2c7616311053dd7db3b1a69043
|
||||
```
|
||||
|
||||
Commandes de vérification :
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=vanilla301,agriculture300,recipes,progression,foodknowledge \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryVanilla301ClientTests=true -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryFocusedTests=vanilla301 -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuaryClientHeapGb=4 \
|
||||
-PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
Journaux et reçus ignorés dans `build/vanilla301-*`, captures et monde de test
|
||||
dans `mods/sanctuary/build/run/clientGameTest/`, graine **301**. Les tests
|
||||
graphiques ciblent Vulkan exclusivement.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Alimentation — trouver, récolter, replanter
|
||||
|
||||
Catalogue **IA-09 / beta.316**, préparation locale du 30 octobre 2026.
|
||||
[Planche illustrée des neuf îles](planche.html) · [Contrat de génération](../../foraging-beta316.md).
|
||||
[Produits du séchoir et 18 recettes culinaires, beta.318](sechoir.md).
|
||||
|
||||
Les emplacements indiquent des **habitats**, pas des coffres garantis à des coordonnées fixes.
|
||||
Une île peut contenir plusieurs biomes : regarder le sol et la végétation, pas seulement la direction.
|
||||
Les plantes sauvages apparaissent uniquement à la décoration des **nouveaux chunks Sanctuary**.
|
||||
Dans les zones déjà explorées, casser à la main les petites herbes/fougères du bon biome
|
||||
donne parfois une récolte à transformer en semences (1 chance sur 8, puis choix parmi les cultures du climat).
|
||||
Le blé garde ses graines vanilla. Les cisailles et Toucher de soie préservent le bloc et n'ajoutent pas de récolte.
|
||||
Les grandes herbes/fougères à deux blocs ne donnent pas ces nouveaux butins : éviter un double paiement.
|
||||
Pour le riz : herbes marines de jungle/mangrove (1 sur 8). Pour les boutures de vanille : lianes tropicales (1 sur 8).
|
||||
Pour les arbres : feuilles de chêne/acacia des habitats chauds admissibles (1 sur 40, puis choix de la pousse).
|
||||
Des chênes/acacias plantés sur place fonctionnent aussi.
|
||||
Ces chemins sont renouvelables avec les mécanismes vanilla de végétation ; aucun chunk n'est repeuplé.
|
||||
|
||||
**Le climat détermine la découverte.** Une semence rapportée peut être cultivée chez soi,
|
||||
sans verrou climatique supplémentaire. Le riz garde sa condition eau/boue, la vanille son support en bois.
|
||||
Les cuissons, fermentations et séchages utilisent les appareils existants ; leurs recettes sont dans JEI.
|
||||
Le chaudron doit être remué avec un bâton, le moulin est manuel, la cuisinière démarre avec son combustible.
|
||||
|
||||
## Les neuf îles
|
||||
|
||||
| Île | Habitat | Cultures et produits |
|
||||
| --- | --- | --- |
|
||||
| [Nord-Ouest](northwest.md) | Prairies et taïgas | Blé, Carotte, Pomme de terre, Betterave, Fraise, Laitue, Oignon, Ail, Chou, Menthe, Tomate, Maïs, Concombre, Poivron, Haricots secs, Basilic |
|
||||
| [Nord](north.md) | Vallées boréales et clairières | Blé, Carotte, Pomme de terre, Betterave, Fraise, Laitue, Oignon, Ail, Chou, Menthe |
|
||||
| [Nord-Est](northeast.md) | Marais frais et jardin pâle | Concombre, Laitue, Chou, Haricots secs, Menthe |
|
||||
| [Ouest](west.md) | Oasis de l'archipel | Tomate, Maïs, Poivron, Aubergine, Raisin, Basilic, Citron, Olives |
|
||||
| [Sanctuary](center.md) | Forêts, prairies et sous-bois tempérés | Blé, Carotte, Pomme de terre, Betterave, Fraise, Tomate, Maïs, Concombre, Poivron, Laitue, Oignon, Ail, Chou, Haricots secs, Basilic, Menthe |
|
||||
| [Est](east.md) | Jungles autour du volcan | Ananas, Riz, Graines de soja, Piment, Cerises de café, Gousse de vanille verte |
|
||||
| [Sud-Ouest](southwest.md) | Savane, prairie sèche et désert | Maïs, Raisin, Pois chiches, Piment, Coriandre, Olives |
|
||||
| [Sud](south.md) | Bosquets et berges des canyons | Maïs, Raisin, Pois chiches, Piment, Coriandre, Olives |
|
||||
| [Sud-Est](southeast.md) | Jungle, mangrove et bambou | Ananas, Riz, Graines de soja, Piment, Cerises de café, Gousse de vanille verte |
|
||||
|
||||
## Toutes les cultures et leurs semences
|
||||
|
||||
| Récolte | Où chercher | À replanter |
|
||||
| --- | --- | --- |
|
||||
| Fraise | Nord-Ouest, Nord, Sanctuary | Graines de fraise |
|
||||
| Tomate | Nord-Ouest, Ouest, Sanctuary | Graines de tomate |
|
||||
| Ananas | Est, Sud-Est | Couronne d'ananas |
|
||||
| Maïs | Nord-Ouest, Ouest, Sanctuary, Sud-Ouest, Sud | Grains de maïs |
|
||||
| Concombre | Nord-Ouest, Nord-Est, Sanctuary | Graines de concombre |
|
||||
| Poivron | Nord-Ouest, Ouest, Sanctuary | Graines de poivron |
|
||||
| Aubergine | Ouest | Graines d'aubergine |
|
||||
| Laitue | Nord-Ouest, Nord, Nord-Est, Sanctuary | Graines de laitue |
|
||||
| Oignon | Nord-Ouest, Nord, Sanctuary | Oignon |
|
||||
| Ail | Nord-Ouest, Nord, Sanctuary | Ail |
|
||||
| Chou | Nord-Ouest, Nord, Nord-Est, Sanctuary | Graines de chou |
|
||||
| Raisin | Ouest, Sud-Ouest, Sud | Pépins de raisin |
|
||||
| Riz | Est, Sud-Est | Riz |
|
||||
| Graines de soja | Est, Sud-Est | Graines de soja |
|
||||
| Haricots secs | Nord-Ouest, Nord-Est, Sanctuary | Haricots secs |
|
||||
| Pois chiches | Sud-Ouest, Sud | Pois chiches |
|
||||
| Piment | Est, Sud-Ouest, Sud, Sud-Est | Graines de piment |
|
||||
| Basilic | Nord-Ouest, Ouest, Sanctuary | Graines de basilic |
|
||||
| Menthe | Nord-Ouest, Nord, Nord-Est, Sanctuary | Bouture de menthe |
|
||||
| Coriandre | Sud-Ouest, Sud | Graines de coriandre |
|
||||
| Blé | Nord-Ouest, Nord, Sanctuary | Graines de blé |
|
||||
| Carotte | Nord-Ouest, Nord, Sanctuary | Carotte |
|
||||
| Pomme de terre | Nord-Ouest, Nord, Sanctuary | Pomme de terre |
|
||||
| Betterave | Nord-Ouest, Nord, Sanctuary | Graines de betterave |
|
||||
| Cerises de café | Est, Sud-Est | Grains de café verts |
|
||||
| Gousse de vanille verte | Est, Sud-Est | Bouture de vanille |
|
||||
| Citron | Ouest | Pousse de citronnier |
|
||||
| Olives | Ouest, Sud-Ouest, Sud | Pousse d'olivier |
|
||||
|
||||
## Ingrédients vanilla et transformations
|
||||
|
||||
Pommes : feuilles de chêne. Baies sucrées : buissons des taïgas. Baies lumineuses : lianes des cavernes luxuriantes. Champignons : sous-bois et grottes admissibles. Canne à sucre : berges, puis sucre à l’établi. Cacao : cabosses des troncs de jungle. Miel : ruches avec une fiole, feu de camp conseillé. Œufs : poules ; lait : seau sur une vache ; viandes : élevage ou chasse ; poissons : pêche ou capture dans les eaux peuplées. Ces règles natives ne sont pas remplacées.
|
||||
|
||||
La farine et la levure alimentent les pâtes ; les récoltes servent aux recettes portées de la 26.2. Fromage, saucisson et vin sont des produits de préparation, pas des plantes à découvrir. Aucun nouveau thé, navet ou fruit absent du catalogue n’est annoncé comme jouable par cette répartition.
|
||||
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"temperate": [
|
||||
"minecraft:plains",
|
||||
"minecraft:sunflower_plains",
|
||||
"minecraft:forest",
|
||||
"minecraft:birch_forest",
|
||||
"minecraft:old_growth_birch_forest",
|
||||
"minecraft:flower_forest",
|
||||
"minecraft:dark_forest",
|
||||
"minecraft:dappled_forest",
|
||||
"minecraft:cherry_grove",
|
||||
"sanctuary_test:adventure_forest",
|
||||
"sanctuary_test:adventure_flowers",
|
||||
"sanctuary_test:adventure_autumn",
|
||||
"sanctuary_test:adventure_cherry",
|
||||
"sanctuary_test:adventure_jungle",
|
||||
"sanctuary_test:adventure_swamp",
|
||||
"sanctuary_test:adventure_grove",
|
||||
"sanctuary_test:adventure_lush",
|
||||
"sanctuary_test:adventure_marsh",
|
||||
"sanctuary:cavern_bamboo_grove",
|
||||
"sanctuary:cavern_birch_forest",
|
||||
"sanctuary:cavern_clearing",
|
||||
"sanctuary:cavern_dark_grove",
|
||||
"sanctuary:cavern_dry_woodland",
|
||||
"sanctuary:cavern_oak_forest",
|
||||
"sanctuary:cavern_rocky_heath",
|
||||
"sanctuary:layered_flower_meadow",
|
||||
"sanctuary:layered_lower_grove",
|
||||
"sanctuary:layered_meadow",
|
||||
"sanctuary:layered_mossy_terraces",
|
||||
"sanctuary:layered_rocky_terraces",
|
||||
"sanctuary:population_bamboo_grove",
|
||||
"sanctuary:population_birch_forest",
|
||||
"sanctuary:population_clearing",
|
||||
"sanctuary:population_dark_grove",
|
||||
"sanctuary:population_dry_woodland",
|
||||
"sanctuary:population_oak_forest",
|
||||
"sanctuary:population_rocky_heath",
|
||||
"sanctuary:rift_bamboo_grove",
|
||||
"sanctuary:rift_birch_forest",
|
||||
"sanctuary:rift_clearing",
|
||||
"sanctuary:rift_dark_grove",
|
||||
"sanctuary:rift_dry_woodland",
|
||||
"sanctuary:rift_oak_forest",
|
||||
"sanctuary:rift_rocky_heath",
|
||||
"sanctuary:starter_forest",
|
||||
"sanctuary:starter_forest_hydrology",
|
||||
"sanctuary:temperate_birch_forest",
|
||||
"sanctuary:temperate_dappled_forest",
|
||||
"sanctuary:temperate_flower_forest",
|
||||
"sanctuary:temperate_forest",
|
||||
"sanctuary:temperate_plains",
|
||||
"sanctuary:woodland_bamboo_grove",
|
||||
"sanctuary:woodland_birch_forest",
|
||||
"sanctuary:woodland_clearing",
|
||||
"sanctuary:woodland_dark_grove",
|
||||
"sanctuary:woodland_dry_woodland",
|
||||
"sanctuary:woodland_oak_forest",
|
||||
"sanctuary:woodland_rocky_heath"
|
||||
],
|
||||
"cool": [
|
||||
"minecraft:meadow",
|
||||
"minecraft:taiga",
|
||||
"minecraft:old_growth_pine_taiga",
|
||||
"minecraft:old_growth_spruce_taiga",
|
||||
"minecraft:snowy_taiga",
|
||||
"minecraft:snowy_plains",
|
||||
"minecraft:grove",
|
||||
"minecraft:windswept_hills",
|
||||
"minecraft:windswept_gravelly_hills",
|
||||
"minecraft:windswept_forest",
|
||||
"sanctuary:north_meadow206",
|
||||
"sanctuary:north_meadow207",
|
||||
"sanctuary:north_old_growth206",
|
||||
"sanctuary:north_old_growth207",
|
||||
"sanctuary:north_snowy_old_growth207",
|
||||
"sanctuary:north_snowy_plains206",
|
||||
"sanctuary:north_snowy_plains207",
|
||||
"sanctuary:north_snowy_taiga207",
|
||||
"sanctuary:north_taiga206",
|
||||
"sanctuary:north_taiga207"
|
||||
],
|
||||
"wet_cool": [
|
||||
"minecraft:swamp",
|
||||
"minecraft:pale_garden",
|
||||
"sanctuary:diagonal_pale229",
|
||||
"sanctuary:diagonal_pale230",
|
||||
"sanctuary:diagonal_swamp226",
|
||||
"sanctuary:diagonal_swamp229"
|
||||
],
|
||||
"warm_dry": [
|
||||
"minecraft:savanna",
|
||||
"minecraft:savanna_plateau",
|
||||
"minecraft:windswept_savanna",
|
||||
"minecraft:desert",
|
||||
"minecraft:badlands",
|
||||
"minecraft:eroded_badlands",
|
||||
"minecraft:wooded_badlands",
|
||||
"sanctuary:south_badlands209",
|
||||
"sanctuary:south_badlands210",
|
||||
"sanctuary:south_badlands211",
|
||||
"sanctuary:south_badlands212",
|
||||
"sanctuary:south_badlands213",
|
||||
"sanctuary:south_badlands216"
|
||||
],
|
||||
"warm": [
|
||||
"minecraft:beach",
|
||||
"sanctuary:west_beach218",
|
||||
"sanctuary:west_oasis218"
|
||||
],
|
||||
"tropical": [
|
||||
"minecraft:jungle",
|
||||
"minecraft:sparse_jungle",
|
||||
"minecraft:bamboo_jungle",
|
||||
"minecraft:mangrove_swamp",
|
||||
"sanctuary:diagonal_mangrove226",
|
||||
"sanctuary:east_bamboo223",
|
||||
"sanctuary:east_bamboo224",
|
||||
"sanctuary:east_giant_jungle223",
|
||||
"sanctuary:east_giant_jungle224",
|
||||
"sanctuary:east_sparse_jungle223",
|
||||
"sanctuary:east_sparse_jungle224"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# Sanctuary — cueillette et agriculture
|
||||
|
||||
[Retour au guide](README.md) · [Planche](planche.html)
|
||||
|
||||
Forêts, prairies et sous-bois tempérés.
|
||||
|
||||
| Aliment | Première récolte | À rapporter / replanter |
|
||||
| --- | --- | --- |
|
||||
| Blé | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de blé. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Carotte | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Carotte. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Pomme de terre | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Pomme de terre. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Betterave | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de betterave. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Fraise | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de fraise. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Tomate | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de tomate. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Maïs | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Grains de maïs. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Concombre | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de concombre. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Poivron | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de poivron. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Laitue | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de laitue. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Oignon | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Oignon. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Ail | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Ail. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Chou | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de chou. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Haricots secs | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Haricots secs. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Basilic | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de basilic. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Menthe | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Bouture de menthe. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
|
||||
## Ressources vanilla
|
||||
|
||||
Pommes des chênes, champignons, miel, œufs, lait, viandes et canne à sucre selon les habitats. Présence selon les habitats et la génération native, sans garantie sur chaque île.
|
||||
|
||||
Les plantes sauvages donnent des ingrédients et des semences, jamais directement des plats cuisinés.
|
||||
Les herbes de ce climat servent aussi de solution de découverte dans les zones déjà explorées : voir les chances et outils dans le [guide](README.md).
|
||||
@@ -0,0 +1,21 @@
|
||||
# Est — cueillette et agriculture
|
||||
|
||||
[Retour au guide](README.md) · [Planche](planche.html)
|
||||
|
||||
Jungles autour du volcan.
|
||||
|
||||
| Aliment | Première récolte | À rapporter / replanter |
|
||||
| --- | --- | --- |
|
||||
| Ananas | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Couronne d'ananas. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Riz | Casser le riz sauvage sur les berges humides ; ou les herbes marines (seagrass) de jungle/mangrove, à la main. | Riz. Planter le grain dans une source d'eau au-dessus de boue. |
|
||||
| Graines de soja | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de soja. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Piment | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de piment. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Cerises de café | Clic droit sur un caféier mûr ; transformer ses cerises en grains verts. Les herbes tropicales peuvent aussi donner un grain. | Grains de café verts. Planter un grain vert sur terre/herbe ; clic droit à maturité. |
|
||||
| Gousse de vanille verte | Clic droit pour les gousses mûres ; casser un segment pour une bouture. Les lianes tropicales cassées à la main peuvent aussi donner une bouture. | Bouture de vanille. Poser la bouture contre un tronc ; elle grimpe vers le haut. |
|
||||
|
||||
## Ressources vanilla
|
||||
|
||||
Cacao sur les troncs de jungle, bambou ; rien dans le cratère. Présence selon les habitats et la génération native, sans garantie sur chaque île.
|
||||
|
||||
Les plantes sauvages donnent des ingrédients et des semences, jamais directement des plats cuisinés.
|
||||
Les herbes de ce climat servent aussi de solution de découverte dans les zones déjà explorées : voir les chances et outils dans le [guide](README.md).
|
||||
@@ -0,0 +1,25 @@
|
||||
# Nord — cueillette et agriculture
|
||||
|
||||
[Retour au guide](README.md) · [Planche](planche.html)
|
||||
|
||||
Vallées boréales et clairières.
|
||||
|
||||
| Aliment | Première récolte | À rapporter / replanter |
|
||||
| --- | --- | --- |
|
||||
| Blé | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de blé. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Carotte | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Carotte. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Pomme de terre | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Pomme de terre. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Betterave | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de betterave. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Fraise | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de fraise. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Laitue | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de laitue. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Oignon | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Oignon. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Ail | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Ail. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Chou | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de chou. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Menthe | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Bouture de menthe. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
|
||||
## Ressources vanilla
|
||||
|
||||
Baies sucrées, lapins, saumon dans les eaux peuplées. Présence selon les habitats et la génération native, sans garantie sur chaque île.
|
||||
|
||||
Les plantes sauvages donnent des ingrédients et des semences, jamais directement des plats cuisinés.
|
||||
Les herbes de ce climat servent aussi de solution de découverte dans les zones déjà explorées : voir les chances et outils dans le [guide](README.md).
|
||||
@@ -0,0 +1,20 @@
|
||||
# Nord-Est — cueillette et agriculture
|
||||
|
||||
[Retour au guide](README.md) · [Planche](planche.html)
|
||||
|
||||
Marais frais et jardin pâle.
|
||||
|
||||
| Aliment | Première récolte | À rapporter / replanter |
|
||||
| --- | --- | --- |
|
||||
| Concombre | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de concombre. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Laitue | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de laitue. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Chou | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de chou. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Haricots secs | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Haricots secs. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Menthe | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Bouture de menthe. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
|
||||
## Ressources vanilla
|
||||
|
||||
Champignons, poissons dans les eaux peuplées. Présence selon les habitats et la génération native, sans garantie sur chaque île.
|
||||
|
||||
Les plantes sauvages donnent des ingrédients et des semences, jamais directement des plats cuisinés.
|
||||
Les herbes de ce climat servent aussi de solution de découverte dans les zones déjà explorées : voir les chances et outils dans le [guide](README.md).
|
||||
@@ -0,0 +1,31 @@
|
||||
# Nord-Ouest — cueillette et agriculture
|
||||
|
||||
[Retour au guide](README.md) · [Planche](planche.html)
|
||||
|
||||
Prairies et taïgas.
|
||||
|
||||
| Aliment | Première récolte | À rapporter / replanter |
|
||||
| --- | --- | --- |
|
||||
| Blé | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de blé. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Carotte | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Carotte. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Pomme de terre | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Pomme de terre. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Betterave | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de betterave. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Fraise | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de fraise. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Laitue | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de laitue. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Oignon | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Oignon. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Ail | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Ail. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Chou | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de chou. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Menthe | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Bouture de menthe. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Tomate | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de tomate. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Maïs | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Grains de maïs. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Concombre | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de concombre. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Poivron | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de poivron. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Haricots secs | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Haricots secs. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Basilic | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de basilic. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
|
||||
## Ressources vanilla
|
||||
|
||||
Baies sucrées, champignons, lapins. Présence selon les habitats et la génération native, sans garantie sur chaque île.
|
||||
|
||||
Les plantes sauvages donnent des ingrédients et des semences, jamais directement des plats cuisinés.
|
||||
Les herbes de ce climat servent aussi de solution de découverte dans les zones déjà explorées : voir les chances et outils dans le [guide](README.md).
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,116 @@
|
||||
# Séchoir — provisions, ingrédients et nouvelles combinaisons
|
||||
|
||||
**Beta.318 locale — 30 octobre 2026.** [Guide des récoltes](README.md) · [Contrat et vérifications](../../dried-cuisine-beta318.md).
|
||||
|
||||
## Recettes implémentées
|
||||
|
||||
Quatre crochets indépendants, un objet par crochet : on peut mélanger quatre aliments différents.
|
||||
Un ingrédient donne un produit séché. Les durées sont des minutes de jeu à 20 ticks/s,
|
||||
avec le bloc chargé ; la pluie et l’immersion suspendent le travail. Un toit protège de la pluie.
|
||||
Les entonnoirs alimentent les crochets libres et retirent uniquement les produits prêts par-dessous.
|
||||
Les aliments prêts se récupèrent à main vide. Les nouveaux produits rejoignent Nourriture et boissons,
|
||||
sans nouvel onglet. Les recettes et leurs durées sont visibles dans JEI.
|
||||
|
||||
| À suspendre | Résultat | Durée |
|
||||
| --- | --- | --- |
|
||||
| Préparation de saucisson | Saucisson séché | 5 min |
|
||||
| Bœuf cru | Viande séchée | 4 min |
|
||||
| Côtelette de porc crue | Viande séchée | 4 min |
|
||||
| Cabillaud cru | Cabillaud séché | 2 min |
|
||||
| Saumon cru | Saumon séché | 2 min |
|
||||
| Raisin | Raisins secs | 1 min 30 s |
|
||||
| Tomate | Tomates séchées | 2 min |
|
||||
| Piment | Piment séché | 1 min 30 s |
|
||||
| Basilic | Basilic séché | 1 min |
|
||||
| Menthe | Menthe séchée | 1 min |
|
||||
| Coriandre | Coriandre séchée | 1 min |
|
||||
| Pomme | Pomme séchée | 1 min 30 s |
|
||||
| Fraise | Fraises séchées | 1 min |
|
||||
| Ananas | Ananas séché | 2 min |
|
||||
| Gousse de vanille verte | Gousse de vanille | 3 min |
|
||||
| Préparation de saucisson pimenté | Saucisson pimenté | 5 min |
|
||||
|
||||
Le bœuf et le porc donnent le même objet **Viande séchée**. Cabillaud et saumon donnent
|
||||
deux produits distincts. L’ananas rend exactement une couronne à la fin du séchage,
|
||||
pour être replanté. Le produit séché ne rend pas une seconde couronne à sa consommation.
|
||||
La gousse de vanille garde son identifiant : sa production passe du fumoir au séchoir.
|
||||
Un ancien fumoir peut donc garder sa gousse verte sans poursuivre ce traitement après la mise à jour.
|
||||
|
||||
Les viandes, poissons, raisins secs, tomates et fruits séchés sont comestibles.
|
||||
Le piment et les trois herbes séchés sont des ingrédients. Le séchage n’ajoute pas de
|
||||
péremption, de combustible ou de bonification hors ligne.
|
||||
|
||||
## Combinaisons déjà utilisables dans la cuisine existante
|
||||
|
||||
Les herbes et piments séchés remplacent leur équivalent frais, un pour un,
|
||||
dans les préparations de cuisine. Les anciennes recettes aux produits frais restent disponibles.
|
||||
La fabrication des semences à partir des récoltes fraîches reste distincte.
|
||||
|
||||
| Plat existant | Ingrédients possibles | Appareil |
|
||||
| --- | --- | --- |
|
||||
| Chili de haricots | Haricots secs + Sauce tomate + Piment frais ou séché | Chaudron |
|
||||
| Ragoût de pois chiches | Pois chiches + Sauce tomate + Coriandre fraîche ou séchée + Piment frais ou séché | Chaudron |
|
||||
| Infusion de menthe | Menthe fraîche ou séchée + Fiole d’eau | Chaudron |
|
||||
| Sauce tomate | Pulpe de tomate + Sel + Basilic frais ou séché | Chaudron |
|
||||
| Sauce piquante | Pulpe de tomate + Piment frais ou séché + Vinaigre | Tonneau |
|
||||
| Kimchi | Chou + Sel + Piment frais ou séché | Tonneau |
|
||||
| Pizza margherita | Pâte crue + Sauce tomate + Fromage + Basilic frais ou séché | Cuisinière |
|
||||
| Curry de pois chiches | Pois chiches + Sauce tomate + Piment frais ou séché + Coriandre fraîche ou séchée | Poêle |
|
||||
| Omelette aux herbes | Œuf + Seau de lait + Basilic, menthe ou coriandre, frais ou séchés | Poêle |
|
||||
| Émincé de mouton épicé | Mouton cru + Oignon + Piment frais ou séché + Coriandre fraîche ou séchée | Poêle |
|
||||
| Poulet tomate-basilic | Poulet cru + Tomate + Basilic frais ou séché | Poêle |
|
||||
|
||||
La vanille séchée conserve aussi sa transformation existante au moulin en poudre de vanille.
|
||||
|
||||
## Les 18 nouvelles recettes implémentées
|
||||
|
||||
Toutes ces recettes sont jouables dans la beta.318 locale. Chaque ingrédient indiqué
|
||||
est payé une fois par lot. Les récipients figurent dans la liste ; ils reviennent une fois
|
||||
à la consommation. Le chaudron demande une dose d’eau par portion, de la chaleur et
|
||||
un coup de bâton pour choisir la préparation ; il conserve ensuite sa recette pour le réassort.
|
||||
Le moulin reste manuel, sans combustible. La cuisinière utilise son combustible habituel.
|
||||
|
||||
| Recette | Ingrédients complets | Appareil | Durée | Quantité |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Pain aux raisins | Pâte crue + Raisins secs | Cuisinière | 12 s | 1 |
|
||||
| Brioche vanillée aux raisins | Pâte crue + Raisins secs + Poudre de vanille + Beurre | Cuisinière | 16 s | 1 |
|
||||
| Biscuits aux fruits secs | Farine de blé + Pomme séchée + Fraises séchées + Beurre | Cuisinière | 12 s | 4 |
|
||||
| Pain aux tomates et basilic | Pâte crue + Tomates séchées + Basilic séché | Cuisinière | 14 s | 1 |
|
||||
| Sandwich du voyageur | Pain + Viande séchée + Tomates séchées + Laitue | Établi | Immédiat, manuel | 1 |
|
||||
| Sandwich au cabillaud séché | Pain + Cabillaud séché + Concombre + Citron | Établi | Immédiat, manuel | 1 |
|
||||
| Riz au saumon séché | Riz + Saumon séché + Coriandre séchée + Bol + Fiole d’eau | Chaudron | 14 s | 1 |
|
||||
| Ragoût du voyageur | Viande séchée + Pomme de terre + Carotte + Basilic séché + Bol + Fiole d’eau | Chaudron | 18 s | 1 |
|
||||
| Soupe de poisson séché | Cabillaud séché + Saumon séché + Oignon + Coriandre séchée + Bol + Fiole d’eau | Chaudron | 16 s | 1 |
|
||||
| Haricots à la viande séchée | Haricots secs + Viande séchée + Tomates séchées + Piment séché ou poudre de piment + Bol + Fiole d’eau | Chaudron | 18 s | 1 |
|
||||
| Salade de pois chiches aux tomates séchées | Pois chiches + Tomates séchées + Huile d'olive + Basilic séché + Bol | Établi | Immédiat, manuel | 1 |
|
||||
| Tomates marinées aux herbes | Tomates séchées + Huile d'olive + Ail + Basilic séché + Bol | Établi | Immédiat, manuel | 1 |
|
||||
| Mélange de fruits secs | Raisins secs + Pomme séchée + Fraises séchées + Ananas séché | Établi | Immédiat, manuel | 1 |
|
||||
| Infusion pomme-menthe | Pomme séchée + Menthe séchée + Fiole de miel + Fiole vide + Fiole d’eau | Chaudron | 11 s | 1 |
|
||||
| Dessert ananas-vanille | Ananas séché + Poudre de vanille + Crème + Bol | Établi | Immédiat, manuel | 1 |
|
||||
| Saucisson pimenté | Préparation de saucisson pimenté | Séchoir | 5 min | 1 |
|
||||
| Poudre de piment | Piment séché | Moulin | Immédiat, manuel | 2 |
|
||||
| Assaisonnement aux herbes | Basilic séché + Coriandre séchée + Sel | Établi | Immédiat, manuel | 2 |
|
||||
|
||||
**Préparation supplémentaire du saucisson pimenté :** à l’établi, une préparation de
|
||||
saucisson + un piment séché (ou une poudre de piment) + une coriandre séchée donnent
|
||||
une préparation pimentée. Elle sèche ensuite cinq minutes. Le résultat se pose et
|
||||
se partage en quatre tranches, comme le saucisson nature. Ce sont donc **19 transformations
|
||||
pour les 18 nouveaux résultats**.
|
||||
|
||||
La poudre de piment remplace aussi le piment dans les anciennes recettes de cuisine
|
||||
et l’assaisonnement remplace les herbes dans l’omelette. L’eau du chaudron peut venir
|
||||
d’une fiole d’eau ou de sa réserve ; les potions à effets sont refusées.
|
||||
|
||||
## Quand ça pique
|
||||
|
||||
Manger un plat épicé donne deux petites réactions visuelles et sonores de coup reçu,
|
||||
espacées de huit ticks (0,4 seconde). Cela ne retire aucun cœur, ne consomme aucune
|
||||
absorption et ne déplace pas le joueur. Les plats doux ne déclenchent pas cet effet.
|
||||
Le saucisson pimenté le déclenche aussi à chaque tranche réellement mangée.
|
||||
|
||||
## Graphismes
|
||||
|
||||
Les nouveaux plats utilisent les sprites existants avec une teinte native ; les saucissons
|
||||
réutilisent leurs modèles cubiques avec des couleurs distinctes
|
||||
pour distinguer les préparations. Les PNG d’origine restent intacts ; ces dessins
|
||||
peuvent recevoir des textures dédiées dans un prochain travail graphique.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Sud — cueillette et agriculture
|
||||
|
||||
[Retour au guide](README.md) · [Planche](planche.html)
|
||||
|
||||
Bosquets et berges des canyons.
|
||||
|
||||
| Aliment | Première récolte | À rapporter / replanter |
|
||||
| --- | --- | --- |
|
||||
| Maïs | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Grains de maïs. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Raisin | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Pépins de raisin. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Pois chiches | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Pois chiches. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Piment | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de piment. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Coriandre | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de coriandre. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Olives | Clic droit sur les feuilles avec fruits. Casser les feuilles pour une pousse ; les feuilles de chêne/acacia du climat correspondant peuvent aussi donner une pousse. | Pousse d'olivier. Planter la pousse sur terre/herbe ; attendre l'arbre. |
|
||||
|
||||
## Ressources vanilla
|
||||
|
||||
Lapins ; les cultures ne poussent pas sur la terre cuite nue. Présence selon les habitats et la génération native, sans garantie sur chaque île.
|
||||
|
||||
Les plantes sauvages donnent des ingrédients et des semences, jamais directement des plats cuisinés.
|
||||
Les herbes de ce climat servent aussi de solution de découverte dans les zones déjà explorées : voir les chances et outils dans le [guide](README.md).
|
||||
@@ -0,0 +1,21 @@
|
||||
# Sud-Est — cueillette et agriculture
|
||||
|
||||
[Retour au guide](README.md) · [Planche](planche.html)
|
||||
|
||||
Jungle, mangrove et bambou.
|
||||
|
||||
| Aliment | Première récolte | À rapporter / replanter |
|
||||
| --- | --- | --- |
|
||||
| Ananas | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Couronne d'ananas. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Riz | Casser le riz sauvage sur les berges humides ; ou les herbes marines (seagrass) de jungle/mangrove, à la main. | Riz. Planter le grain dans une source d'eau au-dessus de boue. |
|
||||
| Graines de soja | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de soja. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Piment | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de piment. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Cerises de café | Clic droit sur un caféier mûr ; transformer ses cerises en grains verts. Les herbes tropicales peuvent aussi donner un grain. | Grains de café verts. Planter un grain vert sur terre/herbe ; clic droit à maturité. |
|
||||
| Gousse de vanille verte | Clic droit pour les gousses mûres ; casser un segment pour une bouture. Les lianes tropicales cassées à la main peuvent aussi donner une bouture. | Bouture de vanille. Poser la bouture contre un tronc ; elle grimpe vers le haut. |
|
||||
|
||||
## Ressources vanilla
|
||||
|
||||
Cacao, pastèques et bambou selon la végétation native. Présence selon les habitats et la génération native, sans garantie sur chaque île.
|
||||
|
||||
Les plantes sauvages donnent des ingrédients et des semences, jamais directement des plats cuisinés.
|
||||
Les herbes de ce climat servent aussi de solution de découverte dans les zones déjà explorées : voir les chances et outils dans le [guide](README.md).
|
||||
@@ -0,0 +1,21 @@
|
||||
# Sud-Ouest — cueillette et agriculture
|
||||
|
||||
[Retour au guide](README.md) · [Planche](planche.html)
|
||||
|
||||
Savane, prairie sèche et désert.
|
||||
|
||||
| Aliment | Première récolte | À rapporter / replanter |
|
||||
| --- | --- | --- |
|
||||
| Maïs | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Grains de maïs. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Raisin | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Pépins de raisin. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Pois chiches | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Pois chiches. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Piment | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de piment. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Coriandre | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de coriandre. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Olives | Clic droit sur les feuilles avec fruits. Casser les feuilles pour une pousse ; les feuilles de chêne/acacia du climat correspondant peuvent aussi donner une pousse. | Pousse d'olivier. Planter la pousse sur terre/herbe ; attendre l'arbre. |
|
||||
|
||||
## Ressources vanilla
|
||||
|
||||
Lapins du désert ; chercher les sols vivants et les berges. Présence selon les habitats et la génération native, sans garantie sur chaque île.
|
||||
|
||||
Les plantes sauvages donnent des ingrédients et des semences, jamais directement des plats cuisinés.
|
||||
Les herbes de ce climat servent aussi de solution de découverte dans les zones déjà explorées : voir les chances et outils dans le [guide](README.md).
|
||||
@@ -0,0 +1,23 @@
|
||||
# Ouest — cueillette et agriculture
|
||||
|
||||
[Retour au guide](README.md) · [Planche](planche.html)
|
||||
|
||||
Oasis de l'archipel.
|
||||
|
||||
| Aliment | Première récolte | À rapporter / replanter |
|
||||
| --- | --- | --- |
|
||||
| Tomate | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de tomate. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Maïs | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Grains de maïs. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Poivron | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de poivron. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Aubergine | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines d'aubergine. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Raisin | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Pépins de raisin. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Basilic | Casser la plante sauvage ; rapporter sa récolte et sa semence. | Graines de basilic. Planter sur terre labourée. Les récoltes se multiplient selon les règles de leur culture. |
|
||||
| Citron | Clic droit sur les feuilles avec fruits. Casser les feuilles pour une pousse ; les feuilles de chêne/acacia du climat correspondant peuvent aussi donner une pousse. | Pousse de citronnier. Planter la pousse sur terre/herbe ; attendre l'arbre. |
|
||||
| Olives | Clic droit sur les feuilles avec fruits. Casser les feuilles pour une pousse ; les feuilles de chêne/acacia du climat correspondant peuvent aussi donner une pousse. | Pousse d'olivier. Planter la pousse sur terre/herbe ; attendre l'arbre. |
|
||||
|
||||
## Ressources vanilla
|
||||
|
||||
Poissons de mer, algues, canne à sucre sur les berges. Présence selon les habitats et la génération native, sans garantie sur chaque île.
|
||||
|
||||
Les plantes sauvages donnent des ingrédients et des semences, jamais directement des plats cuisinés.
|
||||
Les herbes de ce climat servent aussi de solution de découverte dans les zones déjà explorées : voir les chances et outils dans le [guide](README.md).
|
||||
+2
-2
@@ -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.297
|
||||
pack_version=beta.297
|
||||
mod_version=beta.318
|
||||
pack_version=beta.318
|
||||
resource_pack_version=beta.121
|
||||
maven_group=fr.koka.sanctuary
|
||||
jei_version=30.32.0-sanctuary.4
|
||||
|
||||
@@ -20,6 +20,10 @@ public final class AnchorProbeClient {
|
||||
public static void tick(Minecraft mc){
|
||||
if(mc.player==null||mc.level==null)return;
|
||||
var ctl=Probe.control();String who=System.getProperty("sanctuary.mediaClient");
|
||||
if(ctl.has("shaders_enabled")&&fr.koka.sanctuary.client.shader.VanillaLight.enabled()!=ctl.get("shaders_enabled").getAsBoolean())
|
||||
fr.koka.sanctuary.client.shader.VanillaLight.setEnabled(ctl.get("shaders_enabled").getAsBoolean());
|
||||
if(ctl.has("pbr_enabled")&&fr.koka.sanctuary.client.shader.VanillaLight.pbr()!=ctl.get("pbr_enabled").getAsBoolean())
|
||||
fr.koka.sanctuary.client.shader.VanillaLight.setPbr(ctl.get("pbr_enabled").getAsBoolean());
|
||||
if(ctl.has("hide_gui")&&mc.gui.hud.isHidden()!=ctl.get("hide_gui").getAsBoolean())mc.gui.hud.toggle();
|
||||
boolean a=who.endsWith("a");
|
||||
Vec3 aim=a?new Vec3(-3.5,81.5,.5):new Vec3(0,83,1);
|
||||
@@ -33,6 +37,7 @@ public final class AnchorProbeClient {
|
||||
net.minecraft.client.Screenshot.grab(mc.gameDirectory,"anchor-"+Probe.phase+".png",mc.gameRenderer.mainRenderTarget(),1,m->{});captured=Probe.phase;
|
||||
}
|
||||
var row=new JsonObject();row.addProperty("step",done);row.addProperty("screen",mc.gui.screen()==null?"":mc.gui.screen().getClass().getSimpleName());
|
||||
if(ctl.has("inspect_anchor_model"))inspectModel(mc,row);
|
||||
var target=Anchors.target(mc.level,mc.player);row.addProperty("target",target!=null);if(target!=null)row.addProperty("distance",target.distance());
|
||||
if(mc.gui.screen() instanceof AnchorScreen screen){var v=screen.snapshot();row.addProperty("settings",v.settings());row.addProperty("editable",v.editable());row.addProperty("message",v.message());row.addProperty("width",v.width());row.addProperty("height",v.height());}
|
||||
else if(mc.gui.screen()!=null&&mc.gui.screen().getClass().getSimpleName().equals("DisplayMenu")){
|
||||
@@ -40,6 +45,27 @@ public final class AnchorProbeClient {
|
||||
}
|
||||
Probe.write(Probe.LAB.resolve("anchor-"+who+".jsonl"),row);
|
||||
}
|
||||
private static void inspectModel(Minecraft mc,JsonObject row){
|
||||
var state=mc.level.getBlockState(AnchorProbe.POS);
|
||||
if(!state.is(Anchors.BLOCK))return;
|
||||
var parts=new java.util.ArrayList<net.minecraft.client.renderer.block.dispatch.BlockStateModelPart>();
|
||||
mc.getModelManager().getBlockStateModelSet().get(state).collectParts(net.minecraft.util.RandomSource.create(0),parts);
|
||||
int quads=0;for(var part:parts)for(int f=0;f<7;f++)quads+=part.getQuads(f==6?null:Direction.values()[f]).size();
|
||||
row.addProperty("anchor_model_quads",quads);row.addProperty("anchor_render_shape",state.getRenderShape().name());
|
||||
row.addProperty("shaders_active",fr.koka.sanctuary.client.shader.VanillaLight.active());
|
||||
row.addProperty("pbr_enabled",fr.koka.sanctuary.client.shader.VanillaLight.pbr());
|
||||
try{
|
||||
var pbr=fr.koka.sanctuary.client.shader.ProceduralPbr.class.getDeclaredMethod("applied");pbr.setAccessible(true);
|
||||
row.addProperty("pbr_applied",(boolean)pbr.invoke(null));
|
||||
var type=Class.forName("fr.koka.sanctuary.client.shader.PbrTerrain");var ctor=type.getDeclaredConstructor(boolean.class);ctor.setAccessible(true);
|
||||
var candidate=type.getDeclaredMethod("candidate",net.minecraft.world.level.block.state.BlockState.class);candidate.setAccessible(true);
|
||||
for(boolean transparent:new boolean[]{false,true})try(var terrain=(AutoCloseable)ctor.newInstance(transparent)){
|
||||
row.addProperty(transparent?"anchor_pbr_transparent":"anchor_pbr_opaque",(boolean)candidate.invoke(terrain,state));
|
||||
row.addProperty(transparent?"water_pbr_transparent":"water_pbr_opaque",(boolean)candidate.invoke(terrain,net.minecraft.world.level.block.Blocks.WATER.defaultBlockState()));
|
||||
row.addProperty(transparent?"glass_pbr_transparent":"wood_pbr_opaque",(boolean)candidate.invoke(terrain,(transparent?net.minecraft.world.level.block.Blocks.GLASS:net.minecraft.world.level.block.Blocks.OAK_PLANKS).defaultBlockState()));
|
||||
}
|
||||
}catch(Exception e){throw new IllegalStateException(e);}
|
||||
}
|
||||
private static boolean perform(Minecraft mc,String step,JsonObject ctl){
|
||||
if(step.equals("place")){
|
||||
mc.gameMode.useItemOn(mc.player,InteractionHand.MAIN_HAND,new BlockHitResult(new Vec3(-3.5,81.5,1),Direction.SOUTH,new BlockPos(-4,81,0),false));return true;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins { id 'java-library' }
|
||||
|
||||
group = rootProject.maven_group
|
||||
version = '0.3.1'
|
||||
version = '0.3.2'
|
||||
base { archivesName = 'sanctuary-media' }
|
||||
evaluationDependsOn(':sanctuary')
|
||||
|
||||
|
||||
+2
-165
@@ -1,170 +1,7 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"particle": "minecraft:block/oak_planks",
|
||||
"wood": "minecraft:block/oak_planks",
|
||||
"redstone": "minecraft:block/redstone_block"
|
||||
"particle": "minecraft:block/oak_planks"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
16,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"south": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"east": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"west": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"up": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"down": {
|
||||
"texture": "#wood"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
0,
|
||||
15,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
16,
|
||||
16,
|
||||
1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"south": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"east": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"west": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"up": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"down": {
|
||||
"texture": "#wood"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
0,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
1,
|
||||
15,
|
||||
1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"south": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"east": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"west": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"up": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"down": {
|
||||
"texture": "#wood"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
15,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
16,
|
||||
15,
|
||||
1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"south": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"east": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"west": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"up": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"down": {
|
||||
"texture": "#wood"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
1,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
3,
|
||||
3,
|
||||
2
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#redstone"
|
||||
},
|
||||
"south": {
|
||||
"texture": "#redstone"
|
||||
},
|
||||
"east": {
|
||||
"texture": "#redstone"
|
||||
},
|
||||
"west": {
|
||||
"texture": "#redstone"
|
||||
},
|
||||
"up": {
|
||||
"texture": "#redstone"
|
||||
},
|
||||
"down": {
|
||||
"texture": "#redstone"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"elements": []
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "sanctuary_media",
|
||||
"version": "0.3.1",
|
||||
"version": "0.3.2",
|
||||
"name": "Sanctuary Media",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"environment": "*",
|
||||
|
||||
@@ -7,6 +7,7 @@ version = rootProject.mod_version
|
||||
base { archivesName = 'sanctuary' }
|
||||
|
||||
apply from: 'gradle/origin-resources.gradle'
|
||||
apply from: 'gradle/agriculture-resources.gradle'
|
||||
|
||||
configurations { soundPhysicsArtifact { transitive = false } }
|
||||
def soundPhysics = "maven.modrinth:qyVF9oeo:${rootProject.sound_physics_artifact}"
|
||||
@@ -292,7 +293,7 @@ tasks.named('processGametestResources') {
|
||||
|
||||
def villageOnly = providers.gradleProperty('sanctuaryVillageOnly').map { it.toBoolean() }.getOrElse(false)
|
||||
def focused = providers.gradleProperty('sanctuaryFocusedTests').getOrElse('').split(',').findAll { !it.isEmpty() }
|
||||
def focusClasses = [happycreeper: 'HappyCreeperGameTests', whale290: 'FlyingWhale290GameTests', hunter286: 'Hunter286GameTests', smoker285: 'Smoker285GameTests', boomer284: 'Boomer284GameTests', spitter283: 'Spitter283GameTests', charger282: 'Charger282GameTests', infected280: 'Infected280GameTests', claims278: 'ClaimBattle278GameTests', claims277: 'ClaimDemeure277GameTests', menu275: 'Menu275GameTests', claims273: 'ClaimMap273GameTests', claims272: 'ChunkClaims272GameTests', overview271: 'Inhabitant271GameTests', property270: 'Property270GameTests', providence269: 'Providence269GameTests', providence268: 'Providence268GameTests', providence265: 'Providence265GameTests', familiar260: 'Familiar260GameTests', census: 'CensusGameTestsTerrain', insects245: 'Insect245GameTests', map244: 'Atlas244GameTests', ziplines242: 'Zipline242GameTests', waystones239: 'Waystone239GameTests', waystonesLarge239: 'WaystoneLarge239GameTests', forgerie238: 'Forgerie238GameTests', aptitudes236: 'Aptitudes236GameTests', solo232: 'Solo232GameTests', diagonal231: 'Diagonal231GameTests', diagonal230: 'Diagonal230GameTests', diagonal229: 'Diagonal229GameTests', diagonal228: 'Diagonal228GameTests', diagonal227: 'Diagonal227GameTests', diagonal226: 'Diagonal226GameTests', diagonal225: 'Diagonal225GameTests', east224: 'East224GameTests', east223: 'East223GameTests', rock222: 'YellowRock222GameTests', west221: 'West221GameTests', west220: 'West220GameTests', west219: 'West219GameTests', west218: 'West218GameTests', west217: 'West217GameTests', south216: 'South216GameTests', relays215: 'Relays215GameTests', south214: 'South214GameTests', south213: 'South213GameTests', south212: 'South212GameTests', south211: 'South211GameTests', south210: 'South210GameTests', south209: 'South209GameTests', north208: 'North208GameTests', north207: 'North207GameTests', north206: 'North206GameTests', north205: 'North205GameTests', base204: 'Base204GameTests', palace173: 'Palace173GameTests', horde172: 'Horde172GameTests', horde171: 'Horde171GameTests', horde170: 'Horde170GameTests', horde169: 'Horde169GameTests', horde168: 'Horde168GameTests', horde167: 'Horde167GameTests', plans072: 'Plans072GameTests', starter052: 'Starter052GameTests', dispenser051: 'Dispenser051GameTests', head050: 'Head050GameTests', boatshead: 'BoatsHead049GameTests', poultry: 'Poultry048GameTests', starinspect: 'StarInspection047GameTests', skyflight: 'SkyFlight046GameTests', eggaudit: 'Companion044GameTests', headblocks: 'HeadBlocks043GameTests', operator: 'OperatorStars040GameTests', starter: 'Starter039GameTests', gravelayout: 'GraveInventory039GameTests', foodknowledge: 'FoodKnowledge039GameTests', graves: 'GravesFood038GameTests', realtime: 'Realtime037GameTests', corrections: 'Corrections036GameTests', collections: 'Collections035GameTests', notifications: 'Notifications034GameTests', menus: 'Menus033GameTests', aerial: 'Aerial24WorldGameTests', transit: 'Transit29WorldGameTests', village: 'Village306GameTests', drained: 'Drained307GameTests', expeditions: 'AncientExpeditions001GameTests', progression: 'Progression003GameTests', map: 'Atlas005GameTests', demeure: 'Demeure006GameTests', mining: 'Mining008GameTests', building: 'Building009GameTests', inventory: 'Inventory010GameTests', cycle: 'Cycle011GameTests', inventoryflow: 'Inventory012GameTests', sorting: 'Inventory014GameTests', tutorial: 'Tutorial016GameTests', movement: 'Movement017GameTests', accessories: 'Accessory018GameTests', companions: 'Companion019GameTests', familiar: 'Familiar020GameTests', carry: 'Carry027GameTests', familiarhit: 'Familiar029GameTests', refonte: 'Companion032GameTests', lights: 'Dynamic030GameTests', cosmetics: 'HeadCosmetic021GameTests', startup: 'StartupPerformanceGameTests', recipes: 'Recipes024GameTests']
|
||||
def focusClasses = [driedcuisine318: 'DriedCuisine318GameTests', drying317: 'Drying317GameTests', foraging316: 'Foraging316GameTests', sausage315: 'Sausage315GameTests', cheesewine314: 'CheeseWine314GameTests', drying313: 'Drying313GameTests', cauldron312: 'Cauldron312GameTests', cauldron311: 'Cauldron311GameTests', pan310: 'Pan310GameTests', oven308: 'Oven308GameTests', mill306: 'Mill306GameTests', crushing305: 'Crushing305GameTests', kitchen304: 'Kitchen304GameTests', cuisine303: 'Cuisine303GameTests', vanilla301: 'Vanilla301GameTests', agriculture300: 'Agriculture300GameTests', happycreeper: 'HappyCreeperGameTests', whale290: 'FlyingWhale290GameTests', hunter286: 'Hunter286GameTests', smoker285: 'Smoker285GameTests', boomer284: 'Boomer284GameTests', spitter283: 'Spitter283GameTests', charger282: 'Charger282GameTests', infected280: 'Infected280GameTests', claims278: 'ClaimBattle278GameTests', claims277: 'ClaimDemeure277GameTests', menu275: 'Menu275GameTests', claims273: 'ClaimMap273GameTests', claims272: 'ChunkClaims272GameTests', overview271: 'Inhabitant271GameTests', property270: 'Property270GameTests', providence269: 'Providence269GameTests', providence268: 'Providence268GameTests', providence265: 'Providence265GameTests', familiar260: 'Familiar260GameTests', census: 'CensusGameTestsTerrain', insects245: 'Insect245GameTests', map244: 'Atlas244GameTests', ziplines242: 'Zipline242GameTests', waystones239: 'Waystone239GameTests', waystonesLarge239: 'WaystoneLarge239GameTests', forgerie238: 'Forgerie238GameTests', aptitudes236: 'Aptitudes236GameTests', solo232: 'Solo232GameTests', diagonal231: 'Diagonal231GameTests', diagonal230: 'Diagonal230GameTests', diagonal229: 'Diagonal229GameTests', diagonal228: 'Diagonal228GameTests', diagonal227: 'Diagonal227GameTests', diagonal226: 'Diagonal226GameTests', diagonal225: 'Diagonal225GameTests', east224: 'East224GameTests', east223: 'East223GameTests', rock222: 'YellowRock222GameTests', west221: 'West221GameTests', west220: 'West220GameTests', west219: 'West219GameTests', west218: 'West218GameTests', west217: 'West217GameTests', south216: 'South216GameTests', relays215: 'Relays215GameTests', south214: 'South214GameTests', south213: 'South213GameTests', south212: 'South212GameTests', south211: 'South211GameTests', south210: 'South210GameTests', south209: 'South209GameTests', north208: 'North208GameTests', north207: 'North207GameTests', north206: 'North206GameTests', north205: 'North205GameTests', base204: 'Base204GameTests', palace173: 'Palace173GameTests', horde172: 'Horde172GameTests', horde171: 'Horde171GameTests', horde170: 'Horde170GameTests', horde169: 'Horde169GameTests', horde168: 'Horde168GameTests', horde167: 'Horde167GameTests', plans072: 'Plans072GameTests', starter052: 'Starter052GameTests', dispenser051: 'Dispenser051GameTests', head050: 'Head050GameTests', boatshead: 'BoatsHead049GameTests', poultry: 'Poultry048GameTests', starinspect: 'StarInspection047GameTests', skyflight: 'SkyFlight046GameTests', eggaudit: 'Companion044GameTests', headblocks: 'HeadBlocks043GameTests', operator: 'OperatorStars040GameTests', starter: 'Starter039GameTests', gravelayout: 'GraveInventory039GameTests', foodknowledge: 'FoodKnowledge039GameTests', graves: 'GravesFood038GameTests', realtime: 'Realtime037GameTests', corrections: 'Corrections036GameTests', collections: 'Collections035GameTests', notifications: 'Notifications034GameTests', menus: 'Menus033GameTests', aerial: 'Aerial24WorldGameTests', transit: 'Transit29WorldGameTests', village: 'Village306GameTests', drained: 'Drained307GameTests', expeditions: 'AncientExpeditions001GameTests', progression: 'Progression003GameTests', map: 'Atlas005GameTests', demeure: 'Demeure006GameTests', mining: 'Mining008GameTests', building: 'Building009GameTests', inventory: 'Inventory010GameTests', cycle: 'Cycle011GameTests', inventoryflow: 'Inventory012GameTests', sorting: 'Inventory014GameTests', tutorial: 'Tutorial016GameTests', movement: 'Movement017GameTests', accessories: 'Accessory018GameTests', companions: 'Companion019GameTests', familiar: 'Familiar020GameTests', carry: 'Carry027GameTests', familiarhit: 'Familiar029GameTests', refonte: 'Companion032GameTests', lights: 'Dynamic030GameTests', cosmetics: 'HeadCosmetic021GameTests', startup: 'StartupPerformanceGameTests', recipes: 'Recipes024GameTests']
|
||||
focused.each { if (!focusClasses.containsKey(it)) throw new GradleException('Unknown focused native test: ' + it) }
|
||||
inputs.property('focusedNative', focused)
|
||||
def yellowRock222Client = providers.gradleProperty('sanctuaryYellowRock222ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
@@ -417,6 +418,59 @@ tasks.named('processGametestResources') {
|
||||
def zombie287Client = providers.gradleProperty('sanctuaryZombie287ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('zombie287Client', zombie287Client)
|
||||
if (zombie287Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Zombie287ClientChecks') }
|
||||
def sausage315Client = providers.gradleProperty('sanctuarySausage315ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sausage315Client', sausage315Client)
|
||||
if (sausage315Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Sausage315ClientChecks') }
|
||||
def cheeseWine314Client = providers.gradleProperty('sanctuaryCheeseWine314ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('cheeseWine314Client', cheeseWine314Client)
|
||||
if (cheeseWine314Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.CheeseWine314ClientChecks') }
|
||||
def driedCuisine318Client = providers.gradleProperty('sanctuaryDriedCuisine318ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('driedCuisine318Client', driedCuisine318Client)
|
||||
if (driedCuisine318Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.DriedCuisine318ClientChecks') }
|
||||
def drying317Client = providers.gradleProperty('sanctuaryDrying317ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('drying317Client', drying317Client)
|
||||
if (drying317Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Drying317ClientChecks') }
|
||||
|
||||
def drying313Client = providers.gradleProperty('sanctuaryDrying313ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('drying313Client', drying313Client)
|
||||
if (drying313Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Drying313ClientChecks') }
|
||||
def cauldron312Client = providers.gradleProperty('sanctuaryCauldron312ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('cauldron312Client', cauldron312Client)
|
||||
if (cauldron312Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Cauldron312ClientChecks') }
|
||||
def cauldron311Client = providers.gradleProperty('sanctuaryCauldron311ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('cauldron311Client', cauldron311Client)
|
||||
if (cauldron311Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Cauldron311ClientChecks') }
|
||||
def pan310Client = providers.gradleProperty('sanctuaryPan310ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('pan310Client', pan310Client)
|
||||
if (pan310Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Pan310ClientChecks') }
|
||||
def oven308Client = providers.gradleProperty('sanctuaryOven308ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('oven308Client', oven308Client)
|
||||
if (oven308Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Oven308ClientChecks') }
|
||||
def mill306Client = providers.gradleProperty('sanctuaryMill306ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('mill306Client', mill306Client)
|
||||
if (mill306Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Mill306ClientChecks') }
|
||||
def crushing305Client = providers.gradleProperty('sanctuaryCrushing305ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('crushing305Client', crushing305Client)
|
||||
if (crushing305Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Crushing305ClientChecks') }
|
||||
def kitchen304Client = providers.gradleProperty('sanctuaryKitchen304ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('kitchen304Client', kitchen304Client)
|
||||
if (kitchen304Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Kitchen304ClientChecks') }
|
||||
def cuisine303Client = providers.gradleProperty('sanctuaryCuisine303ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('cuisine303Client', cuisine303Client)
|
||||
if (cuisine303Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Cuisine303ClientChecks') }
|
||||
|
||||
def vanilla301Client = providers.gradleProperty('sanctuaryVanilla301ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('vanilla301Client', vanilla301Client)
|
||||
if (vanilla301Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Vanilla301ClientChecks') }
|
||||
|
||||
def foraging316Client = providers.gradleProperty('sanctuaryForaging316ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('foraging316Client', foraging316Client)
|
||||
if (foraging316Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Foraging316ClientChecks') }
|
||||
|
||||
def agriculture300Client = providers.gradleProperty('sanctuaryAgriculture300ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('agriculture300Client', agriculture300Client)
|
||||
if (agriculture300Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Agriculture300ClientChecks') }
|
||||
|
||||
def whale290Client = providers.gradleProperty('sanctuaryWhale290ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('whale290Client', whale290Client)
|
||||
if (whale290Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.FlyingWhale290ClientChecks') }
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
def agricultureResources = layout.buildDirectory.dir('generated/agricultureResources')
|
||||
sourceSets.main.resources.srcDir(agricultureResources)
|
||||
tasks.register('generateAgricultureResources', Exec) {
|
||||
inputs.file(rootProject.file('scripts/agriculture_resources.py'))
|
||||
inputs.file(rootProject.file('scripts/data/agriculture.json'))
|
||||
inputs.file(rootProject.file('scripts/foraging_resources.py'))
|
||||
inputs.file(rootProject.file('scripts/data/foraging.json'))
|
||||
inputs.dir(file('src/main/resources/data/sanctuary/worldgen/biome'))
|
||||
outputs.dir(agricultureResources)
|
||||
doFirst { delete(agricultureResources) }
|
||||
commandLine('python3', rootProject.file('scripts/agriculture_resources.py'), '--output', agricultureResources.get().asFile)
|
||||
}
|
||||
tasks.named('processResources') { dependsOn('generateAgricultureResources') }
|
||||
tasks.matching { it.name == 'sourcesJar' }.configureEach { dependsOn('generateAgricultureResources') }
|
||||
|
||||
def cuisineResources = layout.buildDirectory.dir('generated/cuisineResources')
|
||||
sourceSets.main.resources.srcDir(cuisineResources)
|
||||
tasks.register('generateCuisineResources', Exec) {
|
||||
inputs.file(rootProject.file('scripts/dried_food_resources.py'))
|
||||
inputs.file(rootProject.file('scripts/cuisine_resources.py'))
|
||||
inputs.file(rootProject.file('scripts/data/cuisine.json'))
|
||||
inputs.file(rootProject.file('scripts/data/agriculture.json'))
|
||||
outputs.dir(cuisineResources)
|
||||
doFirst { delete(cuisineResources) }
|
||||
commandLine('python3', rootProject.file('scripts/cuisine_resources.py'), '--output', cuisineResources.get().asFile)
|
||||
}
|
||||
tasks.named('processResources') { dependsOn('generateCuisineResources') }
|
||||
tasks.matching { it.name == 'sourcesJar' }.configureEach { dependsOn('generateCuisineResources') }
|
||||
|
||||
def kitchenResources = layout.buildDirectory.dir('generated/kitchenResources')
|
||||
sourceSets.main.resources.srcDir(kitchenResources)
|
||||
tasks.register('generateKitchenResources', Exec) {
|
||||
inputs.file(rootProject.file('scripts/kitchen_resources.py'))
|
||||
inputs.file(rootProject.file('scripts/drying_resources.py'))
|
||||
inputs.file(rootProject.file('scripts/dried_food_resources.py'))
|
||||
inputs.file(rootProject.file('scripts/data/drying-foods.json'))
|
||||
inputs.file(rootProject.file('scripts/dried_cuisine_resources.py'))
|
||||
inputs.file(rootProject.file('scripts/data/dried-cuisine.json'))
|
||||
inputs.file(rootProject.file('scripts/cheese_wine_resources.py'))
|
||||
inputs.file(rootProject.file('scripts/sausage_serving_resources.py'))
|
||||
inputs.files(['kitchen-assets.json', 'kitchen-crafting.json', 'cuisine.json'].collect { rootProject.file('scripts/data/' + it) })
|
||||
outputs.dir(kitchenResources)
|
||||
doFirst { delete(kitchenResources) }
|
||||
commandLine('python3', rootProject.file('scripts/kitchen_resources.py'), '--output', kitchenResources.get().asFile)
|
||||
}
|
||||
tasks.named('processResources') { dependsOn('generateKitchenResources') }
|
||||
tasks.matching { it.name == 'sourcesJar' }.configureEach { dependsOn('generateKitchenResources') }
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import fr.koka.sanctuary.agriculture.*;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
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.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.phys.*;
|
||||
import static fr.koka.sanctuary.agriculture.AgricultureBlocks.*;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Vulkan/Sodium, actual planting packets, all baked states/items, FR/EN and an actual disk reload. */
|
||||
public final class Agriculture300ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos INTERACTION = new BlockPos(8, 1, 15);
|
||||
private static void check(boolean ok, String message) { if (!ok) throw new AssertionError(message); }
|
||||
|
||||
@Override public void runTest(ClientGameTestContext c) {
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable();
|
||||
var intro = new AtomicBoolean(true);
|
||||
var snapshot = new AtomicReference<Map<BlockPos, BlockState>>();
|
||||
c.runOnClient(m -> {
|
||||
var device = com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();
|
||||
check(device.backendName().equalsIgnoreCase("vulkan"), "Agriculture validation requires Vulkan");
|
||||
System.out.println("AGRICULTURE300_BACKEND " + device.backendName() + " / " + device.name());
|
||||
m.options.renderDistance().set(8); m.options.simulationDistance().set(8); m.options.pauseOnLostFocus = false;
|
||||
});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m -> {
|
||||
if (intro.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
|
||||
IntroClient.setQuickTestIntro(true); Starter039TestSupport.selectFirst(screen);
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
|
||||
press(screen, "sanctuary.progression.enter"); intro.set(false);
|
||||
}
|
||||
});
|
||||
TestWorldSave saved;
|
||||
try (var world = c.worldBuilder().adjustSettings(s -> {
|
||||
s.setName("Agriculture300-" + UUID.randomUUID()); s.setSeed("300"); 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()) {
|
||||
c.waitFor(m -> m.player != null && ProgressionClient.active() && m.gui.screen() == null && !IntroSpawnFlash.active(), 1200);
|
||||
var server = world.getServer();
|
||||
server.runOnServer(s -> {
|
||||
var player = s.getPlayerList().getPlayers().getFirst(); var level = player.level();
|
||||
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");
|
||||
level.getGameRules().set(GameRules.SPAWN_MOBS, false, s);
|
||||
level.getGameRules().set(GameRules.RANDOM_TICK_SPEED, 0, s);
|
||||
gallery(level);
|
||||
level.setBlockAndUpdate(INTERACTION.below(), Blocks.FARMLAND.defaultBlockState().setValue(FarmlandBlock.MOISTURE, 7));
|
||||
player.teleportTo(8.5, 1, 17.5); player.setYRot(180); player.setXRot(20);
|
||||
player.setGameMode(GameType.SURVIVAL); player.getInventory().clearContent();
|
||||
player.getInventory().setItem(0, new ItemStack(AgricultureItems.CORN_KERNELS, 2)); player.inventoryMenu.broadcastChanges();
|
||||
});
|
||||
c.waitFor(m -> m.player.getMainHandItem().is(AgricultureItems.CORN_KERNELS), 400);
|
||||
c.runOnClient(m -> m.gameMode.useItemOn(m.player, InteractionHand.MAIN_HAND, hit(INTERACTION.below())));
|
||||
c.waitFor(m -> m.level.getBlockState(INTERACTION).is(CORN_CROP), 200);
|
||||
server.runOnServer(s -> {
|
||||
var player = s.getPlayerList().getPlayers().getFirst();
|
||||
check(player.level().getBlockState(INTERACTION).is(CORN_CROP) && player.getMainHandItem().getCount() == 1,
|
||||
"Real survival planting packet consumes one seed on the authoritative server");
|
||||
player.getInventory().setItem(0, new ItemStack(Items.BONE_MEAL, 16)); player.inventoryMenu.broadcastChanges();
|
||||
});
|
||||
c.waitFor(m -> m.player.getMainHandItem().is(Items.BONE_MEAL), 200);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
c.runOnClient(m -> m.gameMode.useItemOn(m.player, InteractionHand.MAIN_HAND, hit(INTERACTION)));
|
||||
c.waitTicks(5);
|
||||
}
|
||||
server.runOnServer(s -> check(s.overworld().getBlockState(INTERACTION.above()).is(CORN_CROP_UPPER), "Real bonemeal packets create the upper half"));
|
||||
c.runOnClient(m -> m.gameMode.startDestroyBlock(INTERACTION.above(), Direction.UP));
|
||||
c.waitFor(m -> m.level.getBlockState(INTERACTION).isAir() && m.level.getBlockState(INTERACTION.above()).isAir(), 200);
|
||||
c.runOnClient(m -> {
|
||||
m.getWindow().setWindowed(1280, 960);
|
||||
CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(), true, m.level.registryAccess());
|
||||
check(allItems().size() == 48, "All 48 planting/harvest items are registered");
|
||||
check(BuiltInRegistries.CREATIVE_MODE_TAB.getOptional(SanctuaryMod.id("agriculture")).isEmpty(), "No new creative page is registered");
|
||||
var natural = BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.NATURAL_BLOCKS).value();
|
||||
var food = BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.FOOD_AND_DRINKS).value();
|
||||
for (var item : AgricultureItems.PLANTING_ITEMS) check(natural.getDisplayItems().stream().anyMatch(stack -> stack.is(item)), "Planting item is in vanilla Natural Blocks: " + item);
|
||||
for (var item : AgricultureItems.PRODUCE_ITEMS) check(food.getDisplayItems().stream().anyMatch(stack -> stack.is(item)), "Harvest is in vanilla Food & Drinks: " + item);
|
||||
check(food.getDisplayItems().stream().anyMatch(stack -> stack.is(AgricultureItems.GARLIC_CLOVE)), "Garlic clove is in Food & Drinks");
|
||||
int states = 0;
|
||||
for (var block : blocks()) for (var state : block.getStateDefinition().getPossibleStates()) {
|
||||
var parts = new ArrayList<BlockStateModelPart>();
|
||||
m.getModelManager().getBlockStateModelSet().get(state).collectParts(RandomSource.create(300), parts);
|
||||
int quads = 0;
|
||||
for (var part : parts) for (var direction : new Direction[]{null, Direction.UP, Direction.DOWN, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST})
|
||||
for (var quad : part.getQuads(direction)) {
|
||||
check(quad.materialInfo().sprite().contents().name().getNamespace().equals("sanctuary"), "Actual supplied crop/tree texture is baked: " + state);
|
||||
quads++;
|
||||
}
|
||||
check(quads > 0, "Every registered plant state has visible baked geometry: " + state); states++;
|
||||
if (state.is(ONION_CROP) || state.is(GARLIC_CROP))
|
||||
check(quads == 4, "Onion and garlic use two crossing planes, not a four-plane crop frame");
|
||||
if (state.is(CORN_CROP) || state.is(RICE_CROP))
|
||||
check(quads == 8, "Cereals keep their native field geometry");
|
||||
}
|
||||
for (var stack : allItems()) {
|
||||
var item = new net.minecraft.client.renderer.item.ItemStackRenderState();
|
||||
m.getItemModelResolver().updateForLiving(item, stack, ItemDisplayContext.GUI, m.player);
|
||||
var material = item.pickParticleMaterial(RandomSource.create(300));
|
||||
check(!item.isEmpty() && material != null && material.sprite().contents().name().getNamespace().equals("sanctuary"), "Actual item model resolves its supplied sprite: " + stack);
|
||||
}
|
||||
System.out.println("AGRICULTURE300_MODELS states=" + states + " items=48");
|
||||
});
|
||||
server.runOnServer(s -> {
|
||||
var player = s.getPlayerList().getPlayers().getFirst(); player.setGameMode(GameType.CREATIVE);
|
||||
player.teleportTo(10, 9, 23); player.setYRot(180); player.setXRot(28);
|
||||
player.getAbilities().flying = true; player.onUpdateAbilities();
|
||||
});
|
||||
c.waitTicks(25);
|
||||
c.runOnClient(m -> { m.player.setYRot(180); m.player.setXRot(28); }); c.waitTicks(5);
|
||||
c.getInput().pressKey(com.mojang.blaze3d.platform.InputConstants.KEY_F1); c.waitTicks(4);
|
||||
c.takeScreenshot("agriculture300-farm-and-orchards");
|
||||
server.runOnServer(s -> {
|
||||
var player = s.getPlayerList().getPlayers().getFirst(); player.teleportTo(7.5, 3.2, 25.5); player.setYRot(180); player.setXRot(12);
|
||||
});
|
||||
c.waitTicks(8); c.runOnClient(m -> {m.player.setYRot(180); m.player.setXRot(12);}); c.waitTicks(3);
|
||||
c.takeScreenshot("agriculture300-cross-onion-garlic-and-herbs");
|
||||
c.getInput().pressKey(com.mojang.blaze3d.platform.InputConstants.KEY_F1);
|
||||
for (String locale : List.of("fr_fr", "en_us")) for (int scale : new int[]{2, 3, 4})
|
||||
for (var tabKey : List.of(CreativeModeTabs.NATURAL_BLOCKS, CreativeModeTabs.FOOD_AND_DRINKS)) {
|
||||
c.runOnClient(m -> {
|
||||
m.getLanguageManager().setSelected(locale); m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
|
||||
m.options.guiScale().set(scale); m.resizeGui();
|
||||
for (var stack : allItems()) check(!stack.getHoverName().getString().startsWith("item.sanctuary"), "Translated agricultural item: " + locale + " " + stack);
|
||||
for (var block : blocks()) check(!block.getName().getString().startsWith("block.sanctuary"), "Translated agricultural block: " + locale + " " + block);
|
||||
m.gui.setScreen(new CreativeModeInventoryScreen(m.player, m.level.enabledFeatures(), true));
|
||||
});
|
||||
c.waitTicks(4);
|
||||
c.runOnClient(m -> {
|
||||
var screen = (CreativeModeInventoryScreen)m.gui.screen();
|
||||
var tabs = (net.fabricmc.fabric.api.client.creativetab.v1.FabricCreativeModeInventoryScreen)screen;
|
||||
var tab = BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(tabKey).value();
|
||||
tabs.switchToPage(tabs.getPage(tab)); tabs.setSelectedTab(tab);
|
||||
check(tabs.getSelectedTab() == tab, "The visible tab is the requested vanilla category");
|
||||
screen.mouseScrolled(screen.width / 2.0, screen.height / 2.0, 0, -1000);
|
||||
if (tabKey.equals(CreativeModeTabs.FOOD_AND_DRINKS))
|
||||
screen.mouseScrolled(screen.width / 2.0, screen.height / 2.0, 0, (fr.koka.sanctuary.cuisine.Cuisine.items().size() + fr.koka.sanctuary.cuisine.KitchenAppliances.items().size()) / 9);
|
||||
var visible = screen.getMenu().slots.stream().limit(45).map(slot -> slot.getItem().getItem()).toList();
|
||||
check(visible.contains(tabKey.equals(CreativeModeTabs.NATURAL_BLOCKS) ? AgricultureItems.LEMON_LEAVES : AgricultureItems.GARLIC_CLOVE), "New items are present in the visible native slots");
|
||||
});
|
||||
c.waitTicks(5);
|
||||
c.runOnClient(m -> {
|
||||
var menu = ((CreativeModeInventoryScreen)m.gui.screen()).getMenu();
|
||||
if (tabKey.equals(CreativeModeTabs.FOOD_AND_DRINKS)) {
|
||||
for (var item : List.of(AgricultureItems.BEANS, AgricultureItems.CHICKPEAS))
|
||||
check(menu.slots.stream().limit(45).anyMatch(slot -> !slot.getItem().isEmpty() && slot.getItem().is(item)), "Harvest remains available in actual Food & Drinks slots after synchronization: " + item);
|
||||
}
|
||||
});
|
||||
c.takeScreenshot("agriculture300-" + tabKey.identifier().getPath() + "-" + locale + "-gui" + scale);
|
||||
c.runOnClient(m -> m.gui.setScreen(null));
|
||||
}
|
||||
server.runOnServer(s -> {
|
||||
var level = s.overworld(); var map = new LinkedHashMap<BlockPos, BlockState>();
|
||||
for (var pos : BlockPos.betweenClosed(new BlockPos(-10, 1, -12), new BlockPos(27, 15, 20))) {
|
||||
var state = level.getBlockState(pos);
|
||||
if (BuiltInRegistries.BLOCK.getKey(state.getBlock()).getNamespace().equals("sanctuary")) map.put(pos.immutable(), state);
|
||||
}
|
||||
check(map.values().stream().anyMatch(v -> v.is(LEMON_LEAVES)) && map.values().stream().anyMatch(v -> v.is(OLIVE_LEAVES)), "Both real fruit trees grew");
|
||||
snapshot.set(map);
|
||||
});
|
||||
saved = world.getWorldSave();
|
||||
}
|
||||
try (var world = saved.open()) {
|
||||
c.waitFor(m -> m.player != null && m.gui.screen() == null, 1200);
|
||||
world.getServer().runOnServer(s -> {
|
||||
for (var entry : snapshot.get().entrySet()) check(s.overworld().getBlockState(entry.getKey()).equals(entry.getValue()), "Actual disk reload retains crop/leaf state: " + entry);
|
||||
System.out.println("AGRICULTURE300_DISK_RELOAD states=" + snapshot.get().size());
|
||||
});
|
||||
c.waitTicks(8); c.takeScreenshot("agriculture300-reloaded");
|
||||
}
|
||||
System.out.println("AGRICULTURE300_CLIENT_PASS Vulkan, 20 crops, two trees, coffee, native packets, models, FR/EN, disk reload");
|
||||
}
|
||||
|
||||
private static void gallery(ServerLevel level) {
|
||||
var crops = new ArrayList<>(AgricultureBlocks.crops().entrySet()); crops.sort(Map.Entry.comparingByKey());
|
||||
int index = 0;
|
||||
for (var entry : crops) {
|
||||
var pos = new BlockPos(index % 5 * 3, 1, index / 5 * 3); index++;
|
||||
boolean rice = entry.getValue() == RICE_CROP;
|
||||
level.setBlockAndUpdate(pos.below(), rice ? Blocks.MUD.defaultBlockState() : Blocks.FARMLAND.defaultBlockState().setValue(FarmlandBlock.MOISTURE, 7));
|
||||
if (rice) for (var direction : Direction.Plane.HORIZONTAL) level.setBlockAndUpdate(pos.relative(direction), Blocks.STONE.defaultBlockState());
|
||||
level.setBlockAndUpdate(pos, rice ? Blocks.WATER.defaultBlockState() : entry.getValue().defaultBlockState());
|
||||
if (rice) level.setBlockAndUpdate(pos, RICE_CROP.defaultBlockState());
|
||||
var crop = (AgriculturalCropBlock)entry.getValue();
|
||||
for (int i = 0; i < 10 && !crop.isMaxAge(level.getBlockState(pos)); i++) BoneMealItem.growCrop(new ItemStack(Items.BONE_MEAL), level, pos);
|
||||
}
|
||||
var coffee = new BlockPos(-3, 1, 7);
|
||||
level.setBlockAndUpdate(coffee.below(), Blocks.GRASS_BLOCK.defaultBlockState());
|
||||
level.setBlockAndUpdate(coffee, COFFEE_BUSH.defaultBlockState().setValue(SweetBerryBushBlock.AGE, 3));
|
||||
int specimen = 0;
|
||||
for (var crop : List.of(ONION_CROP, GARLIC_CROP, BASIL_CROP, MINT_CROP, CORIANDER_CROP, LETTUCE_CROP, CABBAGE_CROP, PINEAPPLE_CROP)) {
|
||||
var pos = new BlockPos(specimen++ * 2, 1, 19);
|
||||
level.setBlockAndUpdate(pos.below(), Blocks.FARMLAND.defaultBlockState().setValue(FarmlandBlock.MOISTURE, 7));
|
||||
level.setBlockAndUpdate(pos, crop.getStateForAge(7));
|
||||
}
|
||||
for (int i = 0; i < 2; i++) {
|
||||
var pos = new BlockPos(i == 0 ? -5 : 20, 1, -6);
|
||||
for (var ground : BlockPos.betweenClosed(pos.offset(-3, -1, -3), pos.offset(3, -1, 3))) level.setBlockAndUpdate(ground, Blocks.DIRT.defaultBlockState());
|
||||
var sapling = i == 0 ? LEMON_SAPLING : OLIVE_SAPLING;
|
||||
level.setBlockAndUpdate(pos, sapling.defaultBlockState());
|
||||
for (int n = 0; n < 10 && level.getBlockState(pos).is(sapling); n++) sapling.advanceTree(level, pos, level.getBlockState(pos), RandomSource.create(300 + n));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ItemStack> allItems() {
|
||||
var items = new LinkedHashSet<Item>(AgricultureItems.PLANTING_ITEMS);
|
||||
items.addAll(AgricultureItems.PRODUCE_ITEMS);
|
||||
items.addAll(List.of(AgricultureItems.GARLIC_CLOVE, AgricultureItems.OLIVE_LEAVES, AgricultureItems.LEMON_LEAVES));
|
||||
return items.stream().map(ItemStack::new).toList();
|
||||
}
|
||||
|
||||
private static List<Block> blocks() {
|
||||
var result = new ArrayList<Block>(AgricultureBlocks.crops().values());
|
||||
result.addAll(List.of(TOMATO_CROP_UPPER, CORN_CROP_UPPER, CUCUMBER_CROP_UPPER, BEAN_CROP_UPPER,
|
||||
COFFEE_BUSH, OLIVE_LEAVES, LEMON_LEAVES, OLIVE_SAPLING, LEMON_SAPLING, WILD_PINEAPPLE));
|
||||
return result;
|
||||
}
|
||||
private static BlockHitResult hit(BlockPos pos) { return new BlockHitResult(Vec3.atCenterOf(pos).add(0, .5, 0), Direction.UP, pos, false); }
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.CookingCauldronBlockEntity;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.RecipeService;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.*;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.client.sounds.SoundEventListener;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.phys.*;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Real dropped milk/stirring/serving packets, audio dispatch, Vulkan HUD and disk reopen. */
|
||||
public final class Cauldron311ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos POT=new BlockPos(0,1,0);
|
||||
private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
|
||||
private static void use(ClientGameTestContext c){c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(POT),Direction.UP,POT,false)));c.waitTicks(6);}
|
||||
@Override public void runTest(ClientGameTestContext c){
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable();var intro=new AtomicBoolean(true);var audio=new CopyOnWriteArrayList<String>();
|
||||
SoundEventListener listener=(sound,event,distance)->{if(sound.getX()>=0&&sound.getX()<=1&&sound.getZ()>=0&&sound.getZ()<=1)audio.add(sound.getIdentifier().toString());};
|
||||
c.runOnClient(m->{var device=com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();check(device.backendName().equalsIgnoreCase("vulkan"),"Cauldron validation requires Vulkan");System.out.println("CAULDRON311_BACKEND "+device.backendName()+" / "+device.name());m.options.renderDistance().set(8);m.options.simulationDistance().set(8);m.options.pauseOnLostFocus=false;m.getSoundManager().addListener(listener);});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){IntroClient.setQuickTestIntro(true);Starter039TestSupport.selectFirst(screen);screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());press(screen,"sanctuary.progression.enter");intro.set(false);}});
|
||||
TestWorldSave saved;
|
||||
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Cauldron311-"+UUID.randomUUID());s.setSeed("311");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()){
|
||||
c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);s.clockManager().setTotalTicks(l.dimensionType().defaultClock().orElseThrow(),6000);l.setBlockAndUpdate(new BlockPos(0,0,2),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(POT.below(),Blocks.CAMPFIRE.defaultBlockState());l.setBlockAndUpdate(POT,Blocks.CAULDRON.defaultBlockState());p.setGameMode(GameType.SURVIVAL);p.teleportTo(.5,1,2.5);p.getInventory().clearContent();p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.MILK_BUCKET));});c.waitTicks(8);
|
||||
// Real Q input creates the player's item entity; only position it over the opening.
|
||||
c.getInput().pressKey(o->o.keyDrop);c.waitTicks(5);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var milk=p.level().getEntitiesOfClass(ItemEntity.class,p.getBoundingBox().inflate(4),e->e.getItem().is(Items.MILK_BUCKET)).getFirst();milk.setPos(.5,1.6,.5);milk.setDeltaMovement(0,0,0);milk.setPickUpDelay(200);});
|
||||
c.waitFor(m->m.level.getBlockEntity(POT) instanceof CookingCauldronBlockEntity pot&&pot.getItem(0).is(Items.MILK_BUCKET),200);c.waitTicks(650);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pot=(CookingCauldronBlockEntity)p.level().getBlockEntity(POT);check(pot.progress()==0&&!pot.assemblyComplete()&&pot.getItem(0).is(Items.MILK_BUCKET)&&pot.getItem(6).isEmpty(),"Real 30-second wait cannot cook or fail the dropped milk");p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.STICK));});c.waitTicks(6);c.getInput().lookAt(POT);c.waitTicks(5);c.runOnClient(m->m.getWindow().setWindowed(1280,960));
|
||||
for(String locale:List.of("fr_fr","en_us"))for(int scale:new int[]{2,3,4}){c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();check(m.hitResult instanceof BlockHitResult hit&&hit.getBlockPos().equals(POT),"Actual cauldron HUD targeted: "+m.hitResult+" eye="+m.player.getEyePosition());});c.waitTicks(3);c.takeScreenshot("cauldron311-stir-"+locale+"-gui"+scale);}
|
||||
use(c);c.waitFor(m->((CookingCauldronBlockEntity)m.level.getBlockEntity(POT)).progress()>35,150);c.takeScreenshot("cauldron311-simmering");
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pot=(CookingCauldronBlockEntity)p.level().getBlockEntity(POT);check(pot.getItem(0).is(Items.MILK_BUCKET)&&pot.getItem(6).isEmpty()&&p.getMainHandItem().is(Items.STICK)&&p.getMainHandItem().getCount()==1,"The real gesture consumes neither the stick nor the milk before completion");});
|
||||
c.waitFor(m->((CookingCauldronBlockEntity)m.level.getBlockEntity(POT)).getItem(6).is(Cuisine.item("cream")),250);c.takeScreenshot("cauldron311-cream-and-ejected-bucket");
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pot=(CookingCauldronBlockEntity)p.level().getBlockEntity(POT);check(pot.getItem(6).getCount()==2&&pot.getItem(0).isEmpty(),"Two paid creams finish one recipe");int buckets=p.getInventory().countItem(Items.BUCKET)+p.level().getEntitiesOfClass(ItemEntity.class,new AABB(POT).inflate(4),e->e.getItem().is(Items.BUCKET)).stream().mapToInt(e->e.getItem().getCount()).sum();check(buckets==1,"Exactly one actual empty bucket is ejected");});
|
||||
for(String id:List.of("minecraft:item.bucket.empty","minecraft:item.bottle.empty","minecraft:block.bubble_column.bubble_pop","minecraft:block.brewing_stand.brew"))check(audio.contains(id),"Native audio engine received the preparation sound: "+id+" heard="+audio);
|
||||
saved=world.getWorldSave();
|
||||
}
|
||||
try(var world=saved.open()){
|
||||
c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);var server=world.getServer();server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pot=(CookingCauldronBlockEntity)p.level().getBlockEntity(POT);check(pot.getItem(6).is(Cuisine.item("cream"))&&pot.getItem(6).getCount()==2,"Real disk reopening preserves the paid preparation");p.teleportTo(.5,1,2.5);});c.waitTicks(8);c.getInput().lookAt(POT);c.waitTicks(4);c.takeScreenshot("cauldron311-ready-after-disk");use(c);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(Cuisine.item("cream"))==2&&p.level().getBlockEntity(POT) instanceof CookingCauldronBlockEntity kept&&kept.repeatsRecipe()&&p.getInventory().countItem(Items.STICK)==1,"Actual serving packet returns exactly two creams and retains the authorized cauldron recipe");
|
||||
p.teleportTo(.5,1,2.5);p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.STICK));var milk=new ItemEntity(p.level(),.5,1.5,.5,new ItemStack(Items.MILK_BUCKET));milk.setPickUpDelay(300);p.level().addFreshEntity(milk);
|
||||
});c.waitFor(m->m.level.getBlockEntity(POT) instanceof CookingCauldronBlockEntity,150);
|
||||
server.runOnServer(s->((CookingCauldronBlockEntity)s.overworld().getBlockEntity(POT)).insertIngredient(new ItemStack(Items.CARROT),1));use(c);c.takeScreenshot("cauldron311-incomplete-keeps-milk");
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pot=(CookingCauldronBlockEntity)p.level().getBlockEntity(POT);check(!pot.assemblyComplete()&&pot.getItem(0).is(Items.MILK_BUCKET)&&pot.getItem(6).isEmpty(),"Invalid real stirring never creates a failed dish or consumes milk");p.giveExperienceLevels(4);RecipeService.discover(p,AgricultureItems.TOMATO);RecipeService.buy(p);RecipeService.refresh(p);});
|
||||
c.waitFor(m->RecipeClient.allowed()&&SanctuaryJeiPlugin.runtime()!=null,300);c.runOnClient(m->{var runtime=SanctuaryJeiPlugin.runtime();var type=KitchenJei.types().stream().filter(t->t.getUid().getPath().endsWith("cooking_cauldron")).findFirst().orElseThrow();check(runtime.getRecipeManager().createRecipeLookup(type).includeHidden().get().count()==19,"Nineteen original cauldron recipes remain in JEI");runtime.getRecipesGui().showTypes(List.of(type));});c.waitTicks(5);c.takeScreenshot("cauldron311-jei-nineteen");c.getInput().pressKey(InputConstants.KEY_ESCAPE);
|
||||
}
|
||||
check(audio.contains("minecraft:entity.item.pickup"),"Serving sound reached the actual audio engine");c.runOnClient(m->m.getSoundManager().removeListener(listener));
|
||||
System.out.println("CAULDRON311_AUDIO "+audio);
|
||||
System.out.println("CAULDRON311_CLIENT_PASS Vulkan, Q-drop native milk into empty cauldron, actual 30-second wait without failure, FR/EN HUD GUI2/3/4, actual stick/cooking/serving packets, delayed bucket, native audio dispatch, disk reopen, incomplete mixture preserved and JEI19");
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.CookingCauldronBlockEntity;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.RecipeService;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.*;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.client.sounds.SoundEventListener;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.phys.*;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** One real stir, repeated native top-ups and serving, hot work across disk, audio and explicit stop. */
|
||||
public final class Cauldron312ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos POT=new BlockPos(0,1,0);
|
||||
private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
|
||||
private static void use(ClientGameTestContext c){c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(POT),Direction.UP,POT,false)));c.waitTicks(6);}
|
||||
@Override public void runTest(ClientGameTestContext c){
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable();var intro=new AtomicBoolean(true);var audio=new CopyOnWriteArrayList<String>();
|
||||
SoundEventListener listener=(sound,event,distance)->{if(sound.getX()>=0&&sound.getX()<=1&&sound.getZ()>=0&&sound.getZ()<=1)audio.add(sound.getIdentifier().toString());};
|
||||
c.runOnClient(m->{var device=com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();check(device.backendName().equalsIgnoreCase("vulkan"),"Cauldron validation requires Vulkan");System.out.println("CAULDRON312_BACKEND "+device.backendName()+" / "+device.name());m.options.renderDistance().set(8);m.options.simulationDistance().set(8);m.options.pauseOnLostFocus=false;m.getSoundManager().addListener(listener);});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){IntroClient.setQuickTestIntro(true);Starter039TestSupport.selectFirst(screen);screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());press(screen,"sanctuary.progression.enter");intro.set(false);}});
|
||||
TestWorldSave saved;
|
||||
var beforeTopUp=new java.util.concurrent.atomic.AtomicInteger();
|
||||
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Cauldron312-"+UUID.randomUUID());s.setSeed("312");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()){
|
||||
c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);s.clockManager().setTotalTicks(l.dimensionType().defaultClock().orElseThrow(),6000);l.setBlockAndUpdate(new BlockPos(0,0,2),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(POT.below(),Blocks.CAMPFIRE.defaultBlockState());l.setBlockAndUpdate(POT,Blocks.CAULDRON.defaultBlockState());p.setGameMode(GameType.SURVIVAL);p.teleportTo(.5,1,2.5);p.getInventory().clearContent();p.getInventory().setItem(7,new ItemStack(Items.MILK_BUCKET));p.getInventory().setItem(8,new ItemStack(Items.STICK));});c.waitTicks(8);select(c,7);
|
||||
c.getInput().pressKey(o->o.keyDrop);c.waitTicks(5);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var milk=p.level().getEntitiesOfClass(ItemEntity.class,p.getBoundingBox().inflate(4),e->e.getItem().is(Items.MILK_BUCKET)).getFirst();milk.setPos(.5,1.6,.5);milk.setDeltaMovement(0,0,0);milk.setPickUpDelay(200);});
|
||||
c.waitFor(m->m.level.getBlockEntity(POT) instanceof CookingCauldronBlockEntity pot&&pot.getItem(0).is(Items.MILK_BUCKET),200);c.waitTicks(40);select(c,8);use(c);
|
||||
c.waitFor(m->((CookingCauldronBlockEntity)m.level.getBlockEntity(POT)).progress()>60,150);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pot=(CookingCauldronBlockEntity)p.level().getBlockEntity(POT);beforeTopUp.set(pot.progress());check(p.getMainHandItem().is(Items.STICK)&&p.getMainHandItem().getCount()==1,"The one actual starting gesture preserves its stick");p.getInventory().setItem(7,new ItemStack(Items.MILK_BUCKET));});select(c,7);use(c);
|
||||
server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().getInventory().setItem(7,new ItemStack(Items.MILK_BUCKET)));c.waitTicks(4);use(c);
|
||||
server.runOnServer(s->{var pot=(CookingCauldronBlockEntity)s.overworld().getBlockEntity(POT);check(pot.progress()>=beforeTopUp.get()&&pot.getItem(6).isEmpty()&&pot.repeatsRecipe(),"Two actual compatible additions preserve the unpaid first cycle");});
|
||||
c.waitFor(m->((CookingCauldronBlockEntity)m.level.getBlockEntity(POT)).getItem(6).getCount()==6,440);c.waitTicks(22);c.getInput().lookAt(POT);c.waitTicks(5);c.runOnClient(m->m.getWindow().setWindowed(1280,960));
|
||||
for(String locale:List.of("fr_fr","en_us"))for(int scale:new int[]{2,3,4}){c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();var pot=(CookingCauldronBlockEntity)m.level.getBlockEntity(POT);check(pot.repeatsRecipe()&&pot.repeatState()==CookingCauldronBlockEntity.NEEDS_INPUT&&pot.getItem(6).getCount()==6,"Actual waiting-for-supply HUD synchronized");var text=net.minecraft.network.chat.Component.translatable("hud.sanctuary.cooking_cauldron.stored_status",6,pot.getItem(6).getHoverName(),net.minecraft.network.chat.Component.translatable("hud.sanctuary.cooking_cauldron.resupply"));check(m.font.width(text)+8<=m.getWindow().getGuiScaledWidth(),"Stored result and repeat state fit every tested GUI scale");});c.waitTicks(3);c.takeScreenshot("cauldron312-resupply-"+locale+"-gui"+scale);}
|
||||
server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().getInventory().setItem(7,new ItemStack(Items.MILK_BUCKET)));c.waitTicks(4);use(c);
|
||||
c.waitFor(m->((CookingCauldronBlockEntity)m.level.getBlockEntity(POT)).progress()>60,150);c.takeScreenshot("cauldron312-second-cycle-with-first-output");
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(((CookingCauldronBlockEntity)p.level().getBlockEntity(POT)).getItem(6).getCount()==6&&p.getInventory().countItem(Cuisine.item("cream"))==0,"Milk input with existing output feeds the next cycle without serving or restarting");});use(c);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pot=(CookingCauldronBlockEntity)p.level().getBlockEntity(POT);check(p.getInventory().countItem(Cuisine.item("cream"))==6&&pot.getItem(6).isEmpty()&&pot.progress()>60&&pot.repeatsRecipe(),"Actual serving preserves active work for the second cycle");p.level().setBlockAndUpdate(POT.below(),Blocks.STONE.defaultBlockState());});c.waitTicks(4);saved=world.getWorldSave();
|
||||
}
|
||||
try(var world=saved.open()){
|
||||
c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);var server=world.getServer();server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pot=(CookingCauldronBlockEntity)p.level().getBlockEntity(POT);check(pot.repeatsRecipe()&&pot.repeatedRecipe().getPath().equals("physical_cauldron_cream")&&pot.progress()>60&&pot.progress()<180&&pot.getItem(6).isEmpty()&&pot.getItem(0).is(Items.MILK_BUCKET)&&p.getInventory().countItem(Cuisine.item("cream"))==6,"Actual disk reopen keeps the exact recipe, paid food and unpaid pending cycle");p.teleportTo(.5,1,2.5);p.level().setBlockAndUpdate(POT.below(),Blocks.CAMPFIRE.defaultBlockState());});c.waitTicks(6);c.getInput().lookAt(POT);c.waitFor(m->((CookingCauldronBlockEntity)m.level.getBlockEntity(POT)).getItem(6).getCount()==2,250);c.takeScreenshot("cauldron312-second-cycle-after-disk");c.waitTicks(22);
|
||||
server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().getInventory().setItem(7,new ItemStack(Items.MILK_BUCKET)));select(c,7);use(c);c.waitFor(m->((CookingCauldronBlockEntity)m.level.getBlockEntity(POT)).getItem(6).getCount()==4,260);c.takeScreenshot("cauldron312-third-cycle-without-another-stir");
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();int buckets=p.getInventory().countItem(Items.BUCKET)+p.level().getEntitiesOfClass(ItemEntity.class,new AABB(POT).inflate(12),e->e.isAlive()&&e.getItem().is(Items.BUCKET)).stream().mapToInt(e->e.getItem().getCount()).sum();check(buckets==5&&p.getInventory().countItem(Cuisine.item("cream"))==6,"Exactly five milk payments return exactly five native buckets across the four cycles");});
|
||||
c.getInput().holdKey(o->o.keyShift);c.waitTicks(4);use(c);c.getInput().releaseKey(o->o.keyShift);c.waitTicks(4);
|
||||
server.runOnServer(s->{var pot=(CookingCauldronBlockEntity)s.overworld().getBlockEntity(POT);check(!pot.repeatsRecipe()&&pot.getItem(6).getCount()==4,"Actual empty-handed sneak interaction stops the series while preserving its output");});c.takeScreenshot("cauldron312-explicitly-stopped");use(c);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(Cuisine.item("cream"))==10&&p.getInventory().countItem(Items.STICK)==1&&p.level().getBlockState(POT).is(Blocks.CAULDRON),"Final native serving returns all ten paid creams and restores the stopped vanilla block");});
|
||||
}
|
||||
long completed=audio.stream().filter(id->id.equals("minecraft:block.brewing_stand.brew")).count();check(completed==4,"Exactly one completion cue per paid cycle, with existing output and disk reopen: "+audio);c.runOnClient(m->m.getSoundManager().removeListener(listener));
|
||||
System.out.println("CAULDRON312_AUDIO completed_batches="+completed);
|
||||
System.out.println("CAULDRON312_CLIENT_PASS Vulkan, one actual stir, native Q milk drop and compatible top-ups, FR/EN GUI2/3/4 resupply HUD, accumulated output, actual serving without cancellation, disk pending work, four real cycles, five paid milk/returned buckets, ten creams, one audio cue per batch and native explicit stop");
|
||||
}
|
||||
private static void select(ClientGameTestContext c,int slot){c.runOnClient(m->{m.player.getInventory().setSelectedSlot(slot);m.player.connection.send(new net.minecraft.network.protocol.game.ServerboundSetCarriedItemPacket(slot));});c.waitTicks(4);}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.FermentationBarrelBlockEntity;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.*;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.level.storage.TagValueInput;
|
||||
import net.minecraft.world.phys.*;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Native fermentation, placed cheese slices, actual disk save and real-clock aged drinks. */
|
||||
public final class CheeseWine314ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos CHEESE=new BlockPos(0,1,0),BARREL=new BlockPos(-2,1,0);
|
||||
private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
|
||||
private static void select(ClientGameTestContext c,int slot){c.runOnClient(m->{m.player.getInventory().setSelectedSlot(slot);m.player.connection.send(new net.minecraft.network.protocol.game.ServerboundSetCarriedItemPacket(slot));});c.waitTicks(4);}
|
||||
private static void use(ClientGameTestContext c,BlockPos target){c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(new Vec3(target.getX()+.5,target.getY()+1,target.getZ()+.5),Direction.UP,target,false)));c.waitTicks(6);}
|
||||
private static void accelerateFermentation(net.minecraft.server.MinecraftServer s){var p=s.getPlayerList().getPlayers().getFirst();var b=(FermentationBarrelBlockEntity)p.level().getBlockEntity(BARREL);check(b.startedAtGameTime()>=0,"A paid native recipe has actually started");var tag=b.saveCustomOnly(p.level().registryAccess());tag.putLong("FermentationStartedAt",p.level().getGameTime()-b.activeDuration()+10);b.loadWithComponents(TagValueInput.create(net.minecraft.util.ProblemReporter.DISCARDING,p.level().registryAccess(),tag));}
|
||||
private static void setAge(net.minecraft.server.MinecraftServer s,int days){var p=s.getPlayerList().getPlayers().getFirst();var b=(FermentationBarrelBlockEntity)p.level().getBlockEntity(BARREL);var tag=b.saveCustomOnly(p.level().registryAccess());tag.putLong("WineStartedAtEpochMillis",System.currentTimeMillis()-days*86_400_000L-1000);b.loadWithComponents(TagValueInput.create(net.minecraft.util.ProblemReporter.DISCARDING,p.level().registryAccess(),tag));}
|
||||
@Override public void runTest(ClientGameTestContext c){
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable();var intro=new AtomicBoolean(true);var origin=new AtomicLong();TestWorldSave saved;
|
||||
c.runOnClient(m->{var d=com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();check(d.backendName().equalsIgnoreCase("vulkan"),"Only Vulkan is validated");System.out.println("CHEESEWINE314_BACKEND "+d.backendName()+" / "+d.name());m.options.renderDistance().set(8);m.options.simulationDistance().set(8);m.options.pauseOnLostFocus=false;});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){IntroClient.setQuickTestIntro(true);Starter039TestSupport.selectFirst(screen);screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());press(screen,"sanctuary.progression.enter");intro.set(false);}});
|
||||
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("CheeseWine314-"+UUID.randomUUID());s.setSeed("314");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()){
|
||||
c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);try{fr.koka.sanctuary.realtime.RealtimeService.setMode(s,false);fr.koka.sanctuary.weather.WeatherService.setMode(s,false);}catch(Exception e){throw new IllegalStateException(e);}s.clockManager().setTotalTicks(l.dimensionType().defaultClock().orElseThrow(),6000);l.getWeatherData().setRaining(false);l.setRainLevel(0);l.setBlockAndUpdate(BARREL,KitchenAppliances.FERMENTATION_BARREL.defaultBlockState());l.setBlockAndUpdate(CHEESE.below(),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(new BlockPos(0,0,2),Blocks.STONE.defaultBlockState());((net.minecraft.world.level.storage.ServerLevelData)l.getLevelData()).setGameTime(10000);p.setGameMode(GameType.SURVIVAL);p.teleportTo(.5,1,2.5);p.getInventory().clearContent();p.getInventory().setItem(0,new ItemStack(Cuisine.item("cream")));p.getInventory().setItem(1,new ItemStack(Cuisine.item("salt")));p.getInventory().setItem(2,new ItemStack(Cuisine.item("grape_must")));p.giveExperienceLevels(4);RecipeService.discover(p,Cuisine.item("grape_must"));RecipeService.buy(p);RecipeService.refresh(p);});c.waitTicks(8);
|
||||
select(c,0);use(c,BARREL);select(c,1);use(c,BARREL);server.runOnServer(CheeseWine314ClientChecks::accelerateFermentation);c.waitFor(m->((FermentationBarrelBlockEntity)m.level.getBlockEntity(BARREL)).visibleItem().is(Cuisine.item("cheese")),100);select(c,0);use(c,BARREL);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(Cuisine.item("cheese"))==1&&p.getInventory().countItem(Cuisine.item("cream"))==0&&p.getInventory().countItem(Cuisine.item("salt"))==0,"Native cream and salt interactions paid one cheese");});
|
||||
use(c,CHEESE.below());c.waitFor(m->m.level.getBlockState(CHEESE).getBlock() instanceof fr.koka.sanctuary.cuisine.culinary.block.CheeseBlock,100);c.getInput().lookAt(CHEESE);c.waitTicks(4);c.takeScreenshot("cheesewine314-whole-wheel");
|
||||
c.runOnClient(m->{m.getWindow().setWindowed(1280,960);int states=0;var block=((BlockItem)Cuisine.item("cheese")).getBlock();for(var state:block.getStateDefinition().getPossibleStates()){var model=m.getModelManager().getBlockStateModelSet().get(state);var quads=new ArrayList<net.minecraft.client.renderer.block.dispatch.BlockStateModelPart>();model.collectParts(net.minecraft.util.RandomSource.create(314),quads);check(!quads.isEmpty(),"Every native bite state has a valid model");int count=0;for(var part:quads)for(var direction:new Direction[]{null,Direction.UP,Direction.DOWN,Direction.NORTH,Direction.SOUTH,Direction.EAST,Direction.WEST})for(var q:part.getQuads(direction)){String expected=state.getValue(CakeBlock.BITES)>0&&q.direction()==Direction.WEST?"block/end_stone":"block/yellow_terracotta";check(q.materialInfo().sprite().contents().name().toString().equals("minecraft:"+expected),"Yellow rind/top remain outside; only an eaten cross-section exposes the hole texture");count++;}check(count==6,"Every cheese state has all six visible faces");states++;}check(states==7,"Exactly seven cake slice states");net.minecraft.world.item.CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(),true,m.level.registryAccess());var food=net.minecraft.core.registries.BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.FOOD_AND_DRINKS).value();for(var item:List.of(Cuisine.item("cheese"),Cuisine.item("wine"),KitchenAppliances.TABLE_WINE,KitchenAppliances.RESERVE_WINE,KitchenAppliances.GRAND_CRU_WINE)){var render=new net.minecraft.client.renderer.item.ItemStackRenderState();m.getItemModelResolver().updateForTopItem(render,new ItemStack(item),ItemDisplayContext.GUI,m.level,m.player,314);check(!render.isEmpty()&&!render.pickParticleMaterial(net.minecraft.util.RandomSource.create(314)).sprite().contents().name().getPath().equals("missingno")&&food.getDisplayItems().stream().anyMatch(stack->stack.is(item)),"Native models and existing food category");}System.out.println("CHEESEWINE314_MODELS states=7 items=5 native_food_tab=true");});
|
||||
for(int i=0;i<3;i++){server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().getFoodData().setFoodLevel(0));c.waitFor(m->m.player.getFoodData().getFoodLevel()==0,100);use(c,CHEESE);final int eaten=i+1;server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var state=p.level().getBlockState(CHEESE);check(p.getFoodData().getFoodLevel()==2&&(eaten==7?state.isAir():state.getValue(CakeBlock.BITES)==eaten),"Each real native bite pays a visible slice and restores food");});if(i==2||i==5)c.takeScreenshot("cheesewine314-slices-remaining-"+(6-i));}
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.level().setBlockAndUpdate(new BlockPos(-1,0,2),Blocks.STONE.defaultBlockState());p.teleportTo(-.5,1,2.5);});c.waitTicks(5);c.getInput().lookAt(CHEESE);c.waitTicks(4);c.takeScreenshot("cheesewine314-cut-face-and-yellow-rind");
|
||||
server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().teleportTo(.5,1,2.5));c.waitTicks(5);
|
||||
select(c,2);use(c,BARREL);server.runOnServer(CheeseWine314ClientChecks::accelerateFermentation);c.waitFor(m->((FermentationBarrelBlockEntity)m.level.getBlockEntity(BARREL)).hasMaturingWine(),100);select(c,0);c.getInput().lookAt(BARREL);c.waitTicks(5);
|
||||
for(String locale:List.of("fr_fr","en_us"))for(int scale:new int[]{2,3,4}){c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();for(var item:List.of(Cuisine.item("cheese"),Cuisine.item("wine"),KitchenAppliances.TABLE_WINE,KitchenAppliances.RESERVE_WINE,KitchenAppliances.GRAND_CRU_WINE))check(!new ItemStack(item).getHoverName().getString().matches("(item|block)\\.sanctuary.*"),"Every new quality is localized");});c.waitTicks(3);c.takeScreenshot("cheesewine314-hud-"+locale+"-gui"+scale);}
|
||||
server.runOnServer(s->setAge(s,1));c.waitFor(m->((FermentationBarrelBlockEntity)m.level.getBlockEntity(BARREL)).visibleItem().is(KitchenAppliances.TABLE_WINE),100);c.takeScreenshot("cheesewine314-table-wine-one-real-day");server.runOnServer(s->origin.set(((FermentationBarrelBlockEntity)s.getPlayerList().getPlayers().getFirst().level().getBlockEntity(BARREL)).wineStartedAtEpochMillis()));saved=world.getWorldSave();
|
||||
}
|
||||
try(var world=saved.open()){
|
||||
c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);var server=world.getServer();server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var b=(FermentationBarrelBlockEntity)p.level().getBlockEntity(BARREL);check(b.getItem(4).is(KitchenAppliances.TABLE_WINE)&&b.wineStartedAtEpochMillis()==origin.get(),"Actual disk reopen preserves the quality and UTC origin");});server.runOnServer(s->check(s.getPlayerList().getPlayers().getFirst().level().getBlockState(CHEESE).getValue(CakeBlock.BITES)==3,"Actual disk reopen preserves the three paid eaten slices"));
|
||||
for(int i=3;i<7;i++){server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().getFoodData().setFoodLevel(0));c.waitFor(m->m.player.getFoodData().getFoodLevel()==0,100);use(c,CHEESE);final int eaten=i+1;server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var state=p.level().getBlockState(CHEESE);check(p.getFoodData().getFoodLevel()==2&&(eaten==7?state.isAir():state.getValue(CakeBlock.BITES)==eaten),"Each real native bite pays a visible slice and restores food");});if(i==2||i==5)c.takeScreenshot("cheesewine314-slices-remaining-"+(6-i));}
|
||||
server.runOnServer(s->check(s.getPlayerList().getPlayers().getFirst().getInventory().countItem(Cuisine.item("cheese"))==0,"No whole cheese remains after native placement and seven bites"));c.getInput().lookAt(CHEESE);c.takeScreenshot("cheesewine314-last-slice-consumed");
|
||||
c.getInput().lookAt(BARREL);server.runOnServer(s->setAge(s,3));c.waitFor(m->((FermentationBarrelBlockEntity)m.level.getBlockEntity(BARREL)).visibleItem().is(KitchenAppliances.RESERVE_WINE),100);c.takeScreenshot("cheesewine314-reserve-three-real-days");server.runOnServer(s->setAge(s,7));c.waitFor(m->((FermentationBarrelBlockEntity)m.level.getBlockEntity(BARREL)).visibleItem().is(KitchenAppliances.GRAND_CRU_WINE),100);c.takeScreenshot("cheesewine314-grand-cru-seven-real-days");select(c,0);use(c,BARREL);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(KitchenAppliances.GRAND_CRU_WINE)==1&&((FermentationBarrelBlockEntity)p.level().getBlockEntity(BARREL)).isEmpty(),"Native collection yields just one grand cru from the one paid must");p.getFoodData().setFoodLevel(0);});c.waitFor(m->m.player.getFoodData().getFoodLevel()==0,100);c.getInput().lookAt(0,-40);c.getInput().holdKey(o->o.keyUse);c.waitFor(m->m.player.getMainHandItem().isEmpty(),200);c.getInput().releaseKey(o->o.keyUse);c.waitTicks(4);server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(KitchenAppliances.GRAND_CRU_WINE)==0&&p.getFoodData().getFoodLevel()>0,"The actual native drink consumes the grand cru and restores food");});
|
||||
c.waitFor(m->RecipeClient.allowed()&&SanctuaryJeiPlugin.runtime()!=null,200);c.runOnClient(m->{var runtime=SanctuaryJeiPlugin.runtime();var type=KitchenJei.types().stream().filter(t->t.getUid().getPath().endsWith("wine_maturation")).findFirst().orElseThrow();check(KitchenJei.types().size()==8&&runtime.getRecipeManager().createRecipeLookup(type).includeHidden().get().count()==3,"JEI synchronizes all three real-day maturation stages");runtime.getRecipesGui().showTypes(List.of(type));});c.waitTicks(5);c.takeScreenshot("cheesewine314-jei-real-days");c.getInput().pressKey(InputConstants.KEY_ESCAPE);
|
||||
}
|
||||
System.out.println("CHEESEWINE314_CLIENT_PASS Vulkan, paid native fermentation/cheese placement, seven native slices, models, existing food tab, FR/EN GUI2/3/4, real UTC ageing, disk origin/quality, native grand cru collection/drinking, three JEI real-day stages");
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.multiblock.*;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import static fr.koka.sanctuary.cuisine.machine.CrushableProduceBlock.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.client.screen.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.recipe.*;
|
||||
import fr.koka.sanctuary.cuisine.machine.*;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.phys.*;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.RecipeService;
|
||||
import java.util.*;
|
||||
import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import mezz.jei.api.constants.RecipeTypes;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.*;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.inventory.*;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.SimpleMenuProvider;
|
||||
import net.minecraft.world.inventory.*;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.alchemy.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.*;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Native table packets, real smoker ticks, food catalogue/JEI and actual save reopening. */
|
||||
public final class Crushing305ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos FRUIT=new BlockPos(0,1,0), PARTIAL=new BlockPos(6,1,0), SUPER=new BlockPos(14,3,0);
|
||||
private static final java.util.Map<BlockPos,net.minecraft.world.level.block.state.BlockState> gallery=new LinkedHashMap<>();
|
||||
private static void check(boolean ok, String message) { if (!ok) throw new AssertionError(message); }
|
||||
|
||||
@Override public void runTest(ClientGameTestContext c) {
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable(); var intro = new AtomicBoolean(true);
|
||||
c.runOnClient(m -> {
|
||||
var device = com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();
|
||||
check(device.backendName().equalsIgnoreCase("vulkan"), "Cuisine validation requires Vulkan");
|
||||
System.out.println("CRUSHING305_BACKEND " + device.backendName() + " / " + device.name());
|
||||
m.options.renderDistance().set(8); m.options.simulationDistance().set(8); m.options.pauseOnLostFocus = false;
|
||||
});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m -> {
|
||||
if (intro.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
|
||||
IntroClient.setQuickTestIntro(true); Starter039TestSupport.selectFirst(screen);
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
|
||||
press(screen, "sanctuary.progression.enter"); intro.set(false);
|
||||
}
|
||||
});
|
||||
TestWorldSave saved;
|
||||
try (var world = c.worldBuilder().adjustSettings(s -> {
|
||||
s.setName("Crushing305-" + UUID.randomUUID()); s.setSeed("305"); 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()) {
|
||||
c.waitFor(m -> m.player != null && ProgressionClient.active() && m.gui.screen() == null && !IntroSpawnFlash.active(), 1200);
|
||||
var server=world.getServer();
|
||||
server.runOnServer(s->{
|
||||
var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);
|
||||
l.setBlockAndUpdate(FRUIT.below(),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(FRUIT,KitchenAppliances.TOMATO_BLOCK.defaultBlockState());
|
||||
l.setBlockAndUpdate(PARTIAL.below(),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(PARTIAL,KitchenAppliances.OLIVE_BLOCK.defaultBlockState().setValue(LAYERS,2));
|
||||
p.setGameMode(GameType.SURVIVAL);p.getFoodData().setFoodLevel(20);p.getInventory().clearContent();p.teleportTo(.5,2,.5);
|
||||
});c.waitFor(m->m.player.onGround()&&m.level.getBlockState(FRUIT).is(KitchenAppliances.TOMATO_BLOCK),200);
|
||||
for(int jump=1;jump<=8;jump++){
|
||||
int left=8-jump;
|
||||
c.getInput().holdKey(o->o.keyJump);c.waitTicks(2);c.getInput().releaseKey(o->o.keyJump);
|
||||
c.waitTicks(25);
|
||||
c.runOnClient(m->System.out.println("CRUSHING305_JUMP_TRACE client left="+left+" state="+m.level.getBlockState(FRUIT)+" y="+m.player.getY()+" fall="+m.player.fallDistance+" ground="+m.player.onGround()));
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();System.out.println("CRUSHING305_JUMP_TRACE server left="+left+" state="+p.level().getBlockState(FRUIT)+" y="+p.getY()+" fall="+p.fallDistance+" ground="+p.onGround());});
|
||||
c.waitFor(m->m.player.onGround() && (left==0?m.level.getBlockState(FRUIT).isAir():m.level.getBlockState(FRUIT).is(KitchenAppliances.TOMATO_BLOCK)&&m.level.getBlockState(FRUIT).getValue(LAYERS)==left),200);
|
||||
c.waitTicks(4);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();
|
||||
int portions=p.getInventory().countItem(Cuisine.item("tomato_pulp"))+count(l,new AABB(FRUIT).inflate(4),Cuisine.item("tomato_pulp"));
|
||||
check(portions==(8-left)*9/8,"Native jump pays exact cumulative yield: "+left+" -> "+portions);
|
||||
check(left==0?l.getBlockState(FRUIT).isAir():l.getBlockState(FRUIT).getValue(LAYERS)==left,"No extra fall caused by lowering the block");
|
||||
});
|
||||
}
|
||||
System.out.println("CRUSHING305_NATIVE_JUMPS count=8 layers_removed=8 portions=9");
|
||||
server.runOnServer(s->{
|
||||
var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();gallery.clear();int row=0;
|
||||
for(var block:List.of(KitchenAppliances.TOMATO_BLOCK,KitchenAppliances.GRAPE_BLOCK,KitchenAppliances.OLIVE_BLOCK)){
|
||||
for(int layer=1;layer<=8;layer++){
|
||||
var pos=new BlockPos(layer-1,1,4+row*3);l.setBlockAndUpdate(pos.below(),Blocks.STONE.defaultBlockState());
|
||||
var state=block.defaultBlockState().setValue(LAYERS,layer);l.setBlockAndUpdate(pos,state);gallery.put(pos,state);
|
||||
}row++;
|
||||
}
|
||||
p.teleportTo(4,5,16);p.setGameMode(GameType.CREATIVE);p.getAbilities().flying=true;p.onUpdateAbilities();
|
||||
});c.waitFor(m->Math.abs(m.player.getZ()-16)<.1,200);c.getInput().lookAt(new BlockPos(4,1,7));c.waitTicks(8);
|
||||
c.runOnClient(m->{m.getWindow().setWindowed(1280,960);int models=0;
|
||||
for(var entry:gallery.entrySet()){
|
||||
var state=entry.getValue();var parts=new ArrayList<BlockStateModelPart>();m.getModelManager().getBlockStateModelSet().get(state).collectParts(RandomSource.create(305),parts);
|
||||
int quads=0;float maxY=0;
|
||||
for(var part:parts)for(var direction:new Direction[]{null,Direction.UP,Direction.DOWN,Direction.NORTH,Direction.SOUTH,Direction.EAST,Direction.WEST})for(var quad:part.getQuads(direction)){
|
||||
check(quad.materialInfo().sprite().contents().name().getNamespace().equals("sanctuary"),"Supplied fruit texture resolves at every height");
|
||||
for(int v=0;v<4;v++)maxY=Math.max(maxY,quad.position(v).y());quads++;
|
||||
}
|
||||
check(quads>0 && Math.abs(maxY-state.getValue(LAYERS)/8F)<.0001,"Baked model and collision height agree: "+state+" maxY="+maxY);models++;
|
||||
}
|
||||
CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(),true,m.level.registryAccess());
|
||||
for(var tab:BuiltInRegistries.CREATIVE_MODE_TAB)check(tab.getDisplayItems().stream().noneMatch(i->i.is(KitchenAppliances.FOOD_PRESS.asItem())),"Retired press absent from every creative tab");
|
||||
System.out.println("CRUSHING305_MODELS states="+models+" heights=8 retired_creative=false");
|
||||
});c.waitTicks(5);c.takeScreenshot("crushing305-eight-heights-three-fruits");
|
||||
server.runOnServer(s->{
|
||||
var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();var f=Direction.NORTH;
|
||||
for(int i=0;i<9;i++)l.setBlockAndUpdate(SuperPistons.tile(SUPER,f,i),Blocks.PISTON.defaultBlockState().setValue(net.minecraft.world.level.block.piston.PistonBaseBlock.FACING,f));
|
||||
check(SuperPistons.assemble(l,SUPER,f,false),"Real 3x3 super piston assembly");
|
||||
for(int depth=1;depth<=3;depth++)for(int i=0;i<9;i++){
|
||||
var pos=SuperPistons.tile(SUPER,f,i).relative(f,depth);if(pos.getY()==SUPER.getY()-1)l.setBlockAndUpdate(pos.below(),Blocks.STONE.defaultBlockState());
|
||||
var fruit=List.of(KitchenAppliances.TOMATO_BLOCK,KitchenAppliances.GRAPE_BLOCK,KitchenAppliances.OLIVE_BLOCK).get(i%3);l.setBlockAndUpdate(pos,fruit.defaultBlockState());
|
||||
}
|
||||
p.teleportTo(21,8,-10);
|
||||
});c.waitFor(m->Math.abs(m.player.getX()-21)<.1&&Math.abs(m.player.getZ()+10)<.1,200);c.getInput().lookAt(SUPER);c.waitTicks(5);c.takeScreenshot("crushing305-super-piston-before-27-blocks");
|
||||
server.runOnServer(s->s.overworld().setBlockAndUpdate(SuperPistons.tile(SUPER,Direction.NORTH,0).south(),Blocks.REDSTONE_BLOCK.defaultBlockState()));c.waitTicks(35);
|
||||
server.runOnServer(s->{var l=s.overworld();check(((SuperPistonController)l.getBlockEntity(SUPER)).stroke==3,"Full native powered course");
|
||||
for(int depth=1;depth<=3;depth++)for(var pos:SuperPistons.tiles(SUPER,Direction.NORTH,depth))check(!(l.getBlockState(pos).getBlock() instanceof CrushableProduceBlock),"All 27 blocks crushed");
|
||||
for(String item:List.of("tomato_pulp","grape_must","olive_oil"))check(count(l,new AABB(SUPER).inflate(7),Cuisine.item(item))==81,"Nine blocks give 81 portions each: "+item);
|
||||
System.out.println("CRUSHING305_SUPER_PISTON blocks=27 portions=243 activation=1");
|
||||
});c.takeScreenshot("crushing305-super-piston-after-243-portions");
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.giveExperienceLevels(4);RecipeService.discover(p,AgricultureItems.TOMATO);RecipeService.buy(p);RecipeService.refresh(p);});
|
||||
c.waitFor(m->RecipeClient.allowed()&&SanctuaryJeiPlugin.runtime()!=null,300);c.waitTicks(8);
|
||||
var type=c.computeOnClient(m->{
|
||||
var runtime=SanctuaryJeiPlugin.runtime();var crushing=KitchenJei.types().stream().filter(t->t.getUid().getPath().endsWith("fruit_crushing")).findFirst().orElseThrow();
|
||||
check(runtime.getRecipeManager().createRecipeLookup(crushing).includeHidden().get().count()==3,"Three direct crushing recipes indexed");
|
||||
check(!SanctuaryJeiPlugin.knows(new ItemStack(KitchenAppliances.FOOD_PRESS)),"Compatibility press cannot return through operator catalogue");
|
||||
check(runtime.getIngredientManager().getAllItemStacks().stream().noneMatch(i->i.is(KitchenAppliances.FOOD_PRESS.asItem())),"Press absent from actual JEI ingredients");
|
||||
return crushing;
|
||||
});
|
||||
for(String locale:List.of("fr_fr","en_us"))for(int scale:new int[]{2,3,4}){
|
||||
c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();SanctuaryJeiPlugin.runtime().getRecipesGui().showTypes(List.of(type));});
|
||||
c.waitTicks(5);c.takeScreenshot("crushing305-jei-"+locale+"-gui"+scale);c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(2);
|
||||
}
|
||||
saved=world.getWorldSave();
|
||||
}
|
||||
try(var world=saved.open()){
|
||||
c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);
|
||||
world.getServer().runOnServer(s->{var l=s.overworld();
|
||||
for(var e:gallery.entrySet())check(l.getBlockState(e.getKey()).equals(e.getValue()),"Disk reload preserves every partial state");
|
||||
check(l.getBlockState(PARTIAL).getValue(LAYERS)==2,"Partly crushed olive block persists");
|
||||
var base=PARTIAL.west();l.setBlockAndUpdate(base,Blocks.PISTON.defaultBlockState().setValue(net.minecraft.world.level.block.piston.PistonBaseBlock.FACING,Direction.EAST));l.setBlockAndUpdate(base.west(),Blocks.REDSTONE_BLOCK.defaultBlockState());
|
||||
});c.waitTicks(6);
|
||||
world.getServer().runOnServer(s->{check(count(s.overworld(),new AABB(PARTIAL).inflate(3),Cuisine.item("olive_oil"))==3,"Native piston after reload pays only three remaining portions");System.out.println("CRUSHING305_DISK_RELOAD partial_states=24 native_piston_remaining=3");});
|
||||
}
|
||||
System.out.println("CRUSHING305_CLIENT_PASS Vulkan, eight keyboard jumps, 24 models, 27-block super piston, retired press, JEI FR/EN GUI 2/3/4, disk reload");
|
||||
}
|
||||
private static int count(net.minecraft.server.level.ServerLevel l,AABB box,Item item){return l.getEntitiesOfClass(ItemEntity.class,box,e->e.isAlive()&&e.getItem().is(item)).stream().mapToInt(e->e.getItem().getCount()).sum();}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.Cuisine;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.RecipeService;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import mezz.jei.api.constants.RecipeTypes;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.*;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.inventory.*;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.SimpleMenuProvider;
|
||||
import net.minecraft.world.inventory.*;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.alchemy.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.*;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Native table packets, real smoker ticks, food catalogue/JEI and actual save reopening. */
|
||||
public final class Cuisine303ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos TABLE = new BlockPos(0, 1, 0), SMOKER = new BlockPos(2, 1, 0), CHEST = new BlockPos(4, 1, 0);
|
||||
private static void check(boolean ok, String message) { if (!ok) throw new AssertionError(message); }
|
||||
|
||||
@Override public void runTest(ClientGameTestContext c) {
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable(); var intro = new AtomicBoolean(true);
|
||||
c.runOnClient(m -> {
|
||||
var device = com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();
|
||||
check(device.backendName().equalsIgnoreCase("vulkan"), "Cuisine validation requires Vulkan");
|
||||
System.out.println("CUISINE303_BACKEND " + device.backendName() + " / " + device.name());
|
||||
m.options.renderDistance().set(8); m.options.simulationDistance().set(8); m.options.pauseOnLostFocus = false;
|
||||
});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m -> {
|
||||
if (intro.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
|
||||
IntroClient.setQuickTestIntro(true); Starter039TestSupport.selectFirst(screen);
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
|
||||
press(screen, "sanctuary.progression.enter"); intro.set(false);
|
||||
}
|
||||
});
|
||||
TestWorldSave saved;
|
||||
try (var world = c.worldBuilder().adjustSettings(s -> {
|
||||
s.setName("Cuisine303-" + UUID.randomUUID()); s.setSeed("303"); 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()) {
|
||||
c.waitFor(m -> m.player != null && ProgressionClient.active() && m.gui.screen() == null && !IntroSpawnFlash.active(), 1200);
|
||||
var server = world.getServer();
|
||||
server.runOnServer(s -> {
|
||||
var p = s.getPlayerList().getPlayers().getFirst(); var level = p.level();
|
||||
level.getGameRules().set(GameRules.SPAWN_MOBS, false, s);
|
||||
level.setBlockAndUpdate(TABLE, Blocks.CRAFTING_TABLE.defaultBlockState());
|
||||
level.setBlockAndUpdate(SMOKER, Blocks.SMOKER.defaultBlockState());
|
||||
level.setBlockAndUpdate(CHEST, Blocks.CHEST.defaultBlockState());
|
||||
p.teleportTo(.5, 1, 2.5); p.setGameMode(GameType.SURVIVAL); p.getInventory().clearContent();
|
||||
p.openMenu(new SimpleMenuProvider((id, inventory, player) -> new CraftingMenu(id, inventory, ContainerLevelAccess.create(level, TABLE)), Component.literal("Cuisine")));
|
||||
var menu = p.containerMenu;
|
||||
menu.getSlot(1).set(new ItemStack(AgricultureItems.LETTUCE));
|
||||
menu.getSlot(2).set(new ItemStack(AgricultureItems.CUCUMBER));
|
||||
menu.getSlot(3).set(new ItemStack(AgricultureItems.TOMATO)); menu.broadcastChanges();
|
||||
});
|
||||
c.waitFor(m -> m.gui.screen() instanceof CraftingScreen && m.player.containerMenu.getSlot(0).getItem().is(Cuisine.item("green_salad")), 300);
|
||||
c.takeScreenshot("cuisine303-native-salad-crafting");
|
||||
c.runOnClient(m -> m.gameMode.handleContainerInput(m.player.containerMenu.containerId, 0, 0, ContainerInput.QUICK_MOVE, m.player)); c.waitTicks(6);
|
||||
server.runOnServer(s -> {
|
||||
var p = s.getPlayerList().getPlayers().getFirst();
|
||||
check(p.getInventory().countItem(Cuisine.item("green_salad")) == 1, "Actual native packet crafts one salad");
|
||||
for (int i = 1; i <= 3; i++) check(p.containerMenu.getSlot(i).getItem().isEmpty(), "Actual ingredients consumed once");
|
||||
p.containerMenu.getSlot(1).set(new ItemStack(Cuisine.item("ground_coffee"), 3));
|
||||
p.containerMenu.getSlot(2).set(PotionContents.createItemStack(Items.POTION, Potions.WATER)); p.containerMenu.broadcastChanges();
|
||||
});
|
||||
c.waitFor(m -> m.player.containerMenu.getSlot(0).getItem().is(Cuisine.item("raw_cauldron_coffee")), 200);
|
||||
c.runOnClient(m -> m.gameMode.handleContainerInput(m.player.containerMenu.containerId, 0, 0, ContainerInput.QUICK_MOVE, m.player)); c.waitTicks(6);
|
||||
server.runOnServer(s -> {
|
||||
var p = s.getPlayerList().getPlayers().getFirst();
|
||||
check(p.getInventory().countItem(Cuisine.item("raw_cauldron_coffee")) == 1 && p.containerMenu.getSlot(2).getItem().is(Items.GLASS_BOTTLE), "Actual native crafting returns one water bottle");
|
||||
check(p.containerMenu.getSlot(1).getItem().getCount() == 2, "Exactly one ground coffee consumed");
|
||||
});
|
||||
c.getInput().pressKey(InputConstants.KEY_ESCAPE); c.waitFor(m -> m.gui.screen() == null, 200);
|
||||
server.runOnServer(s -> {
|
||||
var p = s.getPlayerList().getPlayers().getFirst(); var level = p.level();
|
||||
var slot = p.getInventory().findSlotMatchingItem(new ItemStack(Cuisine.item("raw_cauldron_coffee")));
|
||||
var removed = p.getInventory().removeItem(slot, 1);
|
||||
check(removed.getCount() == 1, "The crafted batch moves to the smoker");
|
||||
var smoker = (AbstractFurnaceBlockEntity) level.getBlockEntity(SMOKER);
|
||||
smoker.setItem(0, new ItemStack(Cuisine.item("raw_cauldron_coffee"))); smoker.setItem(1, new ItemStack(Items.COAL));
|
||||
});
|
||||
c.waitTicks(225);
|
||||
server.runOnServer(s -> {
|
||||
var smoker = (AbstractFurnaceBlockEntity) s.overworld().getBlockEntity(SMOKER);
|
||||
check(smoker.getItem(0).isEmpty() && smoker.getItem(2).is(Cuisine.item("coffee")) && smoker.getItem(2).getCount() == 1, "Real native smoker ticks cook the paid batch");
|
||||
var p = s.getPlayerList().getPlayers().getFirst();
|
||||
p.giveExperienceLevels(4); RecipeService.discover(p, AgricultureItems.TOMATO); check(RecipeService.buy(p), "Recipe catalogue unlocked normally"); RecipeService.refresh(p);
|
||||
});
|
||||
c.waitFor(m -> RecipeClient.allowed() && RecipeClient.knowledge.recipes.contains("sanctuary:cold_green_salad") && SanctuaryJeiPlugin.runtime() != null, 300);
|
||||
c.waitTicks(15);
|
||||
c.runOnClient(m -> {
|
||||
var runtime = SanctuaryJeiPlugin.runtime(); var manager = runtime.getRecipeManager(); var actual = new HashSet<String>();
|
||||
manager.createRecipeLookup(RecipeTypes.CRAFTING).includeHidden().get().forEach(r -> actual.add(r.id().identifier().toString()));
|
||||
manager.createRecipeLookup(RecipeTypes.SMELTING).includeHidden().get().forEach(r -> actual.add(r.id().identifier().toString()));
|
||||
manager.createRecipeLookup(RecipeTypes.SMOKING).includeHidden().get().forEach(r -> actual.add(r.id().identifier().toString()));
|
||||
for (var type : fr.koka.sanctuary.cuisine.KitchenJei.types())
|
||||
manager.createRecipeLookup(type).includeHidden().get().forEach(r -> {
|
||||
if (r instanceof net.minecraft.world.item.crafting.RecipeHolder<?> holder) actual.add(holder.id().identifier().toString());
|
||||
});
|
||||
int count = 0;
|
||||
for (var port : Cuisine.CATALOG.getAsJsonArray("ports")) for (var id : port.getAsJsonObject().getAsJsonArray("recipes")) {
|
||||
check(actual.contains(id.getAsString()), "Every new recipe is indexed by JEI: " + id); count++;
|
||||
}
|
||||
var coffeeRecipe = manager.createRecipeLookup(RecipeTypes.CRAFTING).includeHidden().get()
|
||||
.filter(r -> r.id().identifier().toString().equals("sanctuary:prepare_cauldron_coffee")).findFirst().orElseThrow().value();
|
||||
var water = coffeeRecipe.placementInfo().ingredients().get(1).display()
|
||||
.resolveForFirstStack(net.minecraft.world.item.crafting.display.SlotDisplayContext.fromLevel(m.level));
|
||||
check(water.is(Items.POTION) && water.get(net.minecraft.core.component.DataComponents.POTION_CONTENTS).is(Potions.WATER), "JEI displays plain water, not an unspecified potion");
|
||||
runtime.getRecipesGui().show(runtime.getJeiHelpers().getFocusFactory().createFocus(
|
||||
mezz.jei.api.recipe.RecipeIngredientRole.OUTPUT, mezz.jei.api.constants.VanillaTypes.ITEM_STACK, new ItemStack(Cuisine.item("green_salad"))));
|
||||
System.out.println("CUISINE303_JEI recipes=" + count + " plain_water=true");
|
||||
}); c.waitTicks(6); c.takeScreenshot("cuisine303-jei-crafting-recipes");
|
||||
c.runOnClient(m -> {
|
||||
var runtime = SanctuaryJeiPlugin.runtime();
|
||||
runtime.getRecipesGui().show(runtime.getJeiHelpers().getFocusFactory().createFocus(
|
||||
mezz.jei.api.recipe.RecipeIngredientRole.OUTPUT, mezz.jei.api.constants.VanillaTypes.ITEM_STACK, new ItemStack(Cuisine.item("raw_cauldron_coffee"))));
|
||||
}); c.waitTicks(6); c.takeScreenshot("cuisine303-jei-water-and-coffee");
|
||||
c.getInput().pressKey(InputConstants.KEY_ESCAPE); c.waitTicks(4);
|
||||
server.runOnServer(s -> s.getPlayerList().getPlayers().getFirst().setGameMode(GameType.CREATIVE)); c.waitTicks(6);
|
||||
c.runOnClient(m -> {
|
||||
check(Cuisine.items().size() == 129, "All 83 ingredients/meals and 46 uncooked preparations registered");
|
||||
CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(), true, m.level.registryAccess());
|
||||
var food = BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.FOOD_AND_DRINKS).value();
|
||||
for (var item : Cuisine.items()) {
|
||||
var stack = new ItemStack(item); check(food.getDisplayItems().stream().anyMatch(s -> s.is(item)), "Every new item belongs to native Food & Drinks");
|
||||
var state = new net.minecraft.client.renderer.item.ItemStackRenderState();
|
||||
m.getItemModelResolver().updateForTopItem(state, stack, ItemDisplayContext.GUI, m.level, m.player, 0);
|
||||
check(!state.isEmpty() && state.pickParticleMaterial(RandomSource.create(303)).sprite().contents().name().getNamespace().equals("sanctuary"), "Supplied food texture resolves: " + stack);
|
||||
}
|
||||
m.getWindow().setWindowed(1280, 960);
|
||||
System.out.println("CUISINE303_MODELS items=129 native_food_tab=true");
|
||||
});
|
||||
for (String locale : List.of("fr_fr", "en_us")) for (int scale : new int[]{2, 3, 4}) {
|
||||
c.runOnClient(m -> {
|
||||
m.getLanguageManager().setSelected(locale); m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
|
||||
m.options.guiScale().set(scale); m.resizeGui();
|
||||
for (var item : Cuisine.items()) check(!new ItemStack(item).getHoverName().getString().startsWith("item.sanctuary"), "Translated food: " + locale);
|
||||
m.gui.setScreen(new CreativeModeInventoryScreen(m.player, m.level.enabledFeatures(), true));
|
||||
var screen = (CreativeModeInventoryScreen) m.gui.screen();
|
||||
var tabs = (net.fabricmc.fabric.api.client.creativetab.v1.FabricCreativeModeInventoryScreen) screen;
|
||||
var food = BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.FOOD_AND_DRINKS).value();
|
||||
tabs.switchToPage(tabs.getPage(food)); tabs.setSelectedTab(food);
|
||||
screen.mouseScrolled(screen.width / 2.0, screen.height / 2.0, 0, -1000);
|
||||
screen.mouseScrolled(screen.width / 2.0, screen.height / 2.0, 0, 5);
|
||||
}); c.waitTicks(5); c.takeScreenshot("cuisine303-food-" + locale + "-gui" + scale);
|
||||
c.runOnClient(m -> m.gui.setScreen(null));
|
||||
}
|
||||
server.runOnServer(s -> {
|
||||
var chest = (ChestBlockEntity) s.overworld().getBlockEntity(CHEST);
|
||||
chest.setItem(0, new ItemStack(Cuisine.item("ratatouille"), 3));
|
||||
chest.setItem(1, new ItemStack(Cuisine.item("raw_oven_vanilla_cake"), 2));
|
||||
chest.setItem(2, new ItemStack(Cuisine.item("olive_oil"), 9));
|
||||
}); saved = world.getWorldSave();
|
||||
}
|
||||
try (var world = saved.open()) {
|
||||
c.waitFor(m -> m.player != null && m.gui.screen() == null, 1200);
|
||||
world.getServer().runOnServer(s -> {
|
||||
var chest = (ChestBlockEntity) s.overworld().getBlockEntity(CHEST);
|
||||
check(chest.getItem(0).is(Cuisine.item("ratatouille")) && chest.getItem(0).getCount() == 3, "Finished dish saved exactly");
|
||||
check(chest.getItem(1).is(Cuisine.item("raw_oven_vanilla_cake")) && chest.getItem(1).getCount() == 2, "Uncooked preparation saved exactly");
|
||||
check(chest.getItem(2).is(Cuisine.item("olive_oil")) && chest.getItem(2).getCount() == 9, "Ingredient saved exactly");
|
||||
System.out.println("CUISINE303_DISK_RELOAD meals_preparations_ingredients=true");
|
||||
});
|
||||
}
|
||||
System.out.println("CUISINE303_CLIENT_PASS Vulkan, native table packets, real smoker, JEI, 129 item models, native food tab, FR/EN GUI 2/3/4, disk reload");
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.client.screen.KitchenOvenScreen;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.CuredSausageBlock;
|
||||
import fr.koka.sanctuary.cuisine.machine.RedstoneMillBlockEntity;
|
||||
import fr.koka.sanctuary.cuisine.menu.RedstoneMillMenu;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.*;
|
||||
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.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.inventory.*;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.phys.*;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Vulkan: native table/pot/oven/mill/rack payments, saved foods, spicy slices and JEI. */
|
||||
public final class DriedCuisine318ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos OVEN=new BlockPos(0,1,0),TABLE=new BlockPos(2,1,0),POT=new BlockPos(4,1,0),MILL=new BlockPos(-2,1,0),RACK=new BlockPos(-4,1,0),DISPLAY=new BlockPos(0,1,4);
|
||||
private static void check(boolean ok,String text){if(!ok)throw new AssertionError(text);}
|
||||
private static void close(ClientGameTestContext c){c.getInput().pressKey(com.mojang.blaze3d.platform.InputConstants.KEY_ESCAPE);c.waitFor(m->m.gui.screen()==null,100);}
|
||||
private static void select(ClientGameTestContext c,int slot){c.runOnClient(m->{m.player.getInventory().setSelectedSlot(slot);m.player.connection.send(new net.minecraft.network.protocol.game.ServerboundSetCarriedItemPacket(slot));});c.waitTicks(3);}
|
||||
private static void use(ClientGameTestContext c,BlockPos pos){c.getInput().lookAt(pos);c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(pos),Direction.UP,pos,false)));c.waitTicks(5);}
|
||||
private static void click(ClientGameTestContext c,int slot,int button,ContainerInput action){c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,slot,button,action,m.player));c.waitTicks(3);}
|
||||
private static void moveOne(ClientGameTestContext c,Item item,int target){
|
||||
int index=c.computeOnClient(m->m.player.containerMenu.slots.stream().filter(s->s.container==m.player.getInventory()&&s.getItem().is(item)).findFirst().orElseThrow().index);
|
||||
click(c,index,0,ContainerInput.PICKUP);click(c,target,1,ContainerInput.PICKUP);
|
||||
if(c.computeOnClient(m->!m.player.containerMenu.getCarried().isEmpty()))click(c,index,0,ContainerInput.PICKUP);
|
||||
}
|
||||
private static void bite(ClientGameTestContext c,net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext server,int eaten){
|
||||
server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().getFoodData().setFoodLevel(0));c.waitFor(m->m.player.getFoodData().getFoodLevel()==0,100);
|
||||
var angles=new java.util.concurrent.atomic.AtomicReference<float[]>();c.runOnClient(m->{var point=m.level.getBlockState(DISPLAY).getShape(m.level,DISPLAY).bounds().getCenter().add(Vec3.atLowerCornerOf(DISPLAY));var d=point.subtract(m.player.getEyePosition());angles.set(new float[]{(float)(Math.toDegrees(Math.atan2(d.z,d.x))-90),(float)-Math.toDegrees(Math.atan2(d.y,Math.sqrt(d.x*d.x+d.z*d.z)))});});
|
||||
float health=c.computeOnClient(m->m.player.getHealth());c.getInput().lookAt(angles.get()[0],angles.get()[1]);c.waitTicks(4);c.getInput().pressKey(o->o.keyUse);c.waitTicks(4);c.runOnClient(m->check(m.player.hurtTime>0,"Spicy slice receives its first cosmetic hit"));
|
||||
c.waitTicks(8);c.runOnClient(m->check(m.player.hurtTime>0&&m.player.getHealth()==health,"A second cosmetic hit arrives with zero health loss"));
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var state=p.level().getBlockState(DISPLAY);check(p.getFoodData().getFoodLevel()==2&&(eaten==4?state.isAir():state.getValue(CuredSausageBlock.BITES)==eaten),"One real spicy slice restores two food points");});
|
||||
}
|
||||
@Override public void runTest(ClientGameTestContext c){
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable();var intro=new AtomicBoolean(true);
|
||||
c.runOnClient(m->{var d=com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();check(d.backendName().equalsIgnoreCase("vulkan"),"Vulkan required");System.out.println("DRIED318_BACKEND "+d.backendName()+" / "+d.name());m.options.renderDistance().set(6);m.options.simulationDistance().set(6);m.options.pauseOnLostFocus=false;});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){IntroClient.setQuickTestIntro(true);Starter039TestSupport.selectFirst(screen);screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());press(screen,"sanctuary.progression.enter");intro.set(false);}});
|
||||
TestWorldSave save;
|
||||
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("DriedCuisine318-"+UUID.randomUUID());s.setSeed("318");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()){
|
||||
c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);try{fr.koka.sanctuary.realtime.RealtimeService.setMode(s,false);fr.koka.sanctuary.weather.WeatherService.setMode(s,false);}catch(Exception e){throw new IllegalStateException(e);}s.clockManager().setTotalTicks(l.dimensionType().defaultClock().orElseThrow(),6000);l.getWeatherData().setRaining(false);l.setRainLevel(0);
|
||||
l.setBlockAndUpdate(TABLE,Blocks.CRAFTING_TABLE.defaultBlockState());l.setBlockAndUpdate(OVEN,KitchenAppliances.KITCHEN_OVEN.defaultBlockState());l.setBlockAndUpdate(POT.below(),Blocks.CAMPFIRE.defaultBlockState());l.setBlockAndUpdate(POT,KitchenAppliances.COOKING_CAULDRON.defaultBlockState());l.setBlockAndUpdate(MILL,KitchenAppliances.REDSTONE_MILL.defaultBlockState());l.setBlockAndUpdate(RACK.below(),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(RACK,KitchenAppliances.DRYING_RACK.defaultBlockState());l.setBlockAndUpdate(RACK.above(2),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(DISPLAY.below(),Blocks.STONE.defaultBlockState());
|
||||
p.setGameMode(GameType.SURVIVAL);p.teleportTo(2.5,1,2.5);p.getInventory().clearContent();p.getInventory().setItem(0,new ItemStack(Items.BREAD));p.getInventory().setItem(1,new ItemStack(DriedFoods.item("dried_meat")));p.getInventory().setItem(2,new ItemStack(DriedFoods.item("dried_tomato")));p.getInventory().setItem(3,new ItemStack(AgricultureItems.LETTUCE));p.giveExperienceLevels(8);RecipeService.buy(p);for(Item i:DriedCuisine.items())RecipeService.discover(p,i);for(Item i:DriedFoods.items())RecipeService.discover(p,i);RecipeService.refresh(p);
|
||||
});select(c,8);use(c,TABLE);c.waitFor(m->m.player.containerMenu instanceof CraftingMenu,150);
|
||||
moveOne(c,Items.BREAD,1);moveOne(c,DriedFoods.item("dried_meat"),2);moveOne(c,DriedFoods.item("dried_tomato"),3);moveOne(c,AgricultureItems.LETTUCE,4);
|
||||
c.waitFor(m->m.player.containerMenu.getSlot(0).getItem().is(DriedCuisine.item("traveler_sandwich")),150);c.takeScreenshot("dried318-native-sandwich-craft");click(c,0,0,ContainerInput.QUICK_MOVE);close(c);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(DriedCuisine.item("traveler_sandwich"))==1&&p.getInventory().countItem(Items.BREAD)==0&&p.getInventory().countItem(DriedFoods.item("dried_meat"))==0,"Native table pays all sandwich ingredients");p.getInventory().clearContent();p.getInventory().setItem(0,new ItemStack(AgricultureItems.RICE));p.getInventory().setItem(1,new ItemStack(DriedFoods.item("dried_salmon")));p.getInventory().setItem(2,new ItemStack(DriedFoods.item("dried_coriander")));p.getInventory().setItem(3,new ItemStack(Items.BOWL));p.getInventory().setItem(8,new ItemStack(Items.STICK));p.teleportTo(4.5,1,2.5);((CookingCauldronBlockEntity)p.level().getBlockEntity(POT)).setWaterUnits(1);});
|
||||
for(int i=0;i<4;i++){select(c,i);use(c,POT);}server.runOnServer(s->{var pot=(CookingCauldronBlockEntity)s.overworld().getBlockEntity(POT);check(pot.progress()==0&&!pot.assemblyComplete(),"Pot waits for the explicit stir");});select(c,8);use(c,POT);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.getInventory().clearContent();p.getInventory().setItem(0,new ItemStack(Cuisine.item("raw_dough")));p.getInventory().setItem(1,new ItemStack(DriedFoods.item("raisins")));p.getInventory().setItem(2,new ItemStack(Items.COAL));p.teleportTo(.5,1,2.5);p.openMenu((KitchenOvenBlockEntity)p.level().getBlockEntity(OVEN));});
|
||||
c.waitFor(m->m.gui.screen() instanceof KitchenOvenScreen&&m.player.containerMenu.getSlot(38).getItem().is(Cuisine.item("raw_dough")),150);moveOne(c,Cuisine.item("raw_dough"),0);moveOne(c,DriedFoods.item("raisins"),1);moveOne(c,Items.COAL,9);
|
||||
c.runOnClient(m->{CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(),true,m.level.registryAccess());var tab=BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.FOOD_AND_DRINKS).value();for(Item i:DriedCuisine.items()){var render=new net.minecraft.client.renderer.item.ItemStackRenderState();m.getItemModelResolver().updateForTopItem(render,new ItemStack(i),ItemDisplayContext.GUI,m.level,m.player,318);check(!render.isEmpty()&&!render.pickParticleMaterial(net.minecraft.util.RandomSource.create(318)).sprite().contents().name().getPath().equals("missingno"),"Every new icon resolves");check(tab.getDisplayItems().stream().anyMatch(stack->stack.is(i)),"Food & Drinks contains every new result and preparation");}System.out.println("DRIED318_MODELS items=19 missing=0");});
|
||||
for(String lang:List.of("fr_fr","en_us"))for(int scale:new int[]{2,3,4}){c.runOnClient(m->{m.getLanguageManager().setSelected(lang);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();for(Item i:DriedCuisine.items())check(!new ItemStack(i).getHoverName().getString().startsWith("item.sanctuary"),"Translated new food name");check(!DriedCuisine.SPICED_SAUSAGE.getName().getString().startsWith("block.sanctuary"),"Translated placed sausage name");});c.waitTicks(3);c.takeScreenshot("dried318-oven-"+lang+"-gui"+scale);}close(c);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.teleportTo(-1.5,1,2.5);p.getInventory().clearContent();p.getInventory().setItem(0,new ItemStack(DriedFoods.item("dried_chili_pepper"),4));p.openMenu((RedstoneMillBlockEntity)p.level().getBlockEntity(MILL));});
|
||||
c.waitFor(m->m.player.containerMenu instanceof RedstoneMillMenu&&m.player.containerMenu.getSlot(31).getItem().is(DriedFoods.item("dried_chili_pepper")),150);click(c,31,0,ContainerInput.QUICK_MOVE);c.waitFor(m->m.player.containerMenu.getSlot(1).getItem().is(DriedCuisine.item("chili_powder")),150);c.takeScreenshot("dried318-manual-chili-powder");click(c,1,0,ContainerInput.QUICK_MOVE);close(c);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(DriedCuisine.item("chili_powder"))==8&&p.getInventory().countItem(DriedFoods.item("dried_chili_pepper"))==0,"Native mill pays four peppers for eight powder portions");p.getInventory().setItem(1,new ItemStack(KitchenAppliances.RAW_SAUSAGE));p.getInventory().setItem(2,new ItemStack(DriedFoods.item("dried_coriander")));p.teleportTo(2.5,1,2.5);});select(c,7);use(c,TABLE);c.waitFor(m->m.player.containerMenu instanceof CraftingMenu,150);
|
||||
moveOne(c,KitchenAppliances.RAW_SAUSAGE,1);moveOne(c,DriedCuisine.item("chili_powder"),2);moveOne(c,DriedFoods.item("dried_coriander"),3);c.waitFor(m->m.player.containerMenu.getSlot(0).getItem().is(DriedCuisine.item("raw_spiced_sausage")),150);click(c,0,0,ContainerInput.QUICK_MOVE);close(c);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(DriedCuisine.item("chili_powder"))==7&&p.getInventory().countItem(DriedCuisine.item("raw_spiced_sausage"))==1,"Spicy preparation consumes one of the actual milled powder portions");for(int i=0;i<36;i++)if(p.getInventory().getItem(i).is(DriedCuisine.item("raw_spiced_sausage"))){p.getInventory().setItem(8,p.getInventory().removeItem(i,1));break;}p.teleportTo(-3.5,1,2.5);});select(c,8);use(c,RACK);c.runOnClient(m->check(((DryingRackBlockEntity)m.level.getBlockEntity(RACK)).getItem(0).is(DriedCuisine.item("raw_spiced_sausage")),"Actual raw spicy sausage enters its native hook"));c.takeScreenshot("dried318-raw-spicy-sausage");
|
||||
c.waitFor(m->((KitchenOvenBlockEntity)m.level.getBlockEntity(OVEN)).getItem(10).is(DriedCuisine.item("raisin_bread"))&&((CookingCauldronBlockEntity)m.level.getBlockEntity(POT)).getItem(6).is(DriedCuisine.item("dried_salmon_rice")),600);save=world.getWorldSave();
|
||||
}
|
||||
try(var world=save.open()){
|
||||
c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();var rack=(DryingRackBlockEntity)l.getBlockEntity(RACK);check(rack.getItem(0).is(DriedCuisine.item("raw_spiced_sausage"))&&rack.progress(0)>0,"Actual disk save keeps a new spicy preparation and its rack timer");check(((KitchenOvenBlockEntity)l.getBlockEntity(OVEN)).getItem(10).is(DriedCuisine.item("raisin_bread"))&&((CookingCauldronBlockEntity)l.getBlockEntity(POT)).getItem(6).is(DriedCuisine.item("dried_salmon_rice")),"Actual disk save keeps both newly cooked foods");var tag=rack.saveCustomOnly(s.registryAccess());tag.putInt("DryingProgress0",rack.duration(0)-30);rack.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create(net.minecraft.util.ProblemReporter.DISCARDING,s.registryAccess(),tag));p.teleportTo(-3.5,1,2.5);});select(c,8);c.waitFor(m->((DryingRackBlockEntity)m.level.getBlockEntity(RACK)).readyCount()==1,150);c.getInput().lookAt(RACK);c.takeScreenshot("dried318-finished-spicy-sausage");use(c,RACK);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(DriedCuisine.item("spiced_sausage"))==1,"Native collection gives exactly one spicy sausage");for(int i=0;i<36;i++)if(p.getInventory().getItem(i).is(DriedCuisine.item("spiced_sausage"))){p.getInventory().setItem(8,p.getInventory().removeItem(i,1));break;}p.teleportTo(.5,1,6.5);p.getFoodData().setFoodLevel(0);});select(c,8);use(c,DISPLAY.below());c.waitFor(m->m.level.getBlockState(DISPLAY).is(DriedCuisine.SPICED_SAUSAGE),150);c.getInput().lookAt(DISPLAY);c.takeScreenshot("dried318-placed-spicy-sausage");bite(c,server,1);bite(c,server,2);c.takeScreenshot("dried318-two-spicy-slices-left");bite(c,server,3);bite(c,server,4);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(s.overworld().getBlockState(DISPLAY).isAir()&&p.getFoodData().getFoodLevel()==2,"Four native clicks pay four spicy slices, two food each");p.teleportTo(.5,1,2.5);p.openMenu((KitchenOvenBlockEntity)p.level().getBlockEntity(OVEN));});c.waitFor(m->m.gui.screen() instanceof KitchenOvenScreen,150);click(c,10,0,ContainerInput.QUICK_MOVE);close(c);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(DriedCuisine.item("raisin_bread"))==1,"Collect actual oven result after reload");p.teleportTo(4.5,1,2.5);});select(c,8);use(c,POT);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(DriedCuisine.item("dried_salmon_rice"))==1&&p.getInventory().countItem(Items.BOWL)==0,"One served pot meal contains its paid bowl");for(int i=0;i<36;i++)if(p.getInventory().getItem(i).is(DriedCuisine.item("dried_salmon_rice"))){p.getInventory().setItem(6,p.getInventory().removeItem(i,1));break;}p.getFoodData().setFoodLevel(0);});select(c,6);c.getInput().holdKey(o->o.keyUse);c.waitFor(m->m.player.getFoodData().getFoodLevel()>0,150);c.getInput().releaseKey(o->o.keyUse);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(DriedCuisine.item("dried_salmon_rice"))==0&&p.getInventory().countItem(Items.BOWL)==1,"Eating returns exactly the paid bowl");});
|
||||
c.waitFor(m->RecipeClient.allowed()&&SanctuaryJeiPlugin.runtime()!=null,200);
|
||||
for(String[] pair:new String[][]{{"kitchen_oven","cook_raisin_bread"},{"cooking_cauldron","cook_dried_salmon_rice"},{"redstone_mill","mill_chili_powder"},{"drying_rack","dry_spiced_sausage"}}){c.runOnClient(m->{var runtime=SanctuaryJeiPlugin.runtime();var type=KitchenJei.types().stream().filter(t->t.getUid().getPath().endsWith(pair[0])).findFirst().orElseThrow();check(runtime.getRecipeManager().createRecipeLookup(type).includeHidden().get().anyMatch(v->v instanceof RecipeHolder<?> holder&&holder.id().identifier().getPath().equals(pair[1])),"New recipe synchronized in JEI: "+pair[1]);runtime.getRecipesGui().showTypes(List.of(type));});c.waitTicks(4);c.takeScreenshot("dried318-jei-"+pair[0]);close(c);}
|
||||
}
|
||||
System.out.println("DRIED318_CLIENT_PASS Vulkan, 19 icons FR/EN GUI2/3/4, paid table/oven/pot/mill/rack, actual disk save, four spicy slices with two harmless hits each, one bowl returned, synchronized JEI");
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.DryingRackBlockEntity;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.DryingRackBlock;
|
||||
import net.minecraft.world.inventory.*;
|
||||
import net.minecraft.client.gui.screens.inventory.CraftingScreen;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.RecipeService;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.*;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.client.sounds.SoundEventListener;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.phys.*;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Native crafting packets, four physical hooks, bucket weather pause, cuboid models and disk. */
|
||||
public final class Drying313ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos POT=new BlockPos(0,1,0),TABLE=new BlockPos(-3,1,0);
|
||||
private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
|
||||
private static void use(ClientGameTestContext c){c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(POT),Direction.UP,POT,false)));c.waitTicks(6);}
|
||||
@Override public void runTest(ClientGameTestContext c){
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable();var intro=new AtomicBoolean(true);var audio=new CopyOnWriteArrayList<String>();
|
||||
SoundEventListener listener=(sound,event,distance)->{if(sound.getX()>=0&&sound.getX()<=1&&sound.getZ()>=0&&sound.getZ()<=1)audio.add(sound.getIdentifier().toString());};
|
||||
c.runOnClient(m->{var device=com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();check(device.backendName().equalsIgnoreCase("vulkan"),"Cauldron validation requires Vulkan");System.out.println("DRYING313_BACKEND "+device.backendName()+" / "+device.name());m.options.renderDistance().set(8);m.options.simulationDistance().set(8);m.options.pauseOnLostFocus=false;m.getSoundManager().addListener(listener);});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){IntroClient.setQuickTestIntro(true);Starter039TestSupport.selectFirst(screen);screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());press(screen,"sanctuary.progression.enter");intro.set(false);}});
|
||||
TestWorldSave saved;var savedProgress=new java.util.concurrent.atomic.AtomicInteger();
|
||||
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Drying313-"+UUID.randomUUID());s.setSeed("313");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()){
|
||||
c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);try{fr.koka.sanctuary.realtime.RealtimeService.setMode(s,false);fr.koka.sanctuary.weather.WeatherService.setMode(s,false);}catch(Exception e){throw new IllegalStateException(e);}s.clockManager().setTotalTicks(l.dimensionType().defaultClock().orElseThrow(),6000);l.getWeatherData().setRaining(false);l.setRainLevel(0);
|
||||
l.setBlockAndUpdate(TABLE,Blocks.CRAFTING_TABLE.defaultBlockState());l.setBlockAndUpdate(new BlockPos(-3,0,2),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(new BlockPos(0,0,2),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(POT.below(),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(POT.above(),Blocks.STONE.defaultBlockState());p.setGameMode(GameType.SURVIVAL);p.teleportTo(-2.5,1,2.5);p.getInventory().clearContent();p.getInventory().setItem(0,new ItemStack(Items.PORKCHOP,4));p.getInventory().setItem(1,new ItemStack(Cuisine.item("salt"),2));p.getInventory().setItem(2,new ItemStack(AgricultureItems.GARLIC,2));p.getInventory().setItem(3,new ItemStack(Items.STRING,2));p.giveExperienceLevels(4);RecipeService.discover(p,Items.PORKCHOP);RecipeService.buy(p);RecipeService.refresh(p);p.openMenu(l.getBlockState(TABLE).getMenuProvider(l,TABLE));
|
||||
});c.waitFor(m->m.gui.screen() instanceof CraftingScreen,200);
|
||||
click(c,37,0,ContainerInput.PICKUP);click(c,1,1,ContainerInput.PICKUP);click(c,1,1,ContainerInput.PICKUP);click(c,2,1,ContainerInput.PICKUP);click(c,2,1,ContainerInput.PICKUP);
|
||||
for(int i=0;i<3;i++){click(c,38+i,0,ContainerInput.PICKUP);click(c,3+i,0,ContainerInput.PICKUP);}
|
||||
c.waitFor(m->m.player.containerMenu.getSlot(0).getItem().is(KitchenAppliances.RAW_SAUSAGE),200);c.takeScreenshot("drying313-preparation-at-native-table");click(c,0,0,ContainerInput.QUICK_MOVE);c.waitTicks(6);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(KitchenAppliances.RAW_SAUSAGE)==4&&p.getInventory().countItem(Items.PORKCHOP)==0&&p.getInventory().countItem(Items.STRING)==0&&p.getInventory().countItem(Cuisine.item("salt"))==0&&p.getInventory().countItem(AgricultureItems.GARLIC)==0,"Native shift crafting pays four pork, two salt/garlic/thread for four preparations");});c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(5);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();for(int i=0;i<36;i++)if(p.getInventory().getItem(i).is(KitchenAppliances.RAW_SAUSAGE)){var raw=p.getInventory().removeItemNoUpdate(i);p.getInventory().setItem(0,raw);break;}p.getInventory().setItem(8,new ItemStack(KitchenAppliances.DRYING_RACK));p.teleportTo(.5,1,2.5);});c.waitTicks(5);select(c,8);c.getInput().lookAt(POT);c.waitTicks(3);
|
||||
c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(new Vec3(.5,1,.5),Direction.UP,POT.below(),false)));c.waitFor(m->m.level.getBlockEntity(POT) instanceof DryingRackBlockEntity,200);select(c,0);
|
||||
for(int i=0;i<4;i++)use(c);c.waitFor(m->((DryingRackBlockEntity)m.level.getBlockEntity(POT)).dryingCount()==4,200);c.takeScreenshot("drying313-four-hanging-preparations");
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var rack=(DryingRackBlockEntity)p.level().getBlockEntity(POT);check(p.getInventory().countItem(KitchenAppliances.RAW_SAUSAGE)==0&&rack.progress(0)>rack.progress(3),"Four real native insertions pay four preparations and keep separate ages");});use(c);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var rack=(DryingRackBlockEntity)p.level().getBlockEntity(POT);check(rack.getItem(3).isEmpty()&&rack.progress(0)>0&&p.getInventory().countItem(KitchenAppliances.RAW_SAUSAGE)==1,"Taking one unfinished preparation leaves the others drying");});use(c);c.waitTicks(5);
|
||||
c.runOnClient(m->{m.getWindow().setWindowed(1280,960);int models=0;for(var state:KitchenAppliances.DRYING_RACK.getStateDefinition().getPossibleStates()){
|
||||
var parts=new ArrayList<net.minecraft.client.renderer.block.dispatch.BlockStateModelPart>();m.getModelManager().getBlockStateModelSet().get(state).collectParts(net.minecraft.util.RandomSource.create(313),parts);int quads=0;float maxY=0;
|
||||
for(var part:parts)for(var direction:new Direction[]{null,Direction.UP,Direction.DOWN,Direction.NORTH,Direction.SOUTH,Direction.EAST,Direction.WEST})for(var q:part.getQuads(direction)){check(q.materialInfo().sprite().contents().name().getNamespace().equals("minecraft"),"Rack resolves native wood textures");for(int v=0;v<4;v++)maxY=Math.max(maxY,q.position(v).y());quads++;}
|
||||
check(quads==30&&Math.abs(maxY-1)<.0001,"Five frame cuboids resolve at full block height");models++;
|
||||
}check(models==8,"Four orientations and both native water states bake correctly");
|
||||
net.minecraft.world.item.CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(),true,m.level.registryAccess());var food=net.minecraft.core.registries.BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(net.minecraft.world.item.CreativeModeTabs.FOOD_AND_DRINKS).value();for(var item:List.of(KitchenAppliances.RAW_SAUSAGE,KitchenAppliances.CURED_SAUSAGE,KitchenAppliances.DRYING_RACK.asItem())){var render=new net.minecraft.client.renderer.item.ItemStackRenderState();m.getItemModelResolver().updateForTopItem(render,new ItemStack(item),ItemDisplayContext.GUI,m.level,m.player,313);check(!render.isEmpty()&&!render.pickParticleMaterial(net.minecraft.util.RandomSource.create(313)).sprite().contents().name().getPath().equals("missingno")&&food.getDisplayItems().stream().anyMatch(s->s.is(item)),"Native cuboid item model and existing food category: "+item);}
|
||||
System.out.println("DRYING313_MODELS states="+models+" items=3 native_textures=true");
|
||||
});c.getInput().lookAt(POT);c.waitTicks(4);
|
||||
for(String locale:List.of("fr_fr","en_us"))for(int scale:new int[]{2,3,4}){c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();check(m.hitResult instanceof BlockHitResult h&&h.getBlockPos().equals(POT),"Physical rack HUD is targeted");});c.waitTicks(3);c.takeScreenshot("drying313-hud-"+locale+"-gui"+scale);}
|
||||
server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().getInventory().setItem(8,new ItemStack(Items.WATER_BUCKET)));select(c,8);c.getInput().pressKey(o->o.keyUse);c.waitFor(m->m.level.getBlockState(POT).getValue(DryingRackBlock.WATERLOGGED),150);c.waitTicks(4);c.takeScreenshot("drying313-native-water-pause");
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var rack=(DryingRackBlockEntity)p.level().getBlockEntity(POT);check(rack.paused()&&rack.readyCount()==0&&p.getMainHandItem().is(Items.BUCKET),"Actual bucket interaction waterlogs the rack and preserves all raw food");savedProgress.set(rack.progress(0));});saved=world.getWorldSave();
|
||||
}
|
||||
try(var world=saved.open()){
|
||||
c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);var server=world.getServer();server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var rack=(DryingRackBlockEntity)p.level().getBlockEntity(POT);check(rack.dryingCount()==4&&rack.progress(0)==savedProgress.get()&&rack.getBlockState().getValue(DryingRackBlock.WATERLOGGED),"Actual disk reopen preserves all four items and paused progress exactly");p.teleportTo(.5,1,2.5);});c.waitTicks(8);c.getInput().lookAt(POT);select(c,8);c.getInput().pressKey(o->o.keyUse);c.waitFor(m->!m.level.getBlockState(POT).getValue(DryingRackBlock.WATERLOGGED),150);c.waitTicks(8);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var rack=(DryingRackBlockEntity)p.level().getBlockEntity(POT);check(rack.progress(0)>savedProgress.get()&&p.getMainHandItem().is(Items.WATER_BUCKET),"Native bucket drain resumes loaded drying without replacing any preparation");var tag=rack.saveCustomOnly(p.level().registryAccess());for(int i=0;i<4;i++)tag.putInt("DryingProgress"+i,6000-40-i*20);rack.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create(net.minecraft.util.ProblemReporter.DISCARDING,p.level().registryAccess(),tag));p.level().sendBlockUpdated(POT,rack.getBlockState(),rack.getBlockState(),net.minecraft.world.level.block.Block.UPDATE_CLIENTS);});select(c,0);
|
||||
c.waitFor(m->((DryingRackBlockEntity)m.level.getBlockEntity(POT)).readyCount()==4,220);c.takeScreenshot("drying313-four-finished-saucissons");for(int i=0;i<4;i++)use(c);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var rack=(DryingRackBlockEntity)p.level().getBlockEntity(POT);check(rack.isEmpty()&&p.getInventory().countItem(KitchenAppliances.CURED_SAUSAGE)==4&&p.getInventory().countItem(KitchenAppliances.RAW_SAUSAGE)==0,"Four real collection interactions give exactly four cured products");p.level().setBlockAndUpdate(new BlockPos(2,2,0),Blocks.STONE.defaultBlockState());p.getFoodData().setFoodLevel(1);});c.waitFor(m->m.player.getFoodData().getFoodLevel()==1,100);
|
||||
// Since beta.315, share the drying result after native placement, like cheese.
|
||||
c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(new Vec3(2.5,3,.5),Direction.UP,new BlockPos(2,2,0),false)));c.waitFor(m->m.level.getBlockState(new BlockPos(2,3,0)).is(KitchenAppliances.CURED_SAUSAGE_BLOCK),100);
|
||||
c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(new Vec3(2.5,3.125,.5),Direction.UP,new BlockPos(2,3,0),false)));c.waitTicks(6);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(KitchenAppliances.CURED_SAUSAGE)==3&&p.getFoodData().getFoodLevel()>1&&p.level().getBlockState(new BlockPos(2,3,0)).getValue(fr.koka.sanctuary.cuisine.culinary.block.CuredSausageBlock.BITES)==1,"The actual drying result is placed and one slice restores hunger without a whole-item refund");});
|
||||
c.waitFor(m->RecipeClient.allowed()&&SanctuaryJeiPlugin.runtime()!=null,200);c.runOnClient(m->{var runtime=SanctuaryJeiPlugin.runtime();var type=KitchenJei.types().stream().filter(t->t.getUid().getPath().endsWith("drying_rack")).findFirst().orElseThrow();check(runtime.getRecipeManager().createRecipeLookup(type).includeHidden().get().count()==16,"All drying recipes are synchronized in JEI");runtime.getRecipesGui().showTypes(List.of(type));});c.waitTicks(5);c.takeScreenshot("drying313-jei-five-minute-recipe");c.getInput().pressKey(InputConstants.KEY_ESCAPE);
|
||||
}
|
||||
c.runOnClient(m->m.getSoundManager().removeListener(listener));System.out.println("DRYING313_CLIENT_PASS Vulkan, native table shift craft/payout, paid rack placement, four native independent hooks, raw cancellation, 8 baked states/3 native-textured item models, existing food category, FR/EN GUI2/3/4, real bucket pause/drain, disk progress, timed finish/collection, native placed-slice eating and JEI");
|
||||
}
|
||||
private static void click(ClientGameTestContext c,int slot,int button,ContainerInput input){c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,slot,button,input,m.player));c.waitTicks(3);}
|
||||
private static void select(ClientGameTestContext c,int slot){c.runOnClient(m->{m.player.getInventory().setSelectedSlot(slot);m.player.connection.send(new net.minecraft.network.protocol.game.ServerboundSetCarriedItemPacket(slot));});c.waitTicks(4);}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.DryingRackBlock;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.DryingRackBlockEntity;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
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.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.phys.*;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** New dried foods with real native placement, four mixed hooks, bucket pause, disk and consumption. */
|
||||
public final class Drying317ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos POT=new BlockPos(0,1,0);
|
||||
private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
|
||||
private static void select(ClientGameTestContext c,int slot){c.runOnClient(m->{m.player.getInventory().setSelectedSlot(slot);m.player.connection.send(new net.minecraft.network.protocol.game.ServerboundSetCarriedItemPacket(slot));});c.waitTicks(3);}
|
||||
private static void use(ClientGameTestContext c){c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(POT),Direction.UP,POT,false)));c.waitTicks(5);}
|
||||
@Override public void runTest(ClientGameTestContext c){
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable();var intro=new AtomicBoolean(true);var progress=new AtomicInteger();
|
||||
c.runOnClient(m->{var d=com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();check(d.backendName().equalsIgnoreCase("vulkan"),"Vulkan required");System.out.println("DRYING317_BACKEND "+d.backendName()+" / "+d.name());m.options.renderDistance().set(6);m.options.simulationDistance().set(6);m.options.pauseOnLostFocus=false;});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){IntroClient.setQuickTestIntro(true);Starter039TestSupport.selectFirst(screen);screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());press(screen,"sanctuary.progression.enter");intro.set(false);}});
|
||||
TestWorldSave save;
|
||||
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Drying317-"+UUID.randomUUID());s.setSeed("317");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()){
|
||||
c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);try{fr.koka.sanctuary.realtime.RealtimeService.setMode(s,false);fr.koka.sanctuary.weather.WeatherService.setMode(s,false);}catch(Exception e){throw new IllegalStateException(e);}s.clockManager().setTotalTicks(l.dimensionType().defaultClock().orElseThrow(),6000);l.getWeatherData().setRaining(false);l.setRainLevel(0);l.setBlockAndUpdate(POT.below(),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(new BlockPos(0,0,2),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(POT.above(),Blocks.STONE.defaultBlockState());p.setGameMode(GameType.SURVIVAL);p.teleportTo(.5,1,2.5);p.getInventory().clearContent();p.getInventory().setItem(8,new ItemStack(KitchenAppliances.DRYING_RACK));p.getInventory().setItem(0,new ItemStack(Items.BEEF,2));p.getInventory().setItem(1,new ItemStack(Items.COD));p.getInventory().setItem(2,new ItemStack(AgricultureItems.BASIL));p.getInventory().setItem(3,new ItemStack(AgricultureItems.GRAPES));p.giveExperienceLevels(4);RecipeService.buy(p);for(var row:DriedFoods.CATALOG.getAsJsonArray("recipes"))RecipeService.discover(p,BuiltInRegistries.ITEM.getValue(net.minecraft.resources.Identifier.parse(row.getAsJsonObject().get("input").getAsString())));RecipeService.refresh(p);});
|
||||
select(c,8);c.getInput().lookAt(POT);c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(new Vec3(.5,1,.5),Direction.UP,POT.below(),false)));c.waitFor(m->m.level.getBlockEntity(POT) instanceof DryingRackBlockEntity,200);
|
||||
for(int i=0;i<4;i++){select(c,i);use(c);}server.waitFor(s->((DryingRackBlockEntity)s.overworld().getBlockEntity(POT)).dryingCount()==4,150);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(Items.BEEF)==1&&p.getInventory().countItem(Items.COD)==0&&p.getInventory().countItem(AgricultureItems.BASIL)==0&&p.getInventory().countItem(AgricultureItems.GRAPES)==0&&p.getInventory().countItem(KitchenAppliances.DRYING_RACK.asItem())==0,"Real rack placement and four insertions each consume one ingredient");});
|
||||
c.getInput().lookAt(POT);c.waitTicks(6);c.takeScreenshot("drying317-four-different-foods");
|
||||
c.runOnClient(m->{CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(),true,m.level.registryAccess());var tab=BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.FOOD_AND_DRINKS).value();for(Item item:DriedFoods.items()){var render=new net.minecraft.client.renderer.item.ItemStackRenderState();m.getItemModelResolver().updateForTopItem(render,new ItemStack(item),ItemDisplayContext.GUI,m.level,m.player,317);check(!render.isEmpty()&&!render.pickParticleMaterial(net.minecraft.util.RandomSource.create(317)).sprite().contents().name().getPath().equals("missingno"),"Dried food icon resolves its source texture and tint");check(tab.getDisplayItems().stream().anyMatch(stack->stack.is(item)),"Existing Food & Drinks tab contains "+item);}System.out.println("DRYING317_MODELS items="+DriedFoods.items().size()+" missing=0");});
|
||||
for(String locale:List.of("fr_fr","en_us"))for(int scale:new int[]{2,3,4}){c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();for(Item i:DriedFoods.items())check(!new ItemStack(i).getHoverName().getString().startsWith("item.sanctuary"),"Translated dried food name");});c.getInput().lookAt(POT);c.waitTicks(3);c.takeScreenshot("drying317-hud-"+locale+"-gui"+scale);}
|
||||
server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().getInventory().setItem(8,new ItemStack(Items.WATER_BUCKET)));select(c,8);c.getInput().pressKey(o->o.keyUse);c.waitFor(m->m.level.getBlockState(POT).getValue(DryingRackBlock.WATERLOGGED),150);c.waitTicks(4);
|
||||
server.runOnServer(s->{var r=(DryingRackBlockEntity)s.overworld().getBlockEntity(POT);check(r.paused()&&r.readyCount()==0,"Native bucket pauses every mixed hook");progress.set(r.progress(0));});save=world.getWorldSave();
|
||||
}
|
||||
try(var world=save.open()){
|
||||
c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);var server=world.getServer();server.runOnServer(s->{var r=(DryingRackBlockEntity)s.overworld().getBlockEntity(POT);check(r.progress(0)==progress.get()&&r.dryingCount()==4&&r.getBlockState().getValue(DryingRackBlock.WATERLOGGED),"Four different foods and paused work persist on actual disk reload");s.getPlayerList().getPlayers().getFirst().teleportTo(.5,1,2.5);});
|
||||
c.getInput().lookAt(POT);select(c,8);c.getInput().pressKey(o->o.keyUse);c.waitFor(m->!m.level.getBlockState(POT).getValue(DryingRackBlock.WATERLOGGED),150);c.waitTicks(3);
|
||||
server.runOnServer(s->{var r=(DryingRackBlockEntity)s.overworld().getBlockEntity(POT);var tag=r.saveCustomOnly(s.registryAccess());for(int i=0;i<4;i++)tag.putInt("DryingProgress"+i,r.duration(i)-30-i*5);r.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create(net.minecraft.util.ProblemReporter.DISCARDING,s.registryAccess(),tag));s.overworld().sendBlockUpdated(POT,r.getBlockState(),r.getBlockState(),Block.UPDATE_CLIENTS);});
|
||||
select(c,7);c.waitFor(m->((DryingRackBlockEntity)m.level.getBlockEntity(POT)).readyCount()==4,200);c.getInput().lookAt(POT);c.takeScreenshot("drying317-four-dried-results");for(int i=0;i<4;i++)use(c);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();for(String id:List.of("dried_meat","dried_cod","dried_basil","raisins"))check(p.getInventory().countItem(DriedFoods.item(id))==1,"Actual native collection returns one "+id);check(((DryingRackBlockEntity)s.overworld().getBlockEntity(POT)).isEmpty(),"No residual copied output");for(int i=0;i<36;i++)if(p.getInventory().getItem(i).is(DriedFoods.item("dried_meat"))){p.getInventory().setItem(6,p.getInventory().removeItemNoUpdate(i));break;}p.getFoodData().setFoodLevel(0);});
|
||||
select(c,6);c.getInput().holdKey(o->o.keyUse);c.waitFor(m->m.player.getFoodData().getFoodLevel()>0,150);c.getInput().releaseKey(o->o.keyUse);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(DriedFoods.item("dried_meat"))==0&&p.getFoodData().getFoodLevel()>0,"Native eating consumes the real dried meat");});
|
||||
c.waitFor(m->RecipeClient.allowed()&&SanctuaryJeiPlugin.runtime()!=null,200);c.runOnClient(m->{var runtime=SanctuaryJeiPlugin.runtime();var type=KitchenJei.types().stream().filter(t->t.getUid().getPath().endsWith("drying_rack")).findFirst().orElseThrow();check(runtime.getRecipeManager().createRecipeLookup(type).includeHidden().get().count()==16,"All sixteen rack recipes synchronize to JEI");runtime.getRecipesGui().showTypes(List.of(type));});c.waitTicks(5);c.takeScreenshot("drying317-jei-drying-recipes");c.getInput().pressKey(com.mojang.blaze3d.platform.InputConstants.KEY_ESCAPE);
|
||||
}
|
||||
System.out.println("DRYING317_CLIENT_PASS Vulkan, paid mixed inputs, 12 models FR/EN GUI2/3/4, bucket pause/drain, saved timers, native finished collection/eating, 16 JEI recipes");
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import fr.koka.sanctuary.agriculture.*;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
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.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.world.level.*;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** A fresh real Sanctuary island, native generated wild crops, paid survival harvest and all wild models. */
|
||||
public final class Foraging316ClientChecks implements FabricClientGameTest {
|
||||
private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
|
||||
@Override public void runTest(ClientGameTestContext c) {
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable();
|
||||
var intro=new AtomicBoolean(true);
|
||||
var target=new AtomicReference<BlockPos>();
|
||||
var snapshot=new AtomicReference<Map<BlockPos,BlockState>>();
|
||||
var expectedLoot=new AtomicReference<Set<net.minecraft.world.item.Item>>();
|
||||
c.runOnClient(m->{
|
||||
var info=com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();
|
||||
check(info.backendName().equalsIgnoreCase("vulkan"),"Vulkan required");
|
||||
System.out.println("FORAGING316_BACKEND "+info.backendName()+" / "+info.name());
|
||||
m.options.renderDistance().set(5);m.options.simulationDistance().set(5);m.options.pauseOnLostFocus=false;
|
||||
});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m->{
|
||||
if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){
|
||||
IntroClient.setQuickTestIntro(true);Starter039TestSupport.selectFirst(screen);
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
|
||||
press(screen,"sanctuary.progression.enter");intro.set(false);
|
||||
}
|
||||
});
|
||||
net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave save;
|
||||
try(var world=c.worldBuilder().adjustSettings(s->{
|
||||
s.setName("Foraging316-"+UUID.randomUUID());s.setSeed("316");s.setAllowCommands(true);
|
||||
s.setGameMode(WorldCreationUiState.SelectedGameMode.CREATIVE);
|
||||
s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null
|
||||
&&e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());
|
||||
}).create()) {
|
||||
c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null&&!IntroSpawnFlash.active(),2400);
|
||||
var server=world.getServer();
|
||||
server.runOnServer(s->{
|
||||
var player=s.getPlayerList().getPlayers().getFirst();var level=player.level();
|
||||
s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");
|
||||
s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set noon");
|
||||
level.getGameRules().set(GameRules.SPAWN_MOBS,false,s);level.getGameRules().set(GameRules.RANDOM_TICK_SPEED,0,s);
|
||||
var found=new LinkedHashMap<BlockPos,BlockState>();
|
||||
var types=new HashSet<Block>();
|
||||
var habitats=new java.util.TreeMap<String,Integer>();
|
||||
int cx=player.blockPosition().getX()>>4,cz=player.blockPosition().getZ()>>4;
|
||||
for(int dx=-2;dx<=2;dx++)for(int dz=-2;dz<=2;dz++){
|
||||
var chunk=level.getChunk(cx+dx,cz+dz);
|
||||
for(int x=0;x<16;x++)for(int z=0;z<16;z++)for(int y=33;y<384;y++){
|
||||
var p=chunk.getPos().getBlockAt(x,y,z);var state=chunk.getBlockState(p);
|
||||
if(Foraging.wildPlants().containsValue(state.getBlock())){found.put(p,state);types.add(state.getBlock());}
|
||||
if(WildFoodGeneration.vacant(state)&&WildFoodGeneration.soil(chunk.getBlockState(p.below())))
|
||||
habitats.merge(level.getBiome(p).unwrapKey().orElseThrow().identifier()+"/"+Foraging.habitat(level.getBiome(p)),1,Integer::sum);
|
||||
}
|
||||
}
|
||||
System.out.println("FORAGING316_HABITATS "+habitats);
|
||||
check(found.size()>=8&&types.size()>=3,"Real Sanctuary decoration produces several visible wild crops: "+found.size()+" / "+types.size());
|
||||
snapshot.set(found);target.set(found.keySet().stream().max(Comparator.comparingInt(BlockPos::getY)).orElseThrow());
|
||||
var p=target.get();
|
||||
expectedLoot.set(Block.getDrops(level.getBlockState(p),level,p,null).stream().map(net.minecraft.world.item.ItemStack::getItem).collect(java.util.stream.Collectors.toSet()));
|
||||
level.setBlockAndUpdate(p.south(2).below(),Blocks.STONE.defaultBlockState());player.teleportTo(p.getX()+.5,p.getY()+1,p.getZ()+2.5);player.setYRot(180);player.setXRot(35);
|
||||
player.getInventory().clearContent();player.setGameMode(GameType.SURVIVAL);player.inventoryMenu.broadcastChanges();
|
||||
System.out.println("FORAGING316_NATIVE crops="+found.size()+" types="+types.size()+" target="+p);
|
||||
});
|
||||
c.waitTicks(15);c.takeScreenshot("foraging316-native-temperate-food");
|
||||
c.runOnClient(m->m.gameMode.startDestroyBlock(target.get(),Direction.UP));
|
||||
server.waitFor(s->s.overworld().getBlockState(target.get()).isAir(),200);
|
||||
server.runOnServer(s->{
|
||||
var level=s.overworld();var drops=level.getEntitiesOfClass(net.minecraft.world.entity.item.ItemEntity.class,
|
||||
new net.minecraft.world.phys.AABB(target.get()).inflate(2));
|
||||
var player=s.getPlayerList().getPlayers().getFirst();
|
||||
check(drops.stream().anyMatch(e->expectedLoot.get().contains(e.getItem().getItem()))
|
||||
||expectedLoot.get().stream().anyMatch(item->player.getInventory().countItem(item)>0),
|
||||
"Actual survival packet awards the expected native loot from a generated wild plant");
|
||||
snapshot.get().remove(target.get());
|
||||
var p=s.getPlayerList().getPlayers().getFirst();p.setGameMode(GameType.CREATIVE);
|
||||
p.getAbilities().flying=true;p.onUpdateAbilities();
|
||||
});
|
||||
c.runOnClient(m->{
|
||||
for(var block:Foraging.wildPlants().values()){
|
||||
var state=block.defaultBlockState();var parts=new ArrayList<net.minecraft.client.renderer.block.dispatch.BlockStateModelPart>();
|
||||
m.getModelManager().getBlockStateModelSet().get(state).collectParts(net.minecraft.util.RandomSource.create(316),parts);
|
||||
int quads=0;
|
||||
for(var part:parts)for(Direction side:new Direction[]{null,Direction.UP,Direction.DOWN,Direction.NORTH,Direction.SOUTH,Direction.EAST,Direction.WEST})
|
||||
for(var q:part.getQuads(side)){
|
||||
check(!q.materialInfo().sprite().contents().name().getPath().contains("missing"),"Resolved wild crop texture");quads++;
|
||||
}
|
||||
check(quads==4,"Every wild plant has two visible crossed planes: "+state+" "+quads);
|
||||
}
|
||||
for(String lang:List.of("fr_fr","en_us")){
|
||||
m.getLanguageManager().setSelected(lang);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
|
||||
for(var block:Foraging.wildPlants().values())check(!block.getName().getString().startsWith("block.sanctuary"),"Wild names translated: "+lang);
|
||||
}
|
||||
System.out.println("FORAGING316_MODELS wild=24 FR_EN=pass");
|
||||
});
|
||||
c.takeScreenshot("foraging316-harvested-wild-plant");
|
||||
save=world.getWorldSave();
|
||||
}
|
||||
try(var world=save.open()){
|
||||
c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);
|
||||
world.getServer().runOnServer(s->{
|
||||
check(s.overworld().getBlockState(target.get()).isAir(),"Harvested existing chunk is never repopulated on load");
|
||||
snapshot.get().forEach((p,state)->check(s.overworld().getBlockState(p).equals(state),"Existing wild plant survives actual disk reload"));
|
||||
System.out.println("FORAGING316_RELOAD preserved="+snapshot.get().size()+" harvested_stays_empty=true");
|
||||
});
|
||||
c.takeScreenshot("foraging316-reloaded-no-repopulation");
|
||||
}
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.client.screen.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.recipe.*;
|
||||
import fr.koka.sanctuary.cuisine.machine.*;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.phys.*;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.RecipeService;
|
||||
import java.util.*;
|
||||
import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import mezz.jei.api.constants.RecipeTypes;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.*;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.inventory.*;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.SimpleMenuProvider;
|
||||
import net.minecraft.world.inventory.*;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.alchemy.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.*;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Native table packets, real smoker ticks, food catalogue/JEI and actual save reopening. */
|
||||
public final class Kitchen304ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos PAN=new BlockPos(-4,1,0), POT=new BlockPos(-2,1,0), OVEN=new BlockPos(0,1,0), BARREL=new BlockPos(2,1,0), MILL=new BlockPos(4,1,0), PRESS=new BlockPos(6,1,0);
|
||||
private static long barrelStart;
|
||||
private static int barrelDuration;
|
||||
private static void check(boolean ok, String message) { if (!ok) throw new AssertionError(message); }
|
||||
|
||||
@Override public void runTest(ClientGameTestContext c) {
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable(); var intro = new AtomicBoolean(true);
|
||||
c.runOnClient(m -> {
|
||||
var device = com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();
|
||||
check(device.backendName().equalsIgnoreCase("vulkan"), "Cuisine validation requires Vulkan");
|
||||
System.out.println("KITCHEN304_BACKEND " + device.backendName() + " / " + device.name());
|
||||
m.options.renderDistance().set(8); m.options.simulationDistance().set(8); m.options.pauseOnLostFocus = false;
|
||||
});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m -> {
|
||||
if (intro.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
|
||||
IntroClient.setQuickTestIntro(true); Starter039TestSupport.selectFirst(screen);
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
|
||||
press(screen, "sanctuary.progression.enter"); intro.set(false);
|
||||
}
|
||||
});
|
||||
TestWorldSave saved;
|
||||
try (var world = c.worldBuilder().adjustSettings(s -> {
|
||||
s.setName("Kitchen304-" + UUID.randomUUID()); s.setSeed("304"); 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()) {
|
||||
c.waitFor(m -> m.player != null && ProgressionClient.active() && m.gui.screen() == null && !IntroSpawnFlash.active(), 1200);
|
||||
var server=world.getServer();
|
||||
server.runOnServer(s->{
|
||||
var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();
|
||||
l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);
|
||||
((net.minecraft.world.level.storage.ServerLevelData)l.getLevelData()).setGameTime(l.getGameTime()+2000);
|
||||
l.setBlockAndUpdate(PAN,KitchenAppliances.FRYING_PAN.defaultBlockState());
|
||||
l.setBlockAndUpdate(POT,KitchenAppliances.COOKING_CAULDRON.defaultBlockState());
|
||||
l.setBlockAndUpdate(OVEN,KitchenAppliances.KITCHEN_OVEN.defaultBlockState());
|
||||
l.setBlockAndUpdate(BARREL,KitchenAppliances.FERMENTATION_BARREL.defaultBlockState());
|
||||
l.setBlockAndUpdate(MILL,KitchenAppliances.REDSTONE_MILL.defaultBlockState());
|
||||
l.setBlockAndUpdate(PRESS.below(),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(PRESS,KitchenAppliances.TOMATO_BLOCK.defaultBlockState());
|
||||
l.setBlockAndUpdate(PAN.below(),Blocks.CAMPFIRE.defaultBlockState());
|
||||
l.setBlockAndUpdate(POT.below(),Blocks.CAMPFIRE.defaultBlockState());
|
||||
p.teleportTo(-3.5,1,2.5);p.setYRot(180);p.setXRot(25);p.setGameMode(GameType.SURVIVAL);p.getInventory().clearContent();p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.EGG,2));
|
||||
});c.waitTicks(8);
|
||||
c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(PAN),Direction.UP,PAN,false)));c.waitTicks(6);
|
||||
server.runOnServer(s->{
|
||||
var p=s.getPlayerList().getPlayers().getFirst();var pan=(FryingPanBlockEntity)p.level().getBlockEntity(PAN);
|
||||
check(pan.getItem(0).is(Items.EGG) && pan.getItem(0).getCount()==1 && p.getMainHandItem().getCount()==1,"Native interaction packet inserts one paid egg");
|
||||
var tag=pan.saveCustomOnly(p.level().registryAccess());tag.putLong("AssemblyStartedAt",p.level().getGameTime()-601);tag.putLong("LastIngredientChangedAt",p.level().getGameTime()-601);
|
||||
pan.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create(net.minecraft.util.ProblemReporter.DISCARDING,p.level().registryAccess(),tag));
|
||||
var pot=(CookingCauldronBlockEntity)p.level().getBlockEntity(POT);pot.setWaterUnits(3);pot.insertIngredient(new ItemStack(Cuisine.item("ground_coffee")),1);pot.stir();
|
||||
var oven=(KitchenOvenBlockEntity)p.level().getBlockEntity(OVEN);oven.setItem(0,new ItemStack(Cuisine.item("raw_dough")));oven.setItem(9,new ItemStack(Items.COAL));
|
||||
var mill=(RedstoneMillBlockEntity)p.level().getBlockEntity(MILL);mill.setItem(2,new ItemStack(Cuisine.item("wheat_flour"),2));
|
||||
var b=(FermentationBarrelBlockEntity)p.level().getBlockEntity(BARREL);b.insertIngredient(new ItemStack(Cuisine.item("grape_must")),1);
|
||||
|
||||
p.teleportTo(.5,2,5.5);p.setYRot(180);p.setXRot(22);
|
||||
});c.getInput().lookAt(180,22);c.waitTicks(300);
|
||||
server.runOnServer(s->{var l=s.overworld();
|
||||
check(((FryingPanBlockEntity)l.getBlockEntity(PAN)).getItem(4).is(Cuisine.item("fried_egg")),"Real pan ticks cook egg");
|
||||
check(((KitchenOvenBlockEntity)l.getBlockEntity(OVEN)).getItem(10).is(Items.BREAD),"Real oven ticks cook dough");
|
||||
check(((RedstoneMillBlockEntity)l.getBlockEntity(MILL)).getItem(2).is(Cuisine.item("wheat_flour")),"Old stored mill output remains recoverable");
|
||||
});c.takeScreenshot("kitchen304-six-appliances-in-world");
|
||||
c.runOnClient(m->{
|
||||
var blocks=List.of(KitchenAppliances.TOMATO_BLOCK,KitchenAppliances.GRAPE_BLOCK,KitchenAppliances.OLIVE_BLOCK,KitchenAppliances.FRYING_PAN,KitchenAppliances.COOKING_CAULDRON,KitchenAppliances.KITCHEN_OVEN,KitchenAppliances.FERMENTATION_BARREL,KitchenAppliances.REDSTONE_MILL,KitchenAppliances.FOOD_PRESS);
|
||||
int states=0;
|
||||
for(var block:blocks) for(var state:block.getStateDefinition().getPossibleStates()){
|
||||
var parts=new ArrayList<BlockStateModelPart>();
|
||||
m.getModelManager().getBlockStateModelSet().get(state).collectParts(RandomSource.create(304),parts);
|
||||
int quads=0;
|
||||
for(var part:parts)for(var direction:new Direction[]{null,Direction.UP,Direction.DOWN,Direction.NORTH,Direction.SOUTH,Direction.EAST,Direction.WEST})for(var quad:part.getQuads(direction)){
|
||||
check(!quad.materialInfo().sprite().contents().name().getPath().equals("missingno"),"Kitchen block texture resolves: "+state);quads++;
|
||||
}
|
||||
check(quads>0,"Kitchen block has visible geometry: "+state);states++;
|
||||
}
|
||||
CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(),true,m.level.registryAccess());
|
||||
var food=BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.FOOD_AND_DRINKS).value();
|
||||
for(var item:KitchenAppliances.items()){
|
||||
check(food.getDisplayItems().stream().anyMatch(i->i.is(item)),"Appliance in existing food tab");
|
||||
var render=new net.minecraft.client.renderer.item.ItemStackRenderState();
|
||||
m.getItemModelResolver().updateForTopItem(render,new ItemStack(item),ItemDisplayContext.GUI,m.level,m.player,0);check(!render.isEmpty() && !render.pickParticleMaterial(RandomSource.create(304)).sprite().contents().name().getPath().equals("missingno"),"Kitchen item renders "+item);
|
||||
}
|
||||
check(KitchenAppliances.items().size()==15,"Fifteen item entries, no new tab");
|
||||
check(m.level.getBlockEntity(PAN) instanceof FryingPanBlockEntity pan && pan.visibleItems().getFirst().is(Cuisine.item("fried_egg")),"Pan content synchronized");
|
||||
check(m.level.getBlockEntity(BARREL) instanceof FermentationBarrelBlockEntity b && b.visibleItem().is(Cuisine.item("grape_must")) && b.remainingFermentationTicks()>0,"Fermentation HUD and lid synchronized");
|
||||
check(m.level.getBlockState(POT).getValue(CookingCauldronBlock.MIXTURE)==3,"Coffee mixture tint synchronized");
|
||||
System.out.println("KITCHEN304_MODELS states="+states+" items=15 renderers_and_mixture_synced=true");
|
||||
m.getWindow().setWindowed(1280,960);
|
||||
});
|
||||
for(String locale:List.of("fr_fr","en_us")) for(int scale:new int[]{2,3,4}){
|
||||
c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();
|
||||
for(var item:KitchenAppliances.items())check(!new ItemStack(item).getHoverName().getString().matches("(item|block)\\.sanctuary.*"),"FR/EN kitchen label: "+item);});
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.teleportTo(.5,1,2.5);p.openMenu((KitchenOvenBlockEntity)p.level().getBlockEntity(OVEN));});
|
||||
c.waitFor(m->m.gui.screen() instanceof KitchenOvenScreen,200);c.waitTicks(4);c.takeScreenshot("kitchen304-oven-"+locale+"-gui"+scale);
|
||||
c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(3);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.teleportTo(4.5,1,2.5);p.openMenu((RedstoneMillBlockEntity)p.level().getBlockEntity(MILL));});
|
||||
c.waitFor(m->m.gui.screen() instanceof RedstoneMillScreen,200);c.waitTicks(4);c.takeScreenshot("kitchen304-mill-"+locale+"-gui"+scale);
|
||||
c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(3);
|
||||
}
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.giveExperienceLevels(4);RecipeService.discover(p,AgricultureItems.TOMATO);RecipeService.buy(p);RecipeService.refresh(p);});
|
||||
c.waitFor(m->RecipeClient.allowed() && SanctuaryJeiPlugin.runtime()!=null && RecipeClient.knowledge.recipes.contains("sanctuary:physical_pan_fried_egg"),300);c.waitTicks(10);
|
||||
c.runOnClient(m->{var runtime=SanctuaryJeiPlugin.runtime();int count=0;
|
||||
check(KitchenJei.types().size()==8,"Eight actual JEI categories");
|
||||
for(var type:KitchenJei.types()){
|
||||
var values=runtime.getRecipeManager().createRecipeLookup(type).includeHidden().get().toList();check(!values.isEmpty(),"Physical category synchronized "+type);count+=values.size();
|
||||
}check(count==109,"All 109 current appliance recipes indexed in JEI: "+count);System.out.println("KITCHEN304_JEI categories=8 recipes="+count);
|
||||
});
|
||||
for(var type: c.computeOnClient(m->KitchenJei.types())){
|
||||
c.runOnClient(m->SanctuaryJeiPlugin.runtime().getRecipesGui().showTypes(List.of(type)));c.waitTicks(5);
|
||||
c.takeScreenshot("kitchen304-jei-"+type.getUid().getPath().replace('/','-'));c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(3);
|
||||
}
|
||||
server.runOnServer(s->{var b=(FermentationBarrelBlockEntity)s.overworld().getBlockEntity(BARREL);barrelStart=b.startedAtGameTime();barrelDuration=b.activeDuration();check(barrelStart>=0,"Wine still fermenting before real save");});saved=world.getWorldSave();
|
||||
}
|
||||
try(var world=saved.open()){
|
||||
c.waitFor(m->m.player!=null && m.gui.screen()==null,1200);
|
||||
world.getServer().runOnServer(s->{var l=s.overworld();var b=(FermentationBarrelBlockEntity)l.getBlockEntity(BARREL);
|
||||
check(b.startedAtGameTime()==barrelStart && b.activeDuration()==barrelDuration && b.getItem(0).is(Cuisine.item("grape_must")),"Disk reload keeps fermentation deadline and ingredient");
|
||||
check(((FryingPanBlockEntity)l.getBlockEntity(PAN)).getItem(4).is(Cuisine.item("fried_egg")),"Pan meal persists");
|
||||
check(((KitchenOvenBlockEntity)l.getBlockEntity(OVEN)).getItem(10).is(Items.BREAD),"Oven output persists");
|
||||
check(((RedstoneMillBlockEntity)l.getBlockEntity(MILL)).getItem(2).is(Cuisine.item("wheat_flour")),"Mill output persists");
|
||||
check(l.getBlockState(PRESS).is(KitchenAppliances.TOMATO_BLOCK),"Fruit block persists");
|
||||
((net.minecraft.world.level.storage.ServerLevelData)l.getLevelData()).setGameTime(barrelStart+barrelDuration);
|
||||
});c.waitTicks(5);
|
||||
world.getServer().runOnServer(s->{var b=(FermentationBarrelBlockEntity)s.overworld().getBlockEntity(BARREL);check(b.getItem(4).is(Cuisine.item("wine")) && b.getItem(4).getCount()==1 && b.getItem(0).isEmpty(),"Fermentation finishes exactly once after reload and elapsed world time");System.out.println("KITCHEN304_DISK_RELOAD five_devices_and_fruit=true fermentation_completion=true");});
|
||||
}
|
||||
System.out.println("KITCHEN304_CLIENT_PASS Vulkan, native pan packet, real cooking, six appliances, JEI 109 recipes, FR/EN GUI 2/3/4, disk reload");
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.menu.RedstoneMillMenu;
|
||||
import fr.koka.sanctuary.cuisine.culinary.client.screen.*;
|
||||
import fr.koka.sanctuary.cuisine.machine.*;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.RecipeService;
|
||||
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.*;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.inventory.*;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.inventory.*;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Native manual milling packets, localized menus, JEI and actual save reopening. */
|
||||
public final class Mill306ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos MILL=new BlockPos(0,1,0);
|
||||
private static void check(boolean ok, String message) { if (!ok) throw new AssertionError(message); }
|
||||
|
||||
@Override public void runTest(ClientGameTestContext c) {
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable(); var intro = new AtomicBoolean(true);
|
||||
c.runOnClient(m -> {
|
||||
var device = com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();
|
||||
check(device.backendName().equalsIgnoreCase("vulkan"), "Mill validation requires Vulkan");
|
||||
System.out.println("MILL306_BACKEND " + device.backendName() + " / " + device.name());
|
||||
m.options.renderDistance().set(8); m.options.simulationDistance().set(8); m.options.pauseOnLostFocus = false;
|
||||
});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m -> {
|
||||
if (intro.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
|
||||
IntroClient.setQuickTestIntro(true); Starter039TestSupport.selectFirst(screen);
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
|
||||
press(screen, "sanctuary.progression.enter"); intro.set(false);
|
||||
}
|
||||
});
|
||||
TestWorldSave saved;
|
||||
try (var world = c.worldBuilder().adjustSettings(s -> {
|
||||
s.setName("Mill306-" + UUID.randomUUID()); s.setSeed("306"); 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()) {
|
||||
c.waitFor(m -> m.player != null && ProgressionClient.active() && m.gui.screen() == null && !IntroSpawnFlash.active(), 1200);
|
||||
var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);
|
||||
l.setBlockAndUpdate(MILL,KitchenAppliances.REDSTONE_MILL.defaultBlockState());p.setGameMode(GameType.SURVIVAL);p.teleportTo(.5,1,2.5);
|
||||
p.getInventory().clearContent();p.getInventory().setItem(0,new ItemStack(Items.WHEAT,4));p.openMenu((RedstoneMillBlockEntity)l.getBlockEntity(MILL));
|
||||
});c.waitFor(m->m.gui.screen() instanceof RedstoneMillScreen && m.player.containerMenu.getSlot(31).getItem().is(Items.WHEAT),200);
|
||||
c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,31,0,ContainerInput.QUICK_MOVE,m.player));
|
||||
c.waitFor(m->m.player.containerMenu.getSlot(0).getItem().getCount()==4 && m.player.containerMenu.getSlot(1).getItem().is(Cuisine.item("wheat_flour")),200);c.waitTicks(25);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.containerMenu.getSlot(0).getItem().getCount()==4 && ((RedstoneMillBlockEntity)p.level().getBlockEntity(MILL)).isEmpty(),"Native insertion previews without fuel, time or consumption");});
|
||||
c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,1,1,ContainerInput.PICKUP,m.player));c.waitTicks(2);c.takeScreenshot("mill306-mechanical-stroke-down");
|
||||
c.waitTicks(4);c.takeScreenshot("mill306-mechanical-stroke-return");c.waitTicks(8);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.containerMenu.getCarried().is(Cuisine.item("wheat_flour"))&&p.containerMenu.getCarried().getCount()==2&&p.containerMenu.getSlot(0).getItem().getCount()==3,"Native right-click pays one grain, gives the full batch and keeps three");});
|
||||
c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,31,0,ContainerInput.PICKUP,m.player));c.waitTicks(4);
|
||||
c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,1,0,ContainerInput.QUICK_MOVE,m.player));c.waitTicks(8);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(Cuisine.item("wheat_flour"))==8&&p.containerMenu.getSlot(0).getItem().isEmpty()&&p.containerMenu.getSlot(1).getItem().isEmpty(),"Native shift crafts the three remaining paid batches");p.containerMenu.getSlot(0).set(new ItemStack(Items.WHEAT,2));p.containerMenu.broadcastChanges();});
|
||||
c.runOnClient(m->m.getWindow().setWindowed(1280,960));
|
||||
for(String locale:List.of("fr_fr","en_us"))for(int scale:new int[]{2,3,4}){
|
||||
c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();
|
||||
String expected=locale.equals("fr_fr")?"Moulin":"Mill";check(m.gui.screen().getTitle().getString().equals(expected),"Localized menu name");
|
||||
check(new ItemStack(KitchenAppliances.REDSTONE_MILL).getHoverName().getString().equals(expected),"Localized item name");
|
||||
check(Component.translatable("block.sanctuary.redstone_mill").getString().equals(expected),"Localized block name");
|
||||
var menu=(RedstoneMillMenu)m.player.containerMenu;
|
||||
check(menu.getSlot(0).isActive()&&menu.getSlot(1).isActive()&&!menu.getSlot(2).isActive()&&!menu.getSlot(3).isActive(),"Only two working slots, no fuel controls");
|
||||
});c.waitTicks(5);c.takeScreenshot("mill306-manual-"+locale+"-gui"+scale);
|
||||
}
|
||||
c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(6);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(Items.WHEAT)==2&&p.getInventory().countItem(Cuisine.item("wheat_flour"))==8,"Closing native screen returns unused grain only");
|
||||
p.giveExperienceLevels(4);RecipeService.discover(p,AgricultureItems.TOMATO);RecipeService.buy(p);RecipeService.refresh(p);
|
||||
});c.waitFor(m->RecipeClient.allowed()&&SanctuaryJeiPlugin.runtime()!=null,300);c.waitTicks(8);
|
||||
c.runOnClient(m->{var runtime=SanctuaryJeiPlugin.runtime();var type=KitchenJei.types().stream().filter(t->t.getUid().getPath().endsWith("redstone_mill")).findFirst().orElseThrow();
|
||||
check(runtime.getRecipeManager().createRecipeLookup(type).includeHidden().get().count()==10,"Ten manual recipes remain synchronized in JEI");runtime.getRecipesGui().showTypes(List.of(type));
|
||||
});c.waitTicks(5);c.takeScreenshot("mill306-jei-manual-recipes");c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(4);
|
||||
server.runOnServer(s->{var mill=(RedstoneMillBlockEntity)s.overworld().getBlockEntity(MILL);mill.setItem(0,new ItemStack(Items.WHEAT,5));mill.setItem(1,new ItemStack(Items.COAL,7));mill.setItem(2,new ItemStack(Cuisine.item("wheat_flour"),3));});saved=world.getWorldSave();
|
||||
}
|
||||
try(var world=saved.open()){
|
||||
c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);
|
||||
world.getServer().runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var mill=(RedstoneMillBlockEntity)p.level().getBlockEntity(MILL);
|
||||
check(mill.getItem(0).getCount()==5&&mill.getItem(1).getCount()==7&&mill.getItem(2).getCount()==3,"Legacy inventory round trip");p.teleportTo(.5,1,2.5);p.openMenu(mill);
|
||||
});c.waitFor(m->m.gui.screen() instanceof RedstoneMillScreen&&m.player.containerMenu.getSlot(2).getItem().is(Items.COAL),200);c.waitTicks(20);c.takeScreenshot("mill306-stored-items-recovery");
|
||||
c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,2,0,ContainerInput.QUICK_MOVE,m.player));c.waitTicks(4);
|
||||
c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,3,0,ContainerInput.QUICK_MOVE,m.player));c.waitTicks(4);
|
||||
c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(6);
|
||||
world.getServer().runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(Items.COAL)==7&&p.getInventory().countItem(Items.WHEAT)==7&&p.getInventory().countItem(Cuisine.item("wheat_flour"))==11,"Native recovery returns old items and unspent input, without creating the preview");});
|
||||
}
|
||||
System.out.println("MILL306_CLIENT_PASS Vulkan, native insertion/pickup/shift/close, two working slots, mechanical stroke without fire, names FR/EN GUI 2/3/4, JEI ten recipes, disk and old inventory recovery");
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.menu.KitchenOvenMenu;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.KitchenOvenBlockEntity;
|
||||
import fr.koka.sanctuary.cuisine.culinary.client.screen.*;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.RecipeService;
|
||||
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.*;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.inventory.*;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.inventory.*;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Real cooking packets, cancellation, closed-menu continuation, single cooked output and disk reload. */
|
||||
public final class Oven308ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos OVEN=new BlockPos(0,1,0);
|
||||
private static void check(boolean ok, String message) { if (!ok) throw new AssertionError(message); }
|
||||
|
||||
@Override public void runTest(ClientGameTestContext c) {
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable(); var intro = new AtomicBoolean(true);
|
||||
c.runOnClient(m -> {
|
||||
var device = com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();
|
||||
check(device.backendName().equalsIgnoreCase("vulkan"), "Oven validation requires Vulkan");
|
||||
System.out.println("OVEN309_BACKEND " + device.backendName() + " / " + device.name());
|
||||
m.options.renderDistance().set(8); m.options.simulationDistance().set(8); m.options.pauseOnLostFocus = false;
|
||||
});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m -> {
|
||||
if (intro.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
|
||||
IntroClient.setQuickTestIntro(true); Starter039TestSupport.selectFirst(screen);
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
|
||||
press(screen, "sanctuary.progression.enter"); intro.set(false);
|
||||
}
|
||||
});
|
||||
TestWorldSave saved;
|
||||
try (var world = c.worldBuilder().adjustSettings(s -> {
|
||||
s.setName("Oven309-" + UUID.randomUUID()); s.setSeed("309"); 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()) {
|
||||
c.waitFor(m -> m.player != null && ProgressionClient.active() && m.gui.screen() == null && !IntroSpawnFlash.active(), 1200);
|
||||
var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);
|
||||
l.setBlockAndUpdate(OVEN,KitchenAppliances.KITCHEN_OVEN.defaultBlockState());p.setGameMode(GameType.SURVIVAL);p.teleportTo(.5,1,2.5);
|
||||
p.getInventory().clearContent();p.getInventory().setItem(0,new ItemStack(Cuisine.item("raw_dough"),3));p.getInventory().setItem(1,new ItemStack(Items.COAL));
|
||||
p.openMenu((KitchenOvenBlockEntity)l.getBlockEntity(OVEN));
|
||||
});c.waitFor(m->m.gui.screen() instanceof KitchenOvenScreen && m.player.containerMenu.getSlot(38).getItem().getCount()==3,200);
|
||||
click(c,38,ContainerInput.QUICK_MOVE);
|
||||
c.waitFor(m->m.player.containerMenu.getSlot(0).getItem().getCount()==3 && m.player.containerMenu.getSlot(10).getItem().isEmpty(),200);c.waitTicks(25);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var oven=(KitchenOvenBlockEntity)p.level().getBlockEntity(OVEN);
|
||||
check(oven.progress()==0 && oven.getItem(0).getCount()==3 && oven.getItem(10).isEmpty() && p.containerMenu.getCarried().isEmpty(),"No dish or consumed dough before fuel and a full cooking cycle");
|
||||
});c.runOnClient(m->m.getWindow().setWindowed(1280,960));
|
||||
for(String locale:List.of("fr_fr","en_us"))for(int scale:new int[]{2,3,4}){
|
||||
c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();
|
||||
check(m.gui.screen().getTitle().getString().equals(locale.equals("fr_fr")?"Four de cuisine":"Kitchen Oven"),"Localized oven title");
|
||||
var menu=m.player.containerMenu;
|
||||
check(menu.getSlot(11).container==m.player.getInventory() && menu.slots.subList(0,11).stream().noneMatch(Slot::isFake),"Exactly eleven real machine slots and no preview");
|
||||
check(menu.getSlot(10).x==124 && menu.getSlot(10).y==35 && !menu.getSlot(10).mayPlace(new ItemStack(Items.BREAD)),"One centred, extraction-only cooked output");
|
||||
check(!menu.getSlot(9).mayPlace(new ItemStack(Items.STONE)) && menu.getSlot(9).mayPlace(new ItemStack(Items.COAL)),"Client fuel validation follows native cooking components");
|
||||
});c.waitTicks(5);c.takeScreenshot("oven309-grid-"+locale+"-gui"+scale);
|
||||
}
|
||||
c.runOnClient(m->{var menu=m.player.containerMenu;m.gameMode.handleContainerInput(menu.containerId,39,0,ContainerInput.QUICK_MOVE,m.player);
|
||||
check(menu.getSlot(39).getItem().isEmpty() && menu.getSlot(9).getItem().is(Items.COAL),"Immediate client Shift prediction places coal in fuel, not the recipe grid");
|
||||
});c.waitFor(m->((KitchenOvenMenu)m.player.containerMenu).cookingProgress()>=.75F,300);
|
||||
c.takeScreenshot("oven309-cooking-before-cancellation");click(c,0,ContainerInput.PICKUP);c.waitTicks(6);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var oven=(KitchenOvenBlockEntity)p.level().getBlockEntity(OVEN);
|
||||
check(oven.progress()==0 && oven.getItem(0).isEmpty() && oven.getItem(10).isEmpty() && p.containerMenu.getCarried().getCount()==3,"Native extraction cancels nearly completed cooking without reserving ingredients");
|
||||
});c.takeScreenshot("oven309-cancelled");click(c,0,ContainerInput.PICKUP);c.waitTicks(20);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var oven=(KitchenOvenBlockEntity)p.level().getBlockEntity(OVEN);
|
||||
check(oven.progress()>0 && oven.progress()<100 && oven.getItem(0).getCount()==3 && oven.getItem(10).isEmpty(),"Replacing the same dough starts a fresh full cooking cycle");
|
||||
});c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitFor(m->m.gui.screen()==null,100);c.getInput().lookAt(180,25);
|
||||
c.waitFor(m->m.level.getBlockEntity(OVEN) instanceof KitchenOvenBlockEntity oven && oven.getItem(10).is(Items.BREAD) && oven.getItem(10).getCount()==3,900);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var oven=(KitchenOvenBlockEntity)p.level().getBlockEntity(OVEN);
|
||||
check(oven.getItem(0).isEmpty() && oven.getItem(10).getCount()==3 && p.getInventory().countItem(Cuisine.item("raw_dough"))==0,"Three paid cycles continue with the inventory closed");
|
||||
p.openMenu(oven);
|
||||
});c.waitFor(m->m.gui.screen() instanceof KitchenOvenScreen && m.player.containerMenu.getSlot(10).getItem().getCount()==3,200);c.takeScreenshot("oven309-three-cooked-meals");
|
||||
c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(4);saved=world.getWorldSave();
|
||||
}
|
||||
try(var world=saved.open()){
|
||||
c.waitFor(m->m.player!=null && m.gui.screen()==null,1200);
|
||||
world.getServer().runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var oven=(KitchenOvenBlockEntity)p.level().getBlockEntity(OVEN);
|
||||
check(oven.getItem(0).isEmpty() && oven.getItem(10).is(Items.BREAD) && oven.getItem(10).getCount()==3,"Real disk reload preserves exactly the cooked storage");p.teleportTo(.5,1,2.5);p.openMenu(oven);
|
||||
});c.waitFor(m->m.gui.screen() instanceof KitchenOvenScreen && m.player.containerMenu.getSlot(10).getItem().getCount()==3,200);c.takeScreenshot("oven309-finished-after-disk");
|
||||
click(c,10,ContainerInput.QUICK_MOVE);c.waitTicks(5);
|
||||
world.getServer().runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var oven=(KitchenOvenBlockEntity)p.level().getBlockEntity(OVEN);
|
||||
check(p.getInventory().countItem(Items.BREAD)==3 && oven.getItem(10).isEmpty(),"Native result Shift extracts exactly three paid meals after reload");
|
||||
p.giveExperienceLevels(4);RecipeService.discover(p,AgricultureItems.TOMATO);RecipeService.buy(p);RecipeService.refresh(p);
|
||||
});c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(4);
|
||||
c.waitFor(m->RecipeClient.allowed()&&SanctuaryJeiPlugin.runtime()!=null,300);
|
||||
c.runOnClient(m->{var runtime=SanctuaryJeiPlugin.runtime();var type=KitchenJei.types().stream().filter(t->t.getUid().getPath().endsWith("kitchen_oven")).findFirst().orElseThrow();
|
||||
check(runtime.getRecipeManager().createRecipeLookup(type).includeHidden().get().count()==17,"All seventeen oven recipes remain synchronized in JEI");runtime.getRecipesGui().showTypes(List.of(type));
|
||||
});c.waitTicks(5);c.takeScreenshot("oven309-jei-seventeen-recipes");c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(3);
|
||||
}
|
||||
System.out.println("OVEN309_CLIENT_PASS Vulkan, native grid/fuel Shift prediction, single cooked output FR/EN GUI 2/3/4, cancellation, three paid cycles with menu closed, disk reload, result extraction and JEI seventeen recipes");
|
||||
}
|
||||
private static void click(ClientGameTestContext c,int slot,ContainerInput action){c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,slot,0,action,m.player));}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.FryingPanBlock;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.FryingPanBlockEntity;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.RecipeService;
|
||||
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.*;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.phys.*;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Real placement and ingredient packets, Vulkan models/HUD, paid cooking and disk reopen. */
|
||||
public final class Pan310ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos PAN=new BlockPos(0,1,0);
|
||||
private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
|
||||
private static void use(ClientGameTestContext c,BlockPos pos,Direction face,double height){c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(new Vec3(pos.getX()+.5,pos.getY()+height,pos.getZ()+.5),face,pos,false)));c.waitTicks(5);}
|
||||
@Override public void runTest(ClientGameTestContext c){
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable();var intro=new AtomicBoolean(true);
|
||||
c.runOnClient(m->{var device=com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();check(device.backendName().equalsIgnoreCase("vulkan"),"Pan validation requires Vulkan");System.out.println("PAN310_BACKEND "+device.backendName()+" / "+device.name());m.options.renderDistance().set(8);m.options.simulationDistance().set(8);m.options.pauseOnLostFocus=false;});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){IntroClient.setQuickTestIntro(true);Starter039TestSupport.selectFirst(screen);screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());press(screen,"sanctuary.progression.enter");intro.set(false);}});
|
||||
TestWorldSave saved;
|
||||
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Pan310-"+UUID.randomUUID());s.setSeed("310");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()){
|
||||
c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);((net.minecraft.world.level.storage.ServerLevelData)l.getLevelData()).setGameTime(l.getGameTime()+2000);l.setBlockAndUpdate(PAN.below(),Blocks.STONE.defaultBlockState());p.setGameMode(GameType.SURVIVAL);p.teleportTo(.5,1,2.5);p.getInventory().clearContent();p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(KitchenAppliances.FRYING_PAN,5));});c.waitTicks(8);
|
||||
use(c,PAN.below(),Direction.UP,1);
|
||||
c.waitFor(m->m.level.getBlockState(PAN).is(KitchenAppliances.FRYING_PAN),100);
|
||||
for(int count=2;count<=4;count++){use(c,PAN,Direction.UP,(count-1)/4D);final int n=count;c.waitFor(m->m.level.getBlockState(PAN).getValue(FryingPanBlock.PANS)==n,100);}
|
||||
use(c,PAN,Direction.UP,1);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getMainHandItem().getCount()==1&&p.level().getBlockState(PAN.above()).isAir(),"Actual placement packets pay exactly four pans and reject a fifth above");});
|
||||
c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(new Vec3(1,1.5,.5),Direction.EAST,PAN,false)));c.waitTicks(6);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getMainHandItem().isEmpty()&&p.level().getBlockState(PAN.east()).is(KitchenAppliances.FRYING_PAN),"The fifth paid pan extends the horizontal line");p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.EGG,4));});c.waitTicks(5);
|
||||
for(int i=0;i<4;i++)use(c,PAN,Direction.UP,1);
|
||||
for(int i=0;i<4;i++){
|
||||
server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.MILK_BUCKET)));c.waitTicks(4);use(c,PAN,Direction.UP,1);
|
||||
}
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();var pan=(FryingPanBlockEntity)l.getBlockEntity(PAN);check(pan.getItem(0).getCount()==4&&pan.getItem(1).is(Items.MILK_BUCKET)&&pan.getItem(10).is(Items.MILK_BUCKET)&&p.getMainHandItem().isEmpty(),"Native interactions store four paid eggs and four separate milk buckets");
|
||||
for(int count=1;count<=3;count++){var pos=new BlockPos(count-3,1,0);if(pos.equals(PAN))pos=PAN.east();l.setBlockAndUpdate(pos,KitchenAppliances.FRYING_PAN.defaultBlockState().setValue(FryingPanBlock.PANS,count));l.setBlockAndUpdate(pos.below(),Blocks.CAMPFIRE.defaultBlockState());var other=(FryingPanBlockEntity)l.getBlockEntity(pos);other.insertIngredient(new ItemStack(Items.BEEF,4),4);}
|
||||
l.setBlockAndUpdate(PAN.below(),Blocks.CAMPFIRE.defaultBlockState());p.setItemInHand(InteractionHand.MAIN_HAND,ItemStack.EMPTY);p.teleportTo(.5,1,2.5);
|
||||
});c.waitTicks(8);c.getInput().lookAt(180,20);c.waitTicks(5);c.runOnClient(m->{m.getWindow().setWindowed(1280,960);int models=0;
|
||||
for(var state:KitchenAppliances.FRYING_PAN.getStateDefinition().getPossibleStates()){
|
||||
var parts=new ArrayList<BlockStateModelPart>();m.getModelManager().getBlockStateModelSet().get(state).collectParts(RandomSource.create(310),parts);int quads=0;float maxY=0;
|
||||
for(var part:parts)for(var direction:new Direction[]{null,Direction.UP,Direction.DOWN,Direction.NORTH,Direction.SOUTH,Direction.EAST,Direction.WEST})for(var q:part.getQuads(direction)){check(q.materialInfo().sprite().contents().name().toString().equals("sanctuary:block/frying_pan"),"Every layer uses the original resolved pan texture");for(int v=0;v<4;v++)maxY=Math.max(maxY,q.position(v).y());quads++;}
|
||||
check(quads==30*state.getValue(FryingPanBlock.PANS)&&Math.abs(maxY-state.getValue(FryingPanBlock.PANS)/4F)<.0001,"Every lit/count model has all native cuboids at its physical height");models++;
|
||||
}check(models==8,"All pan states baked under Vulkan");System.out.println("PAN310_MODELS states="+models+" heights=4,8,12,16");
|
||||
});
|
||||
for(String locale:List.of("fr_fr","en_us"))for(int scale:new int[]{2,3,4}){c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();check(m.hitResult instanceof BlockHitResult hit&&hit.getBlockPos().equals(PAN),"The actual pan HUD is targeted: "+m.hitResult+" eye="+m.player.getEyePosition());});c.waitTicks(3);c.takeScreenshot("pan310-preparation-"+locale+"-gui"+scale);}
|
||||
server.runOnServer(s->{var l=s.overworld();for(int x=-2;x<=1;x++){var pan=(FryingPanBlockEntity)l.getBlockEntity(new BlockPos(x,1,0));var tag=pan.saveCustomOnly(l.registryAccess());tag.putLong("AssemblyStartedAt",l.getGameTime()-601);tag.putLong("LastIngredientChangedAt",l.getGameTime()-601);pan.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create(net.minecraft.util.ProblemReporter.DISCARDING,l.registryAccess(),tag));}});
|
||||
c.waitFor(m->((FryingPanBlockEntity)m.level.getBlockEntity(PAN)).progress()>40,150);c.takeScreenshot("pan310-four-pans-cooking");
|
||||
c.waitFor(m->((FryingPanBlockEntity)m.level.getBlockEntity(PAN)).getItem(4).getCount()==4,300);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pan=(FryingPanBlockEntity)p.level().getBlockEntity(PAN);check(pan.getItem(4).is(Cuisine.item("omelette"))&&pan.getItem(4).getCount()==4&&pan.getItem(1).is(Items.BUCKET)&&pan.getItem(1).getCount()==4,"Real ticks produce four paid omelettes and return four containers");for(int x=-2;x<=1;x++)if(x!=0){var other=(FryingPanBlockEntity)p.level().getBlockEntity(new BlockPos(x,1,0));check(other.getItem(4).is(Items.COOKED_BEEF)&&other.getItem(4).getCount()==other.panCount(),"Adjacent stacks independently cook a different recipe");}});
|
||||
for(String locale:List.of("fr_fr","en_us")){c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());});c.waitTicks(3);c.takeScreenshot("pan310-ready-four-"+locale);}
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.teleportTo(.5,3,4.5);p.setGameMode(GameType.CREATIVE);p.getAbilities().flying=true;p.onUpdateAbilities();});c.waitTicks(5);c.getInput().lookAt(180,37);c.takeScreenshot("pan310-horizontal-one-two-four-three");saved=world.getWorldSave();
|
||||
}
|
||||
try(var world=saved.open()){
|
||||
c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);var server=world.getServer();server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pan=(FryingPanBlockEntity)p.level().getBlockEntity(PAN);check(pan.panCount()==4&&pan.getItem(4).getCount()==4&&pan.getItem(1).is(Items.BUCKET)&&pan.getItem(1).getCount()==4&&pan.getItem(10).isEmpty(),"Real disk reopening keeps the four pans and paid native inventory");p.setGameMode(GameType.SURVIVAL);p.getAbilities().flying=false;p.onUpdateAbilities();p.teleportTo(.5,1,2.5);p.setItemInHand(InteractionHand.MAIN_HAND,ItemStack.EMPTY);});c.waitTicks(8);c.getInput().lookAt(180,20);c.takeScreenshot("pan310-paid-result-after-disk");
|
||||
c.getInput().holdKey(o->o.keyShift);c.waitTicks(4);use(c,PAN,Direction.UP,1);c.getInput().releaseKey(o->o.keyShift);c.waitTicks(5);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(Cuisine.item("omelette"))==4&&((FryingPanBlockEntity)p.level().getBlockEntity(PAN)).getItem(4).isEmpty(),"Actual shift interaction extracts exactly four cooked meals after disk");});c.runOnClient(m->m.player.getInventory().setSelectedSlot(1));c.waitTicks(4);c.takeScreenshot("pan310-returned-containers-hud");
|
||||
c.getInput().holdKey(o->o.keyShift);c.waitTicks(4);use(c,PAN,Direction.UP,1);c.getInput().releaseKey(o->o.keyShift);c.waitTicks(5);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(Cuisine.item("omelette"))==4&&p.getInventory().countItem(Items.BUCKET)==4&&((FryingPanBlockEntity)p.level().getBlockEntity(PAN)).isEmpty(),"Actual shift interaction returns all four empty buckets exactly once");p.giveExperienceLevels(4);RecipeService.discover(p,AgricultureItems.TOMATO);RecipeService.buy(p);RecipeService.refresh(p);});c.waitFor(m->RecipeClient.allowed()&&SanctuaryJeiPlugin.runtime()!=null,300);
|
||||
c.runOnClient(m->{var runtime=SanctuaryJeiPlugin.runtime();var type=KitchenJei.types().stream().filter(t->t.getUid().getPath().endsWith("frying_pan")).findFirst().orElseThrow();check(runtime.getRecipeManager().createRecipeLookup(type).includeHidden().get().count()==26,"Twenty-six original pan recipes remain in JEI");runtime.getRecipesGui().showTypes(List.of(type));});c.waitTicks(5);c.takeScreenshot("pan310-jei-twenty-six-recipes");c.getInput().pressKey(InputConstants.KEY_ESCAPE);
|
||||
}
|
||||
System.out.println("PAN310_CLIENT_PASS Vulkan, paid native placement 1-4/horizontal/cap, real four eggs + four unstackable milk packets, 8 baked states, FR/EN HUD GUI 2/3/4, real paid cooking, independent adjacent recipes, disk reopening, shift collection, exact containers and JEI26");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -158,7 +158,7 @@ public final class Piston087ClientChecks implements FabricClientGameTest {
|
||||
for(int i=0;i<9;i++){var pos=SuperPistons.tile(C,f,i);l.setBlockAndUpdate(pos,(sticky?Blocks.STICKY_PISTON:Blocks.PISTON).defaultBlockState().setValue(net.minecraft.world.level.block.piston.PistonBaseBlock.FACING,f));}
|
||||
for(int depth=1;depth<=layers;depth++)for(var pos:SuperPistons.tiles(C,f,depth))l.setBlockAndUpdate(pos,Blocks.STONE.defaultBlockState());
|
||||
}
|
||||
private static void wrench(ServerPlayer p,BlockPos pos){UseBlockCallback.EVENT.invoker().interact(p,p.level(),InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(pos),Direction.UP,pos,false));}
|
||||
private static void wrench(ServerPlayer p,BlockPos pos){p.teleportTo(pos.getX()+.5,pos.getY(),pos.getZ()+3.5);UseBlockCallback.EVENT.invoker().interact(p,p.level(),InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(pos),Direction.UP,pos,false));}
|
||||
private static net.minecraft.client.input.KeyEvent enter(){return new net.minecraft.client.input.KeyEvent(InputConstants.KEY_RETURN,0,0);}
|
||||
private static void check(boolean ok,String why){if(!ok)throw new AssertionError(why);}
|
||||
}
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.CuredSausageBlock;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.DryingRackBlockEntity;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import fr.koka.sanctuary.recipes.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.*;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.client.sounds.SoundEventListener;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.level.storage.TagValueInput;
|
||||
import net.minecraft.world.phys.*;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Actual key-use packets through rack/placement/slices, native models, audible bites and disk. */
|
||||
public final class Sausage315ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos FOOD=new BlockPos(0,1,0),RACK=new BlockPos(-2,1,0);
|
||||
private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
|
||||
private static void aim(ClientGameTestContext c,BlockPos pos){
|
||||
var angles=new AtomicReference<float[]>();c.runOnClient(m->{var state=m.level.getBlockState(pos);var shape=state.getShape(m.level,pos);var point=shape.isEmpty()?Vec3.atCenterOf(pos):shape.bounds().getCenter().add(Vec3.atLowerCornerOf(pos));var d=point.subtract(m.player.getEyePosition());angles.set(new float[]{(float)(Math.toDegrees(Math.atan2(d.z,d.x))-90),(float)-Math.toDegrees(Math.atan2(d.y,Math.sqrt(d.x*d.x+d.z*d.z)))});});c.getInput().lookAt(angles.get()[0],angles.get()[1]);c.waitTicks(4);
|
||||
}
|
||||
private static void use(ClientGameTestContext c,BlockPos pos){aim(c,pos);c.runOnClient(m->check(m.hitResult instanceof BlockHitResult h&&h.getBlockPos().equals(pos),"The actual crosshair hits the shrinking food/target"));c.getInput().pressKey(o->o.keyUse);c.waitTicks(6);}
|
||||
private static void bite(ClientGameTestContext c,net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext server,int eaten){
|
||||
server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().getFoodData().setFoodLevel(0));c.waitFor(m->m.player.getFoodData().getFoodLevel()==0,100);use(c,FOOD);server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var state=p.level().getBlockState(FOOD);check(p.getFoodData().getFoodLevel()==2&&(eaten==4?state.isAir():state.getValue(CuredSausageBlock.BITES)==eaten),"A real native key action pays one slice and restores two food points");});
|
||||
}
|
||||
@Override public void runTest(ClientGameTestContext c){
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable();var intro=new AtomicBoolean(true);var audio=new CopyOnWriteArrayList<String>();var facing=new AtomicReference<Direction>();TestWorldSave saved;
|
||||
SoundEventListener listener=(sound,event,distance)->{if(sound.getX()>=0&&sound.getX()<=1&&sound.getZ()>=0&&sound.getZ()<=1)audio.add(sound.getIdentifier().toString());};
|
||||
c.runOnClient(m->{var d=com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();check(d.backendName().equalsIgnoreCase("vulkan"),"Only Vulkan is validated");System.out.println("SAUSAGE315_BACKEND "+d.backendName()+" / "+d.name());m.getWindow().setWindowed(1280,960);m.options.renderDistance().set(8);m.options.simulationDistance().set(8);m.options.pauseOnLostFocus=false;m.getSoundManager().addListener(listener);});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){IntroClient.setQuickTestIntro(true);Starter039TestSupport.selectFirst(screen);screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());press(screen,"sanctuary.progression.enter");intro.set(false);}});
|
||||
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sausage315-"+UUID.randomUUID());s.setSeed("315");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()){
|
||||
c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();l.getGameRules().set(GameRules.SPAWN_MOBS,false,s);try{fr.koka.sanctuary.realtime.RealtimeService.setMode(s,false);fr.koka.sanctuary.weather.WeatherService.setMode(s,false);}catch(Exception e){throw new IllegalStateException(e);}s.clockManager().setTotalTicks(l.dimensionType().defaultClock().orElseThrow(),6000);l.getWeatherData().setRaining(false);l.setRainLevel(0);l.setBlockAndUpdate(RACK,KitchenAppliances.DRYING_RACK.defaultBlockState());l.setBlockAndUpdate(RACK.above(),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(FOOD.below(),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(new BlockPos(0,0,2),Blocks.STONE.defaultBlockState());p.setGameMode(GameType.SURVIVAL);p.teleportTo(.5,1,2.5);p.getInventory().clearContent();p.getInventory().setItem(0,new ItemStack(KitchenAppliances.RAW_SAUSAGE));RecipeService.discover(p,KitchenAppliances.RAW_SAUSAGE);RecipeService.refresh(p);});c.waitTicks(8);
|
||||
use(c,RACK);server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var rack=(DryingRackBlockEntity)p.level().getBlockEntity(RACK);check(rack.dryingCount()==1&&p.getInventory().countItem(KitchenAppliances.RAW_SAUSAGE)==0,"The real hook interaction pays the one raw preparation");var tag=rack.saveCustomOnly(p.level().registryAccess());tag.putInt("DryingProgress0",5990);rack.loadWithComponents(TagValueInput.create(net.minecraft.util.ProblemReporter.DISCARDING,p.level().registryAccess(),tag));});c.waitFor(m->((DryingRackBlockEntity)m.level.getBlockEntity(RACK)).readyCount()==1,120);use(c,RACK);
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(KitchenAppliances.CURED_SAUSAGE)==1&&p.getInventory().countItem(KitchenAppliances.RAW_SAUSAGE)==0&&((DryingRackBlockEntity)p.level().getBlockEntity(RACK)).isEmpty(),"Real retrieval gives exactly one cured sausage from the paid preparation");});
|
||||
use(c,FOOD.below());c.waitFor(m->m.level.getBlockState(FOOD).is(KitchenAppliances.CURED_SAUSAGE_BLOCK),100);server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.getInventory().countItem(KitchenAppliances.CURED_SAUSAGE)==0,"Native placement pays the one cured sausage");facing.set(p.level().getBlockState(FOOD).getValue(CuredSausageBlock.FACING));});aim(c,FOOD);c.takeScreenshot("sausage315-whole-on-table");
|
||||
c.runOnClient(m->{int states=0;for(var state:KitchenAppliances.CURED_SAUSAGE_BLOCK.getStateDefinition().getPossibleStates()){var parts=new ArrayList<net.minecraft.client.renderer.block.dispatch.BlockStateModelPart>();m.getModelManager().getBlockStateModelSet().get(state).collectParts(net.minecraft.util.RandomSource.create(315),parts);int quads=0;var box=state.getShape(m.level,FOOD).bounds();for(var part:parts)for(Direction dir:new Direction[]{null,Direction.UP,Direction.DOWN,Direction.NORTH,Direction.SOUTH,Direction.EAST,Direction.WEST})for(var q:part.getQuads(dir)){check(q.materialInfo().sprite().contents().name().getNamespace().equals("minecraft"),"Sausage uses native meat/thread/cut textures");for(int v=0;v<4;v++){var p=q.position(v);check(p.x()>=box.minX-.0001&&p.x()<=box.maxX+.0001&&p.y()>=box.minY-.0001&&p.y()<=box.maxY+.0001&&p.z()>=box.minZ-.0001&&p.z()<=box.maxZ+.0001,"All rotated visible geometry fits the actual serving outline");}quads++;}check(quads==(state.getValue(CuredSausageBlock.BITES)==0?48:30),"All cuboids and the visible cut bake without missing faces");states++;}check(states==16,"Four orientations times four remaining lengths");CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(),true,m.level.registryAccess());var food=net.minecraft.core.registries.BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.FOOD_AND_DRINKS).value();check(food.getDisplayItems().stream().anyMatch(stack->stack.is(KitchenAppliances.CURED_SAUSAGE))&&KitchenAppliances.items().size()==15,"The same item stays in the existing food tab");var render=new net.minecraft.client.renderer.item.ItemStackRenderState();m.getItemModelResolver().updateForTopItem(render,new ItemStack(KitchenAppliances.CURED_SAUSAGE),ItemDisplayContext.GUI,m.level,m.player,315);check(!render.isEmpty()&&!render.pickParticleMaterial(net.minecraft.util.RandomSource.create(315)).sprite().contents().name().getPath().equals("missingno"),"The historical item model still renders");System.out.println("SAUSAGE315_MODELS states=16 item=1 native_food_tab=true");});
|
||||
bite(c,server,1);c.takeScreenshot("sausage315-one-slice-eaten");bite(c,server,2);c.takeScreenshot("sausage315-half-eaten");
|
||||
for(String locale:List.of("fr_fr","en_us"))for(int scale:new int[]{2,3,4}){c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();check(!new ItemStack(KitchenAppliances.CURED_SAUSAGE).getHoverName().getString().startsWith("item.")&&!net.minecraft.network.chat.Component.translatable("sanctuary.sausage.tooltip").getString().startsWith("sanctuary."),"Serving labels are translated");});c.waitTicks(3);c.takeScreenshot("sausage315-half-"+locale+"-gui"+scale);}
|
||||
saved=world.getWorldSave();
|
||||
}
|
||||
try(var world=saved.open()){
|
||||
c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);var server=world.getServer();server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var state=p.level().getBlockState(FOOD);check(state.is(KitchenAppliances.CURED_SAUSAGE_BLOCK)&&state.getValue(CuredSausageBlock.BITES)==2&&state.getValue(CuredSausageBlock.FACING)==facing.get()&&p.getInventory().countItem(KitchenAppliances.CURED_SAUSAGE)==0,"Actual disk reopen preserves orientation and exactly two eaten slices without refunding the item");});aim(c,FOOD);c.takeScreenshot("sausage315-half-after-disk");bite(c,server,3);c.takeScreenshot("sausage315-last-slice");bite(c,server,4);c.takeScreenshot("sausage315-finished");server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(p.level().getBlockState(FOOD).isAir()&&p.getInventory().countItem(KitchenAppliances.RAW_SAUSAGE)==0&&p.getInventory().countItem(KitchenAppliances.CURED_SAUSAGE)==0,"The fourth real bite consumes the single paid drying result");});
|
||||
}
|
||||
c.waitTicks(4);c.runOnClient(m->m.getSoundManager().removeListener(listener));check(audio.stream().filter(id->id.equals("minecraft:entity.generic.eat")).count()>=4,"The four actual slice interactions produce native eating sounds");System.out.println("SAUSAGE315_AUDIO "+audio);System.out.println("SAUSAGE315_CLIENT_PASS Vulkan, real hook payment, drying result, native crosshair/key placement, four real slices/eight food, 16 baked states, original item model/food tab, FR/EN GUI2/3/4, actual disk orientation/parts and eating audio");
|
||||
}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import fr.koka.sanctuary.agriculture.*;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
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.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.phys.*;
|
||||
import static fr.koka.sanctuary.agriculture.AgricultureBlocks.VANILLA_VINE;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Real survival planting/bonemeal/harvest packets, planar geometry, vanilla tabs and disk reload. */
|
||||
public final class Vanilla301ClientChecks implements FabricClientGameTest {
|
||||
private static final BlockPos BASE = new BlockPos(0, 1, 1);
|
||||
private static void check(boolean ok, String message) { if (!ok) throw new AssertionError(message); }
|
||||
|
||||
@Override public void runTest(ClientGameTestContext c) {
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable(); var intro = new AtomicBoolean(true);
|
||||
var snapshot = new AtomicReference<Map<BlockPos, BlockState>>();
|
||||
c.runOnClient(m -> {
|
||||
var device = com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();
|
||||
check(device.backendName().equalsIgnoreCase("vulkan"), "Vanilla orchid validation requires Vulkan");
|
||||
System.out.println("VANILLA301_BACKEND " + device.backendName() + " / " + device.name());
|
||||
m.options.renderDistance().set(8); m.options.simulationDistance().set(8); m.options.pauseOnLostFocus = false;
|
||||
});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m -> {
|
||||
if (intro.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
|
||||
IntroClient.setQuickTestIntro(true); Starter039TestSupport.selectFirst(screen);
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
|
||||
press(screen, "sanctuary.progression.enter"); intro.set(false);
|
||||
}
|
||||
});
|
||||
TestWorldSave saved;
|
||||
try (var world = c.worldBuilder().adjustSettings(s -> {
|
||||
s.setName("Vanilla301-" + UUID.randomUUID()); s.setSeed("301"); 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()) {
|
||||
c.waitFor(m -> m.player != null && ProgressionClient.active() && m.gui.screen() == null && !IntroSpawnFlash.active(), 1200);
|
||||
var server = world.getServer();
|
||||
server.runOnServer(s -> {
|
||||
var player = s.getPlayerList().getPlayers().getFirst(); var level = player.level();
|
||||
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");
|
||||
level.getGameRules().set(GameRules.SPAWN_MOBS, false, s);
|
||||
level.getGameRules().set(GameRules.RANDOM_TICK_SPEED, 0, s);
|
||||
var logs = List.of(Blocks.OAK_LOG, Blocks.JUNGLE_LOG, Blocks.STRIPPED_BIRCH_LOG, Blocks.CHERRY_LOG);
|
||||
for (int i = 0; i < logs.size(); i++) for (int y = 1; y <= 7; y++)
|
||||
level.setBlockAndUpdate(new BlockPos(i * 4, y, 0), logs.get(i).defaultBlockState());
|
||||
for (int i = 0; i < logs.size(); i++) for (int y = 7; y <= 9; y++)
|
||||
for (int x = -2; x <= 2; x++) for (int z = -2; z <= 2; z++) {
|
||||
if (x == 0 && z == 0 && y == 7 || y == 9 && Math.abs(x) + Math.abs(z) > 2) continue;
|
||||
level.setBlockAndUpdate(new BlockPos(i * 4 + x, y, z), Blocks.OAK_LEAVES.defaultBlockState().setValue(LeavesBlock.PERSISTENT, true));
|
||||
}
|
||||
player.teleportTo(.5, 1, 3.5); player.setYRot(180); player.setXRot(15);
|
||||
player.setGameMode(GameType.SURVIVAL); player.getInventory().clearContent();
|
||||
player.getInventory().setItem(0, new ItemStack(AgricultureItems.VANILLA_CUTTING, 2)); player.inventoryMenu.broadcastChanges();
|
||||
});
|
||||
c.waitFor(m -> m.player.getMainHandItem().is(AgricultureItems.VANILLA_CUTTING), 400);
|
||||
c.runOnClient(m -> m.gameMode.useItemOn(m.player, InteractionHand.MAIN_HAND, hit(BASE.north(), Direction.SOUTH)));
|
||||
c.waitFor(m -> m.level.getBlockState(BASE).is(VANILLA_VINE), 200);
|
||||
c.waitTicks(6);
|
||||
server.runOnServer(s -> {
|
||||
var player = s.getPlayerList().getPlayers().getFirst();
|
||||
check(player.getMainHandItem().getCount() == 1 && VanillaVineBlock.attached(player.level().getBlockState(BASE), Direction.NORTH), "Native survival placement consumes one cutting and attaches to the trunk: count=" + player.getMainHandItem().getCount() + " state=" + player.level().getBlockState(BASE));
|
||||
player.getInventory().setItem(0, new ItemStack(Items.BONE_MEAL, 16)); player.inventoryMenu.broadcastChanges();
|
||||
});
|
||||
c.waitFor(m -> m.player.getMainHandItem().is(Items.BONE_MEAL), 200);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
c.runOnClient(m -> m.gameMode.useItemOn(m.player, InteractionHand.MAIN_HAND, hit(BASE, Direction.SOUTH))); c.waitTicks(5);
|
||||
}
|
||||
server.runOnServer(s -> {
|
||||
var player = s.getPlayerList().getPlayers().getFirst();
|
||||
check(player.level().getBlockState(BASE.above()).is(VANILLA_VINE) && !player.level().getBlockState(BASE.below()).is(VANILLA_VINE), "Actual bonemeal packets extend upward only");
|
||||
check(player.getMainHandItem().getCount() == 12, "Exact native bonemeal consumption");
|
||||
player.getInventory().setItem(0, ItemStack.EMPTY); player.inventoryMenu.broadcastChanges();
|
||||
});
|
||||
c.waitFor(m -> m.player.getMainHandItem().isEmpty(), 200);
|
||||
c.runOnClient(m -> m.gameMode.useItemOn(m.player, InteractionHand.MAIN_HAND, hit(BASE, Direction.SOUTH))); c.waitTicks(5);
|
||||
server.runOnServer(s -> {
|
||||
var level = s.overworld();
|
||||
check(level.getBlockState(BASE).getValue(VanillaVineBlock.AGE) == 1, "Actual empty-hand interaction picks ripe pods");
|
||||
int count = level.getEntitiesOfClass(ItemEntity.class, new AABB(BASE).inflate(1)).stream()
|
||||
.filter(item -> item.getItem().is(AgricultureItems.GREEN_VANILLA_POD)).mapToInt(item -> item.getItem().getCount()).sum();
|
||||
check(count >= 1 && count <= 3, "Server awards the real pod harvest once");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
var base = BASE.offset(i * 4, 0, 0);
|
||||
if (i != 0) level.setBlockAndUpdate(base, vine(0));
|
||||
for (int y = 0; y < 6; y++) {
|
||||
var pos = base.above(y);
|
||||
for (int n = 0; n < 4; n++) BoneMealItem.growCrop(new ItemStack(Items.BONE_MEAL), level, pos);
|
||||
}
|
||||
}
|
||||
// Retain multiple faces and maturity on a supported corner for the actual disk test.
|
||||
level.setBlockAndUpdate(BASE.east(), Blocks.OAK_LOG.defaultBlockState());
|
||||
level.setBlockAndUpdate(BASE, vine(2).setValue(VanillaVineBlock.face(Direction.EAST), true));
|
||||
});
|
||||
c.runOnClient(m -> {
|
||||
int states = 0;
|
||||
for (var state : VANILLA_VINE.getStateDefinition().getPossibleStates()) {
|
||||
var parts = new ArrayList<BlockStateModelPart>();
|
||||
m.getModelManager().getBlockStateModelSet().get(state).collectParts(RandomSource.create(301), parts);
|
||||
int faces = 0; for (var side : Direction.Plane.HORIZONTAL) if (VanillaVineBlock.attached(state, side)) faces++;
|
||||
int quads = 0;
|
||||
for (var part : parts) for (var direction : new Direction[]{null, Direction.UP, Direction.DOWN, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST})
|
||||
for (var quad : part.getQuads(direction)) {
|
||||
var material = quad.materialInfo(); var contents = material.sprite().contents(); var sprite = contents.name();
|
||||
check(sprite.equals(SanctuaryMod.id("block/vanilla_climbing_stage" + state.getValue(VanillaVineBlock.AGE))), "One integrated vanilla sprite is baked for each maturity stage");
|
||||
check(contents.width() == 16 && contents.height() == 16, "All four vanilla growth sprites must be exactly 16 by 16 pixels in the loaded atlas");
|
||||
if (faces == 1) {
|
||||
var side = Direction.NORTH;
|
||||
for (var candidate : Direction.Plane.HORIZONTAL) if (VanillaVineBlock.attached(state, candidate)) side = candidate;
|
||||
for (int v = 0; v < 4; v++) {
|
||||
var p = quad.position(v); float edge = switch (side) { case NORTH -> p.z(); case SOUTH -> 1 - p.z(); case WEST -> p.x(); case EAST -> 1 - p.x(); default -> 1; };
|
||||
check(edge >= 0 && edge <= 1.01 / 16, "Actual baked vertices lie against the trunk face, not around the cell centre");
|
||||
}
|
||||
}
|
||||
check(material.tintIndex() == -1, "The integrated leaves, flowers and pods retain their authored colours");
|
||||
quads++;
|
||||
}
|
||||
check(quads == faces * 2, "One two-sided plane per attached face, no overlapping texture layers"); states++;
|
||||
}
|
||||
CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(), true, m.level.registryAccess());
|
||||
var natural = BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.NATURAL_BLOCKS).value();
|
||||
var food = BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(CreativeModeTabs.FOOD_AND_DRINKS).value();
|
||||
check(natural.getDisplayItems().stream().anyMatch(stack -> stack.is(AgricultureItems.VANILLA_CUTTING)), "Cutting is in the existing Natural tab");
|
||||
for (var item : List.of(AgricultureItems.GREEN_VANILLA_POD, AgricultureItems.VANILLA_POD)) check(food.getDisplayItems().stream().anyMatch(stack -> stack.is(item)), "Pod is in the existing Food & Drinks tab");
|
||||
check(BuiltInRegistries.CREATIVE_MODE_TAB.getOptional(SanctuaryMod.id("agriculture")).isEmpty(), "No new creative tab");
|
||||
for (String locale : List.of("fr_fr", "en_us")) {
|
||||
m.getLanguageManager().setSelected(locale); m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
|
||||
for (var item : List.of(AgricultureItems.VANILLA_CUTTING, AgricultureItems.GREEN_VANILLA_POD, AgricultureItems.VANILLA_POD))
|
||||
check(!new ItemStack(item).getHoverName().getString().startsWith("item.sanctuary"), "Translated vanilla orchid item: " + locale);
|
||||
}
|
||||
m.getWindow().setWindowed(1280, 960);
|
||||
System.out.println("VANILLA301_MODELS states=" + states + " supported=60 single_plane=true new_sprites=4 sprite_size=16x16 legacy_pixels=unchanged");
|
||||
});
|
||||
server.runOnServer(s -> {
|
||||
var player = s.getPlayerList().getPlayers().getFirst(); player.setGameMode(GameType.CREATIVE);
|
||||
player.teleportTo(6.5, 4, 15); player.setYRot(180); player.setXRot(2); player.getAbilities().flying = true; player.onUpdateAbilities();
|
||||
});
|
||||
c.waitTicks(18); c.runOnClient(m -> {m.player.setYRot(180);m.player.setXRot(2);});
|
||||
c.getInput().pressKey(com.mojang.blaze3d.platform.InputConstants.KEY_F1); c.waitTicks(4); c.takeScreenshot("vanilla302-16px-climbs-tree-trunks");
|
||||
server.runOnServer(s -> {var p = s.getPlayerList().getPlayers().getFirst();p.teleportTo(.5, 3.5, 5);p.setYRot(180);p.setXRot(0);});
|
||||
c.waitTicks(8); c.runOnClient(m -> {m.player.setYRot(180);m.player.setXRot(0);}); c.waitTicks(3); c.takeScreenshot("vanilla302-16px-flat-leaves-and-pods");
|
||||
server.runOnServer(s -> {
|
||||
var map = new LinkedHashMap<BlockPos, BlockState>(); var level = s.overworld();
|
||||
for (var pos : BlockPos.betweenClosed(new BlockPos(-1, 1, 0), new BlockPos(13, 8, 2)))
|
||||
if (level.getBlockState(pos).is(VANILLA_VINE)) map.put(pos.immutable(), level.getBlockState(pos));
|
||||
check(map.size() >= 20, "Actual upward cultivation produced a full orchid gallery"); snapshot.set(map);
|
||||
});
|
||||
saved = world.getWorldSave();
|
||||
}
|
||||
try (var world = saved.open()) {
|
||||
c.waitFor(m -> m.player != null && m.gui.screen() == null, 1200);
|
||||
world.getServer().runOnServer(s -> {
|
||||
for (var entry : snapshot.get().entrySet()) check(s.overworld().getBlockState(entry.getKey()).equals(entry.getValue()), "Disk reload retains the actual attachment faces and age");
|
||||
System.out.println("VANILLA301_DISK_RELOAD segments=" + snapshot.get().size());
|
||||
});
|
||||
c.waitTicks(6); c.takeScreenshot("vanilla302-16px-reloaded");
|
||||
}
|
||||
System.out.println("VANILLA301_CLIENT_PASS Vulkan, native survival packets, upward growth, thin tree faces, pods, vanilla tabs, FR/EN and disk reload");
|
||||
}
|
||||
private static BlockState vine(int age) { return VANILLA_VINE.defaultBlockState().setValue(VanillaVineBlock.face(Direction.NORTH), true).setValue(VanillaVineBlock.AGE, age); }
|
||||
private static BlockHitResult hit(BlockPos pos, Direction side) { return new BlockHitResult(Vec3.atCenterOf(pos).add(side.getStepX() * .5, side.getStepY() * .5, side.getStepZ() * .5), side, pos, false); }
|
||||
}
|
||||
+6
@@ -142,6 +142,12 @@ public final class Pbr134ClientChecks implements FabricClientGameTest {
|
||||
check(failures==0,"Close PBR stays stable under subpixel motion: failing distances="+failures);
|
||||
}
|
||||
private static void materials(){
|
||||
try(var materials=new PbrMaterials()){
|
||||
check(!materials.candidate(Blocks.BARRIER.defaultBlockState())&&!materials.candidate(Blocks.MOVING_PISTON.defaultBlockState()),
|
||||
"Invisible and entity-rendered blocks cannot leave normals from their baked terrain model");
|
||||
check(materials.candidate(Blocks.OAK_PLANKS.defaultBlockState())&&materials.candidate(Blocks.GLASS.defaultBlockState()),
|
||||
"Visible wooden and glass terrain models keep their material pass");
|
||||
}
|
||||
int[] flat=new int[16];Arrays.fill(flat,0xFF808080);var profile=PbrMaterials.profile("bricks");
|
||||
for(int p:PbrMaterials.generate(flat,4,4,profile))check((p>>8&0xFFFF)==0x8080,"Flat source has neutral normal");
|
||||
flat[5]=0xFFFFFFFF;var slope=PbrMaterials.generate(flat,4,4,profile);check((slope[4]>>16&255)<128&&(slope[6]>>16&255)>128,"Height gradients produce opposite normal slopes");
|
||||
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import fr.koka.sanctuary.agriculture.*;
|
||||
import java.util.*;
|
||||
import net.fabricmc.fabric.api.gametest.v1.GameTest;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.*;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import static fr.koka.sanctuary.agriculture.AgricultureBlocks.*;
|
||||
|
||||
/** Native placement, harvesting, loot, tree growth and state persistence in a fresh test world. */
|
||||
public final class Agriculture300GameTests {
|
||||
@GameTest public void allTwentyCropsPlantConsumeOneSeedAndGrow(GameTestHelper h) {
|
||||
var player = Progression003GameTests.player(h);
|
||||
h.assertTrue(crops().size() == 20, "Twenty agricultural crops are registered");
|
||||
int index = 0;
|
||||
for (var block : crops().values()) {
|
||||
var crop = (AgriculturalCropBlock)block;
|
||||
var pos = h.absolutePos(new BlockPos(2 + index % 5 * 2, 20, 2 + index++ / 5 * 2));
|
||||
ground(h, pos, crop == RICE_CROP);
|
||||
var seed = crop.defaultBlockState().getCloneItemStack(h.getLevel(), pos, false).getItem();
|
||||
var stack = new ItemStack(seed, 3);
|
||||
player.setItemInHand(InteractionHand.MAIN_HAND, stack);
|
||||
player.setPos(Vec3.atCenterOf(pos).add(0, 0, 2));
|
||||
seed.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND, hit(pos.below())));
|
||||
h.assertTrue(h.getLevel().getBlockState(pos).is(crop) && stack.getCount() == 2,
|
||||
"Native placement consumes exactly one seed: " + crop);
|
||||
for (int i = 0; i < 8 && !crop.isMaxAge(h.getLevel().getBlockState(pos)); i++) {
|
||||
h.assertTrue(BoneMealItem.growCrop(new ItemStack(Items.BONE_MEAL, 1), h.getLevel(), pos), "Native bonemeal grows " + crop);
|
||||
}
|
||||
h.assertTrue(crop.isMaxAge(h.getLevel().getBlockState(pos)), "Crop reaches maturity: " + crop);
|
||||
}
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void immatureAndMatureLootUseNative263Conditions(GameTestHelper h) {
|
||||
var pos = h.absolutePos(new BlockPos(4, 20, 4));
|
||||
for (var block : crops().values()) {
|
||||
var crop = (AgriculturalCropBlock)block;
|
||||
var seed = crop.defaultBlockState().getCloneItemStack(h.getLevel(), pos, false).getItem();
|
||||
var immature = Block.getDrops(crop.getStateForAge(0), h.getLevel(), pos, null);
|
||||
h.assertTrue(immature.size() == 1 && immature.getFirst().is(seed) && immature.getFirst().getCount() == 1,
|
||||
"Immature crop returns its single planting item: " + crop);
|
||||
var mature = Block.getDrops(crop.getStateForAge(7), h.getLevel(), pos, null);
|
||||
h.assertTrue(!mature.isEmpty() && mature.stream().mapToInt(ItemStack::getCount).sum() >= 1,
|
||||
"Mature native loot is not empty: " + crop);
|
||||
if (crop == TOMATO_CROP) h.assertTrue(mature.stream().anyMatch(s -> s.is(AgricultureItems.TOMATO)), "Ripe tomato drops fruit rather than only seeds");
|
||||
if (crop == PINEAPPLE_CROP) h.assertTrue(mature.stream().anyMatch(s -> s.is(AgricultureItems.PINEAPPLE)), "Ripe pineapple drops its fruit");
|
||||
}
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void perennialsHarvestOnceAndKeepTheirPlant(GameTestHelper h) {
|
||||
var player = Progression003GameTests.player(h);
|
||||
var pos = h.absolutePos(new BlockPos(4, 20, 4));
|
||||
for (var block : crops().values()) {
|
||||
if (!(block instanceof HarvestableCropBlock crop)) continue;
|
||||
ground(h, pos, false);
|
||||
h.getLevel().setBlockAndUpdate(pos, crop.getStateForAge(7));
|
||||
clearDrops(h, pos);
|
||||
h.getLevel().getBlockState(pos).useWithoutItem(h.getLevel(), player, hit(pos));
|
||||
int count = dropped(h, pos);
|
||||
h.assertTrue(count >= 1 && count <= 4 && crop.getAge(h.getLevel().getBlockState(pos)) == 3,
|
||||
"One ripe harvest resets the retained plant: " + crop);
|
||||
h.getLevel().getBlockState(pos).useWithoutItem(h.getLevel(), player, hit(pos));
|
||||
h.assertTrue(dropped(h, pos) == count, "Second click cannot duplicate a harvest: " + crop);
|
||||
}
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void tallCropsRespectCeilingsAndSynchronizeTheirUpperHalf(GameTestHelper h) {
|
||||
var pos = h.absolutePos(new BlockPos(4, 20, 4));
|
||||
for (var crop : List.of(TOMATO_CROP, CORN_CROP, CUCUMBER_CROP, BEAN_CROP)) {
|
||||
ground(h, pos, false);
|
||||
h.getLevel().setBlockAndUpdate(pos, crop.getStateForAge(3));
|
||||
h.getLevel().setBlockAndUpdate(pos.above(), Blocks.STONE.defaultBlockState());
|
||||
var boneMeal = new ItemStack(Items.BONE_MEAL, 1);
|
||||
h.assertTrue(!BoneMealItem.growCrop(boneMeal, h.getLevel(), pos) && boneMeal.getCount() == 1,
|
||||
"Blocked height refuses bonemeal without consuming it");
|
||||
h.assertTrue(h.getLevel().getBlockState(pos.above()).is(Blocks.STONE), "Growth cannot replace the ceiling");
|
||||
h.getLevel().removeBlock(pos.above(), false);
|
||||
for (int i = 0; i < 8 && !crop.isMaxAge(h.getLevel().getBlockState(pos)); i++)
|
||||
BoneMealItem.growCrop(new ItemStack(Items.BONE_MEAL), h.getLevel(), pos);
|
||||
var upper = h.getLevel().getBlockState(pos.above());
|
||||
h.assertTrue(upper.getBlock() instanceof TallCropUpperBlock && upper.getValue(TallCropUpperBlock.AGE) == 7,
|
||||
"Upper half synchronizes to actual mature age");
|
||||
h.getLevel().destroyBlock(pos, false);
|
||||
h.assertTrue(h.getLevel().getBlockState(pos.above()).isAir(), "Breaking the base removes the upper half");
|
||||
}
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void upperHarvestAndUpperBreakNeverProduceASecondLoot(GameTestHelper h) {
|
||||
var player = Progression003GameTests.player(h);
|
||||
var pos = h.absolutePos(new BlockPos(4, 20, 4));
|
||||
ground(h, pos, false);
|
||||
h.getLevel().setBlockAndUpdate(pos, TOMATO_CROP.getStateForAge(3));
|
||||
for (int i = 0; i < 8 && !TOMATO_CROP.isMaxAge(h.getLevel().getBlockState(pos)); i++)
|
||||
BoneMealItem.growCrop(new ItemStack(Items.BONE_MEAL), h.getLevel(), pos);
|
||||
clearDrops(h, pos);
|
||||
h.getLevel().getBlockState(pos.above()).useWithoutItem(h.getLevel(), player, hit(pos.above()));
|
||||
h.assertTrue(TOMATO_CROP.getAge(h.getLevel().getBlockState(pos)) == 3 && h.getLevel().getBlockState(pos.above()).isAir(),
|
||||
"Harvesting through the upper half resets the base and removes the top");
|
||||
h.assertTrue(dropped(h, pos) >= 2 && dropped(h, pos) <= 4, "Only the lower plant awards fruit");
|
||||
h.getLevel().setBlockAndUpdate(pos, CORN_CROP.getStateForAge(7));
|
||||
h.getLevel().setBlockAndUpdate(pos.above(), CORN_CROP_UPPER.defaultBlockState().setValue(TallCropUpperBlock.AGE, 7));
|
||||
clearDrops(h, pos);
|
||||
player.gameMode.destroyBlock(pos.above());
|
||||
h.assertTrue(h.getLevel().getBlockState(pos).isAir() && h.getLevel().getBlockState(pos.above()).isAir(), "Mining the top destroys the complete plant");
|
||||
h.assertTrue(items(h, pos).stream().flatMap(e -> java.util.stream.Stream.of(e.getItem())).filter(s -> s.is(AgricultureItems.CORN)).mapToInt(ItemStack::getCount).sum() == 1,
|
||||
"The non-obtainable upper half does not duplicate the corn harvest");
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void riceRequiresMudAndARealWaterSourceAndReturnsItsWater(GameTestHelper h) {
|
||||
var player = Progression003GameTests.player(h);
|
||||
var pos = h.absolutePos(new BlockPos(4, 20, 4));
|
||||
player.setPos(Vec3.atCenterOf(pos).add(0, 0, 2));
|
||||
for (int mode = 0; mode < 4; mode++) {
|
||||
h.getLevel().setBlockAndUpdate(pos.below(), (mode == 2 ? Blocks.DIRT : Blocks.MUD).defaultBlockState());
|
||||
h.getLevel().setBlockAndUpdate(pos, mode == 0 ? Blocks.AIR.defaultBlockState()
|
||||
: mode == 1 ? Blocks.WATER.defaultBlockState().setValue(LiquidBlock.LEVEL, 1) : Blocks.WATER.defaultBlockState());
|
||||
var seeds = new ItemStack(AgricultureItems.RICE, 2);
|
||||
player.setItemInHand(InteractionHand.MAIN_HAND, seeds);
|
||||
seeds.getItem().useOn(new UseOnContext(player, InteractionHand.MAIN_HAND, hit(pos.below())));
|
||||
h.assertTrue(h.getLevel().getBlockState(pos).is(RICE_CROP) == (mode == 3), "Only mud plus source water allows planting; case " + mode);
|
||||
h.assertTrue(seeds.getCount() == (mode == 3 ? 1 : 2), "Rejected planting preserves seeds");
|
||||
}
|
||||
h.getLevel().destroyBlock(pos, true);
|
||||
h.assertTrue(h.getLevel().getFluidState(pos).isSource(), "Harvested rice leaves its native water source");
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void beansDistinguishGreenPodsFromDryBeans(GameTestHelper h) {
|
||||
var player = Progression003GameTests.player(h);
|
||||
var pos = h.absolutePos(new BlockPos(4, 20, 4));
|
||||
ground(h, pos, false);
|
||||
for (int age : new int[]{5, 6, 7}) {
|
||||
h.getLevel().setBlockAndUpdate(pos, BEAN_CROP.getStateForAge(age)); clearDrops(h, pos);
|
||||
h.getLevel().getBlockState(pos).useWithoutItem(h.getLevel(), player, hit(pos));
|
||||
var expected = age == 7 ? AgricultureItems.BEANS : AgricultureItems.GREEN_BEANS;
|
||||
h.assertTrue(!items(h, pos).isEmpty() && items(h, pos).stream().allMatch(e -> e.getItem().is(expected)), "Bean stage determines the actual harvest");
|
||||
h.assertTrue(BEAN_CROP.getAge(h.getLevel().getBlockState(pos)) == 3, "Beans remain planted after picking");
|
||||
}
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void coffeeGrowsAndHarvestsCherriesOnce(GameTestHelper h) {
|
||||
var player = Progression003GameTests.player(h);
|
||||
var pos = h.absolutePos(new BlockPos(4, 20, 4));
|
||||
player.setPos(Vec3.atCenterOf(pos).add(0, 0, 2));
|
||||
h.getLevel().setBlockAndUpdate(pos.below(), Blocks.GRASS_BLOCK.defaultBlockState());
|
||||
player.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(AgricultureItems.COFFEE_BEANS, 2));
|
||||
AgricultureItems.COFFEE_BEANS.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND, hit(pos.below())));
|
||||
h.assertTrue(h.getLevel().getBlockState(pos).is(COFFEE_BUSH), "Coffee beans plant the coffee bush");
|
||||
for (int i = 0; i < 3; i++) BoneMealItem.growCrop(new ItemStack(Items.BONE_MEAL), h.getLevel(), pos);
|
||||
clearDrops(h, pos);
|
||||
h.getLevel().getBlockState(pos).useWithoutItem(h.getLevel(), player, hit(pos));
|
||||
int count = dropped(h, pos);
|
||||
h.assertTrue(count >= 2 && count <= 4 && items(h, pos).stream().allMatch(e -> e.getItem().is(AgricultureItems.COFFEE_CHERRIES)), "Mature coffee awards cherries");
|
||||
h.getLevel().getBlockState(pos).useWithoutItem(h.getLevel(), player, hit(pos));
|
||||
h.assertTrue(dropped(h, pos) == count && h.getLevel().getBlockState(pos).getValue(SweetBerryBushBlock.AGE) == 1, "Picking resets the coffee bush without a duplicate reward");
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void fruitHarvestAndRegrowthKeepNativeLeafProperties(GameTestHelper h) {
|
||||
var player = Progression003GameTests.player(h);
|
||||
var pos = h.absolutePos(new BlockPos(4, 20, 4));
|
||||
for (var leaves : List.of(OLIVE_LEAVES, LEMON_LEAVES)) {
|
||||
var state = leaves.defaultBlockState().setValue(LeavesBlock.PERSISTENT, true).setValue(FruitLeavesBlock.FRUITING, true);
|
||||
h.getLevel().setBlockAndUpdate(pos, state); clearDrops(h, pos);
|
||||
state.useWithoutItem(h.getLevel(), player, hit(pos));
|
||||
var after = h.getLevel().getBlockState(pos);
|
||||
h.assertTrue(after.is(leaves) && after.getValue(LeavesBlock.PERSISTENT) && !after.getValue(FruitLeavesBlock.FRUITING) && dropped(h, pos) == 1,
|
||||
"A fruit harvest retains the actual leaves and native properties");
|
||||
after.useWithoutItem(h.getLevel(), player, hit(pos));
|
||||
h.assertTrue(dropped(h, pos) == 1, "Fruit cannot be collected twice");
|
||||
var random = RandomSource.create(300);
|
||||
for (int i = 0; i < 4096 && !h.getLevel().getBlockState(pos).getValue(FruitLeavesBlock.FRUITING); i++)
|
||||
h.getLevel().getBlockState(pos).randomTick(h.getLevel(), pos, random);
|
||||
h.assertTrue(h.getLevel().getBlockState(pos).getValue(FruitLeavesBlock.FRUITING), "Native random ticks regrow fruit");
|
||||
}
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void lemonSaplingCreatesItsOwnNativeTree(GameTestHelper h) { tree(h, LEMON_SAPLING, LEMON_LEAVES, Blocks.OAK_LOG); }
|
||||
@GameTest public void oliveSaplingCreatesItsOwnNativeTree(GameTestHelper h) { tree(h, OLIVE_SAPLING, OLIVE_LEAVES, Blocks.ACACIA_LOG); }
|
||||
|
||||
@GameTest public void pineappleEatingReturnsExactlyOneCrown(GameTestHelper h) {
|
||||
var player = Progression003GameTests.player(h);
|
||||
player.getInventory().clearContent(); player.getFoodData().setFoodLevel(0);
|
||||
var result = AgricultureItems.PINEAPPLE.finishUsingItem(new ItemStack(AgricultureItems.PINEAPPLE, 2), h.getLevel(), player);
|
||||
h.assertTrue(result.getCount() == 1 && player.getInventory().countItem(AgricultureItems.PINEAPPLE_CROWN) == 1, "One consumed pineapple returns exactly one replantable crown");
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void agriculturalStatesRoundTripAndCompostingUsesNativeComponents(GameTestHelper h) {
|
||||
var states = new ArrayList<BlockState>();
|
||||
for (var block : crops().values()) states.add(((AgriculturalCropBlock)block).getStateForAge(7));
|
||||
states.add(LEMON_LEAVES.defaultBlockState().setValue(FruitLeavesBlock.FRUITING, true));
|
||||
states.add(OLIVE_LEAVES.defaultBlockState().setValue(FruitLeavesBlock.FRUITING, true));
|
||||
states.add(COFFEE_BUSH.defaultBlockState().setValue(SweetBerryBushBlock.AGE, 3));
|
||||
for (var state : states) {
|
||||
var saved = BlockState.CODEC.encodeStart(NbtOps.INSTANCE, state).getOrThrow();
|
||||
h.assertTrue(BlockState.CODEC.parse(NbtOps.INSTANCE, saved).getOrThrow().equals(state), "Native block-state persistence retains age/fruiting: " + state);
|
||||
}
|
||||
for (var item : AgricultureItems.PLANTING_ITEMS)
|
||||
h.assertTrue(new ItemStack(item).has(DataComponents.COMPOSTABLE), "Planting item uses 26.3 compostable component: " + item);
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void nativeRecipesRecoverSeedsAndRoastCoffee(GameTestHelper h) {
|
||||
var manager = h.getLevel().getServer().getRecipeManager();
|
||||
var ingredients = List.of(AgricultureItems.CORN, AgricultureItems.COFFEE_CHERRIES, AgricultureItems.PINEAPPLE);
|
||||
var expected = List.of(AgricultureItems.CORN_KERNELS, AgricultureItems.COFFEE_BEANS, AgricultureItems.PINEAPPLE_CROWN);
|
||||
for (int i = 0; i < ingredients.size(); i++) {
|
||||
var input = net.minecraft.world.item.crafting.CraftingInput.of(1, 1, List.of(new ItemStack(ingredients.get(i))));
|
||||
var output = manager.getRecipeFor(net.minecraft.world.item.crafting.RecipeType.CRAFTING, input, h.getLevel()).orElseThrow().value().assemble(input);
|
||||
h.assertTrue(output.is(expected.get(i)) && output.getCount() == (i == 2 ? 1 : 2), "Native crafting recovers the expected propagules");
|
||||
}
|
||||
var input = new net.minecraft.world.item.crafting.SingleRecipeInput(new ItemStack(AgricultureItems.COFFEE_BEANS));
|
||||
var coffee = manager.getRecipeFor(net.minecraft.world.item.crafting.RecipeType.SMOKING, input, h.getLevel()).orElseThrow().value().assemble(input);
|
||||
h.assertTrue(coffee.is(AgricultureItems.ROASTED_COFFEE_BEANS) && coffee.getCount() == 1, "Native smoker recipe roasts coffee beans");
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void fruitLeavesCanBeCollectedWithNativeShears(GameTestHelper h) {
|
||||
var pos = h.absolutePos(new BlockPos(4, 20, 4));
|
||||
for (var block : List.of(OLIVE_LEAVES, LEMON_LEAVES)) {
|
||||
var drops = Block.getDrops(block.defaultBlockState(), h.getLevel(), pos, null, null, new ItemStack(Items.SHEARS));
|
||||
h.assertTrue(drops.size() == 1 && drops.getFirst().is(block.asItem()), "Native shears retain the fruit leaf block");
|
||||
}
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void nativeRandomTicksGrowRiceAndBothKindsOfTallCrop(GameTestHelper h) {
|
||||
var pos = h.absolutePos(new BlockPos(4, 20, 4));
|
||||
for (var crop : List.of(RICE_CROP, TOMATO_CROP, CORN_CROP, BEAN_CROP)) {
|
||||
ground(h, pos, crop == RICE_CROP);
|
||||
h.getLevel().setBlockAndUpdate(pos, crop.defaultBlockState());
|
||||
var random = RandomSource.create(300);
|
||||
for (int i = 0; i < 4096 && !crop.isMaxAge(h.getLevel().getBlockState(pos)); i++)
|
||||
h.getLevel().getBlockState(pos).randomTick(h.getLevel(), pos, random);
|
||||
h.assertTrue(crop.isMaxAge(h.getLevel().getBlockState(pos)), "Native random growth reaches maturity: " + crop);
|
||||
if (crop != RICE_CROP) h.assertTrue(h.getLevel().getBlockState(pos.above()).getValue(TallCropUpperBlock.AGE) == 7, "Natural growth synchronizes the top too");
|
||||
}
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
private static void tree(GameTestHelper h, FruitTreeSaplingBlock sapling, FruitLeavesBlock leaves, Block log) {
|
||||
var pos = h.absolutePos(new BlockPos(7, 20, 7));
|
||||
for (var p : BlockPos.betweenClosed(pos.offset(-7, -1, -7), pos.offset(7, -1, 7))) h.getLevel().setBlockAndUpdate(p, Blocks.DIRT.defaultBlockState());
|
||||
var witness = pos.offset(7, 8, 7);
|
||||
var original = Blocks.OAK_LEAVES.defaultBlockState().setValue(LeavesBlock.PERSISTENT, true);
|
||||
h.getLevel().setBlockAndUpdate(witness, original);
|
||||
h.getLevel().setBlockAndUpdate(pos, sapling.defaultBlockState());
|
||||
for (int i = 0; i < 8 && h.getLevel().getBlockState(pos).is(sapling); i++)
|
||||
sapling.advanceTree(h.getLevel(), pos, h.getLevel().getBlockState(pos), RandomSource.create(300 + i));
|
||||
h.assertTrue(h.getLevel().getBlockState(pos).is(log), "A planted fruit sapling grows its native trunk");
|
||||
long count = BlockPos.betweenClosedStream(pos.offset(-7, 0, -7), pos.offset(7, 16, 7)).filter(p -> h.getLevel().getBlockState(p).is(leaves)).count();
|
||||
h.assertTrue(count > 5, "The native feature creates fruit leaves directly");
|
||||
h.assertTrue(h.getLevel().getBlockState(witness).equals(original), "An existing neighbouring tree is never converted");
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
private static void ground(GameTestHelper h, BlockPos pos, boolean rice) {
|
||||
h.getLevel().setBlockAndUpdate(pos.below(), rice ? Blocks.MUD.defaultBlockState() : Blocks.FARMLAND.defaultBlockState().setValue(FarmlandBlock.MOISTURE, 7));
|
||||
h.getLevel().setBlockAndUpdate(pos, rice ? Blocks.WATER.defaultBlockState() : Blocks.AIR.defaultBlockState());
|
||||
h.getLevel().setBlockAndUpdate(pos.above(), Blocks.AIR.defaultBlockState());
|
||||
}
|
||||
private static BlockHitResult hit(BlockPos pos) { return new BlockHitResult(Vec3.atCenterOf(pos), Direction.UP, pos, false); }
|
||||
private static List<ItemEntity> items(GameTestHelper h, BlockPos pos) { return h.getLevel().getEntitiesOfClass(ItemEntity.class, new AABB(pos).inflate(.75)); }
|
||||
private static int dropped(GameTestHelper h, BlockPos pos) { return items(h, pos).stream().mapToInt(e -> e.getItem().getCount()).sum(); }
|
||||
private static void clearDrops(GameTestHelper h, BlockPos pos) { items(h, pos).forEach(ItemEntity::discard); }
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.CulinaryAppliance;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.CookingCauldronBlock;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.CookingCauldronBlockEntity;
|
||||
import fr.koka.sanctuary.cuisine.culinary.recipe.CulinaryProcessRecipe;
|
||||
import net.fabricmc.fabric.api.gametest.v1.GameTest;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.crafting.display.SlotDisplayContext;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.*;
|
||||
import net.minecraft.world.phys.*;
|
||||
|
||||
/** Native ingredients, real recipe manager, explicit gestures, paid containers and old storage. */
|
||||
public final class Cauldron311GameTests {
|
||||
private static CookingCauldronBlockEntity pot(GameTestHelper h){
|
||||
var pos=h.absolutePos(new BlockPos(2,6,2));
|
||||
if(h.getLevel().getBlockEntity(pos) instanceof CookingCauldronBlockEntity old)old.clearContent();
|
||||
h.getLevel().removeBlock(pos,false);h.getLevel().setBlockAndUpdate(pos.below(),Blocks.CAMPFIRE.defaultBlockState());
|
||||
h.getLevel().setBlockAndUpdate(pos,KitchenAppliances.COOKING_CAULDRON.defaultBlockState());return (CookingCauldronBlockEntity)h.getLevel().getBlockEntity(pos);
|
||||
}
|
||||
private static void tick(GameTestHelper h,CookingCauldronBlockEntity pot,int count){for(int t=0;t<count;t++)CookingCauldronBlockEntity.serverTick(h.getLevel(),pot.getBlockPos(),pot.getBlockState(),pot);}
|
||||
private static ItemEntity drop(GameTestHelper h,BlockPos pos,ItemStack stack){var e=new ItemEntity(h.getLevel(),pos.getX()+.5,pos.getY()+.5,pos.getZ()+.5,stack);h.getLevel().addFreshEntity(e);return e;}
|
||||
private static int count(GameTestHelper h,BlockPos pos,Item item){return h.getLevel().getEntitiesOfClass(ItemEntity.class,new AABB(pos).inflate(2),e->e.isAlive()&&e.getItem().is(item)).stream().mapToInt(e->e.getItem().getCount()).sum();}
|
||||
private static void milk(GameTestHelper h,CookingCauldronBlockEntity p,int count){for(int i=0;i<count;i++)h.assertTrue(p.insertIngredient(new ItemStack(Items.MILK_BUCKET),1)==1,"One legal milk bucket inserted");}
|
||||
private static CookingCauldronBlockEntity reload(GameTestHelper h,CookingCauldronBlockEntity p,net.minecraft.nbt.CompoundTag tag){var copy=(CookingCauldronBlockEntity)BlockEntity.loadStatic(p.getBlockPos(),p.getBlockState(),tag,h.getLevel().registryAccess());copy.setLevel(h.getLevel());h.getLevel().setBlockEntity(copy);return copy;}
|
||||
|
||||
@GameTest public void droppedMilkWaitsForANativeStickGestureAndReturnsItsBucketOnlyAfterCooking(GameTestHelper h){
|
||||
var pos=h.absolutePos(new BlockPos(2,6,2));h.getLevel().setBlockAndUpdate(pos.below(),Blocks.CAMPFIRE.defaultBlockState());h.getLevel().setBlockAndUpdate(pos,Blocks.CAULDRON.defaultBlockState());
|
||||
var e=drop(h,pos,new ItemStack(Items.MILK_BUCKET));CookingCauldronBlock.tryActivateFromDroppedItem(e);var p=(CookingCauldronBlockEntity)h.getLevel().getBlockEntity(pos);
|
||||
h.assertTrue(e.isRemoved()&&p.waterUnits()==0&&p.getItem(0).is(Items.MILK_BUCKET),"Empty vanilla cauldron captures the unpaid milk bucket");tick(h,p,1400);
|
||||
h.assertTrue(p.getItem(6).isEmpty()&&p.progress()==0&&count(h,pos,Items.BUCKET)==0,"No automatic recipe, failed dish or premature bucket");
|
||||
var player=Progression003GameTests.player(h);player.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.STICK));p.getBlockState().useItemOn(player.getMainHandItem(),h.getLevel(),player,InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(pos),Direction.UP,pos,false));tick(h,p,179);
|
||||
h.assertTrue(p.assemblyComplete()&&p.getItem(0).is(Items.MILK_BUCKET)&&p.getItem(6).isEmpty()&&player.getMainHandItem().getCount()==1,"Native stirring preserves the stick and unpaid milk until the final tick");tick(h,p,1);
|
||||
h.assertTrue(p.getItem(6).is(Cuisine.item("cream"))&&p.getItem(6).getCount()==2&&p.getItem(0).isEmpty()&&count(h,pos,Items.BUCKET)==1,"One full preparation pays one milk bucket and ejects exactly its bucket");h.succeed();
|
||||
}
|
||||
@GameTest public void incompleteMixturesNeverFailAndCanReturnTheirFullIngredients(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);p.insertIngredient(new ItemStack(Items.CARROT),1);tick(h,p,1800);
|
||||
h.assertTrue(!p.stir()&&p.getItem(0).is(Items.MILK_BUCKET)&&p.getItem(1).is(Items.CARROT)&&p.getItem(6).isEmpty()&&count(h,p.getBlockPos(),Items.BUCKET)==0,"Invalid mixture has no destructive timeout or payment");
|
||||
var player=Progression003GameTests.player(h);player.setItemInHand(InteractionHand.MAIN_HAND,ItemStack.EMPTY);
|
||||
for(int i=0;i<2;i++)h.getLevel().getBlockState(p.getBlockPos()).useWithoutItem(h.getLevel(),player,new BlockHitResult(Vec3.atCenterOf(p.getBlockPos()),Direction.UP,p.getBlockPos(),false));
|
||||
h.assertTrue(player.getInventory().countItem(Items.MILK_BUCKET)==1&&player.getInventory().countItem(Items.CARROT)==1&&h.getLevel().getBlockState(p.getBlockPos()).is(Blocks.CAULDRON),"Cancellation returns full milk, raw ingredients and vanilla empty cauldron");h.succeed();
|
||||
}
|
||||
@GameTest public void allTwentyFourRecipesRequireStirringAndPayOnlyAtTheirWholeDeadline(GameTestHelper h){
|
||||
int total=0;
|
||||
for(var holder:h.getLevel().getServer().getRecipeManager().getRecipes())if(holder.value() instanceof CulinaryProcessRecipe r&&r.appliance()==CulinaryAppliance.COOKING_CAULDRON){
|
||||
var p=pot(h);for(var ingredient:r.ingredients())h.assertTrue(p.insertIngredient(ingredient.display().resolveForFirstStack(SlotDisplayContext.fromLevel(h.getLevel())),1)==1,"Native ingredient accepted: "+holder.id());
|
||||
tick(h,p,700);h.assertTrue(p.getItem(6).isEmpty()&&p.progress()==0,"Every recipe waits for the gesture: "+holder.id());h.assertTrue(p.stir(),"Valid manual recipe: "+holder.id());
|
||||
tick(h,p,r.processingTime()-1);h.assertTrue(p.getItem(6).isEmpty(),"No early result: "+holder.id());tick(h,p,1);h.assertTrue(ItemStack.matches(p.getItem(6),r.result()),"Exact historical paid result: "+holder.id());total++;
|
||||
}h.assertTrue(total==24,"All cauldron recipes exercised");h.succeed();
|
||||
}
|
||||
@GameTest public void separateMilkBucketsProduceAPaidBatchWithoutIllegalStacks(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,3);for(int i=0;i<3;i++)h.assertTrue(p.getItem(i).getCount()==1,"Native milk size one");h.assertTrue(p.stir(),"Three identical buckets form one preparation");tick(h,p,180);
|
||||
h.assertTrue(p.getItem(6).is(Cuisine.item("cream"))&&p.getItem(6).getCount()==6&&count(h,p.getBlockPos(),Items.BUCKET)==3,"All three actual buckets are paid exactly once");for(int i=0;i<7;i++)h.assertTrue(p.getItem(i).getCount()<=p.getItem(i).getMaxStackSize(),"Legal saved stacks");h.succeed();
|
||||
}
|
||||
@GameTest public void aFinishedRecipeDoesNotAutomaticallyCookAnotherRecipeFromItsRemainingWater(GameTestHelper h){
|
||||
var p=pot(h);p.setWaterUnits(3);milk(h,p,1);h.assertTrue(p.stir(),"Milk can be prepared over existing water");tick(h,p,180);p.takeFinishedProduct();tick(h,p,1600);
|
||||
h.assertTrue(p.getItem(6).isEmpty()&&p.waterUnits()==3&&p.repeatsRecipe()&&p.repeatedRecipe().getPath().equals("physical_cauldron_cream"),"The remaining water does not turn into salt on its own");h.assertTrue(p.stir(),"Another recipe needs another deliberate gesture");tick(h,p,200);h.assertTrue(p.getItem(6).is(Cuisine.item("salt"))&&p.getItem(6).getCount()==6&&p.waterUnits()==0,"Explicit next preparation pays actual water only");h.succeed();
|
||||
}
|
||||
@GameTest public void changingAnAlmostFinishedPreparationCancelsPaymentAndRequiresAnotherGesture(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);p.stir();tick(h,p,179);p.insertIngredient(new ItemStack(Items.CARROT),1);tick(h,p,400);
|
||||
h.assertTrue(p.progress()==0&&!p.assemblyComplete()&&p.getItem(6).isEmpty()&&p.getItem(0).is(Items.MILK_BUCKET),"Adding an ingredient disarms and cancels unpaid work");p.removeItemNoUpdate(1);tick(h,p,400);
|
||||
h.assertTrue(p.getItem(6).isEmpty()&&p.getItem(0).is(Items.MILK_BUCKET),"Restoring the old ingredients does not arm a new recipe");p.stir();tick(h,p,180);h.assertTrue(p.getItem(6).getCount()==2&&count(h,p.getBlockPos(),Items.BUCKET)==1,"A new complete paid cycle is required");h.succeed();
|
||||
}
|
||||
@GameTest public void waterChangesCancelCookingAndNativeWaterCannotCreateBottles(GameTestHelper h){
|
||||
var p=pot(h);p.setWaterUnits(3);p.insertIngredient(new ItemStack(Cuisine.item("ground_coffee"),3),3);p.stir();tick(h,p,199);p.setWaterUnits(2);tick(h,p,300);
|
||||
h.assertTrue(p.progress()==0&&p.getItem(0).getCount()==3&&p.getItem(6).isEmpty(),"Changing liquid disarms the incomplete cycle");p.stir();tick(h,p,200);
|
||||
h.assertTrue(p.getItem(6).getCount()==2&&p.getItem(0).getCount()==1&&p.waterUnits()==0&&count(h,p.getBlockPos(),Items.GLASS_BOTTLE)==0,"Exactly two real water levels pay two coffees without minting bottles");h.succeed();
|
||||
}
|
||||
@GameTest public void coldCauldronsCannotStartAndAnExplicitHeatedCyclePausesWhenHeatIsRemoved(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);h.getLevel().setBlockAndUpdate(p.getBlockPos().below(),Blocks.STONE.defaultBlockState());h.assertTrue(!p.stir(),"A cold gesture cannot start cooking");tick(h,p,500);
|
||||
h.getLevel().setBlockAndUpdate(p.getBlockPos().below(),Blocks.CAMPFIRE.defaultBlockState());p.stir();tick(h,p,90);h.getLevel().setBlockAndUpdate(p.getBlockPos().below(),Blocks.STONE.defaultBlockState());tick(h,p,500);
|
||||
h.assertTrue(p.progress()==90&&p.getItem(0).is(Items.MILK_BUCKET),"No cold progress or payment");h.getLevel().setBlockAndUpdate(p.getBlockPos().below(),Blocks.CAMPFIRE.defaultBlockState());tick(h,p,90);h.assertTrue(p.getItem(6).getCount()==2,"The armed preparation resumes its remaining heated ticks");h.succeed();
|
||||
}
|
||||
@GameTest public void manualCookingSurvivesNativeStorageWithoutOfflinePayment(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);p.stir();tick(h,p,179);var copy=reload(h,p,p.saveWithFullMetadata(h.getLevel().registryAccess()));
|
||||
h.assertTrue(copy.assemblyComplete()&©.progress()==179&©.getItem(0).is(Items.MILK_BUCKET)&©.getItem(6).isEmpty(),"Storage preserves the manual authorization and unpaid work");tick(h,copy,1);h.assertTrue(copy.getItem(6).getCount()==2&&count(h,p.getBlockPos(),Items.BUCKET)==1,"The remaining tick pays exactly once");h.succeed();
|
||||
}
|
||||
@GameTest public void oldAutomaticProgressIsDisarmedWhileItsSevenSlotsAndWaterStayIntact(GameTestHelper h){
|
||||
var p=pot(h);p.setWaterUnits(2);milk(h,p,1);p.stir();tick(h,p,179);var old=p.saveWithFullMetadata(h.getLevel().registryAccess());old.remove("CauldronPreparationVersion");var copy=reload(h,p,old);tick(h,copy,300);
|
||||
h.assertTrue(!copy.assemblyComplete()&©.progress()==0&©.waterUnits()==2&©.getItem(0).is(Items.MILK_BUCKET)&©.getItem(6).isEmpty(),"Old automatic timers cannot authorize the new preparation");copy.stir();tick(h,copy,180);h.assertTrue(copy.getItem(6).getCount()==2,"Old ingredients remain usable through the new gesture");h.succeed();
|
||||
}
|
||||
@GameTest public void nearbyDroppedIngredientsDoNotActivateOrEnterTheBowl(GameTestHelper h){
|
||||
var pos=h.absolutePos(new BlockPos(2,6,2));h.getLevel().setBlockAndUpdate(pos,Blocks.CAULDRON.defaultBlockState());var outside=new ItemEntity(h.getLevel(),pos.getX()+.05,pos.getY()+.5,pos.getZ()+.05,new ItemStack(Items.MILK_BUCKET));h.getLevel().addFreshEntity(outside);CookingCauldronBlock.tryActivateFromDroppedItem(outside);
|
||||
h.assertTrue(h.getLevel().getBlockState(pos).is(Blocks.CAULDRON)&&outside.getItem().getCount()==1,"Items outside the opening are left alone");outside.discard();var stone=drop(h,pos,new ItemStack(Items.STONE));CookingCauldronBlock.tryActivateFromDroppedItem(stone);h.assertTrue(h.getLevel().getBlockState(pos).is(Blocks.CAULDRON)&&stone.getItem().getCount()==1,"An unrelated item is not a preparation");h.succeed();
|
||||
}
|
||||
@GameTest public void ejectedBucketsAreNeverAbsorbedAgainAndBreakingReturnsOnlyUnpaidItems(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);p.stir();tick(h,p,180);var bucket=h.getLevel().getEntitiesOfClass(ItemEntity.class,new AABB(p.getBlockPos()).inflate(2),e->e.getItem().is(Items.BUCKET)).getFirst();bucket.setPos(Vec3.atCenterOf(p.getBlockPos()));p.takeFinishedProduct();tick(h,p,500);
|
||||
h.assertTrue(bucket.isAlive()&&bucket.getItem().getCount()==1&&p.isEmpty(),"The thrown empty bucket cannot become an ingredient");bucket.discard();
|
||||
p=pot(h);milk(h,p,1);p.insertIngredient(new ItemStack(Items.CARROT),1);var pos=p.getBlockPos();h.getLevel().destroyBlock(pos,true);h.getLevel().destroyBlock(pos,true);
|
||||
h.assertTrue(count(h,pos,Items.MILK_BUCKET)==1&&count(h,pos,Items.CARROT)==1&&count(h,pos,Items.BUCKET)==0&&count(h,pos,KitchenAppliances.FAILED_DISH)==0,"Breaking returns only unconsumed actual ingredients, once");h.succeed();
|
||||
}
|
||||
@GameTest public void nativeHopperInsertionStillWaitsForStirring(GameTestHelper h){
|
||||
var p=pot(h);var pos=p.getBlockPos().above();var state=Blocks.HOPPER.defaultBlockState().setValue(HopperBlock.FACING,Direction.DOWN);h.getLevel().setBlockAndUpdate(pos,state);var hopper=(HopperBlockEntity)h.getLevel().getBlockEntity(pos);hopper.setItem(0,new ItemStack(Items.MILK_BUCKET));hopper.setItem(1,new ItemStack(Items.MILK_BUCKET));
|
||||
for(int t=0;t<40;t++){HopperBlockEntity.pushItemsTick(h.getLevel(),pos,state,hopper);tick(h,p,1);}h.assertTrue(hopper.isEmpty()&&p.getItem(0).is(Items.MILK_BUCKET)&&p.getItem(1).is(Items.MILK_BUCKET)&&p.getItem(6).isEmpty()&&!p.assemblyComplete(),"Native hopper fills physical slots without authorizing cooking");p.stir();tick(h,p,180);h.assertTrue(p.getItem(6).getCount()==4,"The explicitly started hopper-fed batch pays both buckets");h.succeed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.CookingCauldronBlockEntity;
|
||||
import net.fabricmc.fabric.api.gametest.v1.GameTest;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.*;
|
||||
import net.minecraft.world.phys.*;
|
||||
|
||||
/** Continuous paid batches, native replenishment/serving, exact recipe lock and storage. */
|
||||
public final class Cauldron312GameTests {
|
||||
private static CookingCauldronBlockEntity pot(GameTestHelper h){var pos=h.absolutePos(new BlockPos(2,8,2));if(h.getLevel().getBlockEntity(pos) instanceof CookingCauldronBlockEntity old)old.clearContent();h.getLevel().removeBlock(pos,false);h.getLevel().setBlockAndUpdate(pos.below(),Blocks.CAMPFIRE.defaultBlockState());h.getLevel().setBlockAndUpdate(pos,KitchenAppliances.COOKING_CAULDRON.defaultBlockState());return (CookingCauldronBlockEntity)h.getLevel().getBlockEntity(pos);}
|
||||
private static void tick(GameTestHelper h,CookingCauldronBlockEntity p,int n){for(int t=0;t<n;t++)CookingCauldronBlockEntity.serverTick(h.getLevel(),p.getBlockPos(),p.getBlockState(),p);}
|
||||
private static void milk(GameTestHelper h,CookingCauldronBlockEntity p,int n){for(int i=0;i<n;i++)h.assertTrue(p.insertIngredient(new ItemStack(Items.MILK_BUCKET),1)==1,"A real milk bucket enters the series");}
|
||||
private static int buckets(GameTestHelper h,CookingCauldronBlockEntity p){return h.getLevel().getEntitiesOfClass(ItemEntity.class,new AABB(p.getBlockPos()).inflate(2),e->e.isAlive()&&e.getItem().is(Items.BUCKET)).stream().mapToInt(e->e.getItem().getCount()).sum();}
|
||||
private static CookingCauldronBlockEntity reload(GameTestHelper h,CookingCauldronBlockEntity p,net.minecraft.nbt.CompoundTag tag){var c=(CookingCauldronBlockEntity)BlockEntity.loadStatic(p.getBlockPos(),p.getBlockState(),tag,h.getLevel().registryAccess());c.setLevel(h.getLevel());h.getLevel().setBlockEntity(c);return c;}
|
||||
@GameTest public void oneStirRepeatsPaidBatchesWhileProductsAccumulate(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,2);h.assertTrue(p.stir(),"One initial gesture");tick(h,p,180);h.assertTrue(p.getItem(6).getCount()==4&&buckets(h,p)==2,"First real batch");tick(h,p,20);milk(h,p,2);tick(h,p,179);
|
||||
h.assertTrue(p.getItem(6).getCount()==4&&buckets(h,p)==2&&p.getItem(0).is(Items.MILK_BUCKET),"Second batch remains unpaid until its full deadline");tick(h,p,1);
|
||||
h.assertTrue(p.getItem(6).is(Cuisine.item("cream"))&&p.getItem(6).getCount()==8&&buckets(h,p)==4&&p.repeatsRecipe(),"Same output accumulates; each batch pays its own milk and containers");h.succeed();
|
||||
}
|
||||
@GameTest public void servingDoesNotDestroyRecipeMemoryOrAnAlmostFinishedCycle(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);p.stir();tick(h,p,200);milk(h,p,1);tick(h,p,179);var player=Progression003GameTests.player(h);player.setItemInHand(InteractionHand.MAIN_HAND,ItemStack.EMPTY);
|
||||
h.getLevel().getBlockState(p.getBlockPos()).useWithoutItem(h.getLevel(),player,new BlockHitResult(Vec3.atCenterOf(p.getBlockPos()),Direction.UP,p.getBlockPos(),false));
|
||||
h.assertTrue(h.getLevel().getBlockEntity(p.getBlockPos())==p&&p.repeatsRecipe()&&p.progress()==179&&player.getInventory().countItem(Cuisine.item("cream"))==2,"Native serving preserves the cauldron, its recipe and pending work");tick(h,p,1);h.assertTrue(p.getItem(6).getCount()==2&&buckets(h,p)==2,"Next dish completes without restarting");h.succeed();
|
||||
}
|
||||
@GameTest public void compatibleTopUpsDoNotResetTheCycleButDifferentIngredientsDo(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);p.stir();tick(h,p,179);milk(h,p,1);h.assertTrue(p.progress()==179&&p.repeatsRecipe(),"Same recipe replenishment preserves unpaid work");tick(h,p,1);h.assertTrue(p.getItem(6).getCount()==2&&buckets(h,p)==1,"New milk waits for its own complete cycle");tick(h,p,179);h.assertTrue(p.getItem(6).getCount()==2,"Queued milk cannot finish early");tick(h,p,1);h.assertTrue(p.getItem(6).getCount()==4&&buckets(h,p)==2,"The queued portion pays only after a second complete cycle");tick(h,p,20);milk(h,p,1);tick(h,p,179);p.insertIngredient(new ItemStack(Items.CARROT),1);
|
||||
h.assertTrue(!p.repeatsRecipe()&&p.progress()==0&&p.getItem(0).is(Items.MILK_BUCKET),"A different preparation cancels unpaid work");h.succeed();
|
||||
}
|
||||
@GameTest public void fullOutputWaitsWithoutPaymentThenResumesAfterCollection(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,6);p.stir();p.setItem(6,new ItemStack(Cuisine.item("cream"),60));tick(h,p,180);
|
||||
h.assertTrue(p.getItem(6).getCount()==64&&buckets(h,p)==2&&p.getItem(2).is(Items.MILK_BUCKET),"Only room for two paid recipes");tick(h,p,500);h.assertTrue(p.repeatState()==CookingCauldronBlockEntity.OUTPUT_FULL&&buckets(h,p)==2,"Full output cannot consume the four remaining buckets");
|
||||
p.takeFinishedProduct();tick(h,p,180);h.assertTrue(p.getItem(6).getCount()==8&&buckets(h,p)==6,"Collection resumes the retained recipe without another gesture");h.succeed();
|
||||
}
|
||||
@GameTest public void missingMilkNeverReplacesTheRememberedRecipeWithSalt(GameTestHelper h){
|
||||
var p=pot(h);p.setWaterUnits(3);milk(h,p,1);p.stir();tick(h,p,200);p.takeFinishedProduct();tick(h,p,1000);
|
||||
h.assertTrue(p.repeatState()==CookingCauldronBlockEntity.NEEDS_INPUT&&p.getItem(6).isEmpty()&&p.waterUnits()==3&&p.repeatedRecipe().getPath().equals("physical_cauldron_cream"),"Water cannot replace the exact remembered milk recipe");milk(h,p,1);tick(h,p,180);h.assertTrue(p.getItem(6).is(Cuisine.item("cream"))&&p.getItem(6).getCount()==2&&p.waterUnits()==3,"Same recipe resumes when milk arrives");h.succeed();
|
||||
}
|
||||
@GameTest public void refillingRecipeWaterPreservesWorkAndColdOnlyPauses(GameTestHelper h){
|
||||
var p=pot(h);p.setWaterUnits(1);p.insertIngredient(new ItemStack(Cuisine.item("ground_coffee"),4),4);p.stir();tick(h,p,199);p.setWaterUnits(3);h.assertTrue(p.progress()==199&&p.repeatsRecipe(),"Refilling the same recipe's water does not restart");tick(h,p,1);h.assertTrue(p.getItem(6).getCount()==1&&p.getItem(0).getCount()==3&&p.waterUnits()==2,"Refilled water waits for the next full batch");tick(h,p,200);h.assertTrue(p.getItem(6).getCount()==3&&p.getItem(0).getCount()==1&&p.waterUnits()==0,"The next full cycle pays the two queued waters");p.setWaterUnits(1);tick(h,p,100);h.getLevel().setBlockAndUpdate(p.getBlockPos().below(),Blocks.STONE.defaultBlockState());tick(h,p,300);h.assertTrue(p.progress()==100&&p.repeatState()==CookingCauldronBlockEntity.NEEDS_HEAT,"Cold pause preserves the series");h.getLevel().setBlockAndUpdate(p.getBlockPos().below(),Blocks.CAMPFIRE.defaultBlockState());tick(h,p,100);h.assertTrue(p.getItem(6).getCount()==4,"Heat resumes the remaining full cycle");h.succeed();
|
||||
}
|
||||
@GameTest public void nativeHopperCanReplenishAfterTheFirstBatchWithoutAnotherStir(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);p.stir();tick(h,p,200);var pos=p.getBlockPos().above();var state=Blocks.HOPPER.defaultBlockState().setValue(HopperBlock.FACING,Direction.DOWN);h.getLevel().setBlockAndUpdate(pos,state);var hopper=(HopperBlockEntity)h.getLevel().getBlockEntity(pos);hopper.setItem(0,new ItemStack(Items.MILK_BUCKET));hopper.setItem(1,new ItemStack(Items.MILK_BUCKET));
|
||||
for(int t=0;t<400;t++){HopperBlockEntity.pushItemsTick(h.getLevel(),pos,state,hopper);tick(h,p,1);}h.assertTrue(hopper.isEmpty()&&p.getItem(6).getCount()==6&&buckets(h,p)==3,"Native hopper input feeds the remembered recipe while old output remains");h.succeed();
|
||||
}
|
||||
@GameTest public void repetitionAndPendingWorkSurviveNativeStorage(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);p.stir();tick(h,p,200);milk(h,p,1);tick(h,p,179);milk(h,p,1);var c=reload(h,p,p.saveWithFullMetadata(h.getLevel().registryAccess()));h.assertTrue(c.repeatsRecipe()&&c.repeatedRecipe().equals(p.repeatedRecipe())&&c.progress()==179&&c.getItem(6).getCount()==2,"Native storage preserves one output and unpaid work for the next cycle");tick(h,c,1);h.assertTrue(c.getItem(6).getCount()==4&&buckets(h,c)==2,"Reload completes exactly the remaining paid tick, preserving its original quantity");tick(h,c,179);h.assertTrue(c.getItem(6).getCount()==4,"Queued milk still needs its full cycle after reload");tick(h,c,1);h.assertTrue(c.getItem(6).getCount()==6&&buckets(h,c)==3,"Queued native milk completes the following paid cycle");h.succeed();
|
||||
}
|
||||
@GameTest public void earlierManualSavesResumeOnlyTheirAuthorizedRecipe(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);p.stir();tick(h,p,179);var tag=p.saveWithFullMetadata(h.getLevel().registryAccess());tag.remove("CauldronRepeatRecipe");tag.remove("CauldronCycleBatches");var c=reload(h,p,tag);tick(h,c,1);h.assertTrue(c.getItem(6).getCount()==2&&c.repeatedRecipe().getPath().equals("physical_cauldron_cream"),"A real beta.311 manual authorization acquires its exact recipe");
|
||||
c=pot(h);milk(h,c,1);tag=c.saveWithFullMetadata(h.getLevel().registryAccess());tag.remove("CauldronRepeatRecipe");tag.remove("CauldronCycleBatches");c=reload(h,c,tag);tick(h,c,500);h.assertTrue(!c.repeatsRecipe()&&c.getItem(6).isEmpty(),"Unstirred earlier saves cannot authorize themselves");h.succeed();
|
||||
}
|
||||
@GameTest public void missingSavedRecipeStopsWithoutConsumingAnything(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);p.stir();var tag=p.saveWithFullMetadata(h.getLevel().registryAccess());tag.putString("CauldronRepeatRecipe","sanctuary:removed_recipe_312");var c=reload(h,p,tag);tick(h,c,300);h.assertTrue(!c.repeatsRecipe()&&c.getItem(0).is(Items.MILK_BUCKET)&&c.getItem(6).isEmpty(),"Missing exact IDs disarm instead of silently selecting another recipe");h.succeed();
|
||||
}
|
||||
@GameTest public void emptyHandShiftStopsTheSeriesAndRestoresAnEmptyVanillaCauldron(GameTestHelper h){
|
||||
var p=pot(h);milk(h,p,1);p.stir();tick(h,p,200);p.takeFinishedProduct();var player=Progression003GameTests.player(h);player.setItemInHand(InteractionHand.MAIN_HAND,ItemStack.EMPTY);player.setShiftKeyDown(true);h.getLevel().getBlockState(p.getBlockPos()).useWithoutItem(h.getLevel(),player,new BlockHitResult(Vec3.atCenterOf(p.getBlockPos()),Direction.UP,p.getBlockPos(),false));player.setShiftKeyDown(false);h.assertTrue(h.getLevel().getBlockState(p.getBlockPos()).is(Blocks.CAULDRON),"An explicit native stop returns the empty vanilla block");h.succeed();
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.CulinaryAppliance;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.FermentationBarrelBlockEntity;
|
||||
import fr.koka.sanctuary.cuisine.culinary.recipe.*;
|
||||
import fr.koka.sanctuary.cuisine.recipe.WineMaturationRecipe;
|
||||
import net.fabricmc.fabric.api.gametest.v1.GameTest;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.world.*;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.*;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import net.minecraft.world.phys.*;
|
||||
import java.util.*;
|
||||
|
||||
/** Real native cake interactions and deterministic wall-clock maturation boundaries. */
|
||||
public final class CheeseWine314GameTests {
|
||||
private static final long DAY=86_400_000L;
|
||||
private static BlockPos pos(GameTestHelper h){return h.absolutePos(new BlockPos(2,3,2));}
|
||||
private static FermentationBarrelBlockEntity barrel(GameTestHelper h){var p=pos(h);if(h.getLevel().getBlockEntity(p) instanceof FermentationBarrelBlockEntity b)b.clearContent();h.getLevel().removeBlock(p,false);h.getLevel().setBlockAndUpdate(p,KitchenAppliances.FERMENTATION_BARREL.defaultBlockState());return (FermentationBarrelBlockEntity)h.getLevel().getBlockEntity(p);}
|
||||
private static BlockHitResult hit(BlockPos p){return new BlockHitResult(Vec3.atCenterOf(p),Direction.UP,p,false);}
|
||||
private static FermentationBarrelBlockEntity reload(GameTestHelper h,FermentationBarrelBlockEntity b){var copy=(FermentationBarrelBlockEntity)BlockEntity.loadStatic(b.getBlockPos(),b.getBlockState(),b.saveWithFullMetadata(h.getLevel().registryAccess()),h.getLevel().registryAccess());copy.setLevel(h.getLevel());return copy;}
|
||||
private static int drops(GameTestHelper h,Item item){return h.getLevel().getEntitiesOfClass(ItemEntity.class,new AABB(pos(h)).inflate(2),e->e.isAlive()&&e.getItem().is(item)).stream().mapToInt(e->e.getItem().getCount()).sum();}
|
||||
|
||||
@GameTest public void existingCheeseItemPlacesAndPaysOneWholeWheel(GameTestHelper h){
|
||||
var p=Progression003GameTests.player(h);p.getInventory().clearContent();p.setGameMode(net.minecraft.world.level.GameType.SURVIVAL);var pos=pos(h);h.getLevel().setBlockAndUpdate(pos.below(),Blocks.STONE.defaultBlockState());h.getLevel().removeBlock(pos,false);p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Cuisine.item("cheese"),2));
|
||||
var result=p.getMainHandItem().useOn(new UseOnContext(p,InteractionHand.MAIN_HAND,hit(pos.below())));
|
||||
h.assertTrue(result.consumesAction()&&h.getLevel().getBlockState(pos).getBlock() instanceof fr.koka.sanctuary.cuisine.culinary.block.CheeseBlock&&p.getMainHandItem().getCount()==1,"The historic cheese ID places one real paid wheel");
|
||||
h.assertTrue(h.getLevel().getBlockState(pos).getValue(CakeBlock.BITES)==0&&new ItemStack(Cuisine.item("cheese")).get(DataComponents.FOOD)==null,"A whole wheel starts full and is shared after placing, like cake");h.succeed();
|
||||
}
|
||||
@GameTest public void sevenRealSlicesShrinkAndFeedExactlyOnceThenRemoveTheWheel(GameTestHelper h){
|
||||
var p=Progression003GameTests.player(h);var pos=pos(h);var block=((BlockItem)Cuisine.item("cheese")).getBlock();h.getLevel().setBlockAndUpdate(pos.below(),Blocks.STONE.defaultBlockState());h.getLevel().setBlockAndUpdate(pos,block.defaultBlockState());p.setItemInHand(InteractionHand.MAIN_HAND,ItemStack.EMPTY);
|
||||
for(int i=0;i<7;i++){p.getFoodData().setFoodLevel(0);var state=h.getLevel().getBlockState(pos);h.assertTrue(state.getValue(CakeBlock.BITES)==i&&Math.abs(state.getShape(h.getLevel(),pos).bounds().getXsize()-(14-i*2)/16D)<0.0001,"Visible/collision width follows the remaining native slices");h.assertTrue(state.useWithoutItem(h.getLevel(),p,hit(pos)).consumesAction()&&p.getFoodData().getFoodLevel()==2,"One real cake action restores two food points");}
|
||||
h.assertTrue(h.getLevel().getBlockState(pos).isAir(),"Only the seventh bite removes the wheel");h.succeed();
|
||||
}
|
||||
@GameTest public void fullHungerNeverConsumesACheeseSliceAndCandlesNeverCreateCake(GameTestHelper h){
|
||||
var p=Progression003GameTests.player(h);var pos=pos(h);var block=((BlockItem)Cuisine.item("cheese")).getBlock();h.getLevel().setBlockAndUpdate(pos.below(),Blocks.STONE.defaultBlockState());h.getLevel().setBlockAndUpdate(pos,block.defaultBlockState());p.getFoodData().setFoodLevel(20);
|
||||
var before=h.getLevel().getBlockState(pos);before.useWithoutItem(h.getLevel(),p,hit(pos));h.assertTrue(h.getLevel().getBlockState(pos).getValue(CakeBlock.BITES)==0,"A full player does not eat a slice");var candle=new ItemStack(Items.CANDLE);before.useItemOn(candle,h.getLevel(),p,InteractionHand.MAIN_HAND,hit(pos));h.assertTrue(h.getLevel().getBlockState(pos).is(block)&&candle.getCount()==1,"Native candle cake shortcut cannot transform cheese into free cake");h.succeed();
|
||||
}
|
||||
@GameTest public void cheeseBreakageOrLostSupportCannotRegenerateEatenParts(GameTestHelper h){
|
||||
var pos=pos(h);var block=((BlockItem)Cuisine.item("cheese")).getBlock();h.getLevel().setBlockAndUpdate(pos.below(),Blocks.STONE.defaultBlockState());h.getLevel().setBlockAndUpdate(pos,block.defaultBlockState().setValue(CakeBlock.BITES,3));h.getLevel().destroyBlock(pos,true);h.assertTrue(drops(h,Cuisine.item("cheese"))==0,"Breaking a half-eaten wheel cannot refund a whole one");h.getLevel().setBlockAndUpdate(pos,block.defaultBlockState());h.getLevel().destroyBlock(pos.below(),true);h.assertTrue(h.getLevel().getBlockState(pos).isAir()&&drops(h,Cuisine.item("cheese"))==0,"Like cake, lost support removes cheese without a whole-wheel refund");h.succeed();
|
||||
}
|
||||
@GameTest public void sameMustStillFermentsIntoYoungWineAfterExactlyThreeGameMinutes(GameTestHelper h){
|
||||
var b=barrel(h);b.insertIngredient(new ItemStack(Cuisine.item("grape_must")),1);FermentationBarrelBlockEntity.serverTick(h.getLevel(),pos(h),b.getBlockState(),b);var d=(ServerLevelData)h.getLevel().getLevelData();long now=d.getGameTime();try{d.setGameTime(b.startedAtGameTime()+3599);FermentationBarrelBlockEntity.serverTick(h.getLevel(),pos(h),b.getBlockState(),b);h.assertTrue(!b.hasFinishedProduct(),"No premature young wine");d.setGameTime(b.startedAtGameTime()+3600);FermentationBarrelBlockEntity.serverTick(h.getLevel(),pos(h),b.getBlockState(),b);h.assertTrue(b.getItem(4).is(Cuisine.item("wine"))&&b.getItem(0).isEmpty()&&b.wineStartedAtEpochMillis()>0,"One paid young wine starts a real UTC maturation clock");}finally{d.setGameTime(now);}h.succeed();
|
||||
}
|
||||
@GameTest public void exactOneThreeAndSevenRealDayBoundariesUpgradeOneBottleWithoutDuplication(GameTestHelper h){
|
||||
var b=barrel(h);b.setItem(4,new ItemStack(Cuisine.item("wine")));long t=System.currentTimeMillis();b.refreshWineAge(t);Item[] items={Cuisine.item("wine"),KitchenAppliances.TABLE_WINE,KitchenAppliances.RESERVE_WINE,KitchenAppliances.GRAND_CRU_WINE};long[] deadlines={DAY,3*DAY,7*DAY};for(int i=0;i<3;i++){b.refreshWineAge(t+deadlines[i]-1);h.assertTrue(b.getItem(4).is(items[i])&&b.remainingWineAgeMillis()==1,"One millisecond before real deadline retains the previous quality");b.refreshWineAge(t+deadlines[i]);h.assertTrue(b.getItem(4).is(items[i+1])&&b.getItem(4).getCount()==1&&b.wineStartedAtEpochMillis()==t,"Exact absolute day threshold changes only the one paid bottle");}b.refreshWineAge(t+100*DAY);h.assertTrue(b.getItem(4).is(KitchenAppliances.GRAND_CRU_WINE)&&b.getItem(4).getCount()==1&&!b.hasMaturingWine(),"Grand cru is stable and never duplicates");h.succeed();
|
||||
}
|
||||
@GameTest public void nativeDiskFieldsCatchUpSevenRealDaysAndClockRollbackNeverDegradesWine(GameTestHelper h){
|
||||
var b=barrel(h);b.setItem(4,new ItemStack(Cuisine.item("wine")));long t=System.currentTimeMillis();b.refreshWineAge(t);var c=reload(h,b);h.assertTrue(c.wineStartedAtEpochMillis()==t&&c.getItem(4).is(Cuisine.item("wine")),"Native item and UTC origin reload exactly");c.refreshWineAge(t+7*DAY);h.assertTrue(c.getItem(4).is(KitchenAppliances.GRAND_CRU_WINE)&&c.getItem(4).getCount()==1,"One tick catches up a closed server without ticking seven days");c.refreshWineAge(t-1000);h.assertTrue(c.getItem(4).is(KitchenAppliances.GRAND_CRU_WINE),"A backwards host clock never downgrades a paid quality");h.succeed();
|
||||
}
|
||||
@GameTest public void historicalWineStartsFreshAndMinecraftTimeCannotAgeIt(GameTestHelper h){
|
||||
var b=barrel(h);b.setItem(4,new ItemStack(Cuisine.item("wine")));var c=reload(h,b);h.assertTrue(c.wineStartedAtEpochMillis()<0,"Historical output has no invented date");long t=System.currentTimeMillis();c.refreshWineAge(t);var d=(ServerLevelData)h.getLevel().getLevelData();long now=d.getGameTime();try{d.setGameTime(now+100000000);FermentationBarrelBlockEntity.serverTick(h.getLevel(),pos(h),c.getBlockState(),c);h.assertTrue(c.getItem(4).is(Cuisine.item("wine"))&&c.wineStartedAtEpochMillis()==t,"Sleeping and Minecraft time commands cannot advance real maturation");}finally{d.setGameTime(now);}h.succeed();
|
||||
}
|
||||
@GameTest public void extractionClearsAgeAndANewBottleMustWaitItsOwnDays(GameTestHelper h){
|
||||
var b=barrel(h);long t=System.currentTimeMillis();b.setItem(4,new ItemStack(Cuisine.item("wine")));b.refreshWineAge(t);b.refreshWineAge(t+3*DAY);var taken=b.takeOutputOrLastIngredient();h.assertTrue(taken.is(KitchenAppliances.RESERVE_WINE)&&b.wineStartedAtEpochMillis()<0&&b.isEmpty(),"Taking reserve wine gives exactly the current quality and releases the date");b.setItem(4,new ItemStack(Cuisine.item("wine")));b.refreshWineAge(t+3*DAY);h.assertTrue(b.getItem(4).is(Cuisine.item("wine"))&&b.remainingWineAgeMillis()==DAY,"The next bottle inherits no free age");h.succeed();
|
||||
}
|
||||
@GameTest public void breakageDropsExactlyTheAgedBottleAndOneBarrel(GameTestHelper h){
|
||||
var b=barrel(h);long t=System.currentTimeMillis();b.setItem(4,new ItemStack(Cuisine.item("wine")));b.refreshWineAge(t);b.refreshWineAge(t+7*DAY);h.getLevel().destroyBlock(pos(h),true);h.assertTrue(drops(h,KitchenAppliances.GRAND_CRU_WINE)==1&&drops(h,Cuisine.item("wine"))==0&&drops(h,KitchenAppliances.FERMENTATION_BARREL.asItem())==1,"Breakage returns one actual grand cru and one barrel, without a second young bottle");h.succeed();
|
||||
}
|
||||
@GameTest public void allFourQualitiesWorkForVinegarAndImprovedWineKeepsNativeDrinking(GameTestHelper h){
|
||||
var recipe=(CulinaryProcessRecipe)Cuisine303GameTests.recipe(h,"sanctuary:ferment_vinegar");float saturation=0;for(var item:List.of(Cuisine.item("wine"),KitchenAppliances.TABLE_WINE,KitchenAppliances.RESERVE_WINE,KitchenAppliances.GRAND_CRU_WINE)){var stack=new ItemStack(item);h.assertTrue(recipe.matches(new CulinaryRecipeInput(CulinaryAppliance.FERMENTATION_BARREL,List.of(stack)),h.getLevel()),"No quality is locked out of the historical vinegar recipe");var food=stack.get(DataComponents.FOOD);h.assertTrue(food.nutrition()==6&&food.saturation()>saturation&&stack.get(DataComponents.CONSUMABLE).animation()==net.minecraft.world.item.ItemUseAnimation.DRINK,"All four are native drinks, with increasing saturation and stable nutrition");saturation=food.saturation();}h.succeed();
|
||||
}
|
||||
@GameTest public void threeSynchronizedStagesUseRealSecondsAndNeverCompeteWithCrafting(GameTestHelper h){
|
||||
for(var entry:Map.of("table_wine",1,"reserve_wine",3,"grand_cru_wine",7).entrySet()){var r=(WineMaturationRecipe)Cuisine303GameTests.recipe(h,"sanctuary:age_"+entry.getKey());h.assertTrue(r.requiredAgeSeconds()==entry.getValue()*86400&&r.getType()==WineMaturationRecipe.TYPE&&r.isSpecial(),"Each recipe describes total real seconds since young wine, never a manual crafting shortcut");}h.succeed();
|
||||
}
|
||||
@GameTest public void nativeHopperTakesYoungWineAndClearsOnlyItsOwnAge(GameTestHelper h){
|
||||
var b=barrel(h);h.getLevel().setBlockAndUpdate(pos(h).below(),Blocks.HOPPER.defaultBlockState());b.setItem(4,new ItemStack(Cuisine.item("wine")));b.refreshWineAge(System.currentTimeMillis());
|
||||
h.runAfterDelay(20,()->{var hopper=(HopperBlockEntity)h.getLevel().getBlockEntity(pos(h).below());h.assertTrue(b.isEmpty()&&b.wineStartedAtEpochMillis()<0&&hopper.countItem(Cuisine.item("wine"))==1,"A real lower hopper collects exactly the young bottle and clears its origin");long now=System.currentTimeMillis();b.setItem(4,new ItemStack(Cuisine.item("wine")));b.refreshWineAge(now);h.assertTrue(b.wineStartedAtEpochMillis()==now&&b.remainingWineAgeMillis()==DAY,"A new bottle gets its own full real day after automated extraction");h.succeed();});
|
||||
}
|
||||
@GameTest public void discoveringMustTeachesAllThreeMaturationStages(GameTestHelper h){
|
||||
var p=Progression003GameTests.player(h);p.setUUID(UUID.randomUUID());p.setAttached(fr.koka.sanctuary.progression.ProgressionService.RECORD,fr.koka.sanctuary.progression.ProgressRecord.INITIAL.json());fr.koka.sanctuary.progression.ProgressEffects.apply(p,fr.koka.sanctuary.progression.ProgressRecord.INITIAL);
|
||||
fr.koka.sanctuary.recipes.RecipeService.discover(p,Cuisine.item("grape_must"));fr.koka.sanctuary.recipes.RecipeService.refresh(p);for(String stage:List.of("table_wine","reserve_wine","grand_cru_wine"))h.assertTrue(fr.koka.sanctuary.recipes.RecipeService.knowledge(p).recipes.contains("sanctuary:age_"+stage),"The real ageing stages are discoverable from the actual must");h.succeed();
|
||||
}
|
||||
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.machine.*;
|
||||
import fr.koka.sanctuary.multiblock.*;
|
||||
import java.util.*;
|
||||
import net.fabricmc.fabric.api.gametest.v1.GameTest;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.registries.*;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.*;
|
||||
import net.minecraft.world.level.block.piston.PistonBaseBlock;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import static fr.koka.sanctuary.cuisine.machine.CrushableProduceBlock.*;
|
||||
|
||||
public final class Crushing305GameTests {
|
||||
private static List<Block> fruits(){return List.of(KitchenAppliances.TOMATO_BLOCK,KitchenAppliances.GRAPE_BLOCK,KitchenAppliances.OLIVE_BLOCK);}
|
||||
private static Item result(Block b){return b==KitchenAppliances.TOMATO_BLOCK?Cuisine.item("tomato_pulp"):b==KitchenAppliances.GRAPE_BLOCK?Cuisine.item("grape_must"):Cuisine.item("olive_oil");}
|
||||
private static Item raw(Block b){return Cuisine303GameTests.item(b==KitchenAppliances.TOMATO_BLOCK?"sanctuary:tomato":b==KitchenAppliances.GRAPE_BLOCK?"sanctuary:grapes":"sanctuary:olives");}
|
||||
private static BlockPos at(GameTestHelper h,int n){return h.absolutePos(new BlockPos(2+n*4,4,2));}
|
||||
private static int count(GameTestHelper h,BlockPos pos,Item item){return h.getLevel().getEntitiesOfClass(ItemEntity.class,new AABB(pos).inflate(2),e->e.isAlive()&&e.getItem().is(item)).stream().mapToInt(e->e.getItem().getCount()).sum();}
|
||||
|
||||
@GameTest public void eightRealLandingCallbacksReleaseExactlyNinePortionsForEveryFruit(GameTestHelper h){
|
||||
int n=0;var player=Progression003GameTests.player(h);
|
||||
for(Block block:fruits()){
|
||||
var pos=at(h,n++);var l=h.getLevel();l.setBlockAndUpdate(pos.below(),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(pos,block.defaultBlockState());
|
||||
for(int jump=1;jump<=8;jump++){
|
||||
var state=l.getBlockState(pos);block.fallOn(l,state,pos,player,1.1D);
|
||||
h.assertTrue(count(h,pos,result(block))==jump*9/8,"Incremental paid residue: "+block+" jump="+jump);
|
||||
if(jump<8){var next=l.getBlockState(pos);h.assertTrue(next.getValue(LAYERS)==8-jump,"One layer per landing");
|
||||
h.assertTrue(Math.abs(next.getCollisionShape(l,pos).max(Direction.Axis.Y)-(8-jump)/8D)<.00001,"Physical height matches visual layer");
|
||||
}else h.assertTrue(l.getBlockState(pos).isAir(),"Final layer disappears");
|
||||
}
|
||||
}h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void walkingItemsAndFlatteningDropDoNotCrushAdditionalLayers(GameTestHelper h){
|
||||
var l=h.getLevel();var pos=at(h,0);var block=KitchenAppliances.TOMATO_BLOCK;var state=block.defaultBlockState();l.setBlockAndUpdate(pos,state);
|
||||
var player=Progression003GameTests.player(h);block.stepOn(l,pos,state,player);block.fallOn(l,state,pos,player,.125D);
|
||||
block.fallOn(l,state,pos,new ItemEntity(l,0,0,0,new ItemStack(Items.STONE)),3D);
|
||||
h.assertTrue(l.getBlockState(pos).getValue(LAYERS)==8 && count(h,pos,result(block))==0,"No passive crushing, item entities or feedback from shrinking");h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void breakingAndDecompactingCannotRecoverAlreadyCrushedFruit(GameTestHelper h){
|
||||
var l=h.getLevel();int n=0;
|
||||
for(Block block:fruits())for(int layers=1;layers<=8;layers++){
|
||||
var pos=at(h,0);var state=block.defaultBlockState().setValue(LAYERS,layers);l.setBlockAndUpdate(pos.below(),Blocks.STONE.defaultBlockState());l.setBlockAndUpdate(pos,state);
|
||||
var loot=Block.getDrops(state,l,pos,null);int left=remaining(layers,9);
|
||||
h.assertTrue(loot.size()==1 && loot.getFirst().is(layers==8?block.asItem():raw(block)) && loot.getFirst().getCount()==(layers==8?1:left),"Breakage conserves remaining raw yield: "+state);
|
||||
h.assertTrue(layers==8 || loot.stream().noneMatch(s->s.is(block.asItem())),"No partial state drops a new full block");
|
||||
var round=NbtUtils.readBlockState(l.holderLookup(Registries.BLOCK),NbtUtils.writeBlockState(state));h.assertTrue(round.equals(state),"All eight states persist natively");
|
||||
}h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void oldFruitStateWithoutLayersLoadsAsAFullBlock(GameTestHelper h){
|
||||
for(Block block:fruits()){
|
||||
var old=NbtUtils.writeBlockState(block.defaultBlockState());old.remove("Properties");
|
||||
var loaded=NbtUtils.readBlockState(h.getLevel().holderLookup(Registries.BLOCK),old);
|
||||
h.assertTrue(loaded.is(block) && loaded.getValue(LAYERS)==8,"Existing IDs retain full yield without rewriting saves");
|
||||
}h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void ordinaryPistonCrushesRemainingYieldInAllSixDirections(GameTestHelper h){
|
||||
var l=h.getLevel();var fixtures=new ArrayList<BlockPos>();var outputs=new ArrayList<Item>();int n=0;
|
||||
for(Direction facing:Direction.values())for(Block block:fruits()){
|
||||
var base=h.absolutePos(new BlockPos(2+n%3*5,10+n/3*6,2));n++;var fruit=base.relative(facing);fixtures.add(fruit);outputs.add(result(block));
|
||||
l.setBlock(fruit.below(),Blocks.STONE.defaultBlockState(),2);
|
||||
l.setBlock(base,Blocks.PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING,facing),2);
|
||||
l.setBlock(fruit,block.defaultBlockState().setValue(LAYERS,4),2);
|
||||
l.setBlockAndUpdate(base.relative(facing.getOpposite()),Blocks.REDSTONE_BLOCK.defaultBlockState());
|
||||
}
|
||||
h.runAfterDelay(4,()->{for(int i=0;i<fixtures.size();i++){
|
||||
var pos=fixtures.get(i);h.assertTrue(count(h,pos,outputs.get(i))==remaining(4,9),"Real native piston pays remaining five portions, orientation fixture "+i);
|
||||
h.assertTrue(!(l.getBlockState(pos).getBlock() instanceof CrushableProduceBlock),"Native piston replaces crushed fruit");
|
||||
}h.succeed();});
|
||||
}
|
||||
|
||||
@GameTest(maxTicks=80) public void superPistonCrushesTwentySevenMixedBlocksPerFullActivation(GameTestHelper h){
|
||||
var l=h.getLevel();var center=h.absolutePos(new BlockPos(5,128,5));var f=Direction.UP;
|
||||
for(int x=-4;x<=4;x++)for(int z=-4;z<=4;z++)l.setBlockAndUpdate(center.offset(x,-2,z),Blocks.STONE.defaultBlockState());
|
||||
for(int i=0;i<9;i++)l.setBlockAndUpdate(SuperPistons.tile(center,f,i),Blocks.PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING,f));
|
||||
h.assertTrue(SuperPistons.assemble(l,center,f,false),"Assemble actual 3x3 super piston");
|
||||
for(int depth=1;depth<=3;depth++)for(int i=0;i<9;i++)l.setBlockAndUpdate(SuperPistons.tile(center,f,i).relative(f,depth),fruits().get(i%3).defaultBlockState());
|
||||
l.setBlockAndUpdate(SuperPistons.tile(center,f,0).below(),Blocks.REDSTONE_BLOCK.defaultBlockState());
|
||||
h.runAfterDelay(40,()->{
|
||||
var controller=(SuperPistonController)l.getBlockEntity(center);h.assertTrue(controller.stroke==3,"Full powered course reaches third depth");
|
||||
for(int depth=1;depth<=3;depth++)for(var pos:SuperPistons.tiles(center,f,depth))h.assertTrue(!(l.getBlockState(pos).getBlock() instanceof CrushableProduceBlock),"All 27 fruit blocks crushed");
|
||||
var box=new AABB(center).inflate(64,80,64);for(Block block:fruits()){
|
||||
int portions=l.getEntitiesOfClass(ItemEntity.class,box,e->e.isAlive()&&e.getItem().is(result(block))).stream().mapToInt(e->e.getItem().getCount()).sum();
|
||||
h.assertTrue(portions==81,"Nine blocks of each fruit pay exactly 81 portions: "+block+" = "+portions+" items="+l.getEntitiesOfClass(ItemEntity.class,box,e->e.isAlive()&&e.getItem().is(result(block))).stream().map(e->e.getItem()+"@"+e.position()).toList());
|
||||
}h.succeed();
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest public void blockedSuperPistonNeverCrushesOnlyPartOfItsFace(GameTestHelper h){
|
||||
var l=h.getLevel();var center=h.absolutePos(new BlockPos(5,8,5));var f=Direction.UP;
|
||||
for(int i=0;i<9;i++)l.setBlockAndUpdate(SuperPistons.tile(center,f,i),Blocks.PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING,f));
|
||||
h.assertTrue(SuperPistons.assemble(l,center,f,true)==false,"Mixed sticky assembly is refused");
|
||||
h.assertTrue(SuperPistons.assemble(l,center,f,false),"Normal assembly");
|
||||
for(int i=0;i<9;i++)l.setBlockAndUpdate(SuperPistons.tile(center,f,i).above(),(i==0?Blocks.OBSIDIAN:KitchenAppliances.TOMATO_BLOCK).defaultBlockState());
|
||||
var controller=(SuperPistonController)l.getBlockEntity(center);
|
||||
h.assertTrue(!SuperPistons.step(l,center,l.getBlockState(center),controller,true),"Obsidian blocks the entire atomic move");
|
||||
for(int i=1;i<9;i++)h.assertTrue(l.getBlockState(SuperPistons.tile(center,f,i).above()).is(KitchenAppliances.TOMATO_BLOCK),"No fruit changed on failed move");
|
||||
h.assertTrue(l.getEntitiesOfClass(ItemEntity.class,new AABB(center).inflate(7),e->e.getItem().is(Cuisine.item("tomato_pulp"))).isEmpty(),"No early residues");h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void removedPressHasNoRecipeCreativeEntryOrProcessingTicker(GameTestHelper h){
|
||||
h.assertTrue(h.getLevel().getServer().getRecipeManager().byKey(ResourceKey.create(Registries.RECIPE,fr.koka.sanctuary.SanctuaryMod.id("food_press"))).isEmpty(),"No press crafting");
|
||||
h.assertTrue(KitchenAppliances.items().stream().noneMatch(i->i==KitchenAppliances.FOOD_PRESS.asItem()),"No press creative entry");
|
||||
var pos=at(h,0);var state=KitchenAppliances.FOOD_PRESS.defaultBlockState();
|
||||
h.assertTrue(state.getTicker(h.getLevel(),KitchenAppliances.FOOD_PRESS_BLOCK_ENTITY)==null,"Legacy ID has no processor");
|
||||
h.getLevel().setBlockAndUpdate(pos,state);var legacy=(FoodPressBlockEntity)h.getLevel().getBlockEntity(pos);legacy.setItem(2,new ItemStack(Items.BOWL,7));
|
||||
var copy=BlockEntity.loadStatic(pos,state,legacy.saveWithFullMetadata(h.getLevel().registryAccess()),h.getLevel().registryAccess());
|
||||
h.assertTrue(((FoodPressBlockEntity)copy).getItem(2).getCount()==7,"Paid legacy inventory remains recoverable without world conversion");h.succeed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import com.google.gson.*;
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import fr.koka.sanctuary.agriculture.AgricultureItems;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.recipes.*;
|
||||
import java.util.*;
|
||||
import net.fabricmc.fabric.api.gametest.v1.GameTest;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.registries.*;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.resources.*;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.effect.*;
|
||||
import net.minecraft.world.inventory.*;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.alchemy.*;
|
||||
import net.minecraft.world.item.crafting.*;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.*;
|
||||
|
||||
public final class Cuisine303GameTests {
|
||||
@GameTest public void allHistoricalFoodRecipesLoadAndProduceTheirReviewedOutputs(GameTestHelper h) {
|
||||
int ports = 0;
|
||||
for (var element : Cuisine.CATALOG.getAsJsonArray("ports")) {
|
||||
var port = element.getAsJsonObject(); var ingredients = samples(port.getAsJsonArray("ingredients"));
|
||||
var input = grid(ingredients); var ids = port.getAsJsonArray("recipes");
|
||||
if (port.get("native_equivalent").getAsBoolean()) {
|
||||
var shadow = ResourceKey.create(Registries.RECIPE, SanctuaryMod.id(port.get("id").getAsString()));
|
||||
h.assertTrue(h.getLevel().getServer().getRecipeManager().byKey(shadow).isEmpty(), "Existing vanilla meat/fish/potato cooking has no competing port");
|
||||
}
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
var id = ids.get(i).getAsString(); var recipe = recipe(h, id);
|
||||
ItemStack output;
|
||||
if (recipe instanceof fr.koka.sanctuary.cuisine.culinary.recipe.CulinaryProcessRecipe physical) {
|
||||
var processInput = new fr.koka.sanctuary.cuisine.culinary.recipe.CulinaryRecipeInput(physical.appliance(), ingredients);
|
||||
h.assertTrue(physical.matches(processInput, h.getLevel()), "Physical fermentation matches " + id);
|
||||
output = physical.assemble(processInput);
|
||||
} else if (recipe instanceof CraftingRecipe crafting) {
|
||||
h.assertTrue(crafting.matches(input, h.getLevel()), "Actual native crafting matches " + id);
|
||||
output = crafting.assemble(input);
|
||||
if (port.get("hot").getAsBoolean()) {
|
||||
h.assertTrue(output.is(Cuisine.item("raw_" + port.get("id").getAsString())) && output.getCount() == 1, "One batch preparation: " + id);
|
||||
continue;
|
||||
}
|
||||
} else if (recipe instanceof fr.koka.sanctuary.cuisine.recipe.FoodPressingRecipe pressing) {
|
||||
output = pressing.assemble(new SingleRecipeInput(ingredients.getFirst()));
|
||||
} else {
|
||||
var cooking = (AbstractCookingRecipe) recipe;
|
||||
var previous = ids.size() == 3 ? ((CraftingRecipe) recipe(h, ids.get(0).getAsString())).assemble(input) : ingredients.getFirst();
|
||||
var single = new SingleRecipeInput(previous);
|
||||
h.assertTrue(cooking.matches(single, h.getLevel()), "Native cooking matches " + id);
|
||||
output = cooking.assemble(single);
|
||||
}
|
||||
h.assertTrue(output.is(item(port.get("result").getAsString())) && output.getCount() == port.get("count").getAsInt(), "Historical result/count retained: " + id);
|
||||
}
|
||||
ports++;
|
||||
}
|
||||
h.assertTrue(ports == 92, "Complete 92-recipe food catalogue"); h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void nativeCraftingHasNoConflictingFoodOutputs(GameTestHelper h) {
|
||||
for (var element : Cuisine.CATALOG.getAsJsonArray("ports")) {
|
||||
var port = element.getAsJsonObject(); var input = grid(samples(port.getAsJsonArray("ingredients")));
|
||||
var outputs = new HashSet<String>();
|
||||
for (var holder : h.getLevel().getServer().getRecipeManager().getRecipes()) {
|
||||
if (holder.value() instanceof CraftingRecipe crafting && crafting.matches(input, h.getLevel()))
|
||||
outputs.add(BuiltInRegistries.ITEM.getKey(crafting.assemble(input).getItem()).toString());
|
||||
}
|
||||
h.assertTrue(outputs.size() <= 1, "Unambiguous table output for " + port.get("id") + ": " + outputs);
|
||||
}
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void allCulinaryIngredientsHaveASurvivalProductionPath(GameTestHelper h) {
|
||||
var reachable = new HashSet<String>();
|
||||
for (var i : BuiltInRegistries.ITEM) if (BuiltInRegistries.ITEM.getKey(i).getNamespace().equals("minecraft")) reachable.add(BuiltInRegistries.ITEM.getKey(i).toString());
|
||||
for (var i : AgricultureItems.PRODUCE_ITEMS) reachable.add(BuiltInRegistries.ITEM.getKey(i).toString());
|
||||
for (var block : List.of(KitchenAppliances.TOMATO_BLOCK, KitchenAppliances.GRAPE_BLOCK, KitchenAppliances.OLIVE_BLOCK)) {
|
||||
var compressed = h.getLevel().getServer().getRecipeManager().byKey(ResourceKey.create(Registries.RECIPE, BuiltInRegistries.BLOCK.getKey(block))).orElseThrow().value();
|
||||
h.assertTrue(compressed instanceof CraftingRecipe, "Compressed fruit is crafted from reachable produce");
|
||||
for (var ingredient : compressed.placementInfo().ingredients()) h.assertTrue(reachable.contains(BuiltInRegistries.ITEM.getKey(ingredient.display().resolveForFirstStack(net.minecraft.world.item.crafting.display.SlotDisplayContext.fromLevel(h.getLevel())).getItem()).toString()), "Ingredient resolves");
|
||||
reachable.add(BuiltInRegistries.BLOCK.getKey(block).toString());
|
||||
}
|
||||
for (int pass = 0; pass < 92; pass++) for (var e : Cuisine.CATALOG.getAsJsonArray("ports")) {
|
||||
var p = e.getAsJsonObject(); boolean possible = true;
|
||||
for (var ingredient : p.getAsJsonArray("ingredients")) {
|
||||
String id = ingredient.getAsString();
|
||||
if (id.startsWith("#")) possible &= BuiltInRegistries.ITEM.getOrThrow(TagKey.create(Registries.ITEM, Identifier.parse(id.substring(1))))
|
||||
.stream().anyMatch(v -> reachable.contains(BuiltInRegistries.ITEM.getKey(v.value()).toString()));
|
||||
else possible &= reachable.contains(id);
|
||||
}
|
||||
if (possible) reachable.add(p.get("result").getAsString());
|
||||
}
|
||||
for (var e : Cuisine.CATALOG.getAsJsonArray("items")) h.assertTrue(reachable.contains("sanctuary:" + e.getAsJsonObject().get("id").getAsString()), "No orphan food ingredient: " + e);
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void cookingUsesPlainWaterAndRefusesPotionsWithoutConsumingAnything(GameTestHelper h) {
|
||||
var recipe = (CraftingRecipe) recipe(h, "sanctuary:prepare_cauldron_coffee");
|
||||
var water = PotionContents.createItemStack(Items.POTION, Potions.WATER);
|
||||
h.assertTrue(recipe.matches(grid(List.of(new ItemStack(Cuisine.item("ground_coffee")), water)), h.getLevel()), "Plain water accepted");
|
||||
for (var potion : List.of(Potions.POISON, Potions.HEALING, Potions.AWKWARD)) {
|
||||
var stack = PotionContents.createItemStack(Items.POTION, potion);
|
||||
h.assertTrue(!recipe.matches(grid(List.of(new ItemStack(Cuisine.item("ground_coffee")), stack)), h.getLevel()) && stack.getCount() == 1, "Non-water potion refused intact");
|
||||
}
|
||||
water.set(DataComponents.POTION_CONTENTS, new PotionContents(Potions.WATER).withEffectAdded(new MobEffectInstance(MobEffects.POISON, 200)));
|
||||
h.assertTrue(!recipe.matches(grid(List.of(new ItemStack(Cuisine.item("ground_coffee")), water)), h.getLevel()), "Custom potion effects cannot enter food"); h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void nativeCraftingReturnsWaterHoneyMilkAndPineappleRemaindersOnce(GameTestHelper h) {
|
||||
for (String id : List.of("prepare_cauldron_lemon_honey_tea", "prepare_cauldron_cream", "cold_fruit_salad")) {
|
||||
var p = portForCraft(id); var input = grid(samples(p.getAsJsonArray("ingredients")));
|
||||
var remaining = ((CraftingRecipe) recipe(h, "sanctuary:" + id)).getRemainingItems(input);
|
||||
for (int i = 0; i < input.size(); i++) {
|
||||
var stack = input.getItem(i);
|
||||
if (stack.is(Items.POTION) || stack.is(Items.HONEY_BOTTLE)) h.assertTrue(remaining.get(i).is(Items.GLASS_BOTTLE) && remaining.get(i).getCount() == 1, "One original bottle returned");
|
||||
if (stack.is(Items.MILK_BUCKET)) h.assertTrue(remaining.get(i).is(Items.BUCKET) && remaining.get(i).getCount() == 1, "One original milk bucket returned");
|
||||
if (stack.is(AgricultureItems.PINEAPPLE)) h.assertTrue(remaining.get(i).is(AgricultureItems.PINEAPPLE_CROWN) && remaining.get(i).getCount() == 1, "One pineapple crown returned");
|
||||
}
|
||||
}
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void shiftedCraftingConsumesOnlyPaidBatchesAndKeepsTheBottle(GameTestHelper h) {
|
||||
var p = Progression003GameTests.player(h); p.getInventory().clearContent();
|
||||
var table = h.absolutePos(new BlockPos(2, 3, 2)); h.getLevel().setBlockAndUpdate(table, Blocks.CRAFTING_TABLE.defaultBlockState());
|
||||
var menu = new CraftingMenu(1, p.getInventory(), ContainerLevelAccess.create(h.getLevel(), table));
|
||||
menu.getSlot(1).set(new ItemStack(Cuisine.item("ground_coffee"), 8)); menu.getSlot(2).set(PotionContents.createItemStack(Items.POTION, Potions.WATER));
|
||||
h.assertTrue(menu.getSlot(0).getItem().is(Cuisine.item("raw_cauldron_coffee")), "Native table previews preparation");
|
||||
menu.quickMoveStack(p, 0);
|
||||
h.assertTrue(count(p, Cuisine.item("raw_cauldron_coffee")) == 1 && menu.getSlot(1).getItem().getCount() == 7 && menu.getSlot(2).getItem().is(Items.GLASS_BOTTLE), "Shift craft consumes one water and one coffee, returns the bottle once");
|
||||
menu.quickMoveStack(p, 0); h.assertTrue(count(p, Cuisine.item("raw_cauldron_coffee")) == 1, "Cannot duplicate from stale result"); h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void actualSmokerConsumesFuelAndCooksThePreparation(GameTestHelper h) {
|
||||
var pos = h.absolutePos(new BlockPos(3, 4, 3)); h.getLevel().setBlockAndUpdate(pos, Blocks.SMOKER.defaultBlockState());
|
||||
var smoker = (AbstractFurnaceBlockEntity) h.getLevel().getBlockEntity(pos);
|
||||
smoker.setItem(0, new ItemStack(Cuisine.item("raw_pan_ratatouille")));
|
||||
for (int i = 0; i < 300; i++) AbstractFurnaceBlockEntity.serverTick(h.getLevel(), pos, h.getLevel().getBlockState(pos), smoker);
|
||||
h.assertTrue(smoker.getItem(2).isEmpty() && smoker.getItem(0).getCount() == 1, "No free cooking without fuel");
|
||||
smoker.setItem(1, new ItemStack(Items.COAL));
|
||||
for (int i = 0; i < 300; i++) AbstractFurnaceBlockEntity.serverTick(h.getLevel(), pos, h.getLevel().getBlockState(pos), smoker);
|
||||
h.assertTrue(smoker.getItem(0).isEmpty() && smoker.getItem(1).isEmpty() && smoker.getItem(2).is(Cuisine.item("ratatouille")) && smoker.getItem(2).getCount() == 1, "Native smoker produces exactly the paid meal"); h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void foodPropertiesEffectsAndUncookedBatchesRemainDistinct(GameTestHelper h) {
|
||||
for (var e : Cuisine.CATALOG.getAsJsonArray("items")) {
|
||||
var row = e.getAsJsonObject(); var stack = new ItemStack(Cuisine.item(row.get("id").getAsString()));
|
||||
if(row.get("id").getAsString().equals("cheese")) {
|
||||
h.assertTrue(stack.getItem() instanceof net.minecraft.world.item.BlockItem&&!stack.has(DataComponents.FOOD),"Since beta.314 cheese is shared after placing, like cake");
|
||||
continue;
|
||||
}
|
||||
h.assertTrue(stack.has(DataComponents.FOOD) == row.has("food"), "Historical ingredient edibility retained");
|
||||
if (row.has("food")) h.assertTrue(stack.get(DataComponents.FOOD).nutrition() == row.getAsJsonObject("food").get("nutrition").getAsInt(), "Historical meal nutrition retained");
|
||||
}
|
||||
for (var e : Cuisine.CATALOG.getAsJsonArray("preparations")) h.assertTrue(!new ItemStack(Cuisine.item(e.getAsJsonObject().get("id").getAsString())).has(DataComponents.FOOD), "Uncooked preparation cannot be eaten as a finished dish");
|
||||
var player = Progression003GameTests.player(h); player.getFoodData().setFoodLevel(2);
|
||||
var coffee = new ItemStack(Cuisine.item("coffee")); coffee.finishUsingItem(h.getLevel(), player);
|
||||
h.assertTrue(player.hasEffect(MobEffects.SPEED) && player.hasEffect(MobEffects.HASTE), "Actual consumption applies historical coffee effects"); h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void discoveringACropTeachesTheActualCuisineRecipes(GameTestHelper h) {
|
||||
var p = Progression003GameTests.player(h); p.setUUID(UUID.randomUUID());
|
||||
p.setAttached(fr.koka.sanctuary.progression.ProgressionService.RECORD, fr.koka.sanctuary.progression.ProgressRecord.INITIAL.json());
|
||||
fr.koka.sanctuary.progression.ProgressEffects.apply(p, fr.koka.sanctuary.progression.ProgressRecord.INITIAL);
|
||||
RecipeService.discover(p, AgricultureItems.TOMATO); RecipeService.refresh(p);
|
||||
for (var e : Cuisine.CATALOG.getAsJsonArray("ports")) for (var id : e.getAsJsonObject().getAsJsonArray("recipes"))
|
||||
h.assertTrue(RecipeService.knowledge(p).recipes.contains(id.getAsString()), "Native cuisine blueprint discovered: " + id);
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
public static Item item(String id) { return BuiltInRegistries.ITEM.getValue(Identifier.parse(id)); }
|
||||
public static Recipe<?> recipe(GameTestHelper h, String id) { return h.getLevel().getServer().getRecipeManager().byKey(ResourceKey.create(Registries.RECIPE, Identifier.parse(id))).orElseThrow().value(); }
|
||||
public static List<ItemStack> samples(JsonArray ingredients) {
|
||||
var stacks = new ArrayList<ItemStack>();
|
||||
for (var e : ingredients) {
|
||||
String id = e.getAsString();
|
||||
if (id.equals("minecraft:potion")) stacks.add(PotionContents.createItemStack(Items.POTION, Potions.WATER));
|
||||
else if (id.startsWith("#")) stacks.add(new ItemStack(BuiltInRegistries.ITEM.getOrThrow(TagKey.create(Registries.ITEM, Identifier.parse(id.substring(1)))).stream().findFirst().orElseThrow().value()));
|
||||
else stacks.add(new ItemStack(item(id)));
|
||||
}
|
||||
return stacks;
|
||||
}
|
||||
public static CraftingInput grid(List<ItemStack> ingredients) { var list = new ArrayList<>(ingredients); while (list.size() < 9) list.add(ItemStack.EMPTY); return CraftingInput.of(3, 3, list); }
|
||||
private static JsonObject portForCraft(String id) { return Cuisine.CATALOG.getAsJsonArray("ports").asList().stream().map(JsonElement::getAsJsonObject).filter(p -> p.getAsJsonArray("recipes").asList().stream().anyMatch(v -> v.getAsString().equals("sanctuary:" + id))).findFirst().orElseThrow(); }
|
||||
private static int count(ServerPlayer p, Item item) { return p.getInventory().countItem(item); }
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import fr.koka.sanctuary.cuisine.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.CuredSausageBlock;
|
||||
import fr.koka.sanctuary.cuisine.culinary.block.entity.*;
|
||||
import fr.koka.sanctuary.cuisine.culinary.recipe.*;
|
||||
import fr.koka.sanctuary.cuisine.machine.RedstoneMillBlockEntity;
|
||||
import fr.koka.sanctuary.cuisine.menu.RedstoneMillMenu;
|
||||
import fr.koka.sanctuary.cuisine.recipe.*;
|
||||
import fr.koka.sanctuary.recipes.RecipeService;
|
||||
import java.util.*;
|
||||
import net.fabricmc.fabric.api.gametest.v1.GameTest;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.inventory.*;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.alchemy.*;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.item.crafting.*;
|
||||
import net.minecraft.world.item.crafting.display.SlotDisplayContext;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.phys.*;
|
||||
|
||||
/** Paid native crafts, complete oven/pot/drying cycles, food containers and knowledge. */
|
||||
public final class DriedCuisine318GameTests {
|
||||
private static List<JsonObject> dishes() { return DriedCuisine.CATALOG.getAsJsonArray("dishes").asList().stream().map(e -> e.getAsJsonObject()).toList(); }
|
||||
private static String recipeId(JsonObject row) {
|
||||
return "sanctuary:" + switch(row.get("method").getAsString()) {
|
||||
case "crafting" -> "prepare_"; case "mill" -> "mill_"; case "drying" -> "dry_"; default -> "cook_";
|
||||
} + row.get("id").getAsString();
|
||||
}
|
||||
private static List<ItemStack> samples(GameTestHelper h, Recipe<?> recipe) { return recipe.placementInfo().ingredients().stream().map(i -> i.display().resolveForFirstStack(SlotDisplayContext.fromLevel(h.getLevel()))).toList(); }
|
||||
private static ServerPlayer player(GameTestHelper h) { var p=Progression003GameTests.player(h);p.setGameMode(GameType.SURVIVAL);p.getInventory().clearContent();p.setPos(h.absoluteVec(new Vec3(3.5,8,5)));return p; }
|
||||
private static BlockPos pos(GameTestHelper h) { return h.absolutePos(new BlockPos(3,8,3)); }
|
||||
private static void clear(GameTestHelper h) { var p=pos(h);if(h.getLevel().getBlockEntity(p) instanceof net.minecraft.world.Container c)c.clearContent();h.getLevel().removeBlock(p,false);h.getLevel().setBlockAndUpdate(p.below(),Blocks.CAMPFIRE.defaultBlockState());for(var e:h.getLevel().getEntitiesOfClass(net.minecraft.world.entity.item.ItemEntity.class,new AABB(p).inflate(3)))e.discard(); }
|
||||
private static void tick(GameTestHelper h, KitchenOvenBlockEntity oven, int n) { for(int i=0;i<n;i++)KitchenOvenBlockEntity.serverTick(h.getLevel(),oven.getBlockPos(),oven.getBlockState(),oven); }
|
||||
private static void tick(GameTestHelper h, CookingCauldronBlockEntity pot, int n) { for(int i=0;i<n;i++)CookingCauldronBlockEntity.serverTick(h.getLevel(),pot.getBlockPos(),pot.getBlockState(),pot); }
|
||||
|
||||
@GameTest public void allEighteenProposalsHaveConcreteUnambiguousNativeRecipes(GameTestHelper h) {
|
||||
h.assertTrue(dishes().size()==18 && DriedCuisine.items().size()==19,"Eighteen results plus one raw sausage preparation");
|
||||
for(var row:dishes()) {
|
||||
var recipe=Cuisine303GameTests.recipe(h,recipeId(row));var input=samples(h,recipe);long matches;
|
||||
if(recipe instanceof CraftingRecipe r) {
|
||||
var grid=Cuisine303GameTests.grid(input);h.assertTrue(r.assemble(grid).is(DriedCuisine.item(row.get("id").getAsString())),"Correct table result");
|
||||
matches=h.getLevel().getServer().getRecipeManager().getRecipes().stream().filter(holder->holder.value() instanceof CraftingRecipe candidate && candidate.matches(grid,h.getLevel())).count();
|
||||
} else if(recipe instanceof CulinaryProcessRecipe r) {
|
||||
var view=new CulinaryRecipeInput(r.appliance(),input);
|
||||
matches=h.getLevel().getServer().getRecipeManager().getRecipes().stream().filter(holder->holder.value() instanceof CulinaryProcessRecipe candidate && candidate.matches(view,h.getLevel())).count();
|
||||
} else {
|
||||
var view=new SingleRecipeInput(input.getFirst());
|
||||
matches=h.getLevel().getServer().getRecipeManager().getRecipes().stream().filter(holder->
|
||||
(holder.value() instanceof FoodDryingRecipe r && recipe instanceof FoodDryingRecipe && r.matches(view,h.getLevel())) ||
|
||||
(holder.value() instanceof RedstoneMillingRecipe milling && recipe instanceof RedstoneMillingRecipe && milling.matches(view,h.getLevel()))).count();
|
||||
}
|
||||
h.assertTrue(matches==1,"One exact native process for "+recipeId(row));
|
||||
}h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void everyColdDishAndTheRawSausageArePaidThroughTheNativeCraftingMenu(GameTestHelper h) {
|
||||
var ids=new ArrayList<String>();for(var row:dishes())if(row.get("method").getAsString().equals("crafting"))ids.add(recipeId(row));ids.add("sanctuary:prepare_spiced_sausage");
|
||||
for(var id:ids) {
|
||||
var recipe=(CraftingRecipe)Cuisine303GameTests.recipe(h,id);var input=samples(h,recipe);var expected=recipe.assemble(Cuisine303GameTests.grid(input));var p=player(h);
|
||||
var table=pos(h);h.getLevel().setBlockAndUpdate(table,Blocks.CRAFTING_TABLE.defaultBlockState());var menu=new CraftingMenu(1,p.getInventory(),ContainerLevelAccess.create(h.getLevel(),table));
|
||||
for(int i=0;i<input.size();i++)menu.getSlot(i+1).set(input.get(i).copyWithCount(2));
|
||||
h.assertTrue(ItemStack.matches(menu.getSlot(0).getItem(),expected),"Native preview: "+id);
|
||||
menu.clicked(0,0,ContainerInput.PICKUP,p);
|
||||
h.assertTrue(ItemStack.matches(menu.getCarried(),expected),"Native paid result: "+id);
|
||||
for(int i=0;i<input.size();i++)h.assertTrue(menu.getSlot(i+1).getItem().getCount()==1,"Every ingredient pays exactly one unit: "+id);
|
||||
menu.setCarried(ItemStack.EMPTY);menu.removed(p);
|
||||
}h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void fourOvenRecipesKeepIngredientsUntilFullCookingAndRepeatOnlyPaidBatches(GameTestHelper h) {
|
||||
for(var row:dishes())if(row.get("method").getAsString().equals("oven")) {
|
||||
clear(h);var recipe=(CulinaryProcessRecipe)Cuisine303GameTests.recipe(h,recipeId(row));h.getLevel().setBlockAndUpdate(pos(h),KitchenAppliances.KITCHEN_OVEN.defaultBlockState());var oven=(KitchenOvenBlockEntity)h.getLevel().getBlockEntity(pos(h));
|
||||
var input=samples(h,recipe);for(int i=0;i<input.size();i++)oven.setItem(i,input.get(i).copyWithCount(2));oven.setItem(9,new ItemStack(Items.COAL));
|
||||
tick(h,oven,recipe.processingTime()-1);h.assertTrue(oven.getItem(10).isEmpty()&&oven.getItem(0).getCount()==2,"No early output or input payment: "+row);
|
||||
tick(h,oven,1);h.assertTrue(ItemStack.matches(oven.getItem(10),recipe.result())&&oven.getItem(0).getCount()==1,"Full native cycle: "+row);
|
||||
tick(h,oven,recipe.processingTime());h.assertTrue(oven.getItem(10).getCount()==recipe.result().getCount()*2&&oven.getItem(0).isEmpty(),"Second paid batch with menu closed: "+row);
|
||||
}h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void fiveCauldronRecipesNeedWaterContainersAndAStirBeforePayingAtTheDeadline(GameTestHelper h) {
|
||||
for(var row:dishes())if(row.get("method").getAsString().equals("cauldron")) {
|
||||
clear(h);var recipe=(CulinaryProcessRecipe)Cuisine303GameTests.recipe(h,recipeId(row));h.getLevel().setBlockAndUpdate(pos(h),KitchenAppliances.COOKING_CAULDRON.defaultBlockState());var pot=(CookingCauldronBlockEntity)h.getLevel().getBlockEntity(pos(h));
|
||||
for(var source:samples(h,recipe))if(!source.is(Items.POTION))h.assertTrue(pot.insertIngredient(source,1)==1,"Each real ingredient/container is accepted");
|
||||
h.assertTrue(!pot.stir(),"Water is mandatory");pot.setWaterUnits(1);tick(h,pot,recipe.processingTime()+1);
|
||||
h.assertTrue(pot.getItem(6).isEmpty()&&pot.waterUnits()==1,"No automatic intermediate dish before stirring");h.assertTrue(pot.stir(),"Explicit complete preparation: "+row);
|
||||
tick(h,pot,recipe.processingTime()-1);h.assertTrue(pot.getItem(6).isEmpty()&&pot.waterUnits()==1,"No early payment");tick(h,pot,1);
|
||||
h.assertTrue(ItemStack.matches(pot.getItem(6),recipe.result())&&pot.waterUnits()==0,"Full paid pot output: "+row);
|
||||
for(int i=0;i<6;i++)h.assertTrue(pot.getItem(i).isEmpty(),"Every ingredient and serving container is consumed once");
|
||||
}h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void realMillingMakesPaidPowderAndBothSeasoningsWorkInExistingMeals(GameTestHelper h) {
|
||||
clear(h);h.getLevel().setBlockAndUpdate(pos(h),KitchenAppliances.REDSTONE_MILL.defaultBlockState());var mill=(RedstoneMillBlockEntity)h.getLevel().getBlockEntity(pos(h));var p=player(h);var menu=(RedstoneMillMenu)mill.createMenu(1,p.getInventory(),p);
|
||||
menu.getSlot(0).set(new ItemStack(DriedFoods.item("dried_chili_pepper"),4));h.assertTrue(menu.getSlot(1).getItem().is(DriedCuisine.item("chili_powder"))&&menu.getSlot(0).getItem().getCount()==4,"Manual preview keeps peppers");menu.clicked(1,0,ContainerInput.QUICK_MOVE,p);
|
||||
h.assertTrue(p.getInventory().countItem(DriedCuisine.item("chili_powder"))==8&&menu.getSlot(0).getItem().isEmpty(),"Four real peppers pay eight powder portions");menu.removed(p);
|
||||
var chili=(CulinaryProcessRecipe)Cuisine303GameTests.recipe(h,"sanctuary:physical_cauldron_bean_chili");var inputs=new ArrayList<>(samples(h,chili));
|
||||
for(int i=0;i<inputs.size();i++)if(inputs.get(i).is(fr.koka.sanctuary.agriculture.AgricultureItems.CHILI_PEPPER))inputs.set(i,new ItemStack(DriedCuisine.item("chili_powder")));
|
||||
h.assertTrue(chili.matches(new CulinaryRecipeInput(chili.appliance(),inputs),h.getLevel()),"Powder replaces chili in an existing pot recipe");
|
||||
var herbs=List.of(new ItemStack(Items.EGG),new ItemStack(Items.MILK_BUCKET),new ItemStack(DriedCuisine.item("herb_seasoning")));
|
||||
h.assertTrue(h.getLevel().getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING,Cuisine303GameTests.grid(herbs),h.getLevel()).orElseThrow().value().assemble(Cuisine303GameTests.grid(herbs)).is(Cuisine.item("raw_pan_herb_omelette")),"Herb seasoning works at the actual preparation table");h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void spicySausageSurvivesSavedDryingThenPlacesAndSharesFourSlices(GameTestHelper h) {
|
||||
clear(h);h.getLevel().setBlockAndUpdate(pos(h).above(),Blocks.STONE.defaultBlockState());h.getLevel().setBlockAndUpdate(pos(h),KitchenAppliances.DRYING_RACK.defaultBlockState());var rack=(DryingRackBlockEntity)h.getLevel().getBlockEntity(pos(h));rack.insertIngredient(new ItemStack(DriedCuisine.item("raw_spiced_sausage")));
|
||||
for(int i=0;i<3000;i++)DryingRackBlockEntity.serverTick(h.getLevel(),pos(h),rack.getBlockState(),rack);
|
||||
var copy=(DryingRackBlockEntity)BlockEntity.loadStatic(pos(h),rack.getBlockState(),rack.saveWithFullMetadata(h.getLevel().registryAccess()),h.getLevel().registryAccess());copy.setLevel(h.getLevel());h.getLevel().setBlockEntity(copy);
|
||||
h.assertTrue(copy.progress(0)==3000&&!copy.ready(0),"Same saved rack format retains spicy work");
|
||||
for(int i=0;i<2999;i++)DryingRackBlockEntity.serverTick(h.getLevel(),pos(h),copy.getBlockState(),copy);h.assertTrue(!copy.ready(0),"No early sausage");DryingRackBlockEntity.serverTick(h.getLevel(),pos(h),copy.getBlockState(),copy);
|
||||
var food=copy.takeItem();h.assertTrue(food.is(DriedCuisine.item("spiced_sausage"))&&food.is(DriedCuisine.HANGING_SAUSAGES)&©.isEmpty(),"Same full five minutes and sausage renderer tag");
|
||||
var p=player(h);var target=pos(h).east(3);h.getLevel().setBlockAndUpdate(target.below(),Blocks.STONE.defaultBlockState());h.getLevel().removeBlock(target,false);p.setItemInHand(InteractionHand.MAIN_HAND,food);var hit=new BlockHitResult(Vec3.atCenterOf(target.below()),Direction.UP,target.below(),false);
|
||||
var placement=p.getMainHandItem().useOn(new UseOnContext(p,InteractionHand.MAIN_HAND,hit));h.assertTrue(placement.consumesAction()&&p.getMainHandItem().isEmpty(),"Real dried sausage placement: "+placement+" / "+h.getLevel().getBlockState(target)+" / "+p.getMainHandItem());p.getFoodData().setFoodLevel(0);
|
||||
for(int i=0;i<4;i++){var state=h.getLevel().getBlockState(target);h.assertTrue(state.is(DriedCuisine.SPICED_SAUSAGE)&&state.getValue(CuredSausageBlock.BITES)==i,"The exact remaining spicy slices");state.useWithoutItem(h.getLevel(),p,new BlockHitResult(Vec3.atCenterOf(target),Direction.UP,target,false));}
|
||||
h.assertTrue(h.getLevel().getBlockState(target).isAir()&&p.getFoodData().getFoodLevel()==8,"Four real slices, then no whole sausage refund");h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void everyEdibleResultReturnsOnlyItsPaidContainerAndSpicesStayIngredients(GameTestHelper h) {
|
||||
var p=player(h);
|
||||
for(var row:dishes()) {
|
||||
var item=DriedCuisine.item(row.get("id").getAsString());p.getInventory().clearContent();
|
||||
if(row.has("nutrition")) {
|
||||
p.getFoodData().setFoodLevel(0);var stack=new ItemStack(item,2);var result=stack.finishUsingItem(h.getLevel(),p);
|
||||
h.assertTrue(result.getCount()==1&&p.getFoodData().getFoodLevel()==row.get("nutrition").getAsInt(),"One real edible portion: "+row);
|
||||
if(row.has("container"))h.assertTrue(p.getInventory().countItem(Cuisine303GameTests.item(row.get("container").getAsString()))==1,"Exactly one paid serving container returned");
|
||||
}else h.assertTrue(new ItemStack(item).get(DataComponents.FOOD)==null,"Spices and placeable sausage have no fake direct meal");
|
||||
}h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void discoveryAndSurvivalInputsCoverAllNineteenTransformations(GameTestHelper h) {
|
||||
var p=player(h);p.setUUID(UUID.randomUUID());p.setAttached(fr.koka.sanctuary.progression.ProgressionService.RECORD,fr.koka.sanctuary.progression.ProgressRecord.INITIAL.json());fr.koka.sanctuary.progression.ProgressEffects.apply(p,fr.koka.sanctuary.progression.ProgressRecord.INITIAL);
|
||||
var reachable=new HashSet<Item>();for(var i:net.minecraft.core.registries.BuiltInRegistries.ITEM)if(net.minecraft.core.registries.BuiltInRegistries.ITEM.getKey(i).getNamespace().equals("minecraft"))reachable.add(i);
|
||||
reachable.addAll(fr.koka.sanctuary.agriculture.AgricultureItems.PRODUCE_ITEMS);reachable.addAll(Cuisine.items());reachable.addAll(DriedFoods.items());reachable.add(KitchenAppliances.RAW_SAUSAGE);
|
||||
var prep=(CraftingRecipe)Cuisine303GameTests.recipe(h,"sanctuary:prepare_spiced_sausage");for(var ingredient:prep.placementInfo().ingredients())h.assertTrue(ingredient.items().anyMatch(v->reachable.contains(v.value())),"Raw spicy preparation uses survival inputs");reachable.add(DriedCuisine.item("raw_spiced_sausage"));
|
||||
for(var row:dishes()) {
|
||||
var recipe=Cuisine303GameTests.recipe(h,recipeId(row));for(var ingredient:recipe.placementInfo().ingredients())h.assertTrue(ingredient.items().anyMatch(v->reachable.contains(v.value())),"Reachable native ingredient: "+row);
|
||||
for(var sample:samples(h,recipe))if(!sample.is(Items.POTION)&&!sample.is(Items.BOWL)&&!sample.is(Items.GLASS_BOTTLE))RecipeService.discover(p,sample.getItem());RecipeService.refresh(p);
|
||||
h.assertTrue(RecipeService.knowledge(p).recipes.contains(recipeId(row)),"Discover actual blueprint: "+row);reachable.add(DriedCuisine.item(row.get("id").getAsString()));
|
||||
}
|
||||
h.assertTrue(RecipeService.knowledge(p).recipes.contains("sanctuary:prepare_spiced_sausage"),"Spicy preparation blueprint");h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void unsafePotionsNeverMatchANewCauldronDish(GameTestHelper h) {
|
||||
for(var row:dishes())if(row.get("method").getAsString().equals("cauldron")) {
|
||||
var recipe=(CulinaryProcessRecipe)Cuisine303GameTests.recipe(h,recipeId(row));var input=new ArrayList<>(samples(h,recipe));
|
||||
for(int i=0;i<input.size();i++)if(input.get(i).is(Items.POTION))input.set(i,PotionContents.createItemStack(Items.POTION,Potions.POISON));
|
||||
h.assertTrue(!recipe.matches(new CulinaryRecipeInput(recipe.appliance(),input),h.getLevel()),"Potions cannot replace water in "+row);
|
||||
}h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void spicyMealsAndSlicesSendCosmeticHitsWithoutDamageOrKnockback(GameTestHelper h) {
|
||||
var p=player(h);
|
||||
var connection=new net.minecraft.network.Connection(net.minecraft.network.protocol.PacketFlow.SERVERBOUND);
|
||||
var channel=new io.netty.channel.embedded.EmbeddedChannel(connection);
|
||||
var cookie=net.minecraft.server.network.CommonListenerCookie.createInitial(new com.mojang.authlib.GameProfile(p.getUUID(),"spicy-test"),false);
|
||||
p.connection=new net.minecraft.server.network.ServerGamePacketListenerImpl(h.getLevel().getServer(),connection,p,cookie);
|
||||
p.setAbsorptionAmount(3);p.setInvulnerableTime(7);p.hurtTime=2;p.setDeltaMovement(.12,.03,-.2);
|
||||
float health=p.getHealth(),absorption=p.getAbsorptionAmount();var velocity=p.getDeltaMovement();p.getFoodData().setFoodLevel(0);
|
||||
new ItemStack(Cuisine.item("bean_chili"),2).finishUsingItem(h.getLevel(),p);channel.runPendingTasks();
|
||||
long hits=channel.outboundMessages().stream().filter(packet->packet instanceof net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket).count();
|
||||
h.assertTrue(hits==1,"Finishing an existing spicy meal sends one immediate animation packet");
|
||||
p.getFoodData().setFoodLevel(0);new ItemStack(DriedCuisine.item("raisin_bread"),2).finishUsingItem(h.getLevel(),p);channel.runPendingTasks();
|
||||
h.assertTrue(channel.outboundMessages().stream().filter(packet->packet instanceof net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket).count()==hits,"Mild food does not produce fake hits");
|
||||
h.getLevel().setBlockAndUpdate(pos(h).below(),Blocks.STONE.defaultBlockState());h.getLevel().setBlockAndUpdate(pos(h),DriedCuisine.SPICED_SAUSAGE.defaultBlockState());p.getFoodData().setFoodLevel(0);
|
||||
h.getLevel().getBlockState(pos(h)).useWithoutItem(h.getLevel(),p,new BlockHitResult(Vec3.atCenterOf(pos(h)),Direction.UP,pos(h),false));channel.runPendingTasks();
|
||||
h.assertTrue(channel.outboundMessages().stream().filter(packet->packet instanceof net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket).count()==hits+1,"A real spicy sausage slice triggers the same reaction");
|
||||
h.assertTrue(p.getHealth()==health&&p.getAbsorptionAmount()==absorption&&p.getInvulnerableTime()==7&&p.hurtTime==2&&p.getDeltaMovement().equals(velocity),"Cosmetic state: health="+p.getHealth()+"/"+health+", absorption="+p.getAbsorptionAmount()+"/"+absorption+", invulnerable="+p.getInvulnerableTime()+", hurt="+p.hurtTime+", velocity="+p.getDeltaMovement()+"/"+velocity);
|
||||
h.assertTrue(channel.outboundMessages().stream().noneMatch(packet->packet instanceof net.minecraft.network.protocol.game.ClientboundDamageEventPacket),"No damage event is sent");
|
||||
channel.finishAndReleaseAll();h.succeed();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user