Compare commits

..
Author SHA1 Message Date
koka 26f2c2a006 Merge pull request 'release: Sanctuary 26.2.0-alpha.244 — PR #176' (#177) from release/26.2.0-alpha.244 into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #177
2026-09-10 14:55:14 +00:00
koka 72f609f77c release: integrate PR #176 in alpha.244
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-10 14:45:33 +02:00
koka 5f2b17c792 Merge pull request 'feat(onlyfun): feux d'artifice ChrisM, Duncan et autres visages' (#176) from feature/skin-fireworks into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #176
2026-09-10 12:24:00 +00:00
ChrisM-PekandCursor 075214029e feat(onlyfun): add ChrisM, Duncan and other skin-face fireworks
Verification 26.2 / check (pull_request) Canceled after 0s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 23:02:25 +02:00
koka bb418632f2 Merge pull request 'release: Sanctuary 26.2.0-alpha.243 — PR #172, #173 et #174' (#175) from release/26.2.0-alpha.243 into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #175
2026-09-09 13:59:06 +00:00
koka 833a05129c release: integrate merged PRs in alpha.243
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-09 15:49:27 +02:00
koka 8ab2fc727e Merge pull request 'fix(sanctuary): resynchroniser le panneau de quete apres rotation hors chunk' (#172) from fix/issue-167-quest-board-stale-sign into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #172
2026-09-09 13:25:24 +00:00
koka ea80f4a6f4 Merge pull request 'feat(sanctuary): recherche et grilles 3x3/4x4/5x5 au catalogue du shop' (#173) from feature/shop-catalog-search into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #173
2026-09-09 13:25:16 +00:00
koka 2d96d9077b Merge pull request 'feat(onlyfun): feu d'artifice Nourra au visage du skin' (#174) from feature/zayn-firework into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #174
2026-09-09 13:24:59 +00:00
ChrisM-PekandCursor 77fa9c5d21 feat(onlyfun): add Nourra firework with a skin-face burst
Verification 26.2 / check (pull_request) Canceled after 0s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 14:54:45 +02:00
ChrisM-PekandCursor fa5f6003c5 feat(sanctuary): recherche et grilles 3x3/4x4/5x5 au catalogue du shop
Verification 26.2 / check (pull_request) Canceled after 0s
Permet de retrouver un objet dans le catalogue et d'afficher plus de cases sans changer les ventes flash.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 22:18:55 +02:00
ChrisM-PekandCursor 63938d708e fix(sanctuary): resynchroniser le panneau de quête après rotation hors chunk
Verification 26.2 / check (pull_request) Canceled after 0s
Le contrat quotidien était déjà régénéré en data pendant que le panneau gardait l'ancien texte. Un clic réservait donc une autre quête que celle affichée.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 21:23:29 +02:00
koka da61a3b1ec Merge pull request 'release: Sanctuary 26.2.0-alpha.242 — MyDisplay build 4' (#170) from release/26.2.0-alpha.242 into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #170
2026-09-05 19:08:46 +00:00
koka bd7ee21e7f release: update MyDisplay build 4
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-05 20:43:58 +02:00
koka b4a4e25684 Merge pull request 'release: Sanctuary 26.2.0-alpha.241 — optimisations MyDisplay' (#168) from release/26.2.0-alpha.241 into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #168
2026-09-05 07:38:16 +00:00
koka d95d8af8ec release: update MyDisplay performance build
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-05 09:34:04 +02:00
koka 29db709e7e Merge pull request 'release: update MyDisplay build 2' (#166) from release/26.2.0-alpha.240 into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #166
2026-09-04 15:50:59 +00:00
koka 9b432f2ecc release: update MyDisplay build 2
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-04 17:42:34 +02:00
koka d1f9d6a30e Merge pull request 'release: replace Dream Displays with MyDisplay' (#165) from release/26.2.0-alpha.239 into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #165
2026-09-04 14:05:18 +00:00
koka aefb23deee release: update MyDisplay build
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-04 15:37:29 +02:00
koka 7f2b53e41d release: replace Dream Displays with MyDisplay 2026-09-04 14:43:04 +02:00
koka 60819a31ab Merge pull request 'release: update Dream Displays audio test build revision 2' (#164) from release/26.2.0-alpha.238 into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #164
2026-09-04 11:42:04 +00:00
koka e692a051ac release: update Dream Displays audio test build
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-04 13:28:50 +02:00
koka 22fb027398 Merge pull request 'release: Sanctuary 26.2.0-alpha.237 — Dream Displays audio-buffer-test' (#163) from release/26.2.0-alpha.237 into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #163
2026-09-04 10:47:33 +00:00
koka 2d0ae127a9 release: use Dream Displays audio buffer test build
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-04 12:45:26 +02:00
koka 253c8f132e Merge pull request 'release: Sanctuary 26.2.0-alpha.236 — retrait de ZEVENT' (#162) from release/26.2.0-alpha.236 into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #162
2026-09-04 08:44:19 +00:00
koka c973d535a8 chore(pack): remove temporary ZEVENT module
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-04 04:17:21 +02:00
koka a764ba9c42 Merge pull request 'fix(zevent): rétablir la vidéo Twitch et publier l’alpha.235' (#161) from fix/zevent-twitch-playback into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #161
2026-09-04 01:37:16 +00:00
koka 1cb14b997f fix(zevent): decode Twitch streams natively
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-04 03:16:59 +02:00
koka 78457fd80b build(itsalive): declare Canaplia source dependency 2026-09-04 03:16:59 +02:00
koka 8c8f7bbbde Merge pull request 'release: Sanctuary 26.2.0-alpha.234 avec ZEVENT' (#160) from release/26.2.0-alpha.234 into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #160
2026-09-04 00:29:12 +00:00
koka ac4542079f release: add ZEVENT weekend pack in alpha.234
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-04 02:22:24 +02:00
koka 5403938320 Merge pull request 'feat(zevent): ajouter l’écran Twitch temporaire du week-end' (#159) from feature/zevent-screen into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #159
2026-09-04 00:10:50 +00:00
133 changed files with 3446 additions and 1858 deletions
+118
View File
@@ -10,6 +10,124 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve
Aucun changement non publié.
## `26.2.0-alpha.244`
Pull request intégrée : [#176](https://git.botsu.net/koka/sanctuary/pulls/176).
- ajoute huit feux dartifice à portraits : ChrisM, Kaninscheun, Poupoutain, lBeast, CreveForest, SacDChips, Vaselline et Duncan ;
- dessine pour chacun un visage 8×8 et sa capuche en particules orientées vers le joueur, en taille normale ou agrandie ;
- ajoute les huit recettes sans forme, les objets, les textures et les traductions EN/FR/RU correspondantes ;
- conserve le feu dartifice Nourra et son identifiant existant ;
- conserve les sauvegardes, les données OnlyFun v2 et le protocole réseau 3 ; OnlyFun passe à `0.0.0-alpha.29`.
## `26.2.0-alpha.243`
Pull requests intégrées : [#172](https://git.botsu.net/koka/sanctuary/pulls/172), [#173](https://git.botsu.net/koka/sanctuary/pulls/173) et [#174](https://git.botsu.net/koka/sanctuary/pulls/174).
- ajoute une barre de recherche dans longlet catalogue, avec repli des accents et filtrage local ;
- permet de choisir la grille du catalogue : **3×3**, **4×4** ou **5×5** maximum, mémorisée entre les sessions ;
- affiche le numéro de page du catalogue (`2/5`) à côté de la navigation ;
- conserve la grille 3×3 et les neuf emplacements des ventes flash ;
- resynchronise le texte des panneaux de quêtes après une rotation survenue hors chunk et garantit quun rafraîchissement opérateur génère un nouveau contrat ;
- ajoute le feu dartifice Nourra, dont lexplosion dessine le visage et la capuche du skin en particules, en taille normale ou agrandie ;
- conserve les sauvegardes et les protocoles réseau existants ; Sanctuary passe à `0.0.0-alpha.140` et OnlyFun à `0.0.0-alpha.28`.
## `26.2.0-alpha.242`
Dépendance mise à jour : **MyDisplay `1.0.0` (quatrième build fourni)**.
- remplace le troisième JAR MyDisplay sous un nom distinct afin de forcer sa mise à jour par Packwiz ;
- actualise les composants client de performance, de session multimédia et de lecture WaterMedia ;
- conserve ladaptation du manifeste pour le serveur dédié et les traductions EN/FR/RU ;
- conserve WaterMedia `3.0.0.23`, ses binaires `3.0.0.6`, les écrans existants, les sauvegardes et les protocoles ;
- ne modifie aucun des dix modules Sanctuary.
## `26.2.0-alpha.241`
Dépendance mise à jour : **MyDisplay `1.0.0` (troisième build fourni)**.
- remplace le second JAR MyDisplay sous un nom distinct pour forcer sa mise à jour par Packwiz ;
- accélère la copie ARGB vers RGBA avec un `IntBuffer` et adapte la résolution de décodage WaterMedia à la distance ;
- plafonne et priorise les uploads vidéo par proximité, sans masquer les écrans qui attendent leur prochaine image ;
- suspend les uploads hors champ, coupe le rendu au-delà de 128 blocs et regroupe le rendu du mur ;
- partage Streamlink entre les écrans identiques et échelonne le démarrage des lecteurs ;
- évite de créer les décodeurs audio OpenAL/FFmpeg lorsque l’écran est muet et sécurise les captures F2 ;
- conserve WaterMedia `3.0.0.23`, ses binaires `3.0.0.6`, les écrans existants, les sauvegardes et les protocoles.
## `26.2.0-alpha.240`
Dépendance mise à jour : **MyDisplay `1.0.0` (second build fourni)**.
- remplace le premier JAR MyDisplay par une nouvelle révision sous un nom distinct pour contourner les caches Packwiz ;
- ajoute la sélection côté client de l’écran MyDisplay actuellement visé et met à jour la gestion multimédia associée ;
- conserve WaterMedia `3.0.0.23` et WaterMedia Binaries `3.0.0.6` côté client ;
- conserve ladaptation du manifeste pour le serveur dédié et complète toujours EN/FR avec le russe ;
- rend le résolveur des dépendances Packwiz compatible avec les téléchargements directs privés épinglés ;
- conserve les écrans MyDisplay existants, les dix modules Sanctuary, leurs sauvegardes et leurs protocoles.
## `26.2.0-alpha.239`
Dépendances remplacées : **MyDisplay `1.0.0`**, **WaterMedia `3.0.0.23`** et **WaterMedia Binaries `3.0.0.6`**.
- retire Dream Displays et son second build de test audio du client comme du serveur ;
- ajoute MyDisplay côté client et serveur pour les blocs-écrans, leur configuration et leur synchronisation ;
- ajoute WaterMedia et ses binaires natifs côté client uniquement pour la lecture et le rendu multimédia ;
- adapte seulement le manifeste de MyDisplay afin que ses dépendances client optionnelles ne bloquent pas le serveur dédié, et complète ses traductions EN/FR avec le russe ;
- ne convertit pas les écrans Dream Displays déjà placés, qui doivent être remplacés par des écrans MyDisplay ;
- conserve les dix modules Sanctuary, leurs versions, sauvegardes et protocoles sans changement ;
- garde le module temporaire ZEVENT entièrement absent du workspace et du pack.
## `26.2.0-alpha.238`
Dépendance remplacée : **Dream Displays `1.10.0-dev` (audio-buffer-test, révision 2)**.
- remplace le premier JAR de test Dream Displays par la nouvelle révision fournie ;
- utilise un nouveau nom de fichier afin de contourner les caches et de garantir son téléchargement ;
- distribue ce build côté client et serveur avec ses nouvelles empreintes vérifiées ;
- conserve les dix modules Sanctuary, leurs versions, sauvegardes et protocoles sans changement ;
- garde le module temporaire ZEVENT entièrement absent du workspace et du pack.
## `26.2.0-alpha.237`
Dépendance remplacée : **Dream Displays `1.10.0-dev` (audio-buffer-test)**.
- remplace Dream Displays `1.9.6` par le JAR de test fourni pour corriger la gestion du tampon audio ;
- utilise un nom de fichier distinct afin que Packwiz supprime lancien JAR et ne réutilise pas son cache ;
- distribue le nouveau build côté client et serveur depuis le dépôt de téléchargement Sanctuary avec empreintes vérifiées ;
- conserve les dix modules Sanctuary, leurs versions, sauvegardes et protocoles sans changement ;
- garde le module temporaire ZEVENT entièrement absent du workspace et du pack.
## `26.2.0-alpha.236`
Module temporaire supprimé : **ZEVENT `0.0.0-alpha.3`**.
- retire entièrement le sous-projet, le JAR client/serveur et les identifiants `zevent:*` devenus redondants ;
- conserve Dream Displays `1.9.6`, qui fournit directement les écrans et la lecture des flux vidéo ;
- retire automatiquement `zevent-0.0.0-alpha.3.jar` des installations gérées par Packwiz ;
- ne convertit pas les anciens écrans muraux ZEVENT : ils doivent être remplacés par des écrans Dream Displays ;
- conserve sans changement les sauvegardes, inventaires, monnaies et protocoles des dix modules Sanctuary restants.
## `26.2.0-alpha.235`
Module modifié temporairement : **ZEVENT `0.0.0-alpha.3`**.
- remplace MCEF Modern, dont Chromium ne sait pas décoder les directs Twitch en H.264/AAC, par Dream Displays `1.9.6` et son pipeline vidéo natif ;
- conserve le bloc-écran 9 × 5, les chaînes enregistrées, linterface et le protocole réseau ZEVENT 1 sans migration de monde ;
- réutilise directement la texture GPU décodée dans le renderer ZEVENT, avec une limite de 720p et deux flux visibles au maximum par client ;
- maintient le volume dans la catégorie Disques, latténuation jusqu’à 64 blocs et un seul écran audible à la fois ;
- réessaie automatiquement les chaînes momentanément indisponibles et affiche désormais un message explicite lorsquune chaîne est hors ligne ;
- valide le démarrage client natif sur macOS Apple Silicon ainsi que la résolution dun direct Twitch actif en cinq qualités H.264 jusquen 1080p avec audio AAC.
## `26.2.0-alpha.234`
Module ajouté temporairement : **ZEVENT `0.0.0-alpha.2`**.
- ajoute pour le week-end le bloc-écran ZEVENT, une surface multibloc de 9 × 5 qui diffuse une chaîne Twitch officielle dans le monde ;
- permet de saisir un nom de chaîne ou une URL Twitch par clic droit, puis enregistre et synchronise la chaîne côté serveur ;
- relie le volume à la catégorie Disques, latténue jusqu’à 64 blocs et limite chaque client à deux lecteurs visibles ;
- ajoute MCEF Modern `0.3.3+mc26.2.jcef146.0.10` uniquement côté client, sans imposer Chromium au serveur dédié ;
- conserve les données et protocoles des dix modules Sanctuary existants ; ZEVENT démarre en données v1 et protocole 1, avec mise à jour simultanée du client et du serveur.
## `26.2.0-alpha.233`
Modules modifiés : **Sanctuary `0.0.0-alpha.139`** et **Red-Stoner `0.0.0-alpha.20`**.
+25 -4
View File
@@ -1,6 +1,6 @@
# Sanctuary 26.2
Workspace Fabric multi-projet de Sanctuary 2.0. Il construit les dix JAR du pack et le mod événementiel ZEVENT distribué à part, sur Minecraft 26.2, Fabric Loader 0.19.3, Fabric API 0.159.0+26.2, Loom 1.17, Gradle 9.5.1 et Java 25.
Workspace Fabric multi-projet de Sanctuary 2.0. Il construit dix JAR, sur Minecraft 26.2, Fabric Loader 0.19.3, Fabric API 0.159.0+26.2, Loom 1.17, Gradle 9.5.1 et Java 25.
Auteur : **KOKA99CAB**
Licence : **GNU GPL 3.0 or Later** (`GPL-3.0-or-later`)
@@ -19,11 +19,32 @@ Licence : **GNU GPL 3.0 or Later** (`GPL-3.0-or-later`)
| `sanctuary` | Sanctuary | progression et aventure | progression globale, classements publics, Atlas et monde principal flottant |
| `events` | Events | événements serveur | socle vide |
| `masterkey` | MasterKey | administration et diagnostic | laboratoire minimal |
| `zevent` | ZEVENT | diffusion Twitch temporaire | écran mural 9×5, hors pack principal |
Les descriptions FR/EN/RU sont déclarées dans chaque `fabric.mod.json` et dans les fichiers de langue. Les images fournies servent d'icônes Fabric de leur module.
Version courante du pack distribué : **`26.2.0-alpha.233`**. Cette release distribue Another World **`0.0.0-alpha.58`**, Sanctuary **`0.0.0-alpha.139`** et Red-Stoner **`0.0.0-alpha.20`** ; les sept autres modules conservent leur version issue de l'alpha.231.
Version courante du pack distribué : **`26.2.0-alpha.244`**. Cette release conserve les dix modules Sanctuary, notamment Another World **`0.0.0-alpha.58`**, Sanctuary **`0.0.0-alpha.140`**, OnlyFun **`0.0.0-alpha.29`** et Red-Stoner **`0.0.0-alpha.20`**, garde ZEVENT entièrement retiré et met à jour MyDisplay **`1.0.0`** vers le quatrième build fourni, avec WaterMedia **`3.0.0.23`** et WaterMedia Binaries **`3.0.0.6`**.
Lalpha.244 intègre la PR #176. OnlyFun ajoute huit feux dartifice à portraits pour ChrisM, Kaninscheun, Poupoutain, lBeast, CreveForest, SacDChips, Vaselline et Duncan. Chaque explosion reproduit le visage et la capuche de son skin en particules orientées vers le joueur, en taille normale ou agrandie. Les recettes, objets, textures et traductions EN/FR/RU sont inclus, tandis que Nourra, les sauvegardes et le protocole restent compatibles.
Lalpha.243 intègre les PR #172, #173 et #174. Le catalogue du Shop gagne une recherche tolérante aux accents, des grilles 3×3, 4×4 et 5×5 mémorisées ainsi quun compteur de pages, sans modifier les ventes flash. Les panneaux de quêtes resynchronisent leur texte après une rotation hors chunk et les rafraîchissements opérateur produisent bien un nouveau contrat. OnlyFun ajoute enfin le feu dartifice Nourra, dont lexplosion dessine le visage et la capuche du skin en particules. Les sauvegardes et protocoles restent compatibles.
Lalpha.242 remplace le troisième JAR MyDisplay par `mydisplay-1.0.0-sanctuary-4.jar`. Cette révision actualise exclusivement le pipeline client de performance, de session et de lecture WaterMedia, sans changer les blocs, les données ni le protocole réseau. Le manifeste reste compatible avec le serveur dédié, les traductions EN/FR/RU sont alignées, les écrans déjà placés restent compatibles et le nom distinct force Packwiz à retirer le build précédent.
Lalpha.241 remplace le second JAR MyDisplay par `mydisplay-1.0.0-sanctuary-3.jar`. Le rendu garde tous les écrans visibles tout en réduisant la charge client : copie vidéo ARGB vers RGBA par `IntBuffer`, résolution WaterMedia adaptative selon la distance, priorité et budget des uploads, gel de limage hors champ, distance de rendu limitée à 128 blocs, rendu mural groupé et démarrage échelonné. Les flux Streamlink identiques sont partagés, laudio muet ne crée plus de décodeur OpenAL/FFmpeg et les captures F2 réinitialisent proprement le tampon en suspendant les uploads. Le manifeste reste compatible avec le serveur dédié, les traductions EN/FR/RU sont alignées et les écrans déjà placés restent compatibles.
Lalpha.240 remplace le premier JAR MyDisplay par `mydisplay-1.0.0-sanctuary-2.jar`, dont le nom distinct force Packwiz à télécharger le nouveau build et à supprimer le précédent. Cette révision ajoute notamment la sélection côté client dun écran MyDisplay visé. Le manifeste conserve ladaptation serveur dédié et les traductions EN/FR/RU restent alignées. Les écrans MyDisplay déjà placés restent compatibles.
Lalpha.239 distribue MyDisplay côté client et serveur, et réserve WaterMedia ainsi que ses binaires natifs au client. Packwiz retire automatiquement Dream Displays ; ses anciens écrans déjà posés ne sont pas convertis et doivent être remplacés par des écrans MyDisplay.
Lalpha.238 distribue la seconde révision `dreamdisplays-fabric-26.2-audio-buffer-test-2.jar` côté client et serveur.
Lalpha.237 remplace Dream Displays `1.9.6` par le premier build de test `1.10.0-dev` destiné à corriger la gestion du tampon audio.
Lalpha.236 supprime le JAR et le code ZEVENT devenus redondants. Packwiz retire automatiquement `zevent-0.0.0-alpha.3.jar` du client et du serveur, tandis que Dream Displays reste installé des deux côtés. Les écrans `zevent:zevent_screen` et leurs chaînes enregistrées ne sont pas convertibles : ils doivent être remplacés par des écrans Dream Displays. Les sauvegardes et données des dix autres modules restent inchangées.
Lalpha.235 corrige l’écran noir de ZEVENT. MCEF chargeait correctement linterface et les liens Twitch, mais son Chromium ne prenait pas en charge les codecs des directs. L’écran utilise désormais le résolveur HLS et le décodeur natif de Dream Displays, conserve ses options et sa synchronisation serveur, réessaie automatiquement un flux indisponible et indique clairement lorsquune chaîne est hors ligne. Le volume suit toujours la catégorie Disques, décroît jusqu’à 64 blocs et seul l’écran visible le plus proche reste audible.
Lalpha.234 ajoute pour le week-end l’écran Twitch ZEVENT de 9 × 5 blocs. Un clic droit permet de choisir une chaîne officielle ; la sélection est enregistrée par le serveur, tandis que limage et le son sont rendus localement avec MCEF Modern. Le volume suit la catégorie Disques et décroît jusqu’à 64 blocs. Les mondes et les dix modules Sanctuary existants restent compatibles ; le client et le serveur doivent toutefois recevoir le JAR ZEVENT, et seul le client reçoit MCEF.
Lalpha.233 remplace le bloc-écran des trophées par un véritable panneau Vanilla en chêne pâle, posable au sol ou au mur. Son éditeur permet de rechercher une statistique, composer jusqu’à seize pages et choisir laffichage des noms et valeurs ; le propriétaire ou un opérateur garde le contrôle de sa configuration tandis que le changement de page reste public. Litem `redstoner:trophy_panel` conserve son ID et les anciens panneaux posés sont migrés automatiquement. Les classements affichent désormais le vrai pseudo connu du joueur au lieu dun fragment dUUID. Les sauvegardes Sanctuary restent en données v15/protocole 24 ; Red-Stoner passe aux données v5 et au protocole 9, ce qui exige la même version côté client et serveur.
@@ -419,7 +440,7 @@ Le menu pause est lui aussi possédé par Sanctuary. Il ne conserve que Retour a
## Versions du modpack et mises à jour Modrinth
`pack_version` dans `gradle.properties` est la version du pack complet. `mod_version=0.0.0-alpha.0` et `module_lifecycle=standby` sont les valeurs par défaut des modules en veille ; les propriétés `<module>_version` et `<module>_lifecycle` nactivent et ne font avancer que le module concerné. Le contrat de distribution est dans `pack/release.json`. Le sous-projet `zevent` reste volontairement hors de `moduleIds` et des archives du pack : son JAR et sa dépendance cliente MCEF sont distribués séparément pour l’événement.
`pack_version` dans `gradle.properties` est la version du pack complet. `mod_version=0.0.0-alpha.0` et `module_lifecycle=standby` sont les valeurs par défaut des modules en veille ; les propriétés `<module>_version` et `<module>_lifecycle` nactivent et ne font avancer que le module concerné. Le contrat de distribution est dans `pack/release.json`. Depuis lalpha.236, le module temporaire `zevent` nest plus construit ni distribué ; lalpha.240 utilise le second build MyDisplay fourni et son moteur client WaterMedia à la place de Dream Displays.
La version globale du pack nest pas injectée dans les JAR inchangés : leur contenu et leur numéro restent immuables. Le lanceur possède la composition exacte du pack, tandis que chaque manifest conserve la version effective de son module.
+940 -280
View File
File diff suppressed because it is too large Load Diff
+3 -5
View File
@@ -26,11 +26,9 @@ masterkey_version=0.0.0-alpha.9
masterkey_lifecycle=active
iliketomoveit_version=0.0.0-alpha.18
iliketomoveit_lifecycle=active
onlyfun_version=0.0.0-alpha.27
onlyfun_version=0.0.0-alpha.29
onlyfun_lifecycle=active
sanctuary_version=0.0.0-alpha.139
sanctuary_version=0.0.0-alpha.140
sanctuary_lifecycle=active
zevent_version=0.0.0-alpha.1
zevent_lifecycle=active
pack_version=26.2.0-alpha.233
pack_version=26.2.0-alpha.244
maven_group=fr.koka99cab.sanctuary26
+1
View File
@@ -1261,6 +1261,7 @@ tasks.named("processResources") {
tasks.named("sourcesJar") {
dependsOn tasks.named("generateAgricultureResources")
dependsOn tasks.named("generateCanapliaResources")
}
tasks.register("verifyCulinaryReliability") {
+174
View File
@@ -0,0 +1,174 @@
from pathlib import Path
from PIL import Image
TRANSPARENT = -1
PEOPLE = [
("chrism", "ChrisM", "red_dye"),
("kaninscheun", "Kaninscheun", "yellow_dye"),
("poupoutain", "Poupoutain", "light_gray_dye"),
("lbeast", "lBeast", "brown_dye"),
("creveforest", "CreveForest", "green_dye"),
("sacdchips", "SacDChips", "orange_dye"),
("vaselline", "Vaselline", "white_dye"),
("duncan", "Duncan", "pink_dye"),
]
ROOT = Path(__file__).resolve().parents[2]
def rgb(pixel):
red, green, blue, _alpha = pixel
return (red << 16) | (green << 8) | blue
def extract(path):
image = Image.open(path).convert("RGBA")
face = []
hood = []
hood_top = []
inner_top = []
for y in range(8, 16):
for x in range(8, 16):
face.append(rgb(image.getpixel((x, y))))
for y in range(8, 16):
for x in range(40, 48):
red, green, blue, alpha = image.getpixel((x, y))
hood.append(TRANSPARENT if alpha < 128 else (red << 16) | (green << 8) | blue)
for y in range(6, 8):
for x in range(8, 16):
inner_top.append(rgb(image.getpixel((x, y))))
for y in range(6, 8):
for x in range(40, 48):
red, green, blue, alpha = image.getpixel((x, y))
if alpha < 128:
hood_top.append(inner_top[(y - 6) * 8 + (x - 40)])
else:
hood_top.append((red << 16) | (green << 8) | blue)
return face, hood, hood_top
def fmt(values):
lines = []
for index in range(0, len(values), 8):
chunk = values[index:index + 8]
parts = ["-1" if value == TRANSPARENT else f"0x{value:06X}" for value in chunk]
comma = "," if index + 8 < len(values) else ""
lines.append("\t\t" + ", ".join(parts) + comma)
return "\n".join(lines)
NOURRA_FACE = [
0x000000, 0x020202, 0x0A0A0A, 0x111111, 0x111111, 0x0A0A0A, 0x020202, 0x000000,
0x020202, 0x0A0A0A, 0x111111, 0x181818, 0x181818, 0x111111, 0x0A0A0A, 0x020202,
0x0A0A0A, 0x111111, 0x181818, 0x212121, 0x212121, 0x181818, 0x111111, 0x0A0A0A,
0x111111, 0x181818, 0x212121, 0x2B2B2B, 0x2B2B2B, 0x212121, 0x181818, 0x111111,
0x111111, 0x000000, 0x000000, 0xFFF0E1, 0xFFF0E1, 0x000000, 0x000000, 0x111111,
0x0A0A0A, 0xFFFFFF, 0x4A9DFC, 0xFDE8D7, 0xFFF0E1, 0x4A9DFC, 0xFFFFFF, 0x0A0A0A,
0x020202, 0xFFFFFF, 0x4A9DFC, 0xFDE8D7, 0xFDE8D7, 0x4A9DFC, 0xFFFFFF, 0x020202,
0x000000, 0xF0C6A5, 0xF7D5B9, 0xFCE1CA, 0xFCE1CA, 0xF7D5B9, 0xF0C6A5, 0x000000,
]
NOURRA_HOOD = [
0x000000, 0x0D0D0D, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0x0D0D0D, 0x000000,
0x0D0D0D, 0x707070, 0xD8D8D8, 0xE5E5E5, 0xE5E5E5, 0xD8D8D8, 0x707070, 0x0D0D0D,
0xCBCBCB, 0xD8D8D8, 0xFFFFFF, 0x212121, 0x212121, 0x181818, 0xD8D8D8, 0xCBCBCB,
0xD8D8D8, 0x181818, 0x212121, 0x2B2B2B, 0x2B2B2B, 0x212121, 0x181818, 0xD8D8D8,
0x111111, 0x181818, 0x212121, 0x2B2B2B, TRANSPARENT, TRANSPARENT, TRANSPARENT, 0x111111,
0x0A0A0A, 0x111111, 0x181818, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
0x020202, 0x0A0A0A, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, 0x020202, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
]
NOURRA_TOP = [
0xBEBEBE, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0xBEBEBE,
0xB8B8B8, 0xB8B8B8, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0xB8B8B8, 0xB8B8B8,
]
def main():
entries = [("nourra", NOURRA_FACE, NOURRA_HOOD, NOURRA_TOP)]
for ident, _display, _dye in PEOPLE:
face, hood, top = extract(ROOT / "art" / "skins" / f"{ident}.png")
entries.append((ident, face, hood, top))
print(ident, "hood opaque", sum(1 for value in hood if value != TRANSPARENT))
out = ROOT / "src" / "main" / "java" / "fr" / "koka99cab" / "sanctuary26" / "onlyfun" / "item" / "SkinFacePortrait.java"
chunks = [
"package fr.koka99cab.sanctuary26.onlyfun.item;\n\n",
"import net.minecraft.world.item.ItemStack;\n\n",
"/** Baked 8x8 inner face, hood overlay and hood-top strip for each skin firework. */\n",
"public enum SkinFacePortrait {\n",
]
for index, (ident, face, hood, top) in enumerate(entries):
comma = "," if index + 1 < len(entries) else ";"
chunks.append(
f"\t{ident.upper()}(\"{ident}\", new int[] {{\n{fmt(face)}\n\t}}, new int[] {{\n{fmt(hood)}\n\t}}, new int[] {{\n{fmt(top)}\n\t}}){comma}\n"
)
chunks.append("""
public static final int WIDTH = 8;
public static final int HEIGHT = 8;
public static final int TRANSPARENT = -1;
private final String id;
private final int[] face;
private final int[] hood;
private final int[] hoodTop;
SkinFacePortrait(String id, int[] face, int[] hood, int[] hoodTop) {
this.id = id;
this.face = face;
this.hood = hood;
this.hoodTop = hoodTop;
}
public String id() {
return id;
}
public String itemId() {
return id + \"_firework\";
}
public int[] face() {
return face;
}
public int[] hood() {
return hood;
}
public int[] hoodTop() {
return hoodTop;
}
public int opaqueHoodPixels() {
int count = 0;
for (int color : hood) {
if (color != TRANSPARENT) count++;
}
return count;
}
public int[] portrait() {
int[] pixels = new int[WIDTH * (HEIGHT + 2)];
System.arraycopy(hoodTop, 0, pixels, 0, hoodTop.length);
for (int index = 0; index < face.length; index++) {
pixels[hoodTop.length + index] = hood[index] == TRANSPARENT ? face[index] : hood[index];
}
return pixels;
}
public int portraitHeight() {
return HEIGHT + hoodTop.length / WIDTH;
}
public static SkinFacePortrait of(ItemStack stack) {
if (stack.isEmpty() || !(stack.getItem() instanceof SkinFaceFireworkItem item)) return null;
return item.portrait();
}
}
""")
out.write_text("".join(chunks), encoding="utf-8")
print("wrote", out)
if __name__ == "__main__":
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,47 @@
from pathlib import Path
IDS = [
("chrism", "red_dye"),
("kaninscheun", "yellow_dye"),
("poupoutain", "light_gray_dye"),
("lbeast", "brown_dye"),
("creveforest", "green_dye"),
("sacdchips", "orange_dye"),
("vaselline", "white_dye"),
("duncan", "pink_dye"),
]
ROOT = Path(__file__).resolve().parents[2] / "src" / "main" / "resources"
def main():
for ident, dye in IDS:
(ROOT / "assets" / "onlyfun" / "items" / f"{ident}_firework.json").write_text(
'{\n\t"model": {\n\t\t"type": "minecraft:model",\n\t\t"model": "onlyfun:item/%s_firework"\n\t}\n}\n' % ident,
encoding="utf-8",
)
(ROOT / "assets" / "onlyfun" / "models" / "item" / f"{ident}_firework.json").write_text(
'{\n\t"parent": "minecraft:item/generated",\n\t"textures": {\n\t\t"layer0": "onlyfun:item/%s_firework"\n\t}\n}\n' % ident,
encoding="utf-8",
)
(ROOT / "data" / "onlyfun" / "recipe" / f"{ident}_firework.json").write_text(
"""{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:firework_rocket",
"minecraft:%s",
"minecraft:paper"
],
"result": {
"count": 1,
"id": "onlyfun:%s_firework"
}
}
""" % (dye, ident),
encoding="utf-8",
)
print("wrote", ident)
if __name__ == "__main__":
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

@@ -0,0 +1,102 @@
package fr.koka99cab.sanctuary26.onlyfun.client;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFacePortrait;
import fr.koka99cab.sanctuary26.onlyfun.mixin.client.ParticleLifetimeAccessor;
import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.Particle;
import net.minecraft.core.particles.ColorParticleOption;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
import net.minecraft.world.phys.Vec3;
/** Paints the baked skin as a firework burst of coloured sparks, facing the player. */
public final class SkinFaceFireworkClient {
public static final double PIXEL = 1.85D;
public static final double LARGE_PIXEL = 2.7D;
public static final float SPARK_SIZE = 3.6F;
public static final float LARGE_SPARK_SIZE = 5.15F;
public static final int SPARK_LIFETIME_TICKS = 38;
private static final int SUBPIXELS = 2;
private SkinFaceFireworkClient() {
}
public static void explode(FireworkRocketEntity rocket) {
Minecraft minecraft = Minecraft.getInstance();
if (minecraft.level == null || minecraft.particleEngine == null) return;
Vec3 origin = rocket.position();
RandomSource random = rocket.level().getRandom();
minecraft.level.playLocalSound(origin.x, origin.y, origin.z,
SoundEvents.FIREWORK_ROCKET_BLAST, SoundSource.AMBIENT,
20.0F, 0.95F + random.nextFloat() * 0.1F, true);
minecraft.level.addParticle(ColorParticleOption.create(ParticleTypes.FLASH, 0xFFFFFFFF),
origin.x, origin.y, origin.z, 0.0D, 0.0D, 0.0D);
Vec3 look = facing(minecraft, origin);
Vec3 right = look.cross(new Vec3(0.0D, 1.0D, 0.0D));
if (right.lengthSqr() < 1.0E-6D) right = new Vec3(1.0D, 0.0D, 0.0D);
right = right.normalize();
Vec3 up = new Vec3(0.0D, 1.0D, 0.0D);
SkinFacePortrait portrait = SkinFacePortrait.of(rocket.getItem());
if (portrait == null) return;
int[] pixels = portrait.portrait();
int width = SkinFacePortrait.WIDTH;
int height = portrait.portraitHeight();
boolean large = largeBurst(rocket);
double pixel = burstPixel(large);
float sparkSize = burstSparkSize(large);
double midX = (width - 1) / 2.0D;
double midY = (height - 1) / 2.0D;
double step = 1.0D / SUBPIXELS;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int color = visibleSpark(pixels[row * width + col]);
DustParticleOptions dust = new DustParticleOptions(color, sparkSize);
for (int subY = 0; subY < SUBPIXELS; subY++) {
for (int subX = 0; subX < SUBPIXELS; subX++) {
double px = (col - midX) + (subX + 0.5D) * step - 0.5D;
double py = (midY - row) - (subY + 0.5D) * step + 0.5D;
Vec3 offset = right.scale(px * pixel).add(up.scale(py * pixel));
Vec3 pos = origin.add(offset);
Vec3 velocity = offset.scale(0.05D);
Particle spark = minecraft.particleEngine.createParticle(
dust, pos.x, pos.y, pos.z, velocity.x, velocity.y, velocity.z);
if (spark != null) {
((ParticleLifetimeAccessor) spark).onlyfun$setLifetime(SPARK_LIFETIME_TICKS);
}
}
}
}
}
}
public static boolean largeBurst(FireworkRocketEntity rocket) {
return (rocket.getUUID().getLeastSignificantBits() & 1L) != 0L;
}
public static double burstPixel(boolean large) {
return large ? LARGE_PIXEL : PIXEL;
}
public static float burstSparkSize(boolean large) {
return large ? LARGE_SPARK_SIZE : SPARK_SIZE;
}
private static Vec3 facing(Minecraft minecraft, Vec3 origin) {
Vec3 camera = minecraft.gameRenderer.mainCamera().position();
Vec3 horizontal = new Vec3(origin.x - camera.x, 0.0D, origin.z - camera.z);
if (horizontal.lengthSqr() < 1.0E-4D) return new Vec3(0.0D, 0.0D, 1.0D);
return horizontal.normalize();
}
private static int visibleSpark(int rgb) {
int red = rgb >> 16 & 0xFF;
int green = rgb >> 8 & 0xFF;
int blue = rgb & 0xFF;
if (red + green + blue < 24) return 0x2A2A2A;
return rgb;
}
}
@@ -0,0 +1,23 @@
package fr.koka99cab.sanctuary26.onlyfun.item;
import java.util.function.Consumer;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.FireworkRocketItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.TooltipDisplay;
/** Firework rocket that bursts into the baked Nourra skin face. */
public final class NourraFireworkItem extends FireworkRocketItem {
public NourraFireworkItem(Properties properties) {
super(properties);
}
@Override
public void appendHoverText(ItemStack stack, TooltipContext context, TooltipDisplay display,
Consumer<Component> tooltip, TooltipFlag flag) {
tooltip.accept(Component.translatable("tooltip.onlyfun.nourra_firework").withStyle(ChatFormatting.AQUA));
super.appendHoverText(stack, context, display, tooltip, flag);
}
}
@@ -0,0 +1,76 @@
package fr.koka99cab.sanctuary26.onlyfun.item;
import java.util.List;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.Fireworks;
/**
* Baked Nourra head (NameMC 6166a439e9354904): inner face, hood overlay, hood top.
* Other skin fireworks reuse the same flight rules via {@link SkinFacePortrait}.
*/
public final class SkinFaceFireworkExplosions {
public static final int WIDTH = 8;
public static final int HEIGHT = 8;
public static final int TRANSPARENT = -1;
/** Inner head front, top-left origin, row-major RGB. */
public static final int[] FACE = {
0x000000, 0x020202, 0x0A0A0A, 0x111111, 0x111111, 0x0A0A0A, 0x020202, 0x000000,
0x020202, 0x0A0A0A, 0x111111, 0x181818, 0x181818, 0x111111, 0x0A0A0A, 0x020202,
0x0A0A0A, 0x111111, 0x181818, 0x212121, 0x212121, 0x181818, 0x111111, 0x0A0A0A,
0x111111, 0x181818, 0x212121, 0x2B2B2B, 0x2B2B2B, 0x212121, 0x181818, 0x111111,
0x111111, 0x000000, 0x000000, 0xFFF0E1, 0xFFF0E1, 0x000000, 0x000000, 0x111111,
0x0A0A0A, 0xFFFFFF, 0x4A9DFC, 0xFDE8D7, 0xFFF0E1, 0x4A9DFC, 0xFFFFFF, 0x0A0A0A,
0x020202, 0xFFFFFF, 0x4A9DFC, 0xFDE8D7, 0xFDE8D7, 0x4A9DFC, 0xFFFFFF, 0x020202,
0x000000, 0xF0C6A5, 0xF7D5B9, 0xFCE1CA, 0xFCE1CA, 0xF7D5B9, 0xF0C6A5, 0x000000
};
/** Hat/hood overlay on the front; {@link #TRANSPARENT} keeps the inner face visible. */
public static final int[] HOOD = {
0x000000, 0x0D0D0D, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0x0D0D0D, 0x000000,
0x0D0D0D, 0x707070, 0xD8D8D8, 0xE5E5E5, 0xE5E5E5, 0xD8D8D8, 0x707070, 0x0D0D0D,
0xCBCBCB, 0xD8D8D8, 0xFFFFFF, 0x212121, 0x212121, 0x181818, 0xD8D8D8, 0xCBCBCB,
0xD8D8D8, 0x181818, 0x212121, 0x2B2B2B, 0x2B2B2B, 0x212121, 0x181818, 0xD8D8D8,
0x111111, 0x181818, 0x212121, 0x2B2B2B, TRANSPARENT, TRANSPARENT, TRANSPARENT, 0x111111,
0x0A0A0A, 0x111111, 0x181818, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
0x020202, 0x0A0A0A, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, 0x020202, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT
};
/** Front edge of the hood top (skin overlay top rows closest to the face). */
public static final int[] HOOD_TOP = {
0xBEBEBE, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0xBEBEBE,
0xB8B8B8, 0xB8B8B8, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0xB8B8B8, 0xB8B8B8
};
/** Vanilla flight 3 minus half a gunpowder step, so the burst sits between 2 and 3. */
public static final int LIFETIME_TRIM_TICKS = 5;
private SkinFaceFireworkExplosions() {
}
public static Fireworks defaultFireworks() {
return new Fireworks(3, List.of());
}
public static int midFlightLifetime(int vanillaLifetime) {
return Math.max(1, vanillaLifetime - LIFETIME_TRIM_TICKS);
}
public static boolean isSkinFaceRocket(ItemStack stack) {
return SkinFacePortrait.of(stack) != null;
}
public static int opaqueHoodPixels() {
int count = 0;
for (int color : HOOD) {
if (color != TRANSPARENT) count++;
}
return count;
}
/** Hood top (2 rows) then the 8×8 face with the hood overlay composited. */
public static int[] portrait() {
return SkinFacePortrait.NOURRA.portrait();
}
public static int portraitHeight() {
return SkinFacePortrait.NOURRA.portraitHeight();
}
}
@@ -0,0 +1,30 @@
package fr.koka99cab.sanctuary26.onlyfun.item;
import java.util.function.Consumer;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.FireworkRocketItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.TooltipDisplay;
/** Firework rocket that bursts into a baked skin face. */
public final class SkinFaceFireworkItem extends FireworkRocketItem {
private final SkinFacePortrait portrait;
public SkinFaceFireworkItem(SkinFacePortrait portrait, Properties properties) {
super(properties);
this.portrait = portrait;
}
public SkinFacePortrait portrait() {
return portrait;
}
@Override
public void appendHoverText(ItemStack stack, TooltipContext context, TooltipDisplay display,
Consumer<Component> tooltip, TooltipFlag flag) {
tooltip.accept(Component.translatable("tooltip.onlyfun.skin_face_firework").withStyle(ChatFormatting.AQUA));
super.appendHoverText(stack, context, display, tooltip, flag);
}
}
@@ -0,0 +1,269 @@
package fr.koka99cab.sanctuary26.onlyfun.item;
import net.minecraft.world.item.ItemStack;
/** Baked 8x8 inner face, hood overlay and hood-top strip for each skin firework. */
public enum SkinFacePortrait {
NOURRA("nourra", new int[] {
0x000000, 0x020202, 0x0A0A0A, 0x111111, 0x111111, 0x0A0A0A, 0x020202, 0x000000,
0x020202, 0x0A0A0A, 0x111111, 0x181818, 0x181818, 0x111111, 0x0A0A0A, 0x020202,
0x0A0A0A, 0x111111, 0x181818, 0x212121, 0x212121, 0x181818, 0x111111, 0x0A0A0A,
0x111111, 0x181818, 0x212121, 0x2B2B2B, 0x2B2B2B, 0x212121, 0x181818, 0x111111,
0x111111, 0x000000, 0x000000, 0xFFF0E1, 0xFFF0E1, 0x000000, 0x000000, 0x111111,
0x0A0A0A, 0xFFFFFF, 0x4A9DFC, 0xFDE8D7, 0xFFF0E1, 0x4A9DFC, 0xFFFFFF, 0x0A0A0A,
0x020202, 0xFFFFFF, 0x4A9DFC, 0xFDE8D7, 0xFDE8D7, 0x4A9DFC, 0xFFFFFF, 0x020202,
0x000000, 0xF0C6A5, 0xF7D5B9, 0xFCE1CA, 0xFCE1CA, 0xF7D5B9, 0xF0C6A5, 0x000000
}, new int[] {
0x000000, 0x0D0D0D, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0x0D0D0D, 0x000000,
0x0D0D0D, 0x707070, 0xD8D8D8, 0xE5E5E5, 0xE5E5E5, 0xD8D8D8, 0x707070, 0x0D0D0D,
0xCBCBCB, 0xD8D8D8, 0xFFFFFF, 0x212121, 0x212121, 0x181818, 0xD8D8D8, 0xCBCBCB,
0xD8D8D8, 0x181818, 0x212121, 0x2B2B2B, 0x2B2B2B, 0x212121, 0x181818, 0xD8D8D8,
0x111111, 0x181818, 0x212121, 0x2B2B2B, -1, -1, -1, 0x111111,
0x0A0A0A, 0x111111, 0x181818, -1, -1, -1, -1, -1,
0x020202, 0x0A0A0A, -1, -1, -1, -1, -1, -1,
-1, 0x020202, -1, -1, -1, -1, -1, -1
}, new int[] {
0xBEBEBE, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0xBEBEBE,
0xB8B8B8, 0xB8B8B8, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0xB8B8B8, 0xB8B8B8
}),
CHRISM("chrism", new int[] {
0xF59589, 0xF89C90, 0xFEA398, 0xFEA398, 0xFCA297, 0xF89B8F, 0xF2978B, 0xF49488,
0xF59589, 0xFB9D91, 0xFA9D92, 0xFCA095, 0xFCA095, 0xFCA095, 0xF89B8F, 0xF49488,
0xF59589, 0xFB9A8E, 0xFD9E93, 0xFDA196, 0xFDA196, 0xFD9E93, 0xFB9A8E, 0xFF988B,
0xFE8B7E, 0xFE8B7E, 0xFE8B7E, 0xFE8B7E, 0xFE8B7E, 0xFE8B7E, 0xFE8B7E, 0xFE8B7E,
0xFE8B7E, 0x000000, 0xFE8B7E, 0xFE8B7E, 0xFE8B7E, 0xFE8B7E, 0x000000, 0xFD8B7D,
0xF67D70, 0xF68375, 0xFA897C, 0xFE8B7E, 0xFE8B7E, 0xFE8B7E, 0xF68375, 0xF67D70,
0x000000, 0xFFFFFF, 0x000000, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0x000000,
0xEE6858, 0x000000, 0x000000, 0xFF1408, 0xFC0C00, 0x000000, 0x000000, 0xEE6858
}, new int[] {
0xFF8071, 0xFF8274, 0xFF796A, 0xFA7B6C, 0xFE7E70, 0xF97769, 0xFB7F70, 0xFF7A6B,
0xF56E5E, 0xFF9185, 0xFF8375, 0xFF9084, 0xFB8A7E, 0xFE9286, 0xFF8B7E, 0xF68274,
0xFA7263, 0xFF9488, 0xFE9084, 0xFE8C80, 0xFF9286, 0xF77F72, 0xF89084, 0xF97465,
0xFF8273, 0xFF8E82, 0xFA8D81, 0xF68477, 0xF69186, 0xFF8F83, 0xFD9286, 0xFF7767,
0xFF8173, 0xFB887B, 0xFC9084, 0xFF9083, 0xFF978B, 0xFF8375, 0xF48A7E, 0xFF8173,
0xFD7D6E, -1, 0xF5897D, 0xFF8C7F, 0xF3877A, 0xFF897C, -1, 0xFF7060,
0xFC7667, 0xFF897B, 0xFF9488, -1, -1, 0xF37F72, 0xF89186, 0xFD7566,
0xFF8375, 0xFF7F70, 0xF88072, 0xF88173, 0xFC7869, 0xFF8173, 0xFF8678, 0xFF7C6D
}, new int[] {
0xF97566, 0xFF8C7F, 0xF4867A, 0xFF877A, 0xF4786A, 0xFF8678, 0xF97E70, 0xFF7F71,
0xFF897B, 0xFB7566, 0xFF8C7F, 0xF77F72, 0xF77769, 0xFF7C6E, 0xF88578, 0xF87B6D
}),
KANINSCHEUN("kaninscheun", new int[] {
0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D,
0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xFFCC66, 0xEAD900, 0xEDDF2D,
0xEAD900, 0xEDDF2D, 0xEAD900, 0xFFCC66, 0xFFCC66, 0xFFCC66, 0xFFCC66, 0xEDDF2D,
0xEAD900, 0x000000, 0xFFCC66, 0xFFCC66, 0xFFCC66, 0xFFCC66, 0x000000, 0xEDDF2D,
0xEAD900, 0x000000, 0x000000, 0xFFCC66, 0xFFCC66, 0x000000, 0x000000, 0xEDDF2D,
0xEAD900, 0xFFCC66, 0xFFCC66, 0xFFCC66, 0xFFCC66, 0xFFCC66, 0xFFCC66, 0xEDDF2D,
0xEAD900, 0xFFCC66, 0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xFFCC66, 0xEDDF2D,
0xEAD900, 0xEAD900, 0xEDDF2D, 0xFFCC66, 0xFFCC66, 0xEAD900, 0xEAD900, 0xEDDF2D
}, new int[] {
-1, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D, -1,
0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900,
0xEDDF2D, 0xEAD900, 0xEDDF2D, -1, -1, -1, 0xEDDF2D, 0xEAD900,
0xEDDF2D, 0xEAD900, -1, -1, -1, -1, -1, 0xEAD900,
0xEDDF2D, -1, -1, -1, -1, -1, -1, 0xEAD900,
0xEDDF2D, -1, -1, -1, -1, -1, -1, 0xEAD900,
0xEDDF2D, -1, -1, -1, -1, -1, -1, 0xEAD900,
-1, 0xEAD900, -1, -1, -1, -1, 0xEDDF2D, -1
}, new int[] {
0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900,
0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900, 0xEDDF2D, 0xEAD900
}),
POUPOUTAIN("poupoutain", new int[] {
0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456,
0x4B5456, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0x4B5456,
0x4B5456, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0x342404, 0xE7E6E4, 0x4B5456,
0x4B5456, 0xE7E6E4, 0x342404, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0x4B5456,
0x4B5456, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0x4B5456,
0x4B5456, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0x342404, 0xE0DFDE, 0xE7E6E4, 0x4B5456,
0x4B5456, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0xE7E6E4, 0x4B5456,
0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456
}, new int[] {
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1
}, new int[] {
0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456, 0x4B5456,
0x414B4C, 0x414B4C, 0x414B4C, 0x414B4C, 0x414B4C, 0x414B4C, 0x414B4C, 0x414B4C
}),
LBEAST("lbeast", new int[] {
0x8A6C30, 0x8A6B31, 0x644B1A, 0x5D4B1E, 0x896F37, 0x80672A, 0x614B1A, 0x4C3910,
0x957538, 0x816C34, 0x6D5521, 0x715B20, 0xA88C44, 0x9C7E3B, 0x7F6625, 0x7D6325,
0xB59F6D, 0xC1AC74, 0xC8B378, 0xD0BB7C, 0xD9C382, 0xE7CF8A, 0xD2BC7E, 0xC6A574,
0xB39C6C, 0x2B2324, 0x2B2324, 0xD5BA80, 0xD7BE80, 0x2B2324, 0x2B2324, 0xCBA977,
0xDCC285, 0xBAA0A6, 0x42597E, 0xF1D491, 0xD7BD7F, 0x42597E, 0xBAA0A6, 0xECC48B,
0xEACC8C, 0xFAF3F5, 0x87A6D2, 0xFDDB98, 0xDCBE81, 0x87A6D2, 0xFAF3F5, 0xF1C88E,
0xE4C082, 0xF6D695, 0xFBDD96, 0xFBE197, 0xFBE197, 0xFDDC95, 0xF6D695, 0xEBC288,
0xE9C183, 0xEFC68A, 0xF6D695, 0xFDDC94, 0xFBE197, 0xF6D695, 0xF4C98C, 0xDFB67D
}, new int[] {
0x7E652E, 0xC5A253, 0xC5A253, 0xBB984A, 0xD0A64E, 0xBB984A, 0xC5A253, 0x715B29,
0x947438, 0xEAC269, 0x96772C, 0xB69045, 0xD0A64E, 0xBB984A, 0xC5A253, 0x7E652E,
0xB49043, 0xD0A64E, -1, 0xD0A64E, 0xE4C368, 0xBB984A, 0xD0A64E, 0x8A6E32,
0x96772C, -1, -1, -1, -1, 0xBB9853, 0xD1AD5C, 0xB18D46,
0xD0A64E, -1, -1, -1, -1, -1, -1, 0x9C7D39,
-1, -1, -1, -1, -1, -1, -1, 0x9C7D39,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1
}, new int[] {
0x846932, 0x907336, 0xA18340, 0xAE8E45, 0xAE8E45, 0x886F36, 0x7B6431, 0x6E5A2C,
0x7A6029, 0x886F36, 0x95793B, 0xA18340, 0xA18340, 0x95793B, 0x886F36, 0x7B6431
}),
CREVEFOREST("creveforest", new int[] {
0x2D4C54, 0x2D4C54, 0x2D4C54, 0x2D4C54, 0x355C5E, 0x2D4C54, 0x2D4C54, 0x2D4C54,
0x2D4C54, 0x2D4C54, 0x355C5E, 0x355C5E, 0x355C5E, 0x355C5E, 0x2D4C54, 0x2D4C54,
0x2D4C54, 0x355C5E, 0x79A88F, 0x8FC49B, 0x79A88F, 0x79A88F, 0x355C5E, 0x2D4C54,
0x355C5E, 0x79A88F, 0x8FC49B, 0x9ED69C, 0x8FC49B, 0x8FC49B, 0x79A88F, 0x355C5E,
0x355C5E, 0x1E2E33, 0x1E2E33, 0xB7E8A9, 0x9ED69C, 0x1E2E33, 0x1E2E33, 0x355C5E,
0x1E2E33, 0x9BB0AC, 0x344C46, 0xD3F5BA, 0xB7E8A9, 0x344C46, 0x9BB0AC, 0x1E2E33,
0x2D4C54, 0xEBEBEB, 0x79B8AB, 0xD3F5BA, 0xB7E8A9, 0x79B8AB, 0xEBEBEB, 0x355C5E,
0x2D4C54, 0x8FC49B, 0xB7E8A9, 0x9ED69C, 0xB7E8A9, 0xB7E8A9, 0x8FC49B, 0x355C5E
}, new int[] {
-1, 0x549C62, 0x4D8C64, 0x4D8C64, 0x549C62, 0x3D785D, 0x4D8C64, -1,
0x549C62, 0x549C62, 0x4D8C64, 0x4D8C64, 0x4D8C64, 0x3D785D, 0x4D8C64, 0x549C62,
0x4D8C64, 0x4D8C64, 0x3D785D, 0x3D785D, 0x3D785D, 0x355C5E, 0x3D785D, 0x4D8C64,
0x3D785D, 0x3D785D, 0x355C5E, -1, 0x355C5E, 0x2D4C54, 0x355C5E, 0x3D785D,
0x3D785D, -1, 0x2D4C54, -1, -1, -1, 0x355C5E, 0x3D785D,
0x355C5E, -1, -1, -1, -1, -1, -1, 0x355C5E,
0x2D4C54, -1, -1, -1, -1, -1, -1, 0x2D4C54,
-1, -1, -1, -1, -1, -1, -1, -1
}, new int[] {
0x355C5E, 0x355C5E, 0x355C5E, 0x355C5E, 0x3D785D, 0x355C5E, 0x2D4C54, 0x355C5E,
0x2D4C54, 0x549C62, 0x4D8C64, 0x4D8C64, 0x549C62, 0x3D785D, 0x4D8C64, 0x355C5E
}),
SACDCHIPS("sacdchips", new int[] {
0x3E1A05, 0x421D05, 0x391904, 0x421C06, 0x391904, 0x632C08, 0x3D1B05, 0x552607,
0x421D05, 0x421D05, 0x391904, 0xF1DACD, 0xF2DDD1, 0xEFD7C9, 0xEED4C5, 0x632C08,
0xEBCCBA, 0xECCFBE, 0xEDD1C1, 0xE7C3AE, 0xEDD1C1, 0xEED4C5, 0xF2DDD1, 0xECCFBE,
0xECCFBE, 0x341704, 0x341704, 0xE7C3AE, 0xEBCCBA, 0x341704, 0x341704, 0xF1DACD,
0xEBCCBA, 0xEEEEEE, 0x602F10, 0xEDD1C1, 0xF2DDD1, 0x602F10, 0xEEEEEE, 0xEDD1C1,
0xE7C3AE, 0xEDD1C1, 0xE7C3AE, 0xF2DDD1, 0xF1DACD, 0xEDD1C1, 0xE7C3AE, 0xEDD1C1,
0xE6C0AA, 0xECCFBE, 0xEBCCBA, 0x000000, 0x000000, 0xEDD1C1, 0xEAC9B6, 0xEBCCBA,
0xEAC9B6, 0xECCFBE, 0xF2DDD1, 0xECCFBE, 0xEAC9B6, 0xF1DACD, 0xECCFBE, 0xF1DACD
}, new int[] {
-1, 0x180A02, 0x261003, 0x2B1204, 0x341604, 0x210E03, 0x2F1404, -1,
-1, -1, -1, 0x2B1204, 0x210E03, 0x471E06, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 0x261003, 0x180A02, 0x210E03, 0x1D0C02, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1
}, new int[] {
0x3E1A05, 0x3E1A05, 0x210E03, 0x1D0C02, 0x341604, 0x180A02, 0x2F1404, 0x1D0C02,
0x391805, 0x421C06, 0x421C06, 0x3E1A05, 0x471E06, 0x1D0C02, 0x341604, 0x341604
}),
VASELLINE("vaselline", new int[] {
0xEBE176, 0xEBE176, 0xEBE176, 0xE8DC5F, 0xE8DC5F, 0xE8DC5F, 0xEBE176, 0xEBE176,
0xEBE176, 0xE8DC5F, 0xE8D7B1, 0xE8D7B1, 0xE8D7B1, 0xE8D7B1, 0xE8DC5F, 0xEBE176,
0xE8DC5F, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xE8DC5F,
0xE8DC5F, 0xFFFFFF, 0x6F9EE8, 0xE8D7B1, 0xE8D7B1, 0x6F9EE8, 0xFFFFFF, 0xE8DC5F,
0xE8DC5F, 0xE8D7B1, 0xE8D7B1, 0xEEE1C5, 0xEEE1C5, 0xE8D7B1, 0xE8D7B1, 0xE8DC5F,
0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5,
0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xE2CD9D, 0xE2CD9D, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5,
0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5, 0xEEE1C5
}, new int[] {
0xEBE176, 0xEBE176, 0xE8DC5F, 0xE8DC5F, 0xE8DC5F, 0xE8DC5F, 0xEBE176, 0xEBE176,
0xEBE176, 0xE8DC5F, -1, -1, -1, -1, 0xE8DC5F, 0xEBE176,
0xEBE176, -1, -1, -1, -1, -1, -1, 0xE8DC5F,
0xE8DC5F, -1, -1, -1, -1, -1, -1, 0xE8DC5F,
0xE8DC5F, -1, -1, -1, -1, -1, -1, 0xE8DC5F,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1
}, new int[] {
0xEBE176, 0xE8DC5F, 0xE8DC5F, 0xEBE176, 0xEBE176, 0xE8DC5F, 0xE8DC5F, 0xE8DC5F,
0xEBE176, 0xEBE176, 0xEBE176, 0xEBE176, 0xEBE176, 0xEBE176, 0xEBE176, 0xEBE176
}),
DUNCAN("duncan", new int[] {
0x9D5330, 0x9D5330, 0x9D5330, 0x9D5330, 0x9D5330, 0x9D5330, 0x9D5330, 0x9D5330,
0xFFD6AE, 0xFFD7AE, 0xFFD7AE, 0xFFD6AE, 0xFFD6AE, 0xFED7AE, 0xFFD7AF, 0xFFD7AE,
0xFFD7AE, 0xAD3B11, 0xAC3A10, 0xFFD7AE, 0xFFD7AE, 0xAD3B10, 0xAC3B11, 0xFFD6AF,
0xFED6AF, 0xFEFFFF, 0x783018, 0xFFD7AE, 0xFFD7AE, 0xFEFFFE, 0x793019, 0xFFD6AF,
0xFFD6AE, 0x783018, 0x010000, 0xFFD7AF, 0xFED7AE, 0x010100, 0x783018, 0xFFD7AF,
0xFED7AF, 0xFFD7AE, 0xFFD7AF, 0xFED6AF, 0xFED7AE, 0xFED7AE, 0xFED7AF, 0xFFD6AE,
0xFED7AE, 0xFED7AF, 0xFED7AE, 0xCB97A0, 0xCB96A0, 0xFED7AE, 0xFFD7AE, 0xFFD6AE,
0xFFD7AE, 0xFED7AE, 0xFFD7AE, 0xFFD7AF, 0xFFD7AE, 0xFFD7AE, 0xFFD7AE, 0xFED7AE
}, new int[] {
0x9D5330, 0xBC5D30, 0x9D5330, 0x88492A, 0x9D5330, 0x9D5330, 0x88492A, 0x9D5330,
0x9D5330, -1, 0x88492A, -1, -1, 0xBC5D30, -1, 0x9D5330,
-1, 0xE6AB35, 0xE7AA34, -1, -1, 0xE6AA34, 0xE7AA34, -1,
0xE6AB34, -1, -1, 0xE7AB35, 0xE7AB35, -1, -1, 0xE6AB35,
0xE7AB35, -1, -1, 0xE6AB35, 0xE6AA34, -1, -1, 0xE6AB35,
-1, 0xE6AB34, 0xE6AB35, -1, -1, 0xE6AB35, 0xE6AB34, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1
}, new int[] {
0x9D5330, 0x9D5330, 0x9D5330, 0x9D5330, 0x88492A, 0x9D5330, 0x9D5330, 0x88492A,
0x9D5330, 0x9D5330, 0x9D5330, 0xBC5D30, 0x9D5330, 0x9D5330, 0x9D5330, 0x9D5330
});
public static final int WIDTH = 8;
public static final int HEIGHT = 8;
public static final int TRANSPARENT = -1;
private final String id;
private final int[] face;
private final int[] hood;
private final int[] hoodTop;
SkinFacePortrait(String id, int[] face, int[] hood, int[] hoodTop) {
this.id = id;
this.face = face;
this.hood = hood;
this.hoodTop = hoodTop;
}
public String id() {
return id;
}
public String itemId() {
return id + "_firework";
}
public int[] face() {
return face;
}
public int[] hood() {
return hood;
}
public int[] hoodTop() {
return hoodTop;
}
public int opaqueHoodPixels() {
int count = 0;
for (int color : hood) {
if (color != TRANSPARENT) count++;
}
return count;
}
public int[] portrait() {
int[] pixels = new int[WIDTH * (HEIGHT + 2)];
System.arraycopy(hoodTop, 0, pixels, 0, hoodTop.length);
for (int index = 0; index < face.length; index++) {
pixels[hoodTop.length + index] = hood[index] == TRANSPARENT ? face[index] : hood[index];
}
return pixels;
}
public int portraitHeight() {
return HEIGHT + hoodTop.length / WIDTH;
}
public static SkinFacePortrait of(ItemStack stack) {
if (stack.isEmpty()) return null;
if (stack.getItem() instanceof SkinFaceFireworkItem item) return item.portrait();
if (stack.getItem() instanceof NourraFireworkItem) return NOURRA;
return null;
}
}
@@ -0,0 +1,25 @@
package fr.koka99cab.sanctuary26.onlyfun.mixin;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFaceFireworkExplosions;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(FireworkRocketEntity.class)
public abstract class FireworkRocketLifetimeMixin {
@Shadow
private int life;
@Shadow
private int lifetime;
@Inject(method = "tick", at = @At("HEAD"))
private void onlyfun$midFlightLifetime(CallbackInfo callback) {
if (this.life != 0) return;
FireworkRocketEntity rocket = (FireworkRocketEntity) (Object) this;
if (!SkinFaceFireworkExplosions.isSkinFaceRocket(rocket.getItem())) return;
this.lifetime = SkinFaceFireworkExplosions.midFlightLifetime(this.lifetime);
}
}
@@ -0,0 +1,22 @@
package fr.koka99cab.sanctuary26.onlyfun.mixin.client;
import fr.koka99cab.sanctuary26.onlyfun.client.SkinFaceFireworkClient;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFaceFireworkExplosions;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(FireworkRocketEntity.class)
public abstract class FireworkRocketEntityMixin {
@Inject(method = "handleEntityEvent", at = @At("HEAD"), cancellable = true)
private void onlyfun$drawSkinFace(byte id, CallbackInfo callback) {
if (id != 17) return;
FireworkRocketEntity rocket = (FireworkRocketEntity) (Object) this;
if (!rocket.level().isClientSide()) return;
if (!SkinFaceFireworkExplosions.isSkinFaceRocket(rocket.getItem())) return;
SkinFaceFireworkClient.explode(rocket);
callback.cancel();
}
}
@@ -0,0 +1,11 @@
package fr.koka99cab.sanctuary26.onlyfun.mixin.client;
import net.minecraft.client.particle.Particle;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(Particle.class)
public interface ParticleLifetimeAccessor {
@Accessor("lifetime")
void onlyfun$setLifetime(int lifetime);
}
@@ -8,6 +8,7 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
public final class OnlyFunCreativeTab {
@@ -28,6 +29,10 @@ public final class OnlyFunCreativeTab {
output.accept(OnlyFunRegistries.CAPE_LOOTBOX);
output.accept(OnlyFunRegistries.SPAWN_EGG_LOOTBOX);
output.accept(OnlyFunRegistries.ANOMALY_LOOTBOX);
output.accept(OnlyFunRegistries.NOURRA_FIREWORK);
for (Item firework : OnlyFunRegistries.skinFireworks()) {
if (firework != OnlyFunRegistries.NOURRA_FIREWORK) output.accept(firework);
}
output.accept(AbjectionRegistries.SHIT_BALL);
output.accept(AbjectionRegistries.SHIT_CUBE_ITEM);
})
@@ -3,6 +3,10 @@ package fr.koka99cab.sanctuary26.onlyfun.registry;
import fr.koka99cab.sanctuary26.onlyfun.OnlyFunMod;
import fr.koka99cab.sanctuary26.onlyfun.block.LuckyBlockBlock;
import fr.koka99cab.sanctuary26.onlyfun.item.LootboxItem;
import fr.koka99cab.sanctuary26.onlyfun.item.NourraFireworkItem;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFaceFireworkExplosions;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFaceFireworkItem;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFacePortrait;
import fr.koka99cab.sanctuary26.onlyfun.loot.OnlyFunGenerator;
import fr.koka99cab.sanctuary26.onlyfun.loot.OnlyFunSource;
import java.util.function.Function;
@@ -32,6 +36,17 @@ public final class OnlyFunRegistries {
public static final Item CAPE_LOOTBOX = registerLootbox("cape_lootbox", OnlyFunSource.CAPE_LOOTBOX);
public static final Item SPAWN_EGG_LOOTBOX = registerLootbox("spawn_egg_lootbox", OnlyFunSource.SPAWN_EGG_LOOTBOX);
public static final Item ANOMALY_LOOTBOX = registerLootbox("anomaly_lootbox", OnlyFunSource.ANOMALY_LOOTBOX);
public static final Item NOURRA_FIREWORK = registerItem("nourra_firework",
properties -> new NourraFireworkItem(properties.stacksTo(64)
.component(DataComponents.FIREWORKS, SkinFaceFireworkExplosions.defaultFireworks())));
public static final Item CHRISM_FIREWORK = registerSkinFirework(SkinFacePortrait.CHRISM);
public static final Item KANINSCHEUN_FIREWORK = registerSkinFirework(SkinFacePortrait.KANINSCHEUN);
public static final Item POUPOUTAIN_FIREWORK = registerSkinFirework(SkinFacePortrait.POUPOUTAIN);
public static final Item LBEAST_FIREWORK = registerSkinFirework(SkinFacePortrait.LBEAST);
public static final Item CREVEFOREST_FIREWORK = registerSkinFirework(SkinFacePortrait.CREVEFOREST);
public static final Item SACDCHIPS_FIREWORK = registerSkinFirework(SkinFacePortrait.SACDCHIPS);
public static final Item VASELLINE_FIREWORK = registerSkinFirework(SkinFacePortrait.VASELLINE);
public static final Item DUNCAN_FIREWORK = registerSkinFirework(SkinFacePortrait.DUNCAN);
private static boolean initialized;
private OnlyFunRegistries() {
@@ -47,6 +62,13 @@ public final class OnlyFunRegistries {
};
}
public static Item[] skinFireworks() {
return new Item[] {
NOURRA_FIREWORK, CHRISM_FIREWORK, KANINSCHEUN_FIREWORK, POUPOUTAIN_FIREWORK,
LBEAST_FIREWORK, CREVEFOREST_FIREWORK, SACDCHIPS_FIREWORK, VASELLINE_FIREWORK, DUNCAN_FIREWORK
};
}
public static void initialize() {
if (initialized) {
return;
@@ -61,6 +83,16 @@ public final class OnlyFunRegistries {
OnlyFunMod.LOGGER.debug("[{}] Item and block registries initialized.", OnlyFunMod.DISPLAY_NAME);
}
private static Item registerSkinFirework(SkinFacePortrait portrait) {
return registerItem(portrait.itemId(), properties -> new SkinFaceFireworkItem(portrait,
properties.stacksTo(64).component(DataComponents.FIREWORKS, SkinFaceFireworkExplosions.defaultFireworks())));
}
private static Item registerItem(String path, Function<Item.Properties, Item> factory) {
ResourceKey<Item> key = ResourceKey.create(Registries.ITEM, OnlyFunMod.id(path));
return Registry.register(BuiltInRegistries.ITEM, key, factory.apply(new Item.Properties().setId(key)));
}
private static Item registerLootbox(String path, OnlyFunSource source) {
ResourceKey<Item> key = ResourceKey.create(Registries.ITEM, OnlyFunMod.id(path));
return Registry.register(BuiltInRegistries.ITEM, key,
@@ -1,6 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "zevent:block/zevent_screen"
"model": "onlyfun:item/chrism_firework"
}
}
@@ -0,0 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "onlyfun:item/creveforest_firework"
}
}
@@ -0,0 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "onlyfun:item/duncan_firework"
}
}
@@ -0,0 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "onlyfun:item/kaninscheun_firework"
}
}
@@ -0,0 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "onlyfun:item/lbeast_firework"
}
}
@@ -0,0 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "onlyfun:item/nourra_firework"
}
}
@@ -0,0 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "onlyfun:item/poupoutain_firework"
}
}
@@ -0,0 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "onlyfun:item/sacdchips_firework"
}
}
@@ -0,0 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "onlyfun:item/vaselline_firework"
}
}
@@ -6,6 +6,17 @@
"item.onlyfun.cape_lootbox": "Cape Lootbox",
"item.onlyfun.spawn_egg_lootbox": "Spawn Egg Lootbox",
"item.onlyfun.anomaly_lootbox": "Anomaly Lootbox",
"item.onlyfun.nourra_firework": "Nourra Firework",
"item.onlyfun.chrism_firework": "ChrisM Firework",
"item.onlyfun.kaninscheun_firework": "Kaninscheun Firework",
"item.onlyfun.poupoutain_firework": "Poupoutain Firework",
"item.onlyfun.lbeast_firework": "lBeast Firework",
"item.onlyfun.creveforest_firework": "CreveForest Firework",
"item.onlyfun.sacdchips_firework": "SacDChips Firework",
"item.onlyfun.vaselline_firework": "Vaselline Firework",
"item.onlyfun.duncan_firework": "Duncan Firework",
"tooltip.onlyfun.nourra_firework": "The burst paints the skin face, at normal or larger size.",
"tooltip.onlyfun.skin_face_firework": "The burst paints the skin face, at normal or larger size.",
"item.onlyfun.quality_gear": "Quality Gear",
"item.onlyfun.impossible_tool": "Impossible Tool",
"item.onlyfun.impossible_book": "Impossible Book",
@@ -6,6 +6,17 @@
"item.onlyfun.cape_lootbox": "Lootbox de capes",
"item.onlyfun.spawn_egg_lootbox": "Lootbox d'œufs",
"item.onlyfun.anomaly_lootbox": "Lootbox anomalie",
"item.onlyfun.nourra_firework": "Feu d'artifice Nourra",
"item.onlyfun.chrism_firework": "Feu d'artifice ChrisM",
"item.onlyfun.kaninscheun_firework": "Feu d'artifice Kaninscheun",
"item.onlyfun.poupoutain_firework": "Feu d'artifice Poupoutain",
"item.onlyfun.lbeast_firework": "Feu d'artifice lBeast",
"item.onlyfun.creveforest_firework": "Feu d'artifice CreveForest",
"item.onlyfun.sacdchips_firework": "Feu d'artifice SacDChips",
"item.onlyfun.vaselline_firework": "Feu d'artifice Vaselline",
"item.onlyfun.duncan_firework": "Feu d'artifice Duncan",
"tooltip.onlyfun.nourra_firework": "L'explosion dessine le visage, en taille normale ou plus grande.",
"tooltip.onlyfun.skin_face_firework": "L'explosion dessine le visage, en taille normale ou plus grande.",
"item.onlyfun.quality_gear": "Équipement de qualité",
"item.onlyfun.impossible_tool": "Outil impossible",
"item.onlyfun.impossible_book": "Livre impossible",
@@ -6,6 +6,17 @@
"item.onlyfun.cape_lootbox": "Лутбокс плащей",
"item.onlyfun.spawn_egg_lootbox": "Лутбокс яиц призыва",
"item.onlyfun.anomaly_lootbox": "Лутбокс-аномалия",
"item.onlyfun.nourra_firework": "Фейерверк Нурры",
"item.onlyfun.chrism_firework": "Фейерверк ChrisM",
"item.onlyfun.kaninscheun_firework": "Фейерверк Kaninscheun",
"item.onlyfun.poupoutain_firework": "Фейерверк Poupoutain",
"item.onlyfun.lbeast_firework": "Фейерверк lBeast",
"item.onlyfun.creveforest_firework": "Фейерверк CreveForest",
"item.onlyfun.sacdchips_firework": "Фейерверк SacDChips",
"item.onlyfun.vaselline_firework": "Фейерверк Vaselline",
"item.onlyfun.duncan_firework": "Фейерверк Duncan",
"tooltip.onlyfun.nourra_firework": "Взрыв рисует лицо скина обычного или увеличенного размера.",
"tooltip.onlyfun.skin_face_firework": "Взрыв рисует лицо скина обычного или увеличенного размера.",
"item.onlyfun.quality_gear": "Качественное снаряжение",
"item.onlyfun.impossible_tool": "Невозможный инструмент",
"item.onlyfun.impossible_book": "Невозможная книга",
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "onlyfun:item/chrism_firework"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "onlyfun:item/creveforest_firework"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "onlyfun:item/duncan_firework"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "onlyfun:item/kaninscheun_firework"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "onlyfun:item/lbeast_firework"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "onlyfun:item/nourra_firework"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "onlyfun:item/poupoutain_firework"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "onlyfun:item/sacdchips_firework"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "onlyfun:item/vaselline_firework"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

@@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:firework_rocket",
"minecraft:red_dye",
"minecraft:paper"
],
"result": {
"count": 1,
"id": "onlyfun:chrism_firework"
}
}
@@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:firework_rocket",
"minecraft:green_dye",
"minecraft:paper"
],
"result": {
"count": 1,
"id": "onlyfun:creveforest_firework"
}
}
@@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:firework_rocket",
"minecraft:pink_dye",
"minecraft:paper"
],
"result": {
"count": 1,
"id": "onlyfun:duncan_firework"
}
}
@@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:firework_rocket",
"minecraft:yellow_dye",
"minecraft:paper"
],
"result": {
"count": 1,
"id": "onlyfun:kaninscheun_firework"
}
}
@@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:firework_rocket",
"minecraft:brown_dye",
"minecraft:paper"
],
"result": {
"count": 1,
"id": "onlyfun:lbeast_firework"
}
}
@@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:firework_rocket",
"minecraft:light_blue_dye",
"minecraft:paper"
],
"result": {
"count": 1,
"id": "onlyfun:nourra_firework"
}
}
@@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:firework_rocket",
"minecraft:light_gray_dye",
"minecraft:paper"
],
"result": {
"count": 1,
"id": "onlyfun:poupoutain_firework"
}
}
@@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:firework_rocket",
"minecraft:orange_dye",
"minecraft:paper"
],
"result": {
"count": 1,
"id": "onlyfun:sacdchips_firework"
}
}
@@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:firework_rocket",
"minecraft:white_dye",
"minecraft:paper"
],
"result": {
"count": 1,
"id": "onlyfun:vaselline_firework"
}
}
@@ -21,6 +21,13 @@
"fr.koka99cab.sanctuary26.onlyfun.client.OnlyFunClient"
]
},
"mixins": [
"onlyfun.mixins.json",
{
"config": "onlyfun.client.mixins.json",
"environment": "client"
}
],
"depends": {
"fabricloader": ">=0.19.3",
"minecraft": "~26.2",
@@ -0,0 +1,12 @@
{
"required": true,
"package": "fr.koka99cab.sanctuary26.onlyfun.mixin.client",
"compatibilityLevel": "JAVA_25",
"client": [
"FireworkRocketEntityMixin",
"ParticleLifetimeAccessor"
],
"injectors": {
"defaultRequire": 1
}
}
@@ -0,0 +1,11 @@
{
"required": true,
"package": "fr.koka99cab.sanctuary26.onlyfun.mixin",
"compatibilityLevel": "JAVA_25",
"mixins": [
"FireworkRocketLifetimeMixin"
],
"injectors": {
"defaultRequire": 1
}
}
@@ -0,0 +1,76 @@
package fr.koka99cab.sanctuary26.onlyfun.item;
import fr.koka99cab.sanctuary26.onlyfun.client.SkinFaceFireworkClient;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.item.component.Fireworks;
/** Executable check for baked skin-face firework portraits. */
public final class SkinFaceFireworkSmoke {
private SkinFaceFireworkSmoke() {
}
public static void main(String[] args) {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
verify();
}
public static void verify() {
require(SkinFacePortrait.values().length == 9, "Skin firework roster drifted");
require(SkinFacePortrait.WIDTH == 8 && SkinFacePortrait.HEIGHT == 8,
"Face grid is no longer 8×8");
for (SkinFacePortrait portrait : SkinFacePortrait.values()) {
require(portrait.face().length == 64, portrait.id() + " face palette length drifted");
require(portrait.hood().length == 64, portrait.id() + " hood overlay length drifted");
require(portrait.hoodTop().length == 16, portrait.id() + " hood top strip length drifted");
int[] pixels = portrait.portrait();
require(pixels.length == 80 && portrait.portraitHeight() == 10,
portrait.id() + " portrait grid drifted");
require(portrait.itemId().equals(portrait.id() + "_firework"),
portrait.id() + " item id drifted");
}
require(SkinFacePortrait.NOURRA.face()[5 * 8 + 2] == 0x4A9DFC
&& SkinFacePortrait.NOURRA.face()[5 * 8 + 5] == 0x4A9DFC,
"Blue eyes are missing from the Nourra face");
require(SkinFacePortrait.NOURRA.face()[4 * 8 + 3] == 0xFFF0E1,
"Skin tone is missing from the baked Nourra face");
require(SkinFacePortrait.NOURRA.hood()[2] == 0xCBCBCB,
"Hood overlay is missing from the top of the Nourra face");
require(SkinFacePortrait.NOURRA.hood()[5 * 8 + 2] == 0x181818,
"Nourra hood no longer covers the left eye");
require(SkinFacePortrait.NOURRA.hood()[5 * 8 + 5] == SkinFacePortrait.TRANSPARENT,
"Nourra hood opening no longer leaves the right eye visible");
require(SkinFacePortrait.NOURRA.opaqueHoodPixels() == 43,
"Opaque Nourra hood pixel count drifted");
int[] nourra = SkinFacePortrait.NOURRA.portrait();
require(nourra[16 + 5 * 8 + 5] == 0x4A9DFC,
"Right eye is missing from the composited Nourra portrait");
require(nourra[2] == 0xD8D8D8,
"Hood top is missing from the Nourra portrait");
require(SkinFacePortrait.CHRISM.face()[6 * 8 + 1] == 0xFFFFFF,
"ChrisM eyes are missing from the baked face");
require(SkinFacePortrait.DUNCAN.face().length == 64,
"Duncan face is missing");
Fireworks fireworks = SkinFaceFireworkExplosions.defaultFireworks();
require(fireworks.flightDuration() == 3 && fireworks.explosions().isEmpty(),
"Default firework burst contract changed");
require(SkinFaceFireworkExplosions.LIFETIME_TRIM_TICKS == 5
&& SkinFaceFireworkExplosions.midFlightLifetime(45) == 40,
"Mid-flight lifetime trim drifted");
require(SkinFaceFireworkClient.PIXEL == 1.85D, "Portrait scale drifted");
require(SkinFaceFireworkClient.LARGE_PIXEL == 2.7D, "Large portrait scale drifted");
require(SkinFaceFireworkClient.burstPixel(false) == SkinFaceFireworkClient.PIXEL
&& SkinFaceFireworkClient.burstSparkSize(false) == SkinFaceFireworkClient.SPARK_SIZE,
"Normal burst scale drifted");
require(SkinFaceFireworkClient.burstPixel(true) == SkinFaceFireworkClient.LARGE_PIXEL
&& SkinFaceFireworkClient.burstSparkSize(true) == SkinFaceFireworkClient.LARGE_SPARK_SIZE,
"Large burst scale drifted");
require(SkinFaceFireworkClient.SPARK_LIFETIME_TICKS == 38, "Spark lifetime drifted");
System.out.println("Only Fun skin fireworks: 9 baked faces with hood overlay ready.");
}
private static void require(boolean condition, String message) {
if (!condition) throw new IllegalStateException(message);
}
}
@@ -1,5 +1,6 @@
package fr.koka99cab.sanctuary26.onlyfun.loot;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFaceFireworkSmoke;
import fr.koka99cab.sanctuary26.onlyfun.shop.OnlyFunShopCatalogSmoke;
import fr.koka99cab.sanctuary26.onlyfun.world.CobbleDungeonPlacementSmoke;
import java.util.EnumMap;
@@ -18,6 +19,7 @@ public final class OnlyFunGeneratorSmoke {
Bootstrap.bootStrap();
OnlyFunShopCatalogSmoke.verify();
CobbleDungeonPlacementSmoke.verify();
SkinFaceFireworkSmoke.verify();
OnlyFunGenerator.initialize();
require(OnlyFunGenerator.ANOMALY_DENOMINATOR == 256, "Anomaly odds changed from 1/256");
require(OnlyFunGenerator.PITY_START == 24, "Pity threshold changed");
@@ -0,0 +1,51 @@
{
"schema_version": 1,
"status": "released",
"released_in": "26.2.0-alpha.234",
"temporary_until": "2026-09-07",
"pack": {
"source_version": "26.2.0-alpha.233",
"target_version": "26.2.0-alpha.234"
},
"modules": {
"zevent": {
"source_module_version": null,
"target_module_version": "0.0.0-alpha.2",
"data_version": 1,
"network_protocol": 1,
"client_and_server_required": true
}
},
"screen": {
"block_id": "zevent:zevent_screen",
"width_blocks": 9,
"height_blocks": 5,
"server_authoritative_channel": true,
"maximum_channel_input_length": 128,
"maximum_channel_name_length": 25,
"maximum_visible_browsers_per_client": 2,
"audible_distance_blocks": 64
},
"dependencies": {
"mcef_modern": {
"version": "0.3.3+mc26.2.jcef146.0.10",
"modrinth_project_id": "YJFfu5pH",
"modrinth_version_id": "FOXbUPNd",
"environment": "client_only"
}
},
"compatibility": {
"existing_saves_supported": true,
"new_world_required": false,
"save_reset_required": false,
"existing_module_data_versions_changed": false,
"existing_module_network_protocols_changed": false,
"zevent_client_server_update_required": true
},
"distribution": {
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"shared_client_server_mrpack": true,
"mcef_client_only": true
}
}
@@ -0,0 +1,51 @@
{
"schema_version": 1,
"status": "released",
"released_in": "26.2.0-alpha.235",
"temporary_until": "2026-09-07",
"pack": {
"source_version": "26.2.0-alpha.234",
"target_version": "26.2.0-alpha.235"
},
"modules": {
"zevent": {
"source_module_version": "0.0.0-alpha.2",
"target_module_version": "0.0.0-alpha.3",
"data_version": 1,
"network_protocol": 1,
"client_and_server_required": true
}
},
"playback": {
"mcef_removed": true,
"native_hls_decoder": true,
"h264_video": true,
"aac_audio": true,
"maximum_quality_height": 720,
"automatic_retry_seconds": 30,
"offline_feedback": true
},
"dependencies": {
"dream_displays": {
"version": "1.9.6",
"modrinth_project_id": "4IIKQyBu",
"modrinth_version_id": "GxyW2ksW",
"environment": "client_and_server"
}
},
"compatibility": {
"existing_saves_supported": true,
"new_world_required": false,
"save_reset_required": false,
"data_version_changed": false,
"network_protocol_changed": false,
"zevent_client_server_update_required": true
},
"distribution": {
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"shared_client_server_mrpack": true,
"mcef_removed": true,
"dream_displays_required": true
}
}
@@ -0,0 +1,43 @@
{
"schema_version": 1,
"status": "released",
"released_in": "26.2.0-alpha.236",
"pack": {
"source_version": "26.2.0-alpha.235",
"target_version": "26.2.0-alpha.236"
},
"removed_module": {
"id": "zevent",
"source_module_version": "0.0.0-alpha.3",
"target_module_version": null,
"source_directory_removed": true,
"client_jar_removed": true,
"server_jar_removed": true,
"registry_ids_removed": [
"zevent:zevent_screen"
]
},
"replacement": {
"module": "dreamdisplays",
"version": "1.9.6",
"modrinth_project_id": "4IIKQyBu",
"modrinth_version_id": "GxyW2ksW",
"client_and_server_required": true,
"automatic_screen_conversion": false
},
"compatibility": {
"world_reset_required": false,
"world_backup_recommended": true,
"existing_zevent_screens_must_be_replaced": true,
"remaining_module_data_preserved": true,
"remaining_network_protocols_changed": false,
"client_and_server_must_remove_zevent": true
},
"distribution": {
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"zevent_jar_absent": true,
"dream_displays_required": true,
"distributed_module_count": 10
}
}
@@ -0,0 +1,33 @@
{
"schema_version": 1,
"status": "released",
"released_in": "26.2.0-alpha.237",
"pack": {
"source_version": "26.2.0-alpha.236",
"target_version": "26.2.0-alpha.237"
},
"dependency": {
"id": "dreamdisplays",
"source_version": "1.9.6",
"target_version": "1.10.0-dev",
"target_filename": "dreamdisplays-fabric-26.2-audio-buffer-test.jar",
"sha256": "cea9efcea0401ebb44a471f43539c582ffae02fbd9b36d393c7aacdfc198ec74",
"client_and_server_required": true,
"pinned_test_build": true,
"audio_buffer_fix": true
},
"compatibility": {
"world_reset_required": false,
"save_migration_required": false,
"sanctuary_module_versions_changed": false,
"network_protocols_changed": false,
"client_and_server_must_update": true
},
"distribution": {
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"old_dreamdisplays_jar_removed": true,
"zevent_jar_absent": true,
"distributed_module_count": 10
}
}
@@ -0,0 +1,34 @@
{
"schema_version": 1,
"status": "released",
"released_in": "26.2.0-alpha.238",
"pack": {
"source_version": "26.2.0-alpha.237",
"target_version": "26.2.0-alpha.238"
},
"dependency": {
"id": "dreamdisplays",
"source_version": "1.10.0-dev",
"target_version": "1.10.0-dev",
"source_filename": "dreamdisplays-fabric-26.2-audio-buffer-test.jar",
"target_filename": "dreamdisplays-fabric-26.2-audio-buffer-test-2.jar",
"sha256": "fae5681de3209dba98d6359fb878e7cf230cde08e96c08fb71394aed35823188",
"client_and_server_required": true,
"pinned_test_build": true,
"audio_buffer_fix": true
},
"compatibility": {
"world_reset_required": false,
"save_migration_required": false,
"sanctuary_module_versions_changed": false,
"network_protocols_changed": false,
"client_and_server_must_update": true
},
"distribution": {
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"old_dreamdisplays_jar_removed": true,
"zevent_jar_absent": true,
"distributed_module_count": 10
}
}
@@ -0,0 +1,57 @@
{
"schema_version": 1,
"status": "released",
"released_in": "26.2.0-alpha.239",
"pack": {
"source_version": "26.2.0-alpha.238",
"target_version": "26.2.0-alpha.239"
},
"removed_dependencies": [
{
"id": "dreamdisplays",
"version": "1.10.0-dev",
"filename": "dreamdisplays-fabric-26.2-audio-buffer-test-2.jar"
}
],
"added_dependencies": [
{
"id": "mydisplay",
"version": "1.0.0",
"filename": "mydisplay-1.0.0-sanctuary.jar",
"sha256": "22cd63386bc7ba6961f586949f65b4a2a6cb335d75aa155bcdf0a633f1f9d907",
"side": "both",
"manifest_client_dependencies_optional": true,
"languages": ["en_us", "fr_fr", "ru_ru"]
},
{
"id": "watermedia",
"version": "3.0.0.23",
"filename": "watermedia-3.0.0.23.jar",
"sha256": "c3baefc8fbb005e349b3424f7df8f9d89b18524d5f7a358c6df73ca5e1d98b38",
"side": "client"
},
{
"id": "watermedia_binaries",
"version": "3.0.0.6",
"filename": "watermedia-binaries-3.0.0.6.jar",
"sha256": "2d9333061c3d2330033fe68837a31f546dbf58984455b0cfec0deeff6b1f121e",
"side": "client"
}
],
"compatibility": {
"world_reset_required": false,
"save_migration_required": false,
"sanctuary_module_versions_changed": false,
"network_protocols_changed": false,
"client_and_server_must_update": true,
"existing_dreamdisplays_blocks_converted": false
},
"distribution": {
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"old_dreamdisplays_jar_removed": true,
"watermedia_client_only": true,
"zevent_jar_absent": true,
"distributed_module_count": 10
}
}
@@ -0,0 +1,38 @@
{
"schema_version": 1,
"status": "released",
"released_in": "26.2.0-alpha.240",
"pack": {
"source_version": "26.2.0-alpha.239",
"target_version": "26.2.0-alpha.240"
},
"dependency": {
"id": "mydisplay",
"version": "1.0.0",
"source_filename": "mydisplay-1.0.0-sanctuary.jar",
"target_filename": "mydisplay-1.0.0-sanctuary-2.jar",
"source_sha256": "22cd63386bc7ba6961f586949f65b4a2a6cb335d75aa155bcdf0a633f1f9d907",
"target_sha256": "9ff1645731dc2dfdc248e3c07ac923aba9dfe001a1580a981767f948d4f7427f",
"side": "both",
"manifest_client_dependencies_optional": true,
"languages": ["en_us", "fr_fr", "ru_ru"],
"screen_picker_added": true
},
"compatibility": {
"world_reset_required": false,
"save_migration_required": false,
"sanctuary_module_versions_changed": false,
"network_protocols_changed": false,
"client_and_server_must_update": true,
"existing_mydisplay_blocks_compatible": true
},
"distribution": {
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"old_mydisplay_jar_removed": true,
"direct_download_dependencies_skipped_by_modrinth_updater": true,
"watermedia_client_only": true,
"zevent_jar_absent": true,
"distributed_module_count": 10
}
}
@@ -0,0 +1,53 @@
{
"schema_version": 1,
"status": "released",
"released_in": "26.2.0-alpha.241",
"pack": {
"source_version": "26.2.0-alpha.240",
"target_version": "26.2.0-alpha.241"
},
"dependency": {
"id": "mydisplay",
"version": "1.0.0",
"source_filename": "mydisplay-1.0.0-sanctuary-2.jar",
"target_filename": "mydisplay-1.0.0-sanctuary-3.jar",
"source_sha256": "9ff1645731dc2dfdc248e3c07ac923aba9dfe001a1580a981767f948d4f7427f",
"provided_sha256": "45a7dc0f9852ef776bce1ee81f1683b6333e773299b7e5eb96363cf885c8c6ef",
"target_sha256": "97a8b7fc79bb68d661e6d0563c54b90179e6a34339c22a433f858cdff6862cbc",
"side": "both",
"manifest_client_dependencies_optional": true,
"languages": ["en_us", "fr_fr", "ru_ru"]
},
"optimizations": {
"argb_to_rgba_int_buffer": true,
"distance_based_decode_lod": true,
"distant_live_quality_cap_after_five": true,
"proximity_prioritized_upload_budget": true,
"last_frame_kept_when_deferred": true,
"offscreen_cpu_upload_paused": true,
"draw_distance_blocks": 128,
"single_wall_render_pass": true,
"shared_streamlink_by_channel_and_quality": true,
"staggered_player_start": true,
"muted_audio_decoder_disabled": true,
"screenshot_render_buffer_reset": true,
"screenshot_upload_pause": true
},
"compatibility": {
"world_reset_required": false,
"save_migration_required": false,
"sanctuary_module_versions_changed": false,
"network_protocols_changed": false,
"client_and_server_must_update": true,
"existing_mydisplay_blocks_compatible": true,
"all_screens_remain_visible": true
},
"distribution": {
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"old_mydisplay_jar_removed": true,
"watermedia_client_only": true,
"zevent_jar_absent": true,
"distributed_module_count": 10
}
}
@@ -0,0 +1,43 @@
{
"schema_version": 1,
"status": "released",
"released_in": "26.2.0-alpha.242",
"pack": {
"source_version": "26.2.0-alpha.241",
"target_version": "26.2.0-alpha.242"
},
"dependency": {
"id": "mydisplay",
"version": "1.0.0",
"source_filename": "mydisplay-1.0.0-sanctuary-3.jar",
"target_filename": "mydisplay-1.0.0-sanctuary-4.jar",
"source_sha256": "97a8b7fc79bb68d661e6d0563c54b90179e6a34339c22a433f858cdff6862cbc",
"provided_sha256": "4c1bf4aa7140e613ff520b04263cb4733561c2e04b39a21b3cb7783f610119b9",
"target_sha256": "6c3bb4e7f562c6730a6cfa23a162a1fa0641aed7105cb6b809bb5a097aa2e559",
"side": "both",
"manifest_client_dependencies_optional": true,
"languages": ["en_us", "fr_fr", "ru_ru"],
"changed_classes": [
"com.mydisplay.client.media.DisplayPerf",
"com.mydisplay.client.media.DisplaySession",
"com.mydisplay.client.media.WaterMediaBackend",
"com.mydisplay.client.media.WaterMediaBackend$Playback"
]
},
"compatibility": {
"world_reset_required": false,
"save_migration_required": false,
"sanctuary_module_versions_changed": false,
"network_protocols_changed": false,
"client_and_server_must_update": true,
"existing_mydisplay_blocks_compatible": true
},
"distribution": {
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"old_mydisplay_jar_removed": true,
"watermedia_client_only": true,
"zevent_jar_absent": true,
"distributed_module_count": 10
}
}
@@ -0,0 +1,58 @@
{
"schema_version": 1,
"status": "released",
"source": {
"pack_version": "26.2.0-alpha.242",
"modules": {
"sanctuary": "0.0.0-alpha.139",
"onlyfun": "0.0.0-alpha.27"
}
},
"target": {
"pack_version": "26.2.0-alpha.243",
"modules": {
"sanctuary": "0.0.0-alpha.140",
"onlyfun": "0.0.0-alpha.28"
}
},
"integrated_pull_requests": [172, 173, 174],
"shop_catalog": {
"local_search": true,
"accent_folding": true,
"grid_sizes": [3, 4, 5],
"grid_preference_persisted": true,
"page_counter": true,
"flash_sales_unchanged": true
},
"quest_board": {
"stale_sign_resynchronized_after_unloaded_rotation": true,
"operator_refresh_rerolls_contract": true,
"displayed_contract_matches_claimed_contract": true
},
"nourra_firework": {
"registry_id": "onlyfun:nourra_firework",
"skin_face_and_hood_particles": true,
"normal_and_large_bursts": true,
"vanilla_colored_burst_suppressed": true,
"languages": ["en_us", "fr_fr", "ru_ru"]
},
"compatibility": {
"existing_saves_supported": true,
"world_reset_required": false,
"save_migration_required": false,
"persistent_data_formats_changed": false,
"network_protocols_changed": false,
"sanctuary_data_version": 15,
"sanctuary_network_protocol": 24,
"onlyfun_data_version": 2,
"onlyfun_network_protocol": 3,
"client_server_update_required": true
},
"distribution": {
"pack_release_updated": true,
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"shared_client_server_mrpack": true,
"distributed_module_count": 10
}
}
@@ -0,0 +1,62 @@
{
"schema_version": 1,
"status": "released",
"source": {
"pack_version": "26.2.0-alpha.243",
"modules": {
"onlyfun": "0.0.0-alpha.28"
}
},
"target": {
"pack_version": "26.2.0-alpha.244",
"modules": {
"onlyfun": "0.0.0-alpha.29"
}
},
"integrated_pull_requests": [176],
"skin_fireworks": {
"existing_portrait_preserved": "onlyfun:nourra_firework",
"new_portraits": [
"onlyfun:chrism_firework",
"onlyfun:kaninscheun_firework",
"onlyfun:poupoutain_firework",
"onlyfun:lbeast_firework",
"onlyfun:creveforest_firework",
"onlyfun:sacdchips_firework",
"onlyfun:vaselline_firework",
"onlyfun:duncan_firework"
],
"portrait_size": [8, 8],
"hood_overlay": true,
"camera_facing_particles": true,
"normal_and_large_bursts": true,
"shapeless_recipes": {
"chrism": "minecraft:red_dye",
"kaninscheun": "minecraft:yellow_dye",
"poupoutain": "minecraft:light_gray_dye",
"lbeast": "minecraft:brown_dye",
"creveforest": "minecraft:green_dye",
"sacdchips": "minecraft:orange_dye",
"vaselline": "minecraft:white_dye",
"duncan": "minecraft:pink_dye"
},
"languages": ["en_us", "fr_fr", "ru_ru"]
},
"compatibility": {
"existing_saves_supported": true,
"world_reset_required": false,
"save_migration_required": false,
"persistent_data_formats_changed": false,
"network_protocols_changed": false,
"onlyfun_data_version": 2,
"onlyfun_network_protocol": 3,
"client_server_update_required": true
},
"distribution": {
"pack_release_updated": true,
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"shared_client_server_mrpack": true,
"distributed_module_count": 10
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ Cette commande :
1. reconstruit les dix modules Sanctuary ;
2. remplace leurs anciens JAR dans ce pack ;
3. sélectionne la dernière version Modrinth marquée `release` de Fabric API, Simple Voice Chat, Sodium et Iris, ou `beta` pour JEI qui ne publie pas de release 26.2 ; aucune alpha n'est acceptée ;
3. sélectionne la dernière version Modrinth marquée `release` de Fabric API, Simple Voice Chat, Sodium et Iris, ou `beta` pour JEI qui ne publie pas de release 26.2 ; aucune alpha n'est acceptée et les dépendances privées en téléchargement direct restent épinglées sans interrogation Modrinth ;
4. rafraîchit `index.toml` ;
5. exporte `build/distributions/sanctuary-<version>-packwiz.mrpack` ;
6. produit `build/distributions/sanctuary-<version>-packwiz-site.zip`, à extraire sur un hébergement HTTPS statique.
+27 -12
View File
@@ -14,15 +14,15 @@ hash = "41f4f4c88c71d0a23446d5014f7407d55ad477441847099cad24492b032335a0"
[[files]]
file = "mods/ambiance-0.0.0-alpha.22.jar"
hash = "c72a87d3ea187e65f3d459d18954bfc557deeb741deea143e610604a3af5985f"
hash = "960322cd387c47588f6fd76202c0abf2c9993cf0effa82e19f8bb31d86ab07a4"
[[files]]
file = "mods/anotherworld-0.0.0-alpha.58.jar"
hash = "51f0122c222317fd65a642f8568a3d822732a874ecd5e4433592ae96e2a535a8"
hash = "8936b0a5bf3f122afc752cba5e8d96ac92cfb1458785c3fac13d25f51cb4814c"
[[files]]
file = "mods/events-0.0.0-alpha.0.jar"
hash = "1c1623d4d40973346d3143657461e916e38142a867e7a6cf0da054ad998dbb0e"
hash = "1fe3fc2f6ca371f8dde44e0bb535b1e70df0e9c4de3f56002f9f3ec6d5826745"
[[files]]
file = "mods/fabric-api.pw.toml"
@@ -31,7 +31,7 @@ metafile = true
[[files]]
file = "mods/iliketomoveit-0.0.0-alpha.18.jar"
hash = "6a38653f4898e2fe2df6f8935b516073185f7c0d3913164e769ebafcc487b318"
hash = "a964e17e139757f948f67aeae73730c97af2c5932bffcc4d366d084c7cdf53ee"
[[files]]
file = "mods/iris.pw.toml"
@@ -40,7 +40,7 @@ metafile = true
[[files]]
file = "mods/itsalive-0.0.0-alpha.38.jar"
hash = "0e40259acf98cf3d64dfdebe3441ffb3a09aa64de6cf1b2c8dcaa4af7c8d0e82"
hash = "97a29e1d4875265854d0b4757c6e0c286a35e626df1b8d13b465b85502381309"
[[files]]
file = "mods/jei.pw.toml"
@@ -49,23 +49,28 @@ metafile = true
[[files]]
file = "mods/masterkey-0.0.0-alpha.9.jar"
hash = "b42b25f1eabc0b1e7660f0071e8f105d82ceb078b4bd9b7c570df8ce2c59601c"
hash = "95265c4aa4ebb89d5e89e3245917f47a4044db192f88995cfee2bdf63f348b79"
[[files]]
file = "mods/onlyfun-0.0.0-alpha.27.jar"
hash = "4e2c95c54fa3f591d3e1b6f7454211caaa4fb5f7ef31cd5b6aa477d03be18e2e"
file = "mods/mydisplay.pw.toml"
hash = "a5538de6d9c38cf37ff0882451fd025c8a24c7cb250482baa4ca5918d1129b06"
metafile = true
[[files]]
file = "mods/onlyfun-0.0.0-alpha.29.jar"
hash = "c55a23de665475c95c3e5fe085097e3a09bfdf389ca61575161377482deb292a"
[[files]]
file = "mods/ouch-0.0.0-alpha.11.jar"
hash = "8dcec6870fba091adda5df2a2f719e97d0f5dcf86f14c5f99035c7aebaf6c119"
hash = "08f3355b0240c30a65314e8173f2268a1cf4b0a5ae4dac1766e49939e3e5a256"
[[files]]
file = "mods/redstoner-0.0.0-alpha.20.jar"
hash = "669ca9a357874448c68ca45fdf26d827d5094b1ef1ee07f948a564b33cc1a79c"
hash = "39f0cba6213e4efcd40b8b72cf7812b56315e1d637803a87a2672b00bc4ee4a0"
[[files]]
file = "mods/sanctuary-0.0.0-alpha.139.jar"
hash = "62d06cfe57badc5295fdcaf1ad9d0eb6d7ba67345bdc5fc8ca6d689817f70c01"
file = "mods/sanctuary-0.0.0-alpha.140.jar"
hash = "f13f793dea9300543280eb761862c45e9ad86a299c4d1efa11eb7af83d2b7891"
[[files]]
file = "mods/simple-voice-chat.pw.toml"
@@ -76,3 +81,13 @@ metafile = true
file = "mods/sodium.pw.toml"
hash = "f06c0b9150da9877b99cb265d037c067e509340d9bf347e02cef2043c110e963"
metafile = true
[[files]]
file = "mods/watermedia-binaries.pw.toml"
hash = "1b7c4327062eb0f70d815a2461b0ded2a2495eed8fca0fd00d75467a4aa777da"
metafile = true
[[files]]
file = "mods/watermedia.pw.toml"
hash = "c99e2e5e4d26ec3414e6af55f252091fd728a52f1a97c69a14cacdda7af15a88"
metafile = true
+9
View File
@@ -0,0 +1,9 @@
name = "MyDisplay"
filename = "mydisplay-1.0.0-sanctuary-4.jar"
side = "both"
pin = true
[download]
url = "https://download.botsu.cloud/sanctuary/artifacts/mydisplay-1.0.0-sanctuary-4.jar"
hash-format = "sha256"
hash = "6c3bb4e7f562c6730a6cfa23a162a1fa0641aed7105cb6b809bb5a097aa2e559"
@@ -0,0 +1,9 @@
name = "WaterMedia: Binaries"
filename = "watermedia-binaries-3.0.0.6.jar"
side = "client"
pin = true
[download]
url = "https://download.botsu.cloud/sanctuary/artifacts/watermedia-binaries-3.0.0.6.jar"
hash-format = "sha256"
hash = "2d9333061c3d2330033fe68837a31f546dbf58984455b0cfec0deeff6b1f121e"
+9
View File
@@ -0,0 +1,9 @@
name = "WaterMedia"
filename = "watermedia-3.0.0.23.jar"
side = "client"
pin = true
[download]
url = "https://download.botsu.cloud/sanctuary/artifacts/watermedia-3.0.0.23.jar"
hash-format = "sha256"
hash = "c3baefc8fbb005e349b3424f7df8f9d89b18524d5f7a358c6df73ca5e1d98b38"
+2 -2
View File
@@ -1,12 +1,12 @@
name = "Sanctuary 26.2"
author = "KOKA99CAB"
version = "26.2.0-alpha.233"
version = "26.2.0-alpha.244"
pack-format = "packwiz:1.1.0"
[index]
file = "index.toml"
hash-format = "sha256"
hash = "c666b031c2ac27a8f01cc4d12d6e471ea7471e9e5e7ce02567ee8a6649f9142a"
hash = "e31218336db4e25008fa5203ab85cbb85172d0b21425b77ec5bfb20949d5ea3e"
[versions]
fabric = "0.19.3"
+46 -1
View File
@@ -3,7 +3,7 @@
"game": "minecraft",
"name": "Sanctuary 26.2",
"summary": "Sanctuary 26.2 — progression, exploration, worldgen and multiplayer systems by KOKA99CAB.",
"versionId": "26.2.0-alpha.233",
"versionId": "26.2.0-alpha.244",
"files": [
{
"path": "mods/fabric-api-0.159.0+26.2.jar",
@@ -79,6 +79,51 @@
"https://cdn.modrinth.com/data/YL57xq9U/versions/oaD6KQls/iris-fabric-1.11.2%2Bmc26.2.jar"
],
"fileSize": 2820763
},
{
"path": "mods/mydisplay-1.0.0-sanctuary-4.jar",
"hashes": {
"sha1": "2253da2b8e1b8d0111dc63a0d926022acef2c422",
"sha512": "a8fcf35c6d465ef16140a5c699762939aa12e4fccae3891fbbdb38e4f14ba4cfd978193e2ef2c165ecc820927104c5e35b42fd65eeb802a6280dc2faca0e39bd"
},
"env": {
"client": "required",
"server": "required"
},
"downloads": [
"https://download.botsu.cloud/sanctuary/artifacts/mydisplay-1.0.0-sanctuary-4.jar"
],
"fileSize": 112172
},
{
"path": "mods/watermedia-3.0.0.23.jar",
"hashes": {
"sha1": "a644ab079cc9ed19a62b181e0252be5a9a422166",
"sha512": "b2f9c31828285bf7cb039b91e8756b35c2a9e425d614d9c767bdcd752475a7abf4f025d6f6005d74f5c610c298fdadf01e53b29124d1d9fd03554fff71b1993b"
},
"env": {
"client": "required",
"server": "unsupported"
},
"downloads": [
"https://download.botsu.cloud/sanctuary/artifacts/watermedia-3.0.0.23.jar"
],
"fileSize": 3625444
},
{
"path": "mods/watermedia-binaries-3.0.0.6.jar",
"hashes": {
"sha1": "8f132fc2fec0fe91a9a085c11088f0773da187fc",
"sha512": "45b5c1d645f75fb54de4d7e92b54f50372bd9d24c7543b57e6d32ba6b35ce346250bc871ba1a39c8b1988a423d2fca75a4806ae75af502d194455caabb470897"
},
"env": {
"client": "required",
"server": "unsupported"
},
"downloads": [
"https://download.botsu.cloud/sanctuary/artifacts/watermedia-binaries-3.0.0.6.jar"
],
"fileSize": 143725808
}
],
"dependencies": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"schema_version": 1,
"pack_version": "26.2.0-alpha.233",
"pack_version": "26.2.0-alpha.244",
"release_channel": "alpha",
"minecraft_version": "26.2",
"fabric_loader_version": "0.19.3",
@@ -9,7 +9,7 @@
"project_id": "FgOqyAXd",
"project_slug": "sanctuary-koka99",
"project_url": "https://modrinth.com/modpack/sanctuary-koka99",
"primary_file": "sanctuary-26.2.0-alpha.233.mrpack",
"primary_file": "sanctuary-26.2.0-alpha.244.mrpack",
"shared_client_server_pack": true,
"update_owner": "packwiz-installer",
"check_on_launch": true,
+6
View File
@@ -58,6 +58,12 @@ def main() -> int:
for metafile in metafiles:
text = metafile.read_text(encoding="utf-8")
if "[update.modrinth]" not in text:
label = field(text, "name")
if not re.search(r"^\s*pin\s*=\s*true\s*$", text, re.MULTILINE):
raise RuntimeError(f"Direct-download dependency {label!r} must stay pinned")
print(f"{label}: pinned direct download, skipped", flush=True)
continue
project_id = field(text, "mod-id")
current_version = field(text, "version")
release, channel = latest_supported(project_id)
+28 -16
View File
@@ -100,6 +100,14 @@ tasks.register("shopHeaderLayoutSmoke", JavaExec) {
mainClass = "fr.koka99cab.sanctuary26.sanctuary.client.ShopDailyHeaderLayoutSmoke"
}
tasks.register("shopCatalogBrowseSmoke", JavaExec) {
group = "verification"
description = "Checks catalogue search folding, 3x3/4x4/5x5 grids and filtered pagination."
dependsOn tasks.named("testClasses")
classpath = sourceSets.test.runtimeClasspath
mainClass = "fr.koka99cab.sanctuary26.sanctuary.shop.ShopCatalogBrowseSmoke"
}
tasks.register("zombieNamesSmoke", JavaExec) {
group = "verification"
description = "Checks deterministic Xbox-360-style names for every vanilla Zombie subtype."
@@ -215,7 +223,7 @@ tasks.register("verifyBlackMarket") {
def questData = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardData.java").text
def quests = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardService.java").text
def inventoryView = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/inventory/SanctuaryPlayerInventoryView.java").text
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| !data.contains('SAVE_KEY = "black_market"') || !data.contains("DATA_VERSION = 2")
|| !data.contains("ItemStack.CODEC.fieldOf(\"item\")") || !data.contains("replaceListing")
|| !service.contains("LISTING_FEE_RUBIES = 1") || !service.contains("COMMISSION_PERCENT = 5")
@@ -244,7 +252,9 @@ tasks.register("verifyBlackMarket") {
|| !quests.contains("SanctuaryRegistries.questBoardTier")
|| quests.contains("activationTier(")
|| quests.contains("QuestBoardTier.ACTIVATION_COST")
|| !quests.contains("addTreasury(claim.depositCurrency(), claim.depositAmount())")) {
|| !quests.contains("addTreasury(claim.depositCurrency(), claim.depositAmount())")
|| !quests.contains("shouldRewriteSign")
|| !quests.contains("syncLoadedSigns")) {
throw new GradleException("The physical Black Market escrow/delivery contract is incomplete")
}
}
@@ -360,7 +370,8 @@ tasks.register("verifySanctuary") {
tasks.named("shopDeliveryTimingSmoke"), tasks.named("blackMarketModelSmoke"),
tasks.named("questBoardModelSmoke"), tasks.named("questTrackerModelSmoke"),
tasks.named("questIdSuggestionsSmoke"),
tasks.named("shopHeaderLayoutSmoke"), tasks.named("zombieNamesSmoke"),
tasks.named("shopHeaderLayoutSmoke"), tasks.named("shopCatalogBrowseSmoke"),
tasks.named("zombieNamesSmoke"),
tasks.named("capeJoinSyncSmoke"),
tasks.named("inventorySortSmoke"),
tasks.named("verifyBlackMarket")
@@ -2931,7 +2942,7 @@ tasks.register("verifyShopCurrencyRowsRelease") {
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketService.java").text
def shop = file(
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopService.java").text
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.117"
|| migration.shop_currency_inventory?.all_unlocked_rows_counted != true
|| migration.shop_currency_inventory?.overflow_rows_counted != true
@@ -3097,7 +3108,7 @@ tasks.register("verifyProgressivePauseMenu") {
"pack/migrations/26.2.0-alpha.190-progressive-pause-menu-to-alpha.191.json"))
def pauseMenu = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/PauseScreenMixin.java").text
def costs = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/progression/SanctuaryProgressionCosts.java").text
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| migration.target?.pack_version != "26.2.0-alpha.191"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.109"
|| migration.pause_menu?.progression_full_width != true
@@ -3135,7 +3146,7 @@ tasks.register("verifyShopPricingLayout") {
"pack/migrations/26.2.0-alpha.192-shop-pricing-layout-to-alpha.193.json"))
def screen = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryShopScreen.java").text
def progression = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopProgression.java").text
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.110"
|| migration.shop_layout?.locked_offer_buttons != 6
|| migration.shop_layout?.button_slots != [4, 5, 6, 7, 8, 9]
@@ -3166,7 +3177,7 @@ tasks.register("verifyShopCsvBossGates") {
"pack/migrations/26.2.0-alpha.194-shop-csv-boss-gates-to-alpha.195.json"))
def provider = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopCatalogProvider.java").text
def catalog = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopOfferCatalog.java").text
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| migration.target?.pack_version != "26.2.0-alpha.195"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.111"
|| migration.compatibility?.sanctuary_data_version != 11
@@ -3197,7 +3208,7 @@ tasks.register("verifyDeliveryBoxOrientation") {
def blockstate = new JsonSlurper().parse(
file("src/main/resources/assets/sanctuary/blockstates/delivery_box.json"))
def expectedVariants = ["facing=north", "facing=east", "facing=south", "facing=west"].toSet()
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.112"
|| migration.mailbox?.id != "sanctuary:delivery_box"
|| migration.mailbox?.horizontal_facing != true
@@ -3225,7 +3236,7 @@ tasks.register("verifyInventoryShiftClick") {
def mixin = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/AbstractContainerMenuMixin.java").text
def screen = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/AbstractContainerScreenMixin.java").text
def gui = file("INVENTORY_GUI.md").text
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| !transfer.contains("shouldAppendOverflow")
|| !transfer.contains("isVanillaStorageRange")
|| !transfer.contains("hotbarStart")
@@ -3271,7 +3282,7 @@ tasks.register("verifyQuestRequests") {
new JsonSlurper().parse(file("src/main/resources/data/sanctuary/loot_table/blocks/${path}.json"))
.pools[0].entries[0].name
}
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.113"
|| migration.quest_requests?.save_key != "quest_requests"
|| migration.quest_requests?.data_version != 1
@@ -3333,7 +3344,7 @@ tasks.register("verifyQuestRequestContracts") {
def locales = ["fr_fr", "en_us", "ru_ru"].collectEntries { locale ->
[(locale): new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${locale}.json"))]
}
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| migration.source?.pack_version != "26.2.0-alpha.198"
|| migration.source?.modules?.sanctuary != "0.0.0-alpha.114"
|| migration.target?.pack_version != "26.2.0-alpha.199"
@@ -3402,7 +3413,7 @@ tasks.register("verifyQuestRequestEscrow") {
def locales = ["fr_fr", "en_us", "ru_ru"].collectEntries { locale ->
[(locale): new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${locale}.json"))]
}
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| migration.source?.pack_version != "26.2.0-alpha.200"
|| migration.source?.modules?.sanctuary != "0.0.0-alpha.115"
|| migration.target?.pack_version != "26.2.0-alpha.201"
@@ -3481,7 +3492,7 @@ tasks.register("verifyCraftableBannerCape") {
def locales = ["fr_fr", "en_us", "ru_ru"].collect {
new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json"))
}
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.130"
|| migration.white_cape?.crafting_material_count != 6
|| migration.white_cape?.maximum_pattern_layers != 6
@@ -3533,7 +3544,7 @@ tasks.register("verifyFinalSanctuaryTickets") {
def locales = ["fr_fr", "en_us", "ru_ru"].collect {
new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json"))
}
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| !capeRenderer.contains("SHIELD_FACE_WIDTH = 12.0F")
|| !capeRenderer.contains("SHIELD_FACE_HEIGHT = 22.0F")
|| !capeRenderer.contains("CAPE_WIDTH / SHIELD_FACE_WIDTH")
@@ -3587,7 +3598,7 @@ tasks.register("verifyAlphaIndev") {
def locales = ["fr_fr", "en_us", "ru_ru"].collect {
new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json"))
}
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.122"
|| migration.alpha_world?.dimension != "sanctuary:alpha"
|| migration.alpha_world?.legacy_dimension != "portals:alpha"
@@ -3647,7 +3658,7 @@ tasks.register("verifyBackroom") {
def locales = ["fr_fr", "en_us", "ru_ru"].collect {
new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json"))
}
if (project.version.toString() != "0.0.0-alpha.139"
if (project.version.toString() != "0.0.0-alpha.140"
|| migration.source_data_version != 13 || migration.target_data_version != 14
|| migration.integration_modules?.anotherworld?.source != "0.0.0-alpha.56"
|| migration.integration_modules?.anotherworld?.target != "0.0.0-alpha.57"
@@ -3706,6 +3717,7 @@ tasks.named("check") {
dependsOn tasks.named("questTrackerModelSmoke")
dependsOn tasks.named("questTrackerDisplaySmoke")
dependsOn tasks.named("questIdSuggestionsSmoke")
dependsOn tasks.named("shopCatalogBrowseSmoke")
dependsOn tasks.named("alphaIndevGeneratorSmoke")
dependsOn tasks.named("capeJoinSyncSmoke")
dependsOn tasks.named("capeSyncPayloadSmoke")
@@ -116,6 +116,7 @@ public final class SanctuaryClient implements ClientModInitializer {
));
SanctuaryAtlasOptions.load();
SanctuaryClientQuests.load();
SanctuaryShopOptions.load();
SanctuaryBulkActionsClient.initialize();
SanctuaryHealthPreview.register();
CelestialSpyglassSelection.register();
@@ -0,0 +1,71 @@
package fr.koka99cab.sanctuary26.sanctuary.client;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import fr.koka99cab.sanctuary26.sanctuary.SanctuaryMod;
import fr.koka99cab.sanctuary26.sanctuary.shop.ShopCatalogBrowse;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import net.fabricmc.loader.api.FabricLoader;
/** Persisted catalogue grid preference for the Shop screen. */
final class SanctuaryShopOptions {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final Path FILE = FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shop.json");
private static Settings settings = new Settings();
private static boolean loaded;
private SanctuaryShopOptions() {
}
static void load() {
if (loaded) return;
loaded = true;
if (!Files.isRegularFile(FILE)) return;
try (Reader reader = Files.newBufferedReader(FILE, StandardCharsets.UTF_8)) {
Settings decoded = GSON.fromJson(reader, Settings.class);
if (decoded != null) settings = decoded.normalized();
} catch (IOException | RuntimeException exception) {
SanctuaryMod.LOGGER.warn("[{}] Could not read Shop catalogue options.",
SanctuaryMod.DISPLAY_NAME, exception);
}
}
static int catalogGrid() {
load();
return settings.catalogGrid;
}
static void setCatalogGrid(int grid) {
load();
int next = ShopCatalogBrowse.clampGrid(grid);
if (next == settings.catalogGrid) return;
settings.catalogGrid = next;
save();
}
private static void save() {
try {
Files.createDirectories(FILE.getParent());
try (Writer writer = Files.newBufferedWriter(FILE, StandardCharsets.UTF_8)) {
GSON.toJson(settings, writer);
}
} catch (IOException exception) {
SanctuaryMod.LOGGER.warn("[{}] Could not save Shop catalogue options.",
SanctuaryMod.DISPLAY_NAME, exception);
}
}
private static final class Settings {
int catalogGrid = ShopCatalogBrowse.DEFAULT_GRID;
private Settings normalized() {
catalogGrid = ShopCatalogBrowse.clampGrid(catalogGrid);
return this;
}
}
}
@@ -3,6 +3,7 @@ package fr.koka99cab.sanctuary26.sanctuary.client;
import fr.koka99cab.sanctuary26.sanctuary.network.ShopActionPayload;
import fr.koka99cab.sanctuary26.sanctuary.network.ShopRequestPayload;
import fr.koka99cab.sanctuary26.sanctuary.network.ShopSnapshotPayload;
import fr.koka99cab.sanctuary26.sanctuary.shop.ShopCatalogBrowse;
import fr.koka99cab.sanctuary26.sanctuary.shop.ShopOffer;
import fr.koka99cab.sanctuary26.sanctuary.shop.ShopProgression;
import java.util.ArrayList;
@@ -10,7 +11,11 @@ import java.util.List;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
@@ -21,6 +26,8 @@ public final class SanctuaryShopScreen extends Screen {
private static final int PAGE_SIZE = 9;
private static final int GRID_COLUMNS = 3;
private static final int GRID_ROWS = 3;
private static final int TOOLBAR_Y = 48;
private static final int PAGE_LABEL_WIDTH = 44;
private final Screen parent;
private final List<ActionHint> actionHints = new ArrayList<>();
private ShopSnapshotPayload rendered;
@@ -31,6 +38,9 @@ public final class SanctuaryShopScreen extends Screen {
private long requestedRefreshForExpiry = Long.MIN_VALUE;
private int page;
private int renderedExperienceLevel = -1;
private String catalogSearch = "";
private boolean catalogSearchFocused;
private EditBox catalogSearchBox;
public SanctuaryShopScreen(Screen parent) {
super(Component.translatable("screen.sanctuary.shop.title"));
@@ -41,6 +51,7 @@ public final class SanctuaryShopScreen extends Screen {
rendered = SanctuaryClientShop.snapshot();
renderedExperienceLevel = minecraft == null || minecraft.player == null ? -1 : minecraft.player.experienceLevel;
actionHints.clear();
catalogSearchBox = null;
if (rendered == null || !rendered.operator()) {
operatorPage = false;
confirmCatalogReset = false;
@@ -84,25 +95,16 @@ public final class SanctuaryShopScreen extends Screen {
}).bounds(center - 100, 109, 200, 20).build());
} else {
List<ShopOffer> offers = visibleOffers();
int maxPage = offers.isEmpty() ? 0 : (offers.size() - 1) / PAGE_SIZE;
page = Math.min(page, maxPage);
int start = page * PAGE_SIZE;
int end = Math.min(offers.size(), start + PAGE_SIZE);
int pageSize = pageSize();
page = ShopCatalogBrowse.clampPage(page, offers.size(), pageSize);
int start = page * pageSize;
int end = Math.min(offers.size(), start + pageSize);
GridLayout layout = gridLayout();
for (int index = start; index < end; index++) addOfferButtons(offers.get(index), index - start, layout);
if (!catalogPage && rendered != null) addLockedOfferButtons(layout);
if (catalogPage) {
Button previous = addRenderableWidget(Button.builder(Component.literal("<"), button -> {
page = Math.max(0, page - 1); rebuildWidgets();
}).bounds(layout.left(), 48, 32, 18).build());
Button next = addRenderableWidget(Button.builder(Component.literal(">"), button -> {
page++; rebuildWidgets();
}).bounds(layout.left() + 36, 48, 32, 18).build());
previous.active = page > 0;
next.active = (page + 1) * PAGE_SIZE < offers.size();
}
if (catalogPage) addCatalogToolbar(layout, offers.size());
}
if (!requested && ClientPlayNetworking.canSend(ShopRequestPayload.ID)) {
requested = true;
@@ -110,6 +112,40 @@ public final class SanctuaryShopScreen extends Screen {
}
}
private void addCatalogToolbar(GridLayout layout, int offerCount) {
int pageSize = pageSize();
Button previous = addRenderableWidget(Button.builder(Component.literal("<"), button -> {
page = Math.max(0, page - 1); rebuildWidgets();
}).bounds(layout.left(), TOOLBAR_Y, 32, 18).build());
Button next = addRenderableWidget(Button.builder(Component.literal(">"), button -> {
page++; rebuildWidgets();
}).bounds(layout.left() + 36, TOOLBAR_Y, 32, 18).build());
previous.active = page > 0;
next.active = (page + 1) * pageSize < offerCount;
int gridControlsWidth = 34 * 3 + 4 * 2;
int searchX = layout.left() + 72 + PAGE_LABEL_WIDTH + 4;
int searchWidth = Math.max(72, layout.width() - 72 - PAGE_LABEL_WIDTH - 4 - gridControlsWidth - 6);
catalogSearchBox = addRenderableWidget(new EditBox(font, searchX, TOOLBAR_Y, searchWidth, 18,
Component.translatable("screen.sanctuary.shop.search")));
catalogSearchBox.setMaxLength(ShopCatalogBrowse.MAX_SEARCH_LENGTH);
catalogSearchBox.setHint(Component.translatable("screen.sanctuary.shop.search_hint"));
catalogSearchBox.setValue(catalogSearch);
catalogSearchBox.setResponder(this::catalogSearchChanged);
if (catalogSearchFocused) setInitialFocus(catalogSearchBox);
int gridX = searchX + searchWidth + 6;
for (int grid = ShopCatalogBrowse.MIN_GRID; grid <= ShopCatalogBrowse.MAX_GRID; grid++) {
int selected = grid;
Button mode = addRenderableWidget(Button.builder(Component.literal(selected + "×" + selected),
button -> setCatalogGrid(selected))
.bounds(gridX + (selected - ShopCatalogBrowse.MIN_GRID) * 38, TOOLBAR_Y, 34, 18)
.tooltip(Tooltip.create(Component.translatable("screen.sanctuary.shop.grid", selected, selected)))
.build());
mode.active = catalogGrid() != selected;
}
}
private void addOfferButtons(ShopOffer offer, int visibleIndex, GridLayout layout) {
Card card = layout.card(visibleIndex);
int actionY = card.y() + card.height() - 21;
@@ -178,9 +214,11 @@ public final class SanctuaryShopScreen extends Screen {
Component.translatable("screen.sanctuary.shop.slots_maximum", rendered.visibleOfferSlots(), PAGE_SIZE),
layout.left() + Math.min(240, layout.width() / 2) / 2,
ShopDailyHeaderLayout.PROGRESS_Y, 0xFF80E09A);
if (catalogPage) drawCatalogPage(graphics, layout);
}
if (!operatorPage && !rendered.hasDeliveryBox()) graphics.centeredText(font,
Component.translatable("screen.sanctuary.shop.no_box"), width / 2, 58, 0xFFFF7070);
Component.translatable("screen.sanctuary.shop.no_box"), width / 2,
catalogPage ? Math.max(68, height - 54) : 58, 0xFFFF7070);
if (!rendered.status().isEmpty()) graphics.centeredText(font,
Component.translatable("screen.sanctuary.shop.status." + rendered.status()), width / 2, Math.max(68, height - 41), 0xFF80E09A);
if (operatorPage) {
@@ -194,23 +232,66 @@ public final class SanctuaryShopScreen extends Screen {
drawActionHints(graphics, mouseX, mouseY);
}
@Override
public boolean keyPressed(KeyEvent event) {
if (ShopCatalogBrowse.capturesInventoryKey(catalogSearchBox != null && catalogSearchBox.isFocused(),
minecraft != null && minecraft.options.keyInventory.matches(event))) return true;
return super.keyPressed(event);
}
@Override
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
if (catalogSearchBox != null && catalogSearchBox.isFocused()
&& !catalogSearchBox.isMouseOver(event.x(), event.y())) {
catalogSearchFocused = false;
setFocused(null);
}
return super.mouseClicked(event, doubleClick);
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
if (catalogPage && !operatorPage && scrollY != 0.0D) {
int next = page + (scrollY < 0.0D ? 1 : -1);
int clamped = ShopCatalogBrowse.clampPage(next, visibleOffers().size(), pageSize());
if (clamped != page) {
page = clamped;
rebuildWidgets();
return true;
}
}
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
}
private void drawOffers(GuiGraphicsExtractor graphics, int mouseX, int mouseY, GridLayout layout) {
List<ShopOffer> offers = visibleOffers();
int start = Math.min(page * PAGE_SIZE, offers.size());
int end = Math.min(offers.size(), start + PAGE_SIZE);
int pageSize = pageSize();
int start = Math.min(page * pageSize, offers.size());
int end = Math.min(offers.size(), start + pageSize);
for (int index = start; index < end; index++) {
ShopOffer offer = offers.get(index); int visible = index - start;
Card card = layout.card(visible);
graphics.fill(card.x(), card.y(), card.x() + card.width(), card.y() + card.height(), 0x38000000);
graphics.outline(card.x(), card.y(), card.width(), card.height(), 0x697F7F7F);
ItemStack stack = offer.stack();
drawLargeItem(graphics, stack, card.x() + 7, card.y() + 6);
String displayName = font.plainSubstrByWidth(stack.getHoverName().getString(), Math.max(20, card.width() - 49));
graphics.text(font, Component.literal(displayName), card.x() + 45, card.y() + 8, 0xFFFFFFFF);
if (offer.count() > 1) graphics.text(font, Component.literal("×" + offer.count()),
card.x() + 45, card.y() + 21, 0xFFB9B9B9);
if (mouseX >= card.x() + 6 && mouseX < card.x() + 40
&& mouseY >= card.y() + 5 && mouseY < card.y() + 39)
boolean compact = card.width() < 90 || card.height() < 70;
float itemScale = compact ? 1.0F : 2.0F;
int iconSize = Math.round(16 * itemScale);
int itemX = card.x() + (compact ? 4 : 7);
int itemY = card.y() + (compact ? 4 : 6);
drawScaledItem(graphics, stack, itemX, itemY, itemScale);
int textX = itemX + iconSize + 4;
int textWidth = Math.max(12, card.x() + card.width() - 4 - textX);
graphics.text(font, Component.literal(font.plainSubstrByWidth(
stack.getHoverName().getString(), textWidth)), textX, itemY + 2, 0xFFFFFFFF);
if (offer.count() > 1) {
int countY = compact ? itemY + 2 + font.lineHeight : itemY + 15;
if (countY + font.lineHeight <= card.y() + card.height() - 22) {
graphics.text(font, Component.literal("×" + offer.count()), textX, countY, 0xFFB9B9B9);
}
}
if (mouseX >= card.x() && mouseX < card.x() + card.width()
&& mouseY >= card.y() && mouseY < card.y() + card.height() - 21)
graphics.setTooltipForNextFrame(font, stack, mouseX, mouseY);
}
if (!catalogPage && rendered != null) {
@@ -223,7 +304,19 @@ public final class SanctuaryShopScreen extends Screen {
card.x() + card.width() / 2, card.y() + card.height() / 2 - 23, 0xFF8A8A8A);
}
}
if (offers.isEmpty()) graphics.centeredText(font, Component.translatable("screen.sanctuary.shop.catalog_empty"), width / 2, 112, 0xFF555555);
if (offers.isEmpty()) graphics.centeredText(font, Component.translatable(catalogPage
&& !ShopCatalogBrowse.normalize(catalogSearch).isEmpty()
? "screen.sanctuary.shop.catalog_no_results"
: "screen.sanctuary.shop.catalog_empty"), width / 2, 112, 0xFF555555);
}
private void drawCatalogPage(GuiGraphicsExtractor graphics, GridLayout layout) {
int itemCount = visibleOffers().size();
int pageSize = pageSize();
graphics.centeredText(font, Component.translatable("screen.sanctuary.shop.page",
ShopCatalogBrowse.clampPage(page, itemCount, pageSize) + 1,
ShopCatalogBrowse.pageCount(itemCount, pageSize)),
layout.left() + 72 + PAGE_LABEL_WIDTH / 2, TOOLBAR_Y + 5, 0xFFD0D0D0);
}
private void drawActionHints(GuiGraphicsExtractor graphics, int mouseX, int mouseY) {
@@ -243,9 +336,13 @@ public final class SanctuaryShopScreen extends Screen {
}
private static void drawLargeItem(GuiGraphicsExtractor graphics, ItemStack stack, int x, int y) {
drawScaledItem(graphics, stack, x, y, 2.0F);
}
private static void drawScaledItem(GuiGraphicsExtractor graphics, ItemStack stack, int x, int y, float scale) {
graphics.pose().pushMatrix();
graphics.pose().translate(x, y);
graphics.pose().scale(2.0F, 2.0F);
graphics.pose().scale(scale, scale);
graphics.item(stack, 0, 0);
graphics.pose().popMatrix();
}
@@ -290,14 +387,44 @@ public final class SanctuaryShopScreen extends Screen {
private List<ShopOffer> visibleOffers() {
if (rendered == null || operatorPage) return List.of();
return catalogPage ? rendered.catalogOffers() : rendered.dailyOffers();
if (!catalogPage) return rendered.dailyOffers();
return ShopCatalogBrowse.filter(rendered.catalogOffers(), catalogSearch, ShopOffer::itemId,
offer -> offer.stack().getHoverName().getString());
}
private int catalogGrid() {
return SanctuaryShopOptions.catalogGrid();
}
private int pageSize() {
return catalogPage ? ShopCatalogBrowse.pageSize(catalogGrid()) : PAGE_SIZE;
}
private void catalogSearchChanged(String value) {
if (value.equals(catalogSearch)) return;
catalogSearch = value;
catalogSearchFocused = true;
page = 0;
rebuildWidgets();
}
private void setCatalogGrid(int requested) {
int next = ShopCatalogBrowse.clampGrid(requested);
if (next == catalogGrid()) return;
SanctuaryShopOptions.setCatalogGrid(next);
page = 0;
rebuildWidgets();
}
private GridLayout gridLayout() {
int panelWidth = Math.max(240, Math.min(720, width - 12));
int left = width / 2 - panelWidth / 2;
int top = ShopDailyHeaderLayout.GRID_TOP;
int availableHeight = Math.max(174, height - top - 36);
return new GridLayout(left, top, panelWidth, availableHeight);
int grid = catalogGrid();
int columns = catalogPage ? grid : GRID_COLUMNS;
int rows = catalogPage ? grid : GRID_ROWS;
return new GridLayout(left, top, panelWidth, availableHeight, columns, rows);
}
private void action(String action, String itemId) {
if (ClientPlayNetworking.canSend(ShopActionPayload.ID)) ClientPlayNetworking.send(new ShopActionPayload(action, itemId));
@@ -305,12 +432,14 @@ public final class SanctuaryShopScreen extends Screen {
@Override public void onClose() { if (minecraft != null) minecraft.gui.setScreen(parent); }
@Override public boolean isPauseScreen() { return false; }
private record GridLayout(int left, int top, int width, int height) {
private record GridLayout(int left, int top, int width, int height, int columns, int rows) {
private static final int GAP = 6;
Card card(int index) {
int cardWidth = (width - GAP * (GRID_COLUMNS - 1)) / GRID_COLUMNS;
int cardHeight = (height - GAP * (GRID_ROWS - 1)) / GRID_ROWS;
int column = index % GRID_COLUMNS, row = index / GRID_COLUMNS;
int cols = Math.max(1, columns);
int gridRows = Math.max(1, rows);
int cardWidth = (width - GAP * (cols - 1)) / cols;
int cardHeight = (height - GAP * (gridRows - 1)) / gridRows;
int column = index % cols, row = index / cols;
return new Card(left + column * (cardWidth + GAP), top + row * (cardHeight + GAP), cardWidth, cardHeight);
}
}
@@ -75,9 +75,12 @@ final class QuestBoardGenerator {
private QuestBoardGenerator() {}
static QuestBoardData.Contract generate(MinecraftServer server, String boardId, QuestBoardTier tier, long now) {
long day = Math.floorDiv(now, 86_400_000L);
long seed = boardId.hashCode() * 0x9E3779B97F4A7C15L ^ day * 0xD1B54A32D192ED03L ^ tier.ordinal();
RandomSource random = RandomSource.create(seed);
return generate(server, boardId, tier, now, Math.floorDiv(now, 86_400_000L));
}
static QuestBoardData.Contract generate(MinecraftServer server, String boardId, QuestBoardTier tier,
long now, long salt) {
RandomSource random = RandomSource.create(contractSeed(boardId, tier, salt));
List<Template> pool = switch (tier) {
case EMERALD -> EASY;
case RUBY -> MEDIUM;
@@ -93,6 +96,10 @@ final class QuestBoardGenerator {
java.util.Optional.empty(), now);
}
static long contractSeed(String boardId, QuestBoardTier tier, long salt) {
return boardId.hashCode() * 0x9E3779B97F4A7C15L ^ salt * 0xD1B54A32D192ED03L ^ tier.ordinal();
}
static long nextRefreshAt(String boardId, long now) {
LocalDate tomorrow = Instant.ofEpochMilli(now).atZone(ZoneOffset.UTC).toLocalDate().plusDays(1);
long midnight = tomorrow.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli();
@@ -262,17 +262,17 @@ public final class QuestBoardService {
}
private static void tick(MinecraftServer server) {
QuestBoardData data = QuestBoardData.get(server);
syncLoadedSigns(server, data);
if (++tickCounter < 20) return;
tickCounter = 0;
long now = System.currentTimeMillis();
QuestBoardData data = QuestBoardData.get(server);
for (QuestBoardData.Board board : data.boards()) {
ServerLevel level = level(server, board.dimension());
BlockPos pos = BlockPos.of(board.pos());
if (level != null && level.isLoaded(pos) && !(level.getBlockEntity(pos) instanceof SignBlockEntity)) {
removeBoard(server, data, board, "server", "sign_missing", true); continue;
}
if (level != null && level.isLoaded(pos) && refreshedSignLayouts.add(board.id())) refreshSign(server, board);
QuestBoardData.Contract contract = board.contract().orElse(null);
if (contract != null && contract.claim().isPresent() && now >= contract.claim().get().deadlineAt()) {
QuestBoardData.Claim claim = contract.claim().get();
@@ -288,18 +288,41 @@ public final class QuestBoardService {
}
if (!board.frozen() && now >= board.nextRefreshAt()
&& (contract == null || contract.claim().isEmpty())) {
QuestBoardData.Contract generated = QuestBoardGenerator.generate(server, board.id(), board.questTier(), now);
QuestBoardData.Board refreshed = board.withContract(Optional.of(generated),
QuestBoardGenerator.nextRefreshAt(board.id(), now));
QuestBoardData.Board latest = data.board(board.id()).orElse(null);
if (latest == null || latest.frozen()
|| latest.contract().flatMap(QuestBoardData.Contract::claim).isPresent()) continue;
QuestBoardData.Contract generated = QuestBoardGenerator.generate(server, board.id(), latest.questTier(), now);
QuestBoardData.Board refreshed = latest.withContract(Optional.of(generated),
QuestBoardGenerator.nextRefreshAt(latest.id(), now));
data.replace(refreshed);
data.audit(new QuestBoardData.AuditEntry(now, "refresh", "server", board.id(), generated.id(), "daily"));
data.audit(new QuestBoardData.AuditEntry(now, "refresh", "server", latest.id(), generated.id(), "daily"));
refreshSign(server, refreshed);
}
}
}
private static void syncLoadedSigns(MinecraftServer server, QuestBoardData data) {
for (QuestBoardData.Board board : data.boards()) {
ServerLevel level = level(server, board.dimension());
BlockPos pos = BlockPos.of(board.pos());
boolean loaded = level != null && level.isLoaded(pos);
if (!shouldRewriteSign(refreshedSignLayouts, board.id(), loaded)) continue;
refreshSign(server, board);
}
}
/** A loaded board whose layout was forgotten (chunk unload or failed write) must be rewritten. */
static boolean shouldRewriteSign(Set<String> syncedLayouts, String boardId, boolean chunkLoaded) {
if (!chunkLoaded) {
syncedLayouts.remove(boardId);
return false;
}
return syncedLayouts.add(boardId);
}
private static void removeBoard(MinecraftServer server, QuestBoardData data, QuestBoardData.Board board,
String actor, String reason, boolean refundDeposit) {
refreshedSignLayouts.remove(board.id());
if (!data.remove(board)) return;
long now = System.currentTimeMillis();
board.contract().flatMap(QuestBoardData.Contract::claim).ifPresent(claim -> {
@@ -330,7 +353,8 @@ public final class QuestBoardService {
QuestBoardData.Board board = resolve(data, id);
if (board == null || board.contract().flatMap(QuestBoardData.Contract::claim).isPresent()) return false;
long now = System.currentTimeMillis();
QuestBoardData.Contract contract = QuestBoardGenerator.generate(server, board.id(), board.questTier(), now);
QuestBoardData.Contract contract = QuestBoardGenerator.generate(server, board.id(), board.questTier(), now,
now ^ UUID.randomUUID().getMostSignificantBits());
QuestBoardData.Board updated = board.withContract(Optional.of(contract), QuestBoardGenerator.nextRefreshAt(board.id(), now));
data.replace(updated);
data.audit(new QuestBoardData.AuditEntry(now, "operator_refresh", actor, board.id(), contract.id(), ""));
@@ -338,11 +362,15 @@ public final class QuestBoardService {
return true;
}
static void refreshSign(MinecraftServer server, QuestBoardData.Board board) {
static boolean refreshSign(MinecraftServer server, QuestBoardData.Board board) {
ServerLevel level = level(server, board.dimension());
BlockPos pos = BlockPos.of(board.pos());
if (level != null && level.isLoaded(pos) && level.getBlockEntity(pos) instanceof SignBlockEntity sign)
if (level != null && level.isLoaded(pos) && level.getBlockEntity(pos) instanceof SignBlockEntity sign) {
writeSign(level, sign, board);
return true;
}
refreshedSignLayouts.remove(board.id());
return false;
}
private static void writeSign(ServerLevel level, SignBlockEntity sign, QuestBoardData.Board board) {
@@ -370,6 +398,7 @@ public final class QuestBoardService {
sign.setWaxed(true);
sign.setChanged();
level.sendBlockUpdated(sign.getBlockPos(), sign.getBlockState(), sign.getBlockState(), 3);
refreshedSignLayouts.add(board.id());
}
private static void inspect(ServerPlayer player, QuestBoardData.Board board) {
@@ -0,0 +1,93 @@
package fr.koka99cab.sanctuary26.sanctuary.shop;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import java.util.regex.Pattern;
/** Client-side catalogue search, square-grid modes and pagination. */
public final class ShopCatalogBrowse {
public static final int MIN_GRID = 3;
public static final int MAX_GRID = 5;
public static final int DEFAULT_GRID = 3;
public static final int MAX_SEARCH_LENGTH = 48;
private static final Pattern COMBINING_MARKS = Pattern.compile("\\p{M}+");
private static final Pattern NON_ALPHANUMERIC = Pattern.compile("[^\\p{L}\\p{N}]+");
private ShopCatalogBrowse() {
}
public static int clampGrid(int grid) {
return Math.max(MIN_GRID, Math.min(MAX_GRID, grid));
}
public static int pageSize(int grid) {
int size = clampGrid(grid);
return size * size;
}
public static int previousGrid(int grid) {
return clampGrid(clampGrid(grid) - 1);
}
public static int nextGrid(int grid) {
return clampGrid(clampGrid(grid) + 1);
}
public static int pageCount(int itemCount, int pageSize) {
int size = Math.max(0, itemCount);
int safePageSize = Math.max(1, pageSize);
if (size == 0) return 1;
return (size + safePageSize - 1) / safePageSize;
}
public static int clampPage(int page, int itemCount, int pageSize) {
return Math.max(0, Math.min(page, pageCount(itemCount, pageSize) - 1));
}
public static String pageLabel(int page, int itemCount, int pageSize) {
return (clampPage(page, itemCount, pageSize) + 1) + "/" + pageCount(itemCount, pageSize);
}
public static String normalize(String value) {
if (value == null || value.isBlank()) return "";
String withoutMarks = COMBINING_MARKS.matcher(
Normalizer.normalize(value, Normalizer.Form.NFD)).replaceAll("");
return NON_ALPHANUMERIC.matcher(withoutMarks.toLowerCase(Locale.ROOT))
.replaceAll(" ").strip();
}
public static boolean matches(String itemId, String displayName, String query) {
String normalizedQuery = normalize(query);
if (normalizedQuery.isEmpty()) return true;
return matches(itemId, displayName, normalizedQuery.split(" "));
}
public static <T> List<T> filter(List<T> items, String query,
Function<T, String> itemId, Function<T, String> displayName) {
if (items == null || items.isEmpty()) return List.of();
String normalizedQuery = normalize(query);
if (normalizedQuery.isEmpty()) return List.copyOf(items);
String[] terms = normalizedQuery.split(" ");
List<T> matches = new ArrayList<>();
for (T item : items) {
if (matches(itemId.apply(item), displayName.apply(item), terms)) matches.add(item);
}
return List.copyOf(matches);
}
public static boolean capturesInventoryKey(boolean searchFocused, boolean inventoryKey) {
return searchFocused && inventoryKey;
}
private static boolean matches(String itemId, String displayName, String[] terms) {
String searchable = normalize((itemId == null ? "" : itemId) + " "
+ (displayName == null ? "" : displayName));
for (String term : terms) {
if (!searchable.contains(term)) return false;
}
return true;
}
}
@@ -418,6 +418,11 @@
"screen.sanctuary.shop.balance": "%s rubies · %s sapphires",
"screen.sanctuary.shop.no_box": "Place your Mailbox nearby to receive parcels.",
"screen.sanctuary.shop.catalog_empty": "Your permanent catalogue is empty.",
"screen.sanctuary.shop.catalog_no_results": "No catalogue item matches this search.",
"screen.sanctuary.shop.search": "Search the catalogue",
"screen.sanctuary.shop.search_hint": "Search…",
"screen.sanctuary.shop.grid": "%s×%s grid",
"screen.sanctuary.shop.page": "%s/%s",
"screen.sanctuary.shop.operator": "Operator",
"screen.sanctuary.shop.operator_title": "Operator tools",
"screen.sanctuary.shop.operator_description": "Server-authoritative shop maintenance.",
@@ -418,6 +418,11 @@
"screen.sanctuary.shop.balance": "%s rubis · %s saphirs",
"screen.sanctuary.shop.no_box": "Pose ta boîte aux lettres à proximité pour recevoir les colis.",
"screen.sanctuary.shop.catalog_empty": "Ton catalogue permanent est vide.",
"screen.sanctuary.shop.catalog_no_results": "Aucun objet du catalogue ne correspond à cette recherche.",
"screen.sanctuary.shop.search": "Rechercher dans le catalogue",
"screen.sanctuary.shop.search_hint": "Rechercher…",
"screen.sanctuary.shop.grid": "Grille %s×%s",
"screen.sanctuary.shop.page": "%s/%s",
"screen.sanctuary.shop.operator": "Opérateur",
"screen.sanctuary.shop.operator_title": "Outils opérateur",
"screen.sanctuary.shop.operator_description": "Maintenance du Shop validée par le serveur.",
@@ -329,6 +329,11 @@
"screen.sanctuary.shop.balance": "%s рубинов · %s сапфиров",
"screen.sanctuary.shop.no_box": "Поставьте свой ящик доставки рядом, чтобы получать покупки.",
"screen.sanctuary.shop.catalog_empty": "Ваш постоянный каталог пуст.",
"screen.sanctuary.shop.catalog_no_results": "Ни один предмет каталога не соответствует этому поиску.",
"screen.sanctuary.shop.search": "Поиск по каталогу",
"screen.sanctuary.shop.search_hint": "Поиск…",
"screen.sanctuary.shop.grid": "Сетка %s×%s",
"screen.sanctuary.shop.page": "%s/%s",
"screen.sanctuary.shop.operator": "Оператор",
"screen.sanctuary.shop.operator_title": "Инструменты оператора",
"screen.sanctuary.shop.operator_description": "Обслуживание магазина с проверкой на сервере.",

Some files were not shown because too many files have changed in this diff Show More