Integrate Happy Creepers and prepare the complete beta.293 update
Build Sanctuary / build (push) Canceled after 0s
Build Sanctuary / build (push) Canceled after 0s
This commit is contained in:
@@ -1,3 +1,11 @@
|
||||
# beta.293 — Happy Creepers, logo packwiz et mods facultatifs
|
||||
|
||||
- Intégration de la branche `codex/happy-creepers` avec les baleines beta.290.
|
||||
- Logo Sanctuary pendant packwiz et outils prêts dans le ZIP Prism.
|
||||
- Simple Voice Chat, Bobby facultatif, Lithium et Entity Culling épinglés pour 26.3.
|
||||
- Même canal et même instance Prism, profils client/serveur et sauvegardes conservés.
|
||||
- [Contrat et reçu de livraison](docs/release-beta293.md).
|
||||
|
||||
# beta.290 — Baleine fantôme volante
|
||||
|
||||
- Import de l’IA, du modèle animé, de la texture et des effets de la baleine Sanctuary 26.2.
|
||||
|
||||
@@ -7,6 +7,13 @@ correspondantes. Le module Sanctuary Test n’est pas requis.
|
||||
[Intégration et vérification du pack beta.224](docs/four-islands-base224.md).
|
||||
|
||||
|
||||
## beta.293 — livraison intégrée en cours
|
||||
|
||||
[Préparation de la livraison complète](docs/release-beta293.md) : Happy Creepers
|
||||
de la branche dédiée, baleines fantômes, logo packwiz et quatre mods facultatifs.
|
||||
Le serveur Beta, le téléchargement Web et la même instance Prism sont les cibles
|
||||
demandées ; les étapes réellement effectuées sont consignées dans le reçu.
|
||||
|
||||
## beta.292 — voix et mods facultatifs (préparation locale)
|
||||
|
||||
[Simple Voice Chat, Bobby, Lithium et Entity Culling rejoignent le pack](docs/optional-mods-beta292.md).
|
||||
|
||||
@@ -366,3 +366,15 @@ Résultat SHA-256 :
|
||||
`d476a3fedba3af3e463d6461b81cf6ca8702fe10ceb3934f426390d59da63f7e`.
|
||||
Les sons et particules sont natifs à Minecraft, référencés sans copie ; les
|
||||
droits sur les ressources Minecraft dérivées restent à Mojang/Microsoft.
|
||||
|
||||
|
||||
## Happy creepers
|
||||
|
||||
`assets/sanctuary/textures/entity/creeper/happy.png` est repris sans modification
|
||||
du module `itsalive` de Sanctuary 26.2, commit
|
||||
`26f2c2a0061619df1f146f9081d6740c560a7497`, chemin
|
||||
`itsalive/src/main/resources/assets/itsalive/textures/entity/creeper/happy.png`.
|
||||
SHA-256 : `40e22bf0610cfc7ae8ca910b1e6bdce0c759ccbf8714df7566ebe5538cac24f0`.
|
||||
Il s'agit du visage souriant historique sur le modèle natif du creeper. Les
|
||||
droits sur les ressources Minecraft dérivées restent à Mojang/Microsoft.
|
||||
Aucune autre variante de texture ni ressource Mobopia n'est importée.
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Happy creepers — réintégration
|
||||
|
||||
Branche `codex/happy-creepers`, worktree isolé du chantier baleines.
|
||||
Version locale préparée : **beta.291**, sur le socle beta.289.
|
||||
|
||||
Cette fiche conserve le reçu de la branche dédiée. Son commit `0ffd371a` est
|
||||
repris dans la [livraison intégrée beta.293](release-beta293.md), avec les
|
||||
baleines, le logo packwiz et les quatre mods facultatifs. Consulter ce reçu
|
||||
pour l'état de publication et les vérifications de la combinaison finale.
|
||||
|
||||
## Contrat
|
||||
|
||||
Le happy creeper est un état du creeper Minecraft existant. **Maj + clic droit
|
||||
avec une fleur** équipe son unique emplacement cosmétique de tête ; **Maj +
|
||||
clic droit à main vide** rend cette même fleur. Le geste, les contrôles de
|
||||
distance/visibilité, la consommation en survie et la conservation des composants
|
||||
de l'objet sont ceux du système de chapeaux existant. Un emplacement occupé
|
||||
doit être vidé avant d'équiper une autre fleur.
|
||||
|
||||
Toute fleur du tag Minecraft `minecraft:flowers` rend le creeper heureux :
|
||||
visage souriant historique, fleur exacte visible sur la tête animée, aucun
|
||||
ciblage hostile ni explosion. L'équipement annule immédiatement une mèche
|
||||
déjà amorcée, y compris au briquet ; retirer la fleur ne reprend pas cette
|
||||
ancienne mèche. Tant que la fleur est portée, briquet et boule de feu ne
|
||||
l'allument pas et ne sont pas consommés ou usés. Le creeper continue d'utiliser
|
||||
ses déplacements natifs et peut toujours subir des dégâts.
|
||||
|
||||
Retirer la fleur ou la remplacer par un objet sans rapport rétablit le visage,
|
||||
le ciblage et l'explosion ordinaires. Un pot de fleurs, des feuilles ou des
|
||||
graines ne déclenchent pas cet état. La charge électrique est indépendante :
|
||||
un creeper chargé peut être heureux, et la foudre peut charger un happy
|
||||
creeper. Retirer sa fleur conserve sa charge.
|
||||
|
||||
## Sauvegarde et périmètre
|
||||
|
||||
L'état heureux se déduit uniquement de l'objet dans `sanctuary:mob_head`, dont
|
||||
le schéma 1 est réutilisé sans modification. Cette pile est déjà sauvegardée
|
||||
et synchronisée par les chapeaux de mobs. Aucun second drapeau, nouvelle
|
||||
entité, changement de format, migration de monde ou tirage de variante n'est
|
||||
ajouté. Un creeper portant déjà une fleur devient donc heureux avec cette
|
||||
version. L'identité, la santé, la charge et les autres données natives restent
|
||||
sur le même creeper. Le port d'un chapeau conserve sa protection habituelle
|
||||
contre la disparition à distance.
|
||||
|
||||
Les nouvelles apparences de creepers restent pour un prochain ticket. Le
|
||||
suivi du joueur, les apparitions naturelles de happy creepers et les anciennes
|
||||
entités `happy_creeper:*`/`mobopia:*` ne font pas partie de cet incrément.
|
||||
Aucune nouvelle interface n'est ajoutée ; les messages d'équipement et de
|
||||
retrait FR/EN existants sont réutilisés.
|
||||
|
||||
## Vérification
|
||||
|
||||
- Serveur `happycreeper,cosmetics,headblocks` : **28 tests requis réussis**,
|
||||
dont sept tests happy creeper et le test d'infrastructure Fabric. Annulation
|
||||
immédiate d'une vraie mèche à vingt ticks, refus répété du ciblage/allumage,
|
||||
représailles, tous les objets du tag fleurs, récupération de la pile nommée,
|
||||
allumage natif sans fleur et rechargement du même creeper chargé vérifiés.
|
||||
- Parcours client **Vulkan avec Sodium** : **réussi**. Vrais appuis sur Maj et
|
||||
clics souris pour équiper/récupérer une fleur en survie ; synchronisation de
|
||||
la pile et du visage ; AI active pendant 85 ticks près d'un vrai joueur ;
|
||||
sourire, fleur sur la tête animée et aura électrique native vérifiés.
|
||||
Monde plat de développement neuf, graine `290`, Minecraft 26.3 sur Apple M1.
|
||||
Capture : `build/happy-creepers-screenshots/four-states-vulkan.png` ; de gauche
|
||||
à droite : chargé, chargé heureux, heureux, ordinaire.
|
||||
- `check build assemblePack` : **réussi en 3 min 55 s**, contrôles purs et
|
||||
**28 tests serveur** inclus. Export MRpack réussi, versions et dépendances
|
||||
exactes contrôlées ; un seul JAR Sanctuary embarqué, identique au JAR
|
||||
construit ; texture historique identique octet par octet et classes de test
|
||||
absentes. Logs : `build/happy-creepers-final-check-build.log`,
|
||||
`build/happy-creepers-server.log`, `build/happy-creepers-vulkan.log`.
|
||||
Reçu : `build/happy-creepers-validation.json`.
|
||||
|
||||
Commandes reproductibles :
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack \
|
||||
-PsanctuaryFocusedTests=happycreeper,cosmetics,headblocks \
|
||||
-PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
|
||||
-PsanctuaryHappyCreeperClientTests=true -PsanctuaryQuickTests=true \
|
||||
-PsanctuaryClientGraphicsBackend=vulkan -PsanctuarySodiumClientTests=true \
|
||||
-PsanctuaryClientHeapGb=4 -PsanctuaryClientNoVsync=true
|
||||
```
|
||||
|
||||
Le premier essai de `check build` associait par erreur le preset unifié et le
|
||||
profil rapide ; il a échoué au choix du générateur avant les tests. Le profil
|
||||
plat `sanctuaryAtlasOnly=true` évite ce conflit de configuration existant.
|
||||
Aucun monde personnel, serveur Beta, canal packwiz ou Prism n'est modifié.
|
||||
|
||||
## Artefacts locaux
|
||||
|
||||
- [Pack beta.291](../build/Sanctuary-beta.291.mrpack), 16 724 142 octets.
|
||||
SHA-256 : `3c2d630fc85e88b08bac94cc2244e975d3552fcac4d508ad366adea730062468`.
|
||||
- JAR : `mods/sanctuary/build/libs/sanctuary-beta.291.jar`.
|
||||
SHA-256 : `e8e315db1e4831400244d17429723099fbfe3a352387d8a9cf0ecb01d8170e9e`.
|
||||
|
||||
Cette branche part de beta.289 et ne contient pas le travail parallèle des
|
||||
baleines préparé en beta.290. L'intégration des deux branches précède toute
|
||||
éventuelle publication commune. Aucun tag ni artefact distant n'est créé.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Sanctuary beta.293
|
||||
|
||||
- Happy Creepers repris de la branche dédiée `codex/happy-creepers` : Maj +
|
||||
clic droit avec une fleur pour la porter, visage souriant et comportement
|
||||
passif ; retrait à main vide avec le même geste. Charge électrique conservée.
|
||||
- Baleines fantômes volantes et œuf natif de beta.290 inclus.
|
||||
- Logo Sanctuary affiché pendant les téléchargements et mises à jour packwiz,
|
||||
avec l'installateur intégré au ZIP Prism prêt à importer.
|
||||
- Simple Voice Chat 2.6.24+26.3, Bobby 5.2.16+mc26.3, Lithium 0.26.2+mc26.3 et
|
||||
Entity Culling 1.11.3 pour 26.3 disponibles comme choix facultatifs du client.
|
||||
Lithium et la voix font également partie du profil serveur.
|
||||
- Sound Physics 1.5.1, Sodium 0.9.2, JEI embarqué et Minecraft 26.3 / Fabric
|
||||
0.19.5 conservés. La version vocale retenue respecte ces dépendances.
|
||||
- Sauvegardes et réglages restent attachés à la même instance ; aucun changement
|
||||
de génération ou de format de monde. Dozei Stream et Only Fun restent futurs.
|
||||
|
||||
Le ZIP Prism suit le canal stable à chaque lancement. Les anciens utilisateurs
|
||||
gardent leur instance ; la première mise à jour de l'habillage doit aussi
|
||||
installer le nouvel outil packwiz et conserver `--bootstrap-no-update`.
|
||||
@@ -0,0 +1,80 @@
|
||||
# beta.293 — livraison intégrée
|
||||
|
||||
Livraison demandée le **26 octobre 2026** pour essayer le parcours complet :
|
||||
site → ZIP Prism → packwiz avec logo → jeu et serveur Beta.
|
||||
Branche `codex/release-beta293`.
|
||||
|
||||
## Contenu et provenance
|
||||
|
||||
La base conserve les baleines de `a93e2611`, l'habillage packwiz et les quatre
|
||||
mods facultatifs de `97c601cd`. Les Happy Creepers sont repris du commit
|
||||
`0ffd371a` de `codex/happy-creepers`, sans réimplémentation : les règles et la
|
||||
texture restent celles de cette branche dédiée. Les conflits d'intégration
|
||||
portaient sur les versions, les listes de tests et les documents communs.
|
||||
La livraison utilise le prochain compteur libre **beta.293** ; les préparations
|
||||
locales beta.291 et beta.292 ne sont pas publiées séparément.
|
||||
|
||||
[Notes de version](notes-beta293.md), [Happy Creepers](happy-creepers.md),
|
||||
[logo packwiz](packwiz-logo-beta291.md), [mods facultatifs](optional-mods-beta292.md).
|
||||
|
||||
Le test du quota interdimensionnel des baleines attend désormais la fin du
|
||||
chargement des entités du chunk Nether via `waitForEntities`. Ses deux fixtures
|
||||
restent immobiles : l'une au centre du chunk forcé, l'autre en hauteur pour que
|
||||
sa boîte de 36 blocs ne croise pas le nettoyage des tests voisins. Le test
|
||||
d'origine pouvait perdre une fixture avant de vérifier le quota. Le code de
|
||||
jeu reste identique ; **79 tests serveur** passent sur la base réunie, avec
|
||||
Lithium et la voix chargés. Le déplacement de la baleine naturelle reste testé
|
||||
avec son IA active.
|
||||
|
||||
## Cibles de livraison
|
||||
|
||||
- Artefacts immuables et canal client/serveur Gitea ;
|
||||
- CT **350** et service `sanctuary-beta.service`, après arrêt et sauvegarde ;
|
||||
- téléchargement direct de Sanctuary Web, CT **320**, sans modification du code live ;
|
||||
- même instance Prism **Sanctuary Beta**, avec sauvegarde, installateur habillé
|
||||
vérifié et conservation des données personnelles.
|
||||
|
||||
La règle NAT Beta existante `25567 → 192.168.1.122:25565` couvre TCP et UDP.
|
||||
La voix utilisera **UDP 25565 dans la CT**, annoncé comme
|
||||
`beta.sanctuary-minecraft.net:25567`. Aucun changement de Bbox, UPnP, Alpha,
|
||||
RCON ou règles des autres serveurs n'est nécessaire.
|
||||
|
||||
## Validation de la livraison
|
||||
|
||||
`check build assemblePack assembleServerPack` : **réussi en 2 min 46 s**,
|
||||
avec les contrôles purs et **79 tests serveur requis**. Profil natif :
|
||||
`whale290,happycreeper,cosmetics,headblocks,recipes,progression,smoker285,hunter286`,
|
||||
`sanctuaryAtlasOnly=true`, `sanctuaryOptional292ServerTests=true`.
|
||||
Journal : `build/release293/check-build-final.log`.
|
||||
|
||||
Parcours Happy Creepers **Vulkan avec Sodium et les quatre mods chargés** :
|
||||
toutes les assertions en jeu réussissent sur deux essais, capture des quatre
|
||||
états inspectée (`build/release293/happy-four-states-vulkan.png`). Équipement et
|
||||
retrait au clavier/souris, fleur exacte synchronisée, visage restauré, IA
|
||||
passive près du joueur et charge indépendante vérifiés. La fermeture du monde
|
||||
reste bloquée dans la synchronisation `Phaser` de Fabric Client GameTest ; ce
|
||||
contrôle graphique n'est donc pas un succès de tâche Gradle. Le premier dump
|
||||
montre le thread de test dans `TestSingleplayerContextImpl.close`, le serveur
|
||||
dans `ThreadingImpl.enterPhase` et le rendu dans `IntegratedServer.halt`.
|
||||
Ce blocage de l'infrastructure de test est consigné séparément des assertions
|
||||
réussies et du build complet réussi. Les parcours des quatre mods avec et sans
|
||||
Sodium ont leur reçu dans [beta.292](optional-mods-beta292.md).
|
||||
|
||||
Les archives ont été contrôlées avant publication : sept dépendances externes
|
||||
et un seul JAR Sanctuary dans le MRpack ; huit mods du client et quatre du
|
||||
serveur ; JEI embarqué, aucun test ni dépendance facultative imposée dans le
|
||||
JAR Sanctuary. Texture happy identique à la branche dédiée, classes creeper
|
||||
et baleine présentes. ZIP Prism sans mods ni sauvegardes personnelles.
|
||||
|
||||
| Artefact | Octets | SHA-256 |
|
||||
| --- | ---: | --- |
|
||||
| `sanctuary-beta.293.jar` | 18171689 | `4a0a3941f651ba3566ec41940c2f4c820d917121312e3aead23b39f99633ebe3` |
|
||||
| `Sanctuary-beta.293.mrpack` | 16761300 | `80e1d9a54165f279738a6486c8c3cd7e5e90ad7ad4ca81d32a4d4a04b15f0e90` |
|
||||
| `Sanctuary-Prism-beta.293.zip` | 3928525 | `95c57f5e70627a2847d1cc2033c8d9f50f44ef4a302ca892bc727e0c37ece0a9` |
|
||||
| `Sanctuary-Server-beta.293.zip` | 4036536 | `5f3135f7f5b5448023f9873c5242e3c2ab9444c197e1b551d356c8b1120bf01d` |
|
||||
|
||||
## État opérationnel
|
||||
|
||||
Validation et livraison en cours. Les hashes, sauvegardes et contrôles réels
|
||||
seront consignés ici après exécution ; cet état ne vaut pas une publication.
|
||||
Les journaux de cette livraison sont sous `build/release293/`.
|
||||
+1
-1
@@ -8,7 +8,7 @@ Plateforme épinglée : Minecraft **26.3**, Fabric Loader **0.19.5**, Fabric API
|
||||
## Préparer le kit
|
||||
|
||||
Dans les sources : `./gradlew check build assemblePack assembleServerPack`.
|
||||
Le kit de la préparation courante est `build/Sanctuary-Server-beta.292.zip` ; les manifestes locaux sont
|
||||
Le kit de la livraison courante est `build/Sanctuary-Server-beta.293.zip` ; les manifestes locaux sont
|
||||
dans `build/packwiz-server/`. Le ZIP est un amorçage : il télécharge les mods
|
||||
depuis le canal au premier démarrage, il n'est pas une copie hors ligne du pack.
|
||||
Il n'est utilisable sur le canal public qu'après publication de cette version.
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ loom_version=1.17.20
|
||||
fabric_api_version=0.160.5+26.3
|
||||
|
||||
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
|
||||
mod_version=beta.292
|
||||
pack_version=beta.292
|
||||
mod_version=beta.293
|
||||
pack_version=beta.293
|
||||
resource_pack_version=beta.121
|
||||
maven_group=fr.koka.sanctuary
|
||||
jei_version=30.32.0-sanctuary.4
|
||||
|
||||
@@ -152,6 +152,9 @@ tasks.named('runGameTest') {
|
||||
}
|
||||
|
||||
tasks.named('processGametestResources') {
|
||||
def happyCreeper = providers.gradleProperty('sanctuaryHappyCreeperClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sanctuaryHappyCreeperClientTests', happyCreeper)
|
||||
if (happyCreeper) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.HappyCreeperClientChecks') }
|
||||
def optional292 = providers.gradleProperty('sanctuaryOptional292ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sanctuaryOptional292ClientTests', optional292)
|
||||
if (optional292) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Optional292ClientChecks') }
|
||||
@@ -286,7 +289,7 @@ tasks.named('processGametestResources') {
|
||||
|
||||
def villageOnly = providers.gradleProperty('sanctuaryVillageOnly').map { it.toBoolean() }.getOrElse(false)
|
||||
def focused = providers.gradleProperty('sanctuaryFocusedTests').getOrElse('').split(',').findAll { !it.isEmpty() }
|
||||
def focusClasses = [whale290: 'FlyingWhale290GameTests', hunter286: 'Hunter286GameTests', smoker285: 'Smoker285GameTests', boomer284: 'Boomer284GameTests', spitter283: 'Spitter283GameTests', charger282: 'Charger282GameTests', infected280: 'Infected280GameTests', claims278: 'ClaimBattle278GameTests', claims277: 'ClaimDemeure277GameTests', menu275: 'Menu275GameTests', claims273: 'ClaimMap273GameTests', claims272: 'ChunkClaims272GameTests', overview271: 'Inhabitant271GameTests', property270: 'Property270GameTests', providence269: 'Providence269GameTests', providence268: 'Providence268GameTests', providence265: 'Providence265GameTests', familiar260: 'Familiar260GameTests', census: 'CensusGameTestsTerrain', insects245: 'Insect245GameTests', map244: 'Atlas244GameTests', ziplines242: 'Zipline242GameTests', waystones239: 'Waystone239GameTests', waystonesLarge239: 'WaystoneLarge239GameTests', forgerie238: 'Forgerie238GameTests', aptitudes236: 'Aptitudes236GameTests', solo232: 'Solo232GameTests', diagonal231: 'Diagonal231GameTests', diagonal230: 'Diagonal230GameTests', diagonal229: 'Diagonal229GameTests', diagonal228: 'Diagonal228GameTests', diagonal227: 'Diagonal227GameTests', diagonal226: 'Diagonal226GameTests', diagonal225: 'Diagonal225GameTests', east224: 'East224GameTests', east223: 'East223GameTests', rock222: 'YellowRock222GameTests', west221: 'West221GameTests', west220: 'West220GameTests', west219: 'West219GameTests', west218: 'West218GameTests', west217: 'West217GameTests', south216: 'South216GameTests', relays215: 'Relays215GameTests', south214: 'South214GameTests', south213: 'South213GameTests', south212: 'South212GameTests', south211: 'South211GameTests', south210: 'South210GameTests', south209: 'South209GameTests', north208: 'North208GameTests', north207: 'North207GameTests', north206: 'North206GameTests', north205: 'North205GameTests', base204: 'Base204GameTests', palace173: 'Palace173GameTests', horde172: 'Horde172GameTests', horde171: 'Horde171GameTests', horde170: 'Horde170GameTests', horde169: 'Horde169GameTests', horde168: 'Horde168GameTests', horde167: 'Horde167GameTests', plans072: 'Plans072GameTests', starter052: 'Starter052GameTests', dispenser051: 'Dispenser051GameTests', head050: 'Head050GameTests', boatshead: 'BoatsHead049GameTests', poultry: 'Poultry048GameTests', starinspect: 'StarInspection047GameTests', skyflight: 'SkyFlight046GameTests', eggaudit: 'Companion044GameTests', headblocks: 'HeadBlocks043GameTests', operator: 'OperatorStars040GameTests', starter: 'Starter039GameTests', gravelayout: 'GraveInventory039GameTests', foodknowledge: 'FoodKnowledge039GameTests', graves: 'GravesFood038GameTests', realtime: 'Realtime037GameTests', corrections: 'Corrections036GameTests', collections: 'Collections035GameTests', notifications: 'Notifications034GameTests', menus: 'Menus033GameTests', aerial: 'Aerial24WorldGameTests', transit: 'Transit29WorldGameTests', village: 'Village306GameTests', drained: 'Drained307GameTests', expeditions: 'AncientExpeditions001GameTests', progression: 'Progression003GameTests', map: 'Atlas005GameTests', demeure: 'Demeure006GameTests', mining: 'Mining008GameTests', building: 'Building009GameTests', inventory: 'Inventory010GameTests', cycle: 'Cycle011GameTests', inventoryflow: 'Inventory012GameTests', sorting: 'Inventory014GameTests', tutorial: 'Tutorial016GameTests', movement: 'Movement017GameTests', accessories: 'Accessory018GameTests', companions: 'Companion019GameTests', familiar: 'Familiar020GameTests', carry: 'Carry027GameTests', familiarhit: 'Familiar029GameTests', refonte: 'Companion032GameTests', lights: 'Dynamic030GameTests', cosmetics: 'HeadCosmetic021GameTests', startup: 'StartupPerformanceGameTests', recipes: 'Recipes024GameTests']
|
||||
def focusClasses = [happycreeper: 'HappyCreeperGameTests', whale290: 'FlyingWhale290GameTests', hunter286: 'Hunter286GameTests', smoker285: 'Smoker285GameTests', boomer284: 'Boomer284GameTests', spitter283: 'Spitter283GameTests', charger282: 'Charger282GameTests', infected280: 'Infected280GameTests', claims278: 'ClaimBattle278GameTests', claims277: 'ClaimDemeure277GameTests', menu275: 'Menu275GameTests', claims273: 'ClaimMap273GameTests', claims272: 'ChunkClaims272GameTests', overview271: 'Inhabitant271GameTests', property270: 'Property270GameTests', providence269: 'Providence269GameTests', providence268: 'Providence268GameTests', providence265: 'Providence265GameTests', familiar260: 'Familiar260GameTests', census: 'CensusGameTestsTerrain', insects245: 'Insect245GameTests', map244: 'Atlas244GameTests', ziplines242: 'Zipline242GameTests', waystones239: 'Waystone239GameTests', waystonesLarge239: 'WaystoneLarge239GameTests', forgerie238: 'Forgerie238GameTests', aptitudes236: 'Aptitudes236GameTests', solo232: 'Solo232GameTests', diagonal231: 'Diagonal231GameTests', diagonal230: 'Diagonal230GameTests', diagonal229: 'Diagonal229GameTests', diagonal228: 'Diagonal228GameTests', diagonal227: 'Diagonal227GameTests', diagonal226: 'Diagonal226GameTests', diagonal225: 'Diagonal225GameTests', east224: 'East224GameTests', east223: 'East223GameTests', rock222: 'YellowRock222GameTests', west221: 'West221GameTests', west220: 'West220GameTests', west219: 'West219GameTests', west218: 'West218GameTests', west217: 'West217GameTests', south216: 'South216GameTests', relays215: 'Relays215GameTests', south214: 'South214GameTests', south213: 'South213GameTests', south212: 'South212GameTests', south211: 'South211GameTests', south210: 'South210GameTests', south209: 'South209GameTests', north208: 'North208GameTests', north207: 'North207GameTests', north206: 'North206GameTests', north205: 'North205GameTests', base204: 'Base204GameTests', palace173: 'Palace173GameTests', horde172: 'Horde172GameTests', horde171: 'Horde171GameTests', horde170: 'Horde170GameTests', horde169: 'Horde169GameTests', horde168: 'Horde168GameTests', horde167: 'Horde167GameTests', plans072: 'Plans072GameTests', starter052: 'Starter052GameTests', dispenser051: 'Dispenser051GameTests', head050: 'Head050GameTests', boatshead: 'BoatsHead049GameTests', poultry: 'Poultry048GameTests', starinspect: 'StarInspection047GameTests', skyflight: 'SkyFlight046GameTests', eggaudit: 'Companion044GameTests', headblocks: 'HeadBlocks043GameTests', operator: 'OperatorStars040GameTests', starter: 'Starter039GameTests', gravelayout: 'GraveInventory039GameTests', foodknowledge: 'FoodKnowledge039GameTests', graves: 'GravesFood038GameTests', realtime: 'Realtime037GameTests', corrections: 'Corrections036GameTests', collections: 'Collections035GameTests', notifications: 'Notifications034GameTests', menus: 'Menus033GameTests', aerial: 'Aerial24WorldGameTests', transit: 'Transit29WorldGameTests', village: 'Village306GameTests', drained: 'Drained307GameTests', expeditions: 'AncientExpeditions001GameTests', progression: 'Progression003GameTests', map: 'Atlas005GameTests', demeure: 'Demeure006GameTests', mining: 'Mining008GameTests', building: 'Building009GameTests', inventory: 'Inventory010GameTests', cycle: 'Cycle011GameTests', inventoryflow: 'Inventory012GameTests', sorting: 'Inventory014GameTests', tutorial: 'Tutorial016GameTests', movement: 'Movement017GameTests', accessories: 'Accessory018GameTests', companions: 'Companion019GameTests', familiar: 'Familiar020GameTests', carry: 'Carry027GameTests', familiarhit: 'Familiar029GameTests', refonte: 'Companion032GameTests', lights: 'Dynamic030GameTests', cosmetics: 'HeadCosmetic021GameTests', startup: 'StartupPerformanceGameTests', recipes: 'Recipes024GameTests']
|
||||
focused.each { if (!focusClasses.containsKey(it)) throw new GradleException('Unknown focused native test: ' + it) }
|
||||
inputs.property('focusedNative', focused)
|
||||
def yellowRock222Client = providers.gradleProperty('sanctuaryYellowRock222ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
@@ -707,7 +710,7 @@ tasks.named('processGametestResources') {
|
||||
dependencies {
|
||||
// Extra mods are independent pack dependencies, never nested in Sanctuary.
|
||||
def extras = new groovy.json.JsonSlurper().parse(rootProject.file('scripts/data/optional-mods.json'))
|
||||
def clientExtras = providers.gradleProperty('sanctuaryOptional292ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
def clientExtras = providers.gradleProperty('sanctuaryOptional292ClientTests').map { it.toBoolean() }.getOrElse(false) || providers.gradleProperty('sanctuaryOptionalModsRuntime').map { it.toBoolean() }.getOrElse(false)
|
||||
def serverExtras = providers.gradleProperty('sanctuaryOptional292ServerTests').map { it.toBoolean() }.getOrElse(false)
|
||||
if (clientExtras || serverExtras) {
|
||||
extras.each { slug, pin ->
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import fr.koka.sanctuary.cosmetics.*;
|
||||
import fr.koka.sanctuary.creeper.HappyCreepers;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.client.renderer.entity.CreeperRenderer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.*;
|
||||
import net.minecraft.world.entity.monster.Creeper;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Real sneak-clicks, synced flower/face, charged combination and real server AI under Vulkan. */
|
||||
public final class HappyCreeperClientChecks implements FabricClientGameTest {
|
||||
private static void check(boolean ok, String message) { if (!ok) throw new AssertionError(message); }
|
||||
public void runTest(ClientGameTestContext c) {
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable(); var intro = new AtomicBoolean(true);
|
||||
c.runOnClient(m -> {
|
||||
m.options.renderDistance().set(5); m.options.simulationDistance().set(5); m.options.pauseOnLostFocus = false;
|
||||
if (net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("voicechat")) {
|
||||
var voice = de.maxhenkel.voicechat.VoicechatClient.CLIENT_CONFIG;
|
||||
voice.muted.set(true); voice.disabled.set(true); voice.onboardingFinished.set(true);
|
||||
voice.macosCheckMicrophonePermission.set(false);
|
||||
}
|
||||
});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m -> { if (intro.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
|
||||
IntroClient.setQuickTestIntro(true); Starter039TestSupport.selectFirst(screen);
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
|
||||
.filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
|
||||
press(screen, "sanctuary.progression.enter"); intro.set(false);
|
||||
}});
|
||||
try (var world = c.worldBuilder().adjustSettings(s -> {
|
||||
s.setName("HappyCreeper-" + UUID.randomUUID()); s.setSeed("290"); s.setAllowCommands(true);
|
||||
s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);
|
||||
s.setWorldType(s.getNormalPresetList().stream().filter(e -> e.preset() != null && e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());
|
||||
}).create()) {
|
||||
c.waitFor(m -> m.player != null && ProgressionClient.active() && m.gui.screen() == null && !IntroSpawnFlash.active(), 1200);
|
||||
var server = world.getServer();
|
||||
int id = server.computeOnServer(s -> {
|
||||
var p = s.getPlayerList().getPlayers().getFirst(); p.getAbilities().invulnerable = true;
|
||||
s.setDifficulty(net.minecraft.world.Difficulty.NORMAL, true);
|
||||
s.getCommands().performPrefixedCommand(s.createCommandSourceStack(), "sanctuary time vanilla");
|
||||
s.getCommands().performPrefixedCommand(s.createCommandSourceStack(), "time set noon");
|
||||
s.getCommands().performPrefixedCommand(s.createCommandSourceStack(), "sanctuary weather vanilla");
|
||||
s.getCommands().performPrefixedCommand(s.createCommandSourceStack(), "weather clear");
|
||||
p.level().getGameRules().set(GameRules.SPAWN_MOBS, false, s);
|
||||
p.getInventory().clearContent(); p.teleportTo(.5, 1, .5); p.setYRot(0); p.setXRot(0);
|
||||
fr.koka.sanctuary.inventory.AccessoryAccess.of(p).clearContent(); FamiliarService.tick(p);
|
||||
p.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(Items.POPPY)); p.inventoryMenu.broadcastChanges();
|
||||
for (int x = -9; x <= 9; x++) for (int z = -3; z <= 12; z++)
|
||||
p.level().setBlockAndUpdate(new BlockPos(x, 0, z), Blocks.STONE.defaultBlockState());
|
||||
var mob = EntityTypes.CREEPER.create(p.level(), EntitySpawnReason.COMMAND); mob.setNoAi(true);
|
||||
mob.setPos(.5, 1, 2.5); mob.setYRot(180); mob.yHeadRot = mob.yBodyRot = 180; p.level().addFreshEntity(mob); return mob.getId();
|
||||
});
|
||||
c.runOnClient(m -> m.getWindow().setWindowed(1280, 960));
|
||||
c.waitFor(m -> m.level.getEntity(id) != null && m.player.getMainHandItem().is(Items.POPPY), 200);
|
||||
c.getInput().holdKey(o -> o.keyShift); c.waitTicks(5); click(c, id); c.getInput().releaseKey(o -> o.keyShift);
|
||||
c.waitFor(m -> m.level.getEntity(id) instanceof Creeper mob && HappyCreepers.isHappy(mob) && m.player.getMainHandItem().isEmpty(), 200);
|
||||
c.runOnClient(m -> {
|
||||
var mob = (Creeper) m.level.getEntity(id); var renderer = (CreeperRenderer) m.getEntityRenderDispatcher().getRenderer(mob);
|
||||
var state = renderer.createRenderState(mob, 1); var hat = ((MobHeadState.Access) state).sanctuary$mobHat();
|
||||
check(hat.active && !hat.head.block.isEmpty(), "Exact equipped flower reaches the native head layer");
|
||||
check(renderer.getTextureLocation(state).equals(SanctuaryMod.id("textures/entity/creeper/happy.png")), "Synced flower selects the restored smile");
|
||||
check(m.getResourceManager().getResource(renderer.getTextureLocation(state)).isPresent(), "Historical happy texture is packaged");
|
||||
});
|
||||
server.runOnServer(s -> { var mob = (Creeper) s.getPlayerList().getPlayers().getFirst().level().getEntity(id); mob.setNoAi(false); });
|
||||
c.waitTicks(85);
|
||||
server.runOnServer(s -> {
|
||||
var p = s.getPlayerList().getPlayers().getFirst(); var mob = (Creeper) p.level().getEntity(id);
|
||||
check(mob != null && mob.isAlive() && mob.getTarget() == null && !mob.isIgnited(), "Real happy AI survives next to a survival player without attacking");
|
||||
mob.setNoAi(true); mob.teleportTo(.5, 1, 2.5);
|
||||
}); c.waitTicks(5);
|
||||
c.getInput().holdKey(o -> o.keyShift); c.waitTicks(5); click(c, id); c.getInput().releaseKey(o -> o.keyShift);
|
||||
c.waitFor(m -> m.level.getEntity(id) instanceof Creeper mob && !HappyCreepers.isHappy(mob), 200);
|
||||
c.runOnClient(m -> {
|
||||
var mob = (Creeper) m.level.getEntity(id); var renderer = (CreeperRenderer) m.getEntityRenderDispatcher().getRenderer(mob);
|
||||
check(!((MobHeadState.Access) renderer.createRenderState(mob, 1)).sanctuary$mobHat().active, "Removing the flower clears its render state");
|
||||
check(!renderer.getTextureLocation(renderer.createRenderState(mob, 1)).getNamespace().equals("sanctuary"), "Removal restores the native face");
|
||||
});
|
||||
server.runOnServer(s -> {
|
||||
var p = s.getPlayerList().getPlayers().getFirst(); check(p.getInventory().countItem(Items.POPPY) == 1, "Real empty-hand gesture returns the one flower");
|
||||
p.level().getEntity(id).discard(); p.teleportTo(.5, 1, -2.5);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
var mob = EntityTypes.CREEPER.create(p.level(), EntitySpawnReason.COMMAND); mob.setNoAi(true);
|
||||
mob.setPos((i - 1.5) * 2 + .5, 1, 4.5); mob.setYRot(180); mob.yHeadRot = mob.yBodyRot = 180;
|
||||
if (i == 1 || i == 2) HeadEquipment.set(mob, new ItemStack(i == 1 ? Items.POPPY : Items.PINK_TULIP));
|
||||
if (i >= 2) mob.thunderHit(p.level(), EntityTypes.LIGHTNING_BOLT.create(p.level(), EntitySpawnReason.COMMAND));
|
||||
mob.clearFire(); p.level().addFreshEntity(mob);
|
||||
}
|
||||
}); c.waitTicks(20);
|
||||
c.runOnClient(m -> { m.player.setYRot(0); m.player.setXRot(4);
|
||||
int happy = 0, chargedHappy = 0;
|
||||
for (var mob : m.level.getEntitiesOfClass(Creeper.class, m.player.getBoundingBox().inflate(20))) {
|
||||
var renderer = (CreeperRenderer) m.getEntityRenderDispatcher().getRenderer(mob); var state = renderer.createRenderState(mob, 1);
|
||||
if (((MobHeadState.Access) state).sanctuary$mobHat().happyCreeper) { happy++; if (state.isPowered) chargedHappy++; }
|
||||
}
|
||||
check(happy == 2 && chargedHappy == 1, "Normal, happy, charged-happy and charged states coexist on the real client");
|
||||
});
|
||||
c.getInput().pressKey(InputConstants.KEY_F1); c.waitTicks(3); c.takeScreenshot("happy-creepers-four-states"); c.getInput().pressKey(InputConstants.KEY_F1);
|
||||
System.out.println("HAPPY_CREEPER_CLIENT_PASS: Vulkan, real sneak-click equipment/removal, exact synced flower, restored face, passive server AI, independent charge");
|
||||
} finally { intro.set(false); c.getInput().releaseKey(o -> o.keyShift); }
|
||||
}
|
||||
|
||||
private static void click(ClientGameTestContext c, int id) {
|
||||
c.runOnClient(m -> {
|
||||
var e = m.level.getEntity(id); var delta = e.getBoundingBox().getCenter().subtract(m.player.getEyePosition());
|
||||
m.player.setYRot((float) Math.toDegrees(Math.atan2(-delta.x, delta.z)));
|
||||
m.player.setXRot((float) -Math.toDegrees(Math.atan2(delta.y, Math.sqrt(delta.x * delta.x + delta.z * delta.z))));
|
||||
}); c.waitTicks(3);
|
||||
c.runOnClient(m -> check(m.hitResult instanceof net.minecraft.world.phys.EntityHitResult hit && hit.getEntity().getId() == id, "Mouse actually aims at the creeper"));
|
||||
c.getInput().pressMouse(InputConstants.MOUSE_BUTTON_RIGHT); c.waitTicks(7);
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -107,10 +107,19 @@ public final class FlyingWhale290GameTests {
|
||||
FlyingWhales.SPAWN_EGG.useOn(context);
|
||||
h.assertTrue(FlyingWhales.countLoaded(server) == 1 && p.getMainHandItem().getCount() == 2,
|
||||
"Real survival egg use spawns one whale and consumes one egg");
|
||||
var first = level.getEntities(FlyingWhales.TYPE, Entity::isAlive).getFirst();
|
||||
first.setNoAi(true);
|
||||
// Its 36-block hitbox must not overlap neighbouring tests' cleanup areas.
|
||||
first.setPos(p.position().add(0, 80, 0));
|
||||
var nether = server.getLevel(Level.NETHER); nether.setChunkForced(0, 0, true); nether.getChunk(0, 0);
|
||||
nether.waitForEntities(new net.minecraft.world.level.ChunkPos(0, 0), 0);
|
||||
var other = FlyingWhales.TYPE.create(nether, EntitySpawnReason.COMMAND);
|
||||
other.setPos(0, 64, 0); nether.addFreshEntity(other);
|
||||
// Keep the quota fixture inside its single forced chunk during the delay.
|
||||
other.setNoAi(true); other.setPos(8, 64, 8); nether.addFreshEntity(other);
|
||||
h.runAfterDelay(5, () -> {
|
||||
h.assertTrue(FlyingWhales.countLoaded(server) == 2,
|
||||
"Both quota fixtures remain loaded before egg use; firstRemoved=" + first.isRemoved()
|
||||
+ ", netherRemoved=" + other.isRemoved());
|
||||
FlyingWhales.SPAWN_EGG.useOn(context);
|
||||
h.assertTrue(FlyingWhales.countLoaded(server) == 2 && p.getMainHandItem().getCount() == 2,
|
||||
"Two across dimensions block the third egg without consuming it; count=" + FlyingWhales.countLoaded(server)
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import fr.koka.sanctuary.cosmetics.*;
|
||||
import fr.koka.sanctuary.creeper.HappyCreepers;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import net.fabricmc.fabric.api.gametest.v1.GameTest;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.ProblemReporter;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.*;
|
||||
import net.minecraft.world.entity.monster.Creeper;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.level.storage.*;
|
||||
|
||||
public final class HappyCreeperGameTests {
|
||||
@GameTest public void ordinaryHeadGestureConsumesAndReturnsTheSameFlower(GameTestHelper h) {
|
||||
var p = player(h); var mob = creeper(h); mob.setPos(p.position().add(0, 0, 1));
|
||||
var flower = new ItemStack(Items.POPPY, 3);
|
||||
flower.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME, net.minecraft.network.chat.Component.literal("Rose"));
|
||||
p.setItemInHand(InteractionHand.MAIN_HAND, flower); p.setShiftKeyDown(true);
|
||||
h.assertTrue(MobHeadService.equip(p, mob), "Existing head gesture equips the flower");
|
||||
h.assertTrue(flower.getCount() == 2 && HappyCreepers.isHappy(mob), "One real flower is consumed and calms the creeper");
|
||||
h.assertTrue(ItemStack.matches(HeadCosmeticService.appearance(mob), flower.copyWithCount(1)), "Exact flower and components are synchronized");
|
||||
h.assertTrue(mob.requiresCustomPersistence(), "Equipped creeper does not despawn at distance");
|
||||
p.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY);
|
||||
h.assertTrue(MobHeadService.equip(p, mob) && !HappyCreepers.isHappy(mob), "Empty-hand head gesture removes happiness");
|
||||
h.assertTrue(p.getInventory().countItem(Items.POPPY) == 1 && p.getInventory().getNonEquipmentItems().stream()
|
||||
.anyMatch(s -> s.is(Items.POPPY) && ItemStack.matches(s, flower.copyWithCount(1))), "The same named flower returns once");
|
||||
mob.discard(); h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void everyTaggedFlowerWorksButOtherHeadObjectsDoNot(GameTestHelper h) {
|
||||
var mob = creeper(h); int flowers = 0;
|
||||
for (var item : BuiltInRegistries.ITEM) {
|
||||
var stack = new ItemStack(item);
|
||||
if (!HappyCreepers.isFlower(stack)) continue;
|
||||
HeadEquipment.set(mob, stack);
|
||||
h.assertTrue(HappyCreepers.isHappy(mob), "Tagged flower is accepted: " + item); flowers++;
|
||||
}
|
||||
h.assertTrue(flowers >= 15, "The exact Minecraft version resolves its flower tag");
|
||||
for (var item : java.util.List.of(Items.FLOWER_POT, Items.OAK_LEAVES, Items.WHEAT_SEEDS, Items.STONE)) {
|
||||
HeadEquipment.set(mob, new ItemStack(item));
|
||||
h.assertTrue(!HappyCreepers.isHappy(mob), "A different head object keeps the ordinary temperament");
|
||||
}
|
||||
HeadEquipment.set(mob, ItemStack.EMPTY); mob.discard(); h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void flowerImmediatelyCancelsAnAlreadyIgnitedFuse(GameTestHelper h) {
|
||||
var mob = creeper(h); var p = player(h);
|
||||
mob.setTarget(p); mob.ignite();
|
||||
for (int i = 0; i < 20; i++) mob.tick();
|
||||
h.assertTrue(mob.isIgnited() && mob.getSwelling(1) > 0, "Real native fuse is already progressing");
|
||||
HeadEquipment.set(mob, new ItemStack(Items.DANDELION));
|
||||
h.assertTrue(!mob.isIgnited() && mob.getTarget() == null && mob.getSwelling(1) == 0, "Equipment immediately clears target, ignition and accumulated fuse");
|
||||
for (int i = 0; i < 60; i++) { mob.setTarget(p); mob.setSwellDir(1); mob.ignite(); mob.tick(); }
|
||||
h.assertTrue(mob.isAlive() && mob.getTarget() == null && !mob.isIgnited() && mob.getSwellDir() < 0 && mob.getSwelling(1) == 0,
|
||||
"Repeated targeting and ignition cannot make a happy creeper explode");
|
||||
HeadEquipment.set(mob, ItemStack.EMPTY); mob.tick();
|
||||
h.assertTrue(mob.isAlive() && !mob.isIgnited() && mob.getSwelling(1) == 0, "Removing the flower cannot resume an old fuse");
|
||||
mob.discard(); h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void happyCreeperRejectsRetaliationAndKeepsItsOrdinaryMovement(GameTestHelper h) {
|
||||
var mob = creeper(h); var p = player(h); mob.setNoAi(false);
|
||||
HeadEquipment.set(mob, new ItemStack(Items.CORNFLOWER));
|
||||
mob.hurtServer(h.getLevel(), mob.damageSources().playerAttack(p), 1);
|
||||
for (int i = 0; i < 40; i++) mob.tick();
|
||||
h.assertTrue(mob.getTarget() == null && !mob.isAggressive() && mob.isAlive() && !mob.isNoAi(), "Damage does not restore hostility or disable the native AI");
|
||||
HeadEquipment.set(mob, ItemStack.EMPTY); mob.setTarget(p);
|
||||
h.assertTrue(mob.getTarget() == p, "Removing the flower restores normal targeting");
|
||||
mob.discard(); h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void flintAndFireChargeCannotIgniteOrSpendItemsWhileHappy(GameTestHelper h) {
|
||||
var mob = creeper(h); var p = player(h);
|
||||
HeadEquipment.set(mob, new ItemStack(Items.BLUE_ORCHID));
|
||||
for (var hand : InteractionHand.values()) for (var item : java.util.List.of(Items.FLINT_AND_STEEL, Items.FIRE_CHARGE)) {
|
||||
var stack = new ItemStack(item, item == Items.FIRE_CHARGE ? 3 : 1);
|
||||
var before = stack.copy(); p.setItemInHand(hand, stack);
|
||||
mob.interact(p, hand, net.minecraft.world.phys.Vec3.ZERO);
|
||||
h.assertTrue(!mob.isIgnited() && ItemStack.matches(stack, before), "Igniter remains intact in either hand");
|
||||
}
|
||||
HeadEquipment.set(mob, ItemStack.EMPTY); mob.discard(); h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void chargedHappyCreeperKeepsIdentityFlowerAndChargeAfterNativeReload(GameTestHelper h) {
|
||||
var mob = creeper(h);
|
||||
HeadEquipment.set(mob, new ItemStack(Items.PINK_TULIP));
|
||||
mob.thunderHit(h.getLevel(), EntityTypes.LIGHTNING_BOLT.create(h.getLevel(), EntitySpawnReason.COMMAND));
|
||||
h.assertTrue(HappyCreepers.isHappy(mob) && mob.isPowered(), "Lightning charges a happy creeper without changing its temperament");
|
||||
var output = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, h.getLevel().registryAccess());
|
||||
h.assertTrue(mob.save(output), "Vanilla entity saves normally");
|
||||
var loaded = (Creeper) EntityType.loadEntityRecursive(TagValueInput.create(ProblemReporter.DISCARDING,
|
||||
h.getLevel().registryAccess(), output.buildResult()), h.getLevel(), EntitySpawnReason.LOAD, e -> e);
|
||||
h.assertTrue(loaded.getType() == EntityTypes.CREEPER && loaded.getUUID().equals(mob.getUUID()) && loaded.isPowered()
|
||||
&& HappyCreepers.isHappy(loaded) && HeadEquipment.get(loaded).is(Items.PINK_TULIP), "Existing head save preserves the same creeper, flower and independent electric charge");
|
||||
loaded.ignite(); loaded.tick(); h.assertTrue(!loaded.isIgnited() && loaded.isAlive(), "Reloaded flower still prevents explosions");
|
||||
HeadEquipment.set(loaded, ItemStack.EMPTY);
|
||||
h.assertTrue(loaded.isPowered() && !HappyCreepers.isHappy(loaded), "Taking the flower leaves the electric charge intact");
|
||||
HeadEquipment.set(mob, ItemStack.EMPTY); mob.discard(); loaded.discard(); h.succeed();
|
||||
}
|
||||
|
||||
@GameTest public void ordinaryAndUnequippedCreepersStillExplodeNormally(GameTestHelper h) {
|
||||
for (boolean previouslyHappy : new boolean[]{false, true}) {
|
||||
var mob = creeper(h);
|
||||
if (previouslyHappy) { HeadEquipment.set(mob, new ItemStack(Items.POPPY)); HeadEquipment.set(mob, ItemStack.EMPTY); }
|
||||
mob.ignite(); h.assertTrue(mob.isIgnited(), "Normal ignition is available without a flower");
|
||||
for (int i = 0; i < 31 && !mob.isRemoved(); i++) mob.tick();
|
||||
h.assertTrue(mob.isRemoved(), "Native fuse and explosion still finish without a flower");
|
||||
}
|
||||
h.succeed();
|
||||
}
|
||||
|
||||
private static Creeper creeper(GameTestHelper h) {
|
||||
var mob = EntityTypes.CREEPER.create(h.getLevel(), EntitySpawnReason.COMMAND);
|
||||
mob.setNoAi(true); mob.setPos(h.absoluteVec(new net.minecraft.world.phys.Vec3(2, 2, 2))); return mob;
|
||||
}
|
||||
private static ServerPlayer player(GameTestHelper h) {
|
||||
var p = Progression003GameTests.player(h); p.setUUID(java.util.UUID.randomUUID());
|
||||
p.setAttached(ProgressionService.RECORD, ProgressRecord.INITIAL.json()); ProgressEffects.apply(p, ProgressRecord.INITIAL); return p;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
"entrypoints": {
|
||||
"client": ["fr.koka.sanctuary.gametest.Starter039TestSupport"],
|
||||
"preLaunch": ["fr.koka.sanctuary.gametest.Inventory010MixinAudit"],
|
||||
"fabric-gametest": ["fr.koka.sanctuary.gametest.FlyingWhale290GameTests", "fr.koka.sanctuary.gametest.Hunter286GameTests", "fr.koka.sanctuary.gametest.Smoker285GameTests", "fr.koka.sanctuary.gametest.Boomer284GameTests", "fr.koka.sanctuary.gametest.Spitter283GameTests", "fr.koka.sanctuary.gametest.Charger282GameTests", "fr.koka.sanctuary.gametest.Infected280GameTests", "fr.koka.sanctuary.gametest.Menu275GameTests", "fr.koka.sanctuary.gametest.ClaimMap273GameTests", "fr.koka.sanctuary.gametest.ChunkClaims272GameTests", "fr.koka.sanctuary.gametest.Inhabitant271GameTests","fr.koka.sanctuary.gametest.Property270GameTests","fr.koka.sanctuary.gametest.Providence269GameTests","fr.koka.sanctuary.gametest.Providence268GameTests","fr.koka.sanctuary.gametest.Providence265GameTests","fr.koka.sanctuary.gametest.Insect245GameTests","fr.koka.sanctuary.gametest.Forgerie238GameTests","fr.koka.sanctuary.gametest.Aptitudes236GameTests","fr.koka.sanctuary.gametest.Diagonal231GameTests", "fr.koka.sanctuary.gametest.Diagonal230GameTests", "fr.koka.sanctuary.gametest.Diagonal229GameTests", "fr.koka.sanctuary.gametest.Diagonal228GameTests", "fr.koka.sanctuary.gametest.Diagonal227GameTests", "fr.koka.sanctuary.gametest.Diagonal226GameTests", "fr.koka.sanctuary.gametest.Diagonal225GameTests", "fr.koka.sanctuary.gametest.East224GameTests", "fr.koka.sanctuary.gametest.East223GameTests", "fr.koka.sanctuary.gametest.YellowRock222GameTests", "fr.koka.sanctuary.gametest.West221GameTests", "fr.koka.sanctuary.gametest.West220GameTests", "fr.koka.sanctuary.gametest.West219GameTests", "fr.koka.sanctuary.gametest.West218GameTests", "fr.koka.sanctuary.gametest.West217GameTests", "fr.koka.sanctuary.gametest.Base204GameTests", "fr.koka.sanctuary.gametest.North205GameTests", "fr.koka.sanctuary.gametest.Palace173GameTests", "fr.koka.sanctuary.gametest.Plans072GameTests", "fr.koka.sanctuary.gametest.Starter052GameTests", "fr.koka.sanctuary.gametest.Dispenser051GameTests", "fr.koka.sanctuary.gametest.Head050GameTests", "fr.koka.sanctuary.gametest.Poultry048GameTests", "fr.koka.sanctuary.gametest.StarInspection047GameTests", "fr.koka.sanctuary.gametest.SkyFlight046GameTests", "fr.koka.sanctuary.gametest.GravesFood038GameTests","fr.koka.sanctuary.gametest.Realtime037GameTests","fr.koka.sanctuary.gametest.Corrections036GameTests","fr.koka.sanctuary.gametest.Collections035GameTests","fr.koka.sanctuary.gametest.Notifications034GameTests","fr.koka.sanctuary.gametest.Menus033GameTests","${gametest_entrypoint}", "fr.koka.sanctuary.gametest.Swamp27WorldGameTests", "fr.koka.sanctuary.gametest.Caves28WorldGameTests", "fr.koka.sanctuary.gametest.Terraces29WorldGameTests", "fr.koka.sanctuary.gametest.River30WorldGameTests", "fr.koka.sanctuary.gametest.Transit29WorldGameTests", "fr.koka.sanctuary.gametest.Alpha28NativeStructuresGameTests", "fr.koka.sanctuary.gametest.Village306GameTests", "fr.koka.sanctuary.gametest.Drained307GameTests", "fr.koka.sanctuary.gametest.BlockKnowledgeGameTests", "fr.koka.sanctuary.gametest.CensusGameTestsTerrain", "fr.koka.sanctuary.gametest.CensusStockGameTests", "fr.koka.sanctuary.gametest.MaterialActivityGameTests", "fr.koka.sanctuary.gametest.AncientExpeditions001GameTests", "fr.koka.sanctuary.gametest.Progression003GameTests", "fr.koka.sanctuary.gametest.Atlas005GameTests", "fr.koka.sanctuary.gametest.Demeure006GameTests", "fr.koka.sanctuary.gametest.Mining008GameTests", "fr.koka.sanctuary.gametest.Building009GameTests", "fr.koka.sanctuary.gametest.Inventory010GameTests", "fr.koka.sanctuary.gametest.Cycle011GameTests", "fr.koka.sanctuary.gametest.Inventory012GameTests", "fr.koka.sanctuary.gametest.Inventory014GameTests", "fr.koka.sanctuary.gametest.Tutorial016GameTests", "fr.koka.sanctuary.gametest.Movement017GameTests", "fr.koka.sanctuary.gametest.Accessory018GameTests", "fr.koka.sanctuary.gametest.Companion019GameTests", "fr.koka.sanctuary.gametest.Companion032GameTests", "fr.koka.sanctuary.gametest.Recipes024GameTests", "fr.koka.sanctuary.gametest.Carry027GameTests", "fr.koka.sanctuary.gametest.Familiar029GameTests", "fr.koka.sanctuary.gametest.Dynamic030GameTests", "fr.koka.sanctuary.gametest.GraveInventory039GameTests", "fr.koka.sanctuary.gametest.FoodKnowledge039GameTests", "fr.koka.sanctuary.gametest.OperatorStars040GameTests", "fr.koka.sanctuary.gametest.Starter039GameTests", "fr.koka.sanctuary.gametest.Horde167GameTests", "fr.koka.sanctuary.gametest.Horde168GameTests", "fr.koka.sanctuary.gametest.Horde169GameTests", "fr.koka.sanctuary.gametest.Horde170GameTests", "fr.koka.sanctuary.gametest.Horde171GameTests", "fr.koka.sanctuary.gametest.Horde172GameTests", "fr.koka.sanctuary.gametest.Waystone239GameTests"],
|
||||
"fabric-gametest": ["fr.koka.sanctuary.gametest.HappyCreeperGameTests", "fr.koka.sanctuary.gametest.FlyingWhale290GameTests", "fr.koka.sanctuary.gametest.Hunter286GameTests", "fr.koka.sanctuary.gametest.Smoker285GameTests", "fr.koka.sanctuary.gametest.Boomer284GameTests", "fr.koka.sanctuary.gametest.Spitter283GameTests", "fr.koka.sanctuary.gametest.Charger282GameTests", "fr.koka.sanctuary.gametest.Infected280GameTests", "fr.koka.sanctuary.gametest.Menu275GameTests", "fr.koka.sanctuary.gametest.ClaimMap273GameTests", "fr.koka.sanctuary.gametest.ChunkClaims272GameTests", "fr.koka.sanctuary.gametest.Inhabitant271GameTests","fr.koka.sanctuary.gametest.Property270GameTests","fr.koka.sanctuary.gametest.Providence269GameTests","fr.koka.sanctuary.gametest.Providence268GameTests","fr.koka.sanctuary.gametest.Providence265GameTests","fr.koka.sanctuary.gametest.Insect245GameTests","fr.koka.sanctuary.gametest.Forgerie238GameTests","fr.koka.sanctuary.gametest.Aptitudes236GameTests","fr.koka.sanctuary.gametest.Diagonal231GameTests", "fr.koka.sanctuary.gametest.Diagonal230GameTests", "fr.koka.sanctuary.gametest.Diagonal229GameTests", "fr.koka.sanctuary.gametest.Diagonal228GameTests", "fr.koka.sanctuary.gametest.Diagonal227GameTests", "fr.koka.sanctuary.gametest.Diagonal226GameTests", "fr.koka.sanctuary.gametest.Diagonal225GameTests", "fr.koka.sanctuary.gametest.East224GameTests", "fr.koka.sanctuary.gametest.East223GameTests", "fr.koka.sanctuary.gametest.YellowRock222GameTests", "fr.koka.sanctuary.gametest.West221GameTests", "fr.koka.sanctuary.gametest.West220GameTests", "fr.koka.sanctuary.gametest.West219GameTests", "fr.koka.sanctuary.gametest.West218GameTests", "fr.koka.sanctuary.gametest.West217GameTests", "fr.koka.sanctuary.gametest.Base204GameTests", "fr.koka.sanctuary.gametest.North205GameTests", "fr.koka.sanctuary.gametest.Palace173GameTests", "fr.koka.sanctuary.gametest.Plans072GameTests", "fr.koka.sanctuary.gametest.Starter052GameTests", "fr.koka.sanctuary.gametest.Dispenser051GameTests", "fr.koka.sanctuary.gametest.Head050GameTests", "fr.koka.sanctuary.gametest.Poultry048GameTests", "fr.koka.sanctuary.gametest.StarInspection047GameTests", "fr.koka.sanctuary.gametest.SkyFlight046GameTests", "fr.koka.sanctuary.gametest.GravesFood038GameTests","fr.koka.sanctuary.gametest.Realtime037GameTests","fr.koka.sanctuary.gametest.Corrections036GameTests","fr.koka.sanctuary.gametest.Collections035GameTests","fr.koka.sanctuary.gametest.Notifications034GameTests","fr.koka.sanctuary.gametest.Menus033GameTests","${gametest_entrypoint}", "fr.koka.sanctuary.gametest.Swamp27WorldGameTests", "fr.koka.sanctuary.gametest.Caves28WorldGameTests", "fr.koka.sanctuary.gametest.Terraces29WorldGameTests", "fr.koka.sanctuary.gametest.River30WorldGameTests", "fr.koka.sanctuary.gametest.Transit29WorldGameTests", "fr.koka.sanctuary.gametest.Alpha28NativeStructuresGameTests", "fr.koka.sanctuary.gametest.Village306GameTests", "fr.koka.sanctuary.gametest.Drained307GameTests", "fr.koka.sanctuary.gametest.BlockKnowledgeGameTests", "fr.koka.sanctuary.gametest.CensusGameTestsTerrain", "fr.koka.sanctuary.gametest.CensusStockGameTests", "fr.koka.sanctuary.gametest.MaterialActivityGameTests", "fr.koka.sanctuary.gametest.AncientExpeditions001GameTests", "fr.koka.sanctuary.gametest.Progression003GameTests", "fr.koka.sanctuary.gametest.Atlas005GameTests", "fr.koka.sanctuary.gametest.Demeure006GameTests", "fr.koka.sanctuary.gametest.Mining008GameTests", "fr.koka.sanctuary.gametest.Building009GameTests", "fr.koka.sanctuary.gametest.Inventory010GameTests", "fr.koka.sanctuary.gametest.Cycle011GameTests", "fr.koka.sanctuary.gametest.Inventory012GameTests", "fr.koka.sanctuary.gametest.Inventory014GameTests", "fr.koka.sanctuary.gametest.Tutorial016GameTests", "fr.koka.sanctuary.gametest.Movement017GameTests", "fr.koka.sanctuary.gametest.Accessory018GameTests", "fr.koka.sanctuary.gametest.Companion019GameTests", "fr.koka.sanctuary.gametest.Companion032GameTests", "fr.koka.sanctuary.gametest.Recipes024GameTests", "fr.koka.sanctuary.gametest.Carry027GameTests", "fr.koka.sanctuary.gametest.Familiar029GameTests", "fr.koka.sanctuary.gametest.Dynamic030GameTests", "fr.koka.sanctuary.gametest.GraveInventory039GameTests", "fr.koka.sanctuary.gametest.FoodKnowledge039GameTests", "fr.koka.sanctuary.gametest.OperatorStars040GameTests", "fr.koka.sanctuary.gametest.Starter039GameTests", "fr.koka.sanctuary.gametest.Horde167GameTests", "fr.koka.sanctuary.gametest.Horde168GameTests", "fr.koka.sanctuary.gametest.Horde169GameTests", "fr.koka.sanctuary.gametest.Horde170GameTests", "fr.koka.sanctuary.gametest.Horde171GameTests", "fr.koka.sanctuary.gametest.Horde172GameTests", "fr.koka.sanctuary.gametest.Waystone239GameTests"],
|
||||
"fabric-client-gametest": ["fr.koka.sanctuary.gametest.SanctuaryClientRenderTests"]
|
||||
},
|
||||
"mixins": ["sanctuary-gametest.mixins.json"],
|
||||
|
||||
@@ -5,4 +5,5 @@ public final class MobHeadState {
|
||||
public HeadCosmeticState head;
|
||||
public float yaw,pitch,scale=1;
|
||||
public boolean active;
|
||||
public boolean happyCreeper;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public final class HeadEquipment {
|
||||
public static ItemStack get(LivingEntity e){return e instanceof Player p?AccessoryAccess.of(p).getItem(AccessoryContainer.HEAD):e instanceof Mob m?mob(m).stack:ItemStack.EMPTY;}
|
||||
public static void set(LivingEntity e,ItemStack stack){
|
||||
if(e instanceof Player p)AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,stack);
|
||||
else if(e instanceof Mob m){HeadBlockService.release(m);mob(m).stack=stack;mob(m).saved=null;MobHeadService.track(m);MobHeadService.publish(m);}
|
||||
else if(e instanceof Mob m){HeadBlockService.release(m);mob(m).stack=stack;mob(m).saved=null;fr.koka.sanctuary.creeper.HappyCreepers.headChanged(m);MobHeadService.track(m);MobHeadService.publish(m);}
|
||||
}
|
||||
public static void replace(LivingEntity e,ItemStack expected,ItemStack replacement){
|
||||
if(e instanceof Player p)AccessoryAccess.of(p).replaceHeadState(expected,replacement);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package fr.koka.sanctuary.creeper;
|
||||
|
||||
import fr.koka.sanctuary.cosmetics.HeadCosmeticService;
|
||||
import fr.koka.sanctuary.cosmetics.HeadEquipment;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.monster.Creeper;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Item;
|
||||
|
||||
/** Happiness is derived from the existing authoritative head slot, never saved twice. */
|
||||
public final class HappyCreepers {
|
||||
public static final TagKey<Item> FLOWERS = TagKey.create(Registries.ITEM, Identifier.withDefaultNamespace("flowers"));
|
||||
public interface Access { void sanctuary$calm(); }
|
||||
private HappyCreepers() {}
|
||||
|
||||
public static boolean isFlower(ItemStack stack) { return stack.is(FLOWERS); }
|
||||
|
||||
public static boolean isHappy(Creeper creeper) {
|
||||
return isFlower(creeper.level().isClientSide()
|
||||
? HeadCosmeticService.appearance(creeper) : HeadEquipment.get(creeper));
|
||||
}
|
||||
|
||||
public static void headChanged(Mob mob) {
|
||||
if (mob instanceof Creeper creeper && isHappy(creeper)) ((Access) creeper).sanctuary$calm();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package fr.koka.sanctuary.mixin;
|
||||
|
||||
import fr.koka.sanctuary.creeper.HappyCreepers;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.tags.ItemTags;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.monster.Creeper;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.*;
|
||||
|
||||
@Mixin(Creeper.class)
|
||||
public abstract class HappyCreeperMixin implements HappyCreepers.Access {
|
||||
@Shadow @Final private static EntityDataAccessor<Boolean> DATA_IS_IGNITED;
|
||||
@Shadow private int swell;
|
||||
@Shadow private int oldSwell;
|
||||
|
||||
public void sanctuary$calm() {
|
||||
var creeper = (Creeper) (Object) this;
|
||||
if (creeper.getTarget() != null || creeper.isAggressive()) creeper.getNavigation().stop();
|
||||
creeper.setTarget(null);
|
||||
creeper.setAggressive(false);
|
||||
creeper.setSwellDir(-1);
|
||||
creeper.getEntityData().set(DATA_IS_IGNITED, false);
|
||||
swell = oldSwell = 0;
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
private void sanctuary$stayCalm(CallbackInfo ci) {
|
||||
if (HappyCreepers.isHappy((Creeper) (Object) this)) sanctuary$calm();
|
||||
}
|
||||
|
||||
@Inject(method = "setTarget", at = @At("HEAD"), cancellable = true)
|
||||
private void sanctuary$noHostileTarget(LivingEntity target, CallbackInfo ci) {
|
||||
if (target != null && HappyCreepers.isHappy((Creeper) (Object) this)) ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "setSwellDir", at = @At("HEAD"), cancellable = true)
|
||||
private void sanctuary$noFuse(int direction, CallbackInfo ci) {
|
||||
if (direction > 0 && HappyCreepers.isHappy((Creeper) (Object) this)) ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "ignite", at = @At("HEAD"), cancellable = true)
|
||||
private void sanctuary$noIgnition(CallbackInfo ci) {
|
||||
if (HappyCreepers.isHappy((Creeper) (Object) this)) ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "explodeCreeper", at = @At("HEAD"), cancellable = true)
|
||||
private void sanctuary$noExplosion(CallbackInfo ci) {
|
||||
if (HappyCreepers.isHappy((Creeper) (Object) this)) { sanctuary$calm(); ci.cancel(); }
|
||||
}
|
||||
|
||||
@Inject(method = "mobInteract", at = @At("HEAD"), cancellable = true)
|
||||
private void sanctuary$keepIgniter(Player player, InteractionHand hand, CallbackInfoReturnable<InteractionResult> cir) {
|
||||
if (HappyCreepers.isHappy((Creeper) (Object) this) && player.getItemInHand(hand).is(ItemTags.CREEPER_IGNITERS))
|
||||
cir.setReturnValue(InteractionResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package fr.koka.sanctuary.mixin.client;
|
||||
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import fr.koka.sanctuary.client.MobHeadState;
|
||||
import fr.koka.sanctuary.creeper.HappyCreepers;
|
||||
import net.minecraft.client.renderer.entity.CreeperRenderer;
|
||||
import net.minecraft.client.renderer.entity.state.CreeperRenderState;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.entity.monster.Creeper;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.*;
|
||||
|
||||
@Mixin(CreeperRenderer.class)
|
||||
public abstract class HappyCreeperRendererMixin {
|
||||
@Unique private static final Identifier SANCTUARY_HAPPY = SanctuaryMod.id("textures/entity/creeper/happy.png");
|
||||
|
||||
@Inject(method = "extractRenderState(Lnet/minecraft/world/entity/monster/Creeper;Lnet/minecraft/client/renderer/entity/state/CreeperRenderState;F)V", at = @At("TAIL"))
|
||||
private void sanctuary$happyFace(Creeper creeper, CreeperRenderState state, float partial, CallbackInfo ci) {
|
||||
((MobHeadState.Access) state).sanctuary$mobHat().happyCreeper = HappyCreepers.isHappy(creeper);
|
||||
}
|
||||
|
||||
@Inject(method = "getTextureLocation(Lnet/minecraft/client/renderer/entity/state/CreeperRenderState;)Lnet/minecraft/resources/Identifier;", at = @At("HEAD"), cancellable = true)
|
||||
private void sanctuary$texture(CreeperRenderState state, CallbackInfoReturnable<Identifier> cir) {
|
||||
if (((MobHeadState.Access) state).sanctuary$mobHat().happyCreeper) cir.setReturnValue(SANCTUARY_HAPPY);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
@@ -4,6 +4,7 @@
|
||||
"plugin": "fr.koka.sanctuary.mixin.OptionalModMixins",
|
||||
"compatibilityLevel": "JAVA_25",
|
||||
"mixins": [
|
||||
"HappyCreeperMixin",
|
||||
"InhabitantAdvancementAccess",
|
||||
"ChunkMiningSpeedMixin",
|
||||
"PropertyLevelMixin",
|
||||
@@ -200,6 +201,7 @@
|
||||
"NaturalSnowEditMixin"
|
||||
],
|
||||
"client": [
|
||||
"client.HappyCreeperRendererMixin",
|
||||
"client.ReflectionSceneMixin",
|
||||
"client.PbrAnimationClockMixin",
|
||||
"client.PbrAnimationSpriteMixin",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name = "Sanctuary"
|
||||
author = "KOKA99CAB"
|
||||
version = "beta.292"
|
||||
version = "beta.293"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
|
||||
Reference in New Issue
Block a user