Add Sanctuary gems, original clay colors and shared discovery commands for beta.126
Build Sanctuary / build (push) Canceled after 0s
@@ -20,13 +20,13 @@ La [vision complète](docs/vision.md) conserve les intentions ; le
|
|||||||
L’économie, les claims et les dimensions décrits dans la vision restent
|
L’économie, les claims et les dimensions décrits dans la vision restent
|
||||||
à implémenter.
|
à implémenter.
|
||||||
|
|
||||||
La version **beta.125** étend les sculptures au centre, aux bords et aux coins,
|
La version **beta.126** ajoute le rubis et le saphir, leurs minerais et leurs
|
||||||
en bas, au milieu ou en haut du bloc. Les éclats reprennent la couleur des voxels
|
blocs, rangés après l'émeraude. Leur génération est configurable et limitée
|
||||||
et le Clay Workshop propose les animaux déjà découverts, en plus des imports GLB.
|
aux nouveaux chunks de Sanctuary. L'argile classique du Clay Workshop conserve
|
||||||
Le [ticket de livraison](docs/sculpture-placement-beta125.md) décrit le placement,
|
les couleurs originales des modèles. La commande `/sanctuary discovery`
|
||||||
la compatibilité des anciennes sculptures et les vérifications. La release est
|
enrichit les découvertes communes, globalement ou précisément, et actualise
|
||||||
publiée et l’instance **Sanctuary Beta** synchronisée, sauvegardes et réglages
|
les recettes et catalogues liés. Le [ticket de livraison](docs/gems-beta126.md)
|
||||||
conservés. Packs normal/Test et template sont aussi dans le dossier `build/`.
|
décrit les règles, la compatibilité et les vérifications.
|
||||||
La [livraison précédente](docs/clay-workshop-integration-beta110.md) réunit déjà
|
La [livraison précédente](docs/clay-workshop-integration-beta110.md) réunit déjà
|
||||||
l’atelier, les œufs et la neige saisonnière dans l’instance **Sanctuary Beta**.
|
l’atelier, les œufs et la neige saisonnière dans l’instance **Sanctuary Beta**.
|
||||||
Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées.
|
Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées.
|
||||||
@@ -37,6 +37,14 @@ La [carte native beta.005](docs/atlas-beta005.md) prend le relais après
|
|||||||
l’étude de Xaero et des alternatives libres.
|
l’étude de Xaero et des alternatives libres.
|
||||||
|
|
||||||
|
|
||||||
|
## beta.126 — Gemmes, argile et découvertes
|
||||||
|
|
||||||
|
Rubis et saphir avec filons configurables, couleurs originales avec l'argile,
|
||||||
|
commandes de découverte globales ou ciblées.
|
||||||
|
[Contrat et vérifications](docs/gems-beta126.md) ·
|
||||||
|
[Pack normal](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.126/Sanctuary-beta.126.mrpack) ·
|
||||||
|
[Pack de test](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.126/Sanctuary-Test-beta.126.mrpack).
|
||||||
|
|
||||||
## beta.125 — Sculptures : placement, éclats et animaux
|
## beta.125 — Sculptures : placement, éclats et animaux
|
||||||
|
|
||||||
Coins et trois hauteurs sur la grille 1/16 ; particules issues des voxels ;
|
Coins et trois hauteurs sur la grille 1/16 ; particules issues des voxels ;
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
# GEM-01 — Gemmes, argile et découvertes — beta.126
|
||||||
|
|
||||||
|
Contrat du 17 septembre 2026, branche `codex/gems-beta126`, Minecraft 26.3.
|
||||||
|
|
||||||
|
Deux gemmes natives complètent l'émeraude : `sanctuary:ruby` et
|
||||||
|
`sanctuary:sapphire`. Chaque famille comprend un bloc de stockage, un minerai
|
||||||
|
ordinaire et un minerai des abîmes. Les huit PNG du créateur sont copiés à
|
||||||
|
l'identique ; leurs empreintes figurent dans `tools/gems-textures-beta126.json`.
|
||||||
|
Les noms des fichiers français `saphir` sont associés aux identifiants anglais
|
||||||
|
stables `sapphire`, sans modifier les pixels. Les deux items utilisent les fichiers de remplacement
|
||||||
|
`Desktop/ruby.png` et `Desktop/saphir.png` fournis en fin de livraison.
|
||||||
|
|
||||||
|
L'ordre créatif est émeraude, rubis, saphir : ingrédients, blocs de stockage,
|
||||||
|
minerais ordinaires et minerais des abîmes dans leurs onglets natifs respectifs.
|
||||||
|
Minage à la pioche de fer ou mieux, résistance et expérience comme l'émeraude,
|
||||||
|
Fortune, Toucher de soie, cuisson/four à fusion et conversion 9 gemmes ↔ 1 bloc.
|
||||||
|
Les blocs peuvent former un socle de balise et les gemmes servir de paiement.
|
||||||
|
Les recettes rejoignent la collection actuelle de l'émeraude, sans ajouter de
|
||||||
|
transactions de villageois. Libellés français et anglais, tags communs `c:`.
|
||||||
|
|
||||||
|
## Génération procédurale et contrat de compatibilité
|
||||||
|
|
||||||
|
Profil initial **gemmes 1 / beta.126** : quatre tentatives par chunk et par
|
||||||
|
gemme, filons natifs de taille 3, hauteur uniforme Y0–256, probabilité de rejet
|
||||||
|
au contact de l'air de 0,25. Ces nombres sont un réglage initial à équilibrer
|
||||||
|
lors de la future refonte ; aucun quota de gemmes par chunk n'est garanti.
|
||||||
|
Les variantes ordinaires remplacent les roches du tag vanilla
|
||||||
|
`stone_ore_replaceables`, les variantes des abîmes celles de
|
||||||
|
`deepslate_ore_replaceables`.
|
||||||
|
|
||||||
|
Les fichiers `worldgen/feature/ore_<gemme>.json`,
|
||||||
|
`worldgen/placed_feature/ore_<gemme>.json` et les tags de biomes
|
||||||
|
`has_ore/<gemme>` séparent les cibles, la forme, la fréquence, la hauteur et
|
||||||
|
les régions. Un datapack peut les remplacer sans recompiler le mod. Le code
|
||||||
|
réutilise `OreFeature` et le hasard natif issu de la graine ; il limite ces
|
||||||
|
filons aux mondes utilisant `SanctuaryChunkGenerator`. Les biomes vanilla
|
||||||
|
partagés avec Sanctuary restent compatibles sans ajouter les gemmes aux
|
||||||
|
mondes vanilla, au Nether ou à l'End.
|
||||||
|
|
||||||
|
Cette évolution additive est autorisée pour les **nouveaux chunks uniquement**.
|
||||||
|
Aucun remplissage rétrospectif, aucune régénération, aucun remplacement de chunk
|
||||||
|
sauvegardé ni activation d'expansion. Les codecs du relief, les dimensions et
|
||||||
|
le format des sauvegardes restent inchangés. Les filons suivent les protections
|
||||||
|
existantes de la décoration native. Les anciens profils expérimentaux précédant
|
||||||
|
le générateur Sanctuary unifié ne sont pas étendus.
|
||||||
|
|
||||||
|
## Argile spéciale dans le Clay Workshop
|
||||||
|
|
||||||
|
Le bloc d'argile vanilla conserve les couleurs échantillonnées sur le modèle
|
||||||
|
original, animal découvert ou import GLB. Les argiles colorées et les autres
|
||||||
|
blocs continuent à transférer leur palette de texture. Une même règle est
|
||||||
|
appliquée à l'aperçu client et au résultat serveur. Le changement de matériau
|
||||||
|
invalide le résultat même si les deux palettes sont identiques. Le texte de
|
||||||
|
l'atelier explique l'exception en français et en anglais.
|
||||||
|
|
||||||
|
Une sculpture coûte toujours un bloc ; aucune recette, découverte, donnée de
|
||||||
|
sculpture déjà fabriquée ni règle de placement n'est modifiée.
|
||||||
|
|
||||||
|
## Commande de découverte commune
|
||||||
|
|
||||||
|
`/sanctuary discovery all` découvre tous les blocs, objets et espèces enregistrés.
|
||||||
|
Les variantes `blocks all`, `items all` et `mobs all` ciblent une catégorie ;
|
||||||
|
`block <identifiant>`, `item <identifiant>` et `mob <identifiant>` ciblent une
|
||||||
|
entrée avec la complétion native. Un sélecteur de joueurs en fin de commande
|
||||||
|
est facultatif ; sans cible, elle s'applique au joueur qui l'exécute.
|
||||||
|
|
||||||
|
Exemples :
|
||||||
|
|
||||||
|
```mcfunction
|
||||||
|
/sanctuary discovery all
|
||||||
|
/sanctuary discovery mobs all
|
||||||
|
/sanctuary discovery block minecraft:stone
|
||||||
|
/sanctuary discovery item minecraft:diamond
|
||||||
|
/sanctuary discovery mob minecraft:cow
|
||||||
|
/sanctuary discovery all @a
|
||||||
|
```
|
||||||
|
|
||||||
|
La permission suit « Autoriser les commandes » en solo et les droits opérateur
|
||||||
|
sur serveur. La console doit préciser la cible. Les identifiants invalides,
|
||||||
|
l'air et les types d'entités sans espèce découvrable sont refusés.
|
||||||
|
|
||||||
|
Les blocs passent par le registre de découvertes et son événement existant ;
|
||||||
|
les objets passent par `RecipeService.discover`. Les collections et recettes
|
||||||
|
sont ensuite recalculées normalement. Les espèces rejoignent `seen_mobs`,
|
||||||
|
la même donnée que l'observation naturelle, consommée par les familiers,
|
||||||
|
le Métabli et le Clay Workshop. Il n'existe aucun drapeau de déblocage parallèle.
|
||||||
|
Les catalogues déjà ouverts sont à rouvrir après la commande.
|
||||||
|
|
||||||
|
La commande conserve les quantités possédées et les compteurs de combat,
|
||||||
|
minage, pose et fabrication. Elle ne donne pas d'objets, n'achète pas les skills
|
||||||
|
et n'attribue pas d'advancements. Les recettes réservées aux opérateurs gardent
|
||||||
|
leurs règles. Les historiques personnels sont enrichis sans changer leur
|
||||||
|
schéma ; la commande refuse les données illisibles plutôt que les remplacer.
|
||||||
|
|
||||||
|
## Vérifications
|
||||||
|
|
||||||
|
Client natif Minecraft 26.3 / Java 25, `Gems126ClientChecks`, réussi en
|
||||||
|
1 min 08 s. Les mondes sont jetables, sans sauvegarde personnelle ouverte :
|
||||||
|
|
||||||
|
- Les huit items et six blocs sont présents, avec les huit textures exactes.
|
||||||
|
Quatre groupes créatifs respectent émeraude → rubis → saphir, sans doublons.
|
||||||
|
- Douze recettes natives, découverte du bloc de stockage, outil fer minimum,
|
||||||
|
Fortune III, Toucher de soie et balises passent les contrôles serveur.
|
||||||
|
- Graine **126**, profil gemmes 1, 81 chunks autour de l'origine : 7 rubis dans
|
||||||
|
la pierre et 8 dans la roche des abîmes ; 15 saphirs dans la pierre et 10 dans
|
||||||
|
la roche des abîmes. La génération est refusée dans le monde plat vanilla.
|
||||||
|
Les minerais persistent à la reconnexion, et un minerai retiré ne réapparaît pas.
|
||||||
|
- L'argile conserve exactement les couleurs des voxels d'une vache et d'un GLB
|
||||||
|
texturé. Bois et argile colorée transfèrent toujours leur palette. Le retour
|
||||||
|
à l'argile, l'égalité aperçu/résultat et le coût d'un bloc en survie sont vérifiés.
|
||||||
|
- La commande native respecte « Autoriser les commandes », accepte une cible
|
||||||
|
et la console, refuse les identifiants invalides et permet une espèce, un
|
||||||
|
bloc, un objet, toutes les espèces ou tout le contenu. Les recettes liées
|
||||||
|
sont recalculées. La répétition est idempotente et les découvertes survivent
|
||||||
|
à la reconnexion sans inventer de compteurs de combat ou de possession.
|
||||||
|
|
||||||
|
Logs et captures dans `build/gems126-client.log` et
|
||||||
|
`mods/sanctuary/build/run/clientGameTest/screenshots/`. Le GameTest dédié
|
||||||
|
reste exclu conformément au refus antérieur de son EULA. Le build complet et
|
||||||
|
les archives sont vérifiés séparément avant publication.
|
||||||
|
|
||||||
|
|
||||||
|
Build final `./gradlew check build assemblePack assembleTestPack
|
||||||
|
-x :sanctuary:runGameTest` réussi en **2 min 10 s**, 126 tâches. Les contrôles
|
||||||
|
natifs de gameplay précèdent uniquement le remplacement final des deux PNG
|
||||||
|
d'items ; les huit fichiers livrés sont vérifiés à l'octet près contre les
|
||||||
|
sources, tous en 16 × 16. Les JAR de sources correspondent aux sources courantes.
|
||||||
|
L'audit entre les binaires beta.125 et beta.126 ne trouve que les 112 entrées
|
||||||
|
de production prévues. Les archives normal/Test contiennent le même mod et
|
||||||
|
le template reproduit exactement ses ressources exportables.
|
||||||
|
|
||||||
|
| Artefact | SHA-256 |
|
||||||
|
| --- | --- |
|
||||||
|
| `Sanctuary-beta.126.mrpack` | `716221ce24549781ab91f5437ad2310497b198a4b61c43c40c67a27984365ae5` |
|
||||||
|
| `Sanctuary-Test-beta.126.mrpack` | `bbf22d7dd72e6746538b4ec28686f699225d4a59372304e5db42140a80e88bd1` |
|
||||||
|
| `sanctuary-beta.126.jar` | `85dbe1b41d98751029bb62e87d259522f8609758896c797b1e02fa7656ec0342` |
|
||||||
@@ -9,8 +9,8 @@ loom_version=1.17.20
|
|||||||
fabric_api_version=0.160.5+26.3
|
fabric_api_version=0.160.5+26.3
|
||||||
|
|
||||||
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
|
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
|
||||||
mod_version=beta.125
|
mod_version=beta.126
|
||||||
pack_version=beta.125
|
pack_version=beta.126
|
||||||
resource_pack_version=beta.121
|
resource_pack_version=beta.121
|
||||||
maven_group=fr.koka.sanctuary
|
maven_group=fr.koka.sanctuary
|
||||||
jei_version=30.32.0-sanctuary.3
|
jei_version=30.32.0-sanctuary.3
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ tasks.named('runGameTest') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('processGametestResources') {
|
tasks.named('processGametestResources') {
|
||||||
|
def gems126Client = providers.gradleProperty('sanctuaryGems126ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||||
|
inputs.property('sanctuaryGems126ClientTests', gems126Client)
|
||||||
|
if (gems126Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Gems126ClientChecks') }
|
||||||
|
|
||||||
def sculpture125Client = providers.gradleProperty('sanctuarySculpture125ClientTests').map { it.toBoolean() }.getOrElse(false)
|
def sculpture125Client = providers.gradleProperty('sanctuarySculpture125ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||||
inputs.property('sanctuarySculpture125ClientTests', sculpture125Client)
|
inputs.property('sanctuarySculpture125ClientTests', sculpture125Client)
|
||||||
if (sculpture125Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Sculpture125ClientChecks') }
|
if (sculpture125Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Sculpture125ClientChecks') }
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package fr.koka.sanctuary.client;
|
||||||
|
|
||||||
|
import fr.koka.sanctuary.companion.CompanionService;
|
||||||
|
import fr.koka.sanctuary.plans.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.*;
|
||||||
|
import net.fabricmc.fabric.api.client.gametest.v1.context.*;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.entity.EntityTypes;
|
||||||
|
import net.minecraft.world.inventory.ContainerInput;
|
||||||
|
import net.minecraft.world.item.*;
|
||||||
|
import net.minecraft.world.level.GameType;
|
||||||
|
|
||||||
|
/** Native preview, server result and survival payment for the special plain-clay material. */
|
||||||
|
final class Clay126Checks {
|
||||||
|
static void exercise(ClientGameTestContext c,TestServerContext server){
|
||||||
|
var pos=new BlockPos(4,1,4);
|
||||||
|
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.teleportTo(3,1,3);p.setGameMode(GameType.SURVIVAL);p.getAbilities().invulnerable=true;p.onUpdateAbilities();p.level().setBlockAndUpdate(pos,Statuary.WORKSHOP.defaultBlockState());CompanionService.save(p,CompanionService.record(p).note("seen_mobs","cow"));Statuary.open(p,pos);p.containerMenu.getSlot(0).set(new ItemStack(Items.CLAY,2));p.containerMenu.broadcastChanges();});
|
||||||
|
c.waitForScreen(ClayWorkshopScreen.class);c.runOnClient(m->{m.options.guiScale().set(2);m.getWindow().setWindowed(1280,800);m.resizeGui();((ClayWorkshopScreen)m.gui.screen()).load(EntityTypes.COW);});
|
||||||
|
c.waitFor(m->{var screen=(ClayWorkshopScreen)m.gui.screen();return !screen.busy()&&screen.selected()!=null&&screen.selected().equals(StatuaryEntity.sculpture(m.player.containerMenu.getSlot(1).getItem()));},250);
|
||||||
|
var cow=c.computeOnClient(m->((ClayWorkshopScreen)m.gui.screen()).selected());check(cow.cells().stream().map(VoxelSculpture.Cell::color).distinct().count()>3,"Cow source retains texture colors");
|
||||||
|
for(var material:List.of(Items.OAK_PLANKS,fr.koka.sanctuary.building.ColoredBricks.families().getFirst().clay().asItem(),Items.CLAY)){
|
||||||
|
server.runOnServer(s->{var menu=s.getPlayerList().getPlayers().getFirst().containerMenu;menu.getSlot(0).set(new ItemStack(material,2));menu.broadcastChanges();});
|
||||||
|
var expected=c.computeOnClient(m->ClayWorkshopMenu.appearance(cow,material,SculptureMaterialSampler.sample(new ItemStack(material))));
|
||||||
|
check(material==Items.CLAY?expected.equals(cow):!expected.equals(cow),"Only plain clay retains original colors");
|
||||||
|
c.waitFor(m->expected.equals(StatuaryEntity.sculpture(m.player.containerMenu.getSlot(1).getItem())),180);
|
||||||
|
c.runOnClient(m->check(ItemStack.matches(((ClayWorkshopScreen)m.gui.screen()).preview(),m.player.containerMenu.getSlot(1).getItem()),"Preview matches authoritative material switch"));
|
||||||
|
}
|
||||||
|
c.takeScreenshot("clay126-original-cow");
|
||||||
|
c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,1,0,ContainerInput.PICKUP,m.player));
|
||||||
|
server.waitFor(s->{var menu=s.getPlayerList().getPlayers().getFirst().containerMenu;return cow.equals(StatuaryEntity.sculpture(menu.getCarried()))&&menu.getSlot(0).getItem().getCount()==1;},100);
|
||||||
|
c.waitFor(m->cow.equals(StatuaryEntity.sculpture(m.player.containerMenu.getCarried()))&&m.player.containerMenu.getSlot(0).getItem().getCount()==1,100);
|
||||||
|
Path file=PlanClient.directory().resolve("clay126-"+UUID.randomUUID()+".glb");
|
||||||
|
try{Files.createDirectories(file.getParent());Files.write(file,Statuary106ClientChecks.fixture());var id=GlbModel.load(file).id();c.runOnClient(m->((ClayWorkshopScreen)m.gui.screen()).load(file));
|
||||||
|
c.waitFor(m->{var screen=(ClayWorkshopScreen)m.gui.screen();return !screen.busy()&&screen.selected()!=null&&screen.selected().modelId().equals(id)&&screen.selected().equals(StatuaryEntity.sculpture(m.player.containerMenu.getSlot(1).getItem()));},250);
|
||||||
|
c.runOnClient(m->{var screen=(ClayWorkshopScreen)m.gui.screen();check(screen.selected().cells().stream().map(VoxelSculpture.Cell::color).distinct().count()>1,"Imported textured GLB retains its original colors");check(ItemStack.matches(screen.preview(),m.player.containerMenu.getSlot(1).getItem()),"Imported preview equals native result");});
|
||||||
|
}catch(Exception e){throw new AssertionError(e);}finally{try{Files.deleteIfExists(file);}catch(Exception ignored){}}
|
||||||
|
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.closeContainer();p.setGameMode(GameType.CREATIVE);p.getAbilities().flying=true;p.onUpdateAbilities();});c.waitFor(m->m.gui.screen()==null,100);
|
||||||
|
System.out.println("CLAY126_PASS original animal and GLB colors, oak and colored clay palette transfer, switch back, matching previews and one clay paid in survival");
|
||||||
|
}
|
||||||
|
private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package fr.koka.sanctuary.client;
|
||||||
|
|
||||||
|
import fr.koka.sanctuary.companion.*;
|
||||||
|
import fr.koka.sanctuary.knowledge.*;
|
||||||
|
import fr.koka.sanctuary.plans.*;
|
||||||
|
import fr.koka.sanctuary.recipes.*;
|
||||||
|
import java.util.*;
|
||||||
|
import net.fabricmc.fabric.api.client.gametest.v1.context.*;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.stats.Stats;
|
||||||
|
import net.minecraft.world.entity.EntityTypes;
|
||||||
|
import net.minecraft.world.item.*;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
|
||||||
|
final class Discovery126Checks {
|
||||||
|
static void exercise(ClientGameTestContext c,TestServerContext server){
|
||||||
|
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var dispatcher=s.getCommands().getDispatcher();
|
||||||
|
var source=p.createCommandSourceStack();var root=dispatcher.getRoot().getChild("sanctuary").getChild("discovery");
|
||||||
|
s.getWorldData().setAllowCommands(false);check(!root.canUse(source),"Discovery commands respect native Allow Commands");
|
||||||
|
try{dispatcher.execute("sanctuary discovery all",source);throw new AssertionError("Denied command executed");}catch(com.mojang.brigadier.exceptions.CommandSyntaxException expected){}
|
||||||
|
s.getWorldData().setAllowCommands(true);
|
||||||
|
var before=CompanionService.record(p);var items=new TreeSet<>(RecipeService.knowledge(p).items);int kills=p.getStats().getValue(Stats.ENTITY_KILLED,EntityTypes.WITHER);
|
||||||
|
command(p,"mob minecraft:wither");check(StatueDiscoveries.known(p).contains("minecraft:wither"),"Precise species unlocks sculpture catalogue");check(CompanionService.record(p).notes().get("seen_mobs").contains("wither"),"Same actual seen_mobs record");check(RecipeService.knowledge(p).items.equals(items),"Mob discovery invents no unrelated item discovery");check(p.getStats().getValue(Stats.ENTITY_KILLED,EntityTypes.WITHER)==kills,"No fake kill stat");
|
||||||
|
command(p,"block minecraft:pink_glazed_terracotta");var block=BlockKnowledgeService.knowledge(p,Blocks.GLAZED_TERRACOTTA.pick(DyeColor.PINK));check(block.seen()&&!block.possessed()&&block.mined()==0&&block.placed()==0,"Precise block is seen without fabricated possession or stats");check(RecipeService.knowledge(p).items.contains("minecraft:pink_glazed_terracotta")&&RecipeService.knowledge(p).collections.contains("sanctuary:terracotta"),"Block event unlocks its normal recipe collection");
|
||||||
|
command(p,"item minecraft:amethyst_shard");check(RecipeService.knowledge(p).items.contains("minecraft:amethyst_shard")&&RecipeService.knowledge(p).recipes.contains("minecraft:spyglass"),"Precise item unlocks linked recipes");
|
||||||
|
String old=CompanionService.record(p).json();check(run(p,"mob minecraft:item")==0,"Nonmob type refused");check(CompanionService.record(p).json().equals(old),"Rejected request does not change discoveries");
|
||||||
|
try{dispatcher.execute("sanctuary discovery item sanctuary:missing_item",source);throw new AssertionError("Missing ID accepted");}catch(com.mojang.brigadier.exceptions.CommandSyntaxException expected){}
|
||||||
|
command(p,"mobs all");allSpecies(p);var pos=new BlockPos(4,1,4);Statuary.open(p,pos);check(((ClayWorkshopMenu)p.containerMenu).view.statues().equals(StatueDiscoveries.known(p)),"Workshop sees the shared full catalogue");p.closeContainer();
|
||||||
|
command(p,"all");all(p);String species=CompanionService.record(p).json(),recipes=RecipeService.knowledge(p).json();var blocks=BlockKnowledgeService.snapshot(p);command(p,"all @s");check(species.equals(CompanionService.record(p).json())&&recipes.equals(RecipeService.knowledge(p).json())&&blocks.equals(BlockKnowledgeService.snapshot(p)),"Repeated all with target selector is idempotent");
|
||||||
|
check(CompanionService.record(p).paid()==before.paid()&&CompanionService.record(p).unlocked()==before.unlocked(),"Discoveries do not fabricate paid skills");
|
||||||
|
try{check(dispatcher.execute("sanctuary discovery mob minecraft:cow "+p.getGameProfile().name(),s.createCommandSourceStack())==1,"Console can address an online player");}catch(Exception e){throw new AssertionError(e);}
|
||||||
|
System.out.println("DISCOVERY126_PASS native permissions, precise block/item/species, linked recipes, shared catalogue, all three categories, all, selector, console, idempotence and unchanged combat/ownership counters");
|
||||||
|
});
|
||||||
|
c.waitFor(m->m.gui.screen()==null,100);
|
||||||
|
}
|
||||||
|
static void all(ServerPlayer p){
|
||||||
|
allSpecies(p);check(BlockKnowledgeService.snapshot(p).stream().allMatch(BlockKnowledge::known),"All block discoveries");
|
||||||
|
var knowledge=RecipeService.knowledge(p);for(var item:BuiltInRegistries.ITEM)if(item!=Items.AIR)check(knowledge.items.contains(BuiltInRegistries.ITEM.getKey(item).toString()),"All registered items discovered");
|
||||||
|
check(knowledge.recipes.contains("minecraft:spyglass")&&knowledge.recipes.contains("sanctuary:ruby_block"),"Vanilla and Sanctuary recipes are derived from discoveries");
|
||||||
|
}
|
||||||
|
static void allSpecies(ServerPlayer p){check(StatueDiscoveries.known(p).size()==BuiltInRegistries.ENTITY_TYPE.stream().filter(t->SpawnEggItem.byId(t).isPresent()).count(),"All registered species discovered");}
|
||||||
|
static void command(ServerPlayer p,String args){check(run(p,args)==1,"Discovery command succeeds: "+args);}
|
||||||
|
private static int run(ServerPlayer p,String args){try{return p.level().getServer().getCommands().getDispatcher().execute("sanctuary discovery "+args,p.createCommandSourceStack());}catch(Exception e){throw new AssertionError(e);}}
|
||||||
|
private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package fr.koka.sanctuary.client;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.InputConstants;
|
||||||
|
import fr.koka.sanctuary.SanctuaryMod;
|
||||||
|
import fr.koka.sanctuary.gems.*;
|
||||||
|
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
|
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
|
||||||
|
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||||
|
import net.minecraft.client.input.KeyEvent;
|
||||||
|
import net.minecraft.core.*;
|
||||||
|
import net.minecraft.core.registries.*;
|
||||||
|
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.tags.*;
|
||||||
|
import net.minecraft.util.RandomSource;
|
||||||
|
import net.minecraft.world.item.*;
|
||||||
|
import net.minecraft.world.item.crafting.*;
|
||||||
|
import net.minecraft.world.item.enchantment.Enchantments;
|
||||||
|
import net.minecraft.world.level.block.*;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||||
|
|
||||||
|
/** Actual native registration, recipes, drops, tab order and naturally decorated Sanctuary chunks. */
|
||||||
|
public final class Gems126ClientChecks implements FabricClientGameTest {
|
||||||
|
public void runTest(ClientGameTestContext c){
|
||||||
|
var intro=new AtomicBoolean(true);
|
||||||
|
c.runOnClient(m->{m.options.renderDistance().set(5);m.options.simulationDistance().set(5);m.options.pauseOnLostFocus=false;IntroClient.setQuickTestIntro(true);});
|
||||||
|
ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){
|
||||||
|
IntroClient.setQuickTestIntro(true);Starter039TestSupport.selectFirst(screen);var enter=new KeyEvent(InputConstants.KEY_RETURN,0,0);
|
||||||
|
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter);
|
||||||
|
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals("sanctuary.progression.enter")).findFirst().orElseThrow().onPress(enter);intro.set(false);
|
||||||
|
}});
|
||||||
|
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Gems126 registry "+UUID.randomUUID());s.setSeed("126");s.setAllowCommands(true);s.setGameMode(WorldCreationUiState.SelectedGameMode.CREATIVE);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
|
||||||
|
c.waitFor(m->ProgressionClient.active()&&m.player!=null&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);
|
||||||
|
world.getServer().runOnServer(s->{
|
||||||
|
var p=s.getPlayerList().getPlayers().getFirst();var l=p.level();var pos=new BlockPos(0,4,0);var iron=new ItemStack(Items.IRON_PICKAXE);var silk=iron.copy();silk.enchant(l.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).getOrThrow(Enchantments.SILK_TOUCH),1);var fortune=iron.copy();fortune.enchant(l.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).getOrThrow(Enchantments.FORTUNE),3);
|
||||||
|
for(var f:Gems.FAMILIES){
|
||||||
|
for(var b:List.of(f.block(),f.ore(),f.deepslateOre())){var state=b.defaultBlockState();check(state.is(BlockTags.MINEABLE_WITH_PICKAXE)&&state.is(BlockTags.NEEDS_IRON_TOOL),"Native mining tags "+b);check(iron.isCorrectToolForDrops(state)&&!new ItemStack(Items.STONE_PICKAXE).isCorrectToolForDrops(state),"Iron tier enforced "+b);}
|
||||||
|
drop(Block.getDrops(f.block().defaultBlockState(),l,pos,null,p,iron),f.block().asItem(),1);
|
||||||
|
for(var ore:List.of(f.ore(),f.deepslateOre())){
|
||||||
|
drop(Block.getDrops(ore.defaultBlockState(),l,pos,null,p,iron),f.gem(),1);drop(Block.getDrops(ore.defaultBlockState(),l,pos,null,p,silk),ore.asItem(),1);
|
||||||
|
int max=0;for(int i=0;i<48;i++){var drops=Block.getDrops(ore.defaultBlockState(),l,pos,null,p,fortune);check(drops.size()==1&&drops.getFirst().is(f.gem()),"Fortune gem type");int n=drops.getFirst().getCount();check(n>=1&&n<=4,"Native Fortune III range");max=Math.max(max,n);}check(max>1,"Fortune increases gem yield");
|
||||||
|
var input=new SingleRecipeInput(new ItemStack(ore));for(var type:List.of(RecipeType.SMELTING,RecipeType.BLASTING)){var recipe=s.getRecipeManager().getRecipeFor(type,input,l).orElseThrow().value();check(recipe.assemble(input).is(f.gem()),"Native furnace recipe");}
|
||||||
|
}
|
||||||
|
var input=CraftingInput.of(3,3,Collections.nCopies(9,new ItemStack(f.gem())));drop(List.of(s.getRecipeManager().getRecipeFor(RecipeType.CRAFTING,input,l).orElseThrow().value().assemble(input)),f.block().asItem(),1);
|
||||||
|
input=CraftingInput.of(1,1,List.of(new ItemStack(f.block())));drop(List.of(s.getRecipeManager().getRecipeFor(RecipeType.CRAFTING,input,l).orElseThrow().value().assemble(input)),f.gem(),9);
|
||||||
|
check(f.block().defaultBlockState().is(BlockTags.BEACON_BASE_BLOCKS)&&new ItemStack(f.gem()).is(ItemTags.BEACON_PAYMENT_ITEMS),"Native beacon compatibility");
|
||||||
|
var feature=l.registryAccess().lookupOrThrow(Registries.FEATURE).getOrThrow(ResourceKey.create(Registries.FEATURE,SanctuaryMod.id("ore_"+f.id()))).value();check(feature instanceof GemOreFeature,"Data feature decoded");
|
||||||
|
check(!feature.place(l,l.getChunkSource().getGenerator(),RandomSource.create(126),pos),"Gem veins do not run in ordinary flat/vanilla worlds");
|
||||||
|
fr.koka.sanctuary.recipes.RecipeService.discover(p,f.gem());fr.koka.sanctuary.recipes.RecipeService.refresh(p);check(fr.koka.sanctuary.recipes.RecipeService.knowledge(p).recipes.contains("sanctuary:"+f.id()+"_block"),"Native discovery unlocks storage recipe");
|
||||||
|
}
|
||||||
|
int x=0;for(var block:List.of(Blocks.EMERALD_BLOCK,Gems.RUBY.block(),Gems.SAPPHIRE.block(),Blocks.EMERALD_ORE,Gems.RUBY.ore(),Gems.SAPPHIRE.ore(),Blocks.DEEPSLATE_EMERALD_ORE,Gems.RUBY.deepslateOre(),Gems.SAPPHIRE.deepslateOre())){l.setBlockAndUpdate(new BlockPos(x%3,1,x/3),block.defaultBlockState());x++;}
|
||||||
|
p.teleportTo(1.5,3,7);p.getAbilities().flying=true;p.onUpdateAbilities();p.getInventory().setItem(0,new ItemStack(Items.EMERALD));p.getInventory().setItem(1,new ItemStack(Gems.RUBY.gem()));p.getInventory().setItem(2,new ItemStack(Gems.SAPPHIRE.gem()));p.inventoryMenu.broadcastFullState();
|
||||||
|
System.out.println("GEMS126_SERVER_PASS eight registered items, six blocks, native loot/Fortune/Silk Touch, twelve recipes, mining tiers, beacons, discoveries and vanilla generation exclusion");
|
||||||
|
});
|
||||||
|
c.runOnClient(m->{CreativeModeTabs.tryRebuildTabContents(m.level.enabledFeatures(),true,m.level.registryAccess());order(CreativeModeTabs.INGREDIENTS,Items.EMERALD,Gems.RUBY.gem(),Gems.SAPPHIRE.gem());order(CreativeModeTabs.BUILDING_BLOCKS,Items.EMERALD_BLOCK,Gems.RUBY.block().asItem(),Gems.SAPPHIRE.block().asItem());order(CreativeModeTabs.NATURAL_BLOCKS,Items.EMERALD_ORE,Gems.RUBY.ore().asItem(),Gems.SAPPHIRE.ore().asItem());order(CreativeModeTabs.NATURAL_BLOCKS,Items.DEEPSLATE_EMERALD_ORE,Gems.RUBY.deepslateOre().asItem(),Gems.SAPPHIRE.deepslateOre().asItem());
|
||||||
|
for(var f:Gems.FAMILIES)for(var block:List.of(f.block(),f.ore(),f.deepslateOre())){var parts=new ArrayList<net.minecraft.client.renderer.block.dispatch.BlockStateModelPart>();m.getModelManager().getBlockStateModelSet().get(block.defaultBlockState()).collectParts(RandomSource.create(0),parts);check(!parts.isEmpty(),"Baked gem block model");for(var part:parts)for(var face:Direction.values())for(var quad:part.getQuads(face))check(!quad.materialInfo().sprite().contents().name().toString().contains("missingno"),"Native texture atlas resolves supplied gem PNG");}
|
||||||
|
System.out.println("GEMS126_CREATIVE_PASS emerald, ruby, sapphire adjacency in four groups; all six baked block models");
|
||||||
|
});
|
||||||
|
c.waitTicks(10);c.runOnClient(m->m.player.lookAt(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES,new net.minecraft.world.phys.Vec3(1.5,1.5,1.5)));c.waitTicks(4);c.takeScreenshot("gems126-gallery");
|
||||||
|
Clay126Checks.exercise(c,world.getServer());
|
||||||
|
Discovery126Checks.exercise(c,world.getServer());
|
||||||
|
}
|
||||||
|
intro.set(true);net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave saved;var found=new HashMap<BlockPos,BlockState>();
|
||||||
|
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Gems126 natural "+UUID.randomUUID());s.setSeed("126");s.setAllowCommands(true);s.setGenerateStructures(false);s.setGameMode(WorldCreationUiState.SelectedGameMode.CREATIVE);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());}).create()){
|
||||||
|
c.waitFor(m->m.player!=null&&m.gui.screen()==null&&ProgressionClient.active()&&!IntroSpawnFlash.active(),2400);
|
||||||
|
world.getServer().runOnServer(s->{var l=s.overworld();check(l.getChunkSource().getGenerator() instanceof fr.koka.sanctuary.worldgen.SanctuaryChunkGenerator,"Actual Sanctuary generator");
|
||||||
|
for(int cx=-4;cx<=4;cx++)for(int cz=-4;cz<=4;cz++){var chunk=l.getChunk(cx,cz);var cursor=new BlockPos.MutableBlockPos();for(int x=0;x<16;x++)for(int z=0;z<16;z++)for(int y=0;y<=256;y++){cursor.set(cx*16+x,y,cz*16+z);var state=chunk.getBlockState(cursor);if(state.is(Gems.RUBY.ore())||state.is(Gems.RUBY.deepslateOre())||state.is(Gems.SAPPHIRE.ore())||state.is(Gems.SAPPHIRE.deepslateOre()))found.put(cursor.immutable(),state);}}
|
||||||
|
for(var f:Gems.FAMILIES){long ordinary=found.values().stream().filter(b->b.is(f.ore())).count(),deep=found.values().stream().filter(b->b.is(f.deepslateOre())).count();System.out.println("GEMS126_NATURAL "+f.id()+" stone="+ordinary+" deepslate="+deep+" seed=126 chunks=81");check(ordinary+deep>0,"Both gems generate naturally without structure placement: "+f.id());}
|
||||||
|
var biome=l.getBiome(found.keySet().iterator().next()).value();var features=biome.getGenerationSettings().features().get(GenerationStep.Decoration.UNDERGROUND_ORES.ordinal());for(var f:Gems.FAMILIES)check(features.stream().anyMatch(h->h.is(ResourceKey.create(Registries.PLACED_FEATURE,SanctuaryMod.id("ore_"+f.id())))),"Native biome includes configurable gem placement");
|
||||||
|
Discovery126Checks.command(s.getPlayerList().getPlayers().getFirst(),"all");
|
||||||
|
var mined=found.keySet().iterator().next();l.setBlockAndUpdate(mined,Blocks.AIR.defaultBlockState());found.put(mined,Blocks.AIR.defaultBlockState());
|
||||||
|
});saved=world.getWorldSave();
|
||||||
|
}
|
||||||
|
try(var world=saved.open()){
|
||||||
|
c.waitFor(m->m.player!=null&&m.gui.screen()==null,2400);world.getServer().runOnServer(s->{Discovery126Checks.all(s.getPlayerList().getPlayers().getFirst());for(var e:found.entrySet())check(s.overworld().getBlockState(e.getKey())==e.getValue(),"Generated ores persist on reload without repopulation");});
|
||||||
|
}
|
||||||
|
System.out.println("GEMS126_PASS registry, recipes, drops, creative order, native models, natural Sanctuary generation seed 126 and save/reconnect");
|
||||||
|
}
|
||||||
|
private static void order(ResourceKey<CreativeModeTab> tab,Item anchor,Item ruby,Item sapphire){var items=new ArrayList<>(BuiltInRegistries.CREATIVE_MODE_TAB.getOrThrow(tab).value().getDisplayItems());int i=0;while(i<items.size()&&!items.get(i).is(anchor))i++;check(i+2<items.size()&&items.get(i+1).is(ruby)&&items.get(i+2).is(sapphire),"Emerald/ruby/sapphire order in "+tab);check(items.stream().filter(v->v.is(ruby)||v.is(sapphire)).count()==2,"No duplicate creative entries");}
|
||||||
|
private static void drop(List<ItemStack> drops,Item item,int n){check(drops.size()==1&&drops.getFirst().is(item)&&drops.getFirst().getCount()==n,"Native loot/craft expected "+n+" "+item+", got "+drops);}
|
||||||
|
private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
|
||||||
|
}
|
||||||
@@ -48,7 +48,7 @@ public final class Sculpture116ClientChecks implements FabricClientGameTest {
|
|||||||
var rendered=new HashSet<VoxelSculpture>();
|
var rendered=new HashSet<VoxelSculpture>();
|
||||||
for(var item:List.of(Items.STONE,Items.OAK_PLANKS,Items.GOLD_BLOCK,Items.WOOL.pick(DyeColor.RED),Items.GLASS,Items.OAK_LEAVES,Items.CLAY,Items.DEEPSLATE)){
|
for(var item:List.of(Items.STONE,Items.OAK_PLANKS,Items.GOLD_BLOCK,Items.WOOL.pick(DyeColor.RED),Items.GLASS,Items.OAK_LEAVES,Items.CLAY,Items.DEEPSLATE)){
|
||||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.getInventory().clearContent();var menu=(ClayWorkshopMenu)p.containerMenu;menu.getSlot(0).set(new ItemStack(item,2));check(menu.getSlot(1).getItem().isEmpty(),"Previous material result invalidates immediately");menu.broadcastChanges();});
|
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.getInventory().clearContent();var menu=(ClayWorkshopMenu)p.containerMenu;menu.getSlot(0).set(new ItemStack(item,2));check(menu.getSlot(1).getItem().isEmpty(),"Previous material result invalidates immediately");menu.broadcastChanges();});
|
||||||
var expected=c.computeOnClient(m->SculptureMaterialSampler.sample(new ItemStack(item)).apply(source));rendered.add(expected);
|
var expected=c.computeOnClient(m->ClayWorkshopMenu.appearance(source,item,SculptureMaterialSampler.sample(new ItemStack(item))));rendered.add(expected);
|
||||||
c.waitFor(m->expected.equals(StatuaryEntity.sculpture(m.player.containerMenu.getSlot(1).getItem())),180);
|
c.waitFor(m->expected.equals(StatuaryEntity.sculpture(m.player.containerMenu.getSlot(1).getItem())),180);
|
||||||
c.runOnClient(m->check(ItemStack.matches(((ClayWorkshopScreen)m.gui.screen()).preview(),m.player.containerMenu.getSlot(1).getItem()),"Preview equals server-owned result"));
|
c.runOnClient(m->check(ItemStack.matches(((ClayWorkshopScreen)m.gui.screen()).preview(),m.player.containerMenu.getSlot(1).getItem()),"Preview equals server-owned result"));
|
||||||
check(StatuaryEntity.decode(StatuaryEntity.encode(expected)).equals(expected),"Palette colors persist in unchanged schema 1");
|
check(StatuaryEntity.decode(StatuaryEntity.encode(expected)).equals(expected),"Palette colors persist in unchanged schema 1");
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public final class Statuary106ClientChecks implements FabricClientGameTest {
|
|||||||
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Statuary106 "+UUID.randomUUID());s.setSeed("106");s.setAllowCommands(true);s.setGameMode(WorldCreationUiState.SelectedGameMode.CREATIVE);
|
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Statuary106 "+UUID.randomUUID());s.setSeed("106");s.setAllowCommands(true);s.setGameMode(WorldCreationUiState.SelectedGameMode.CREATIVE);
|
||||||
s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
|
s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
|
||||||
saved=world.getWorldSave();c.waitFor(m->ProgressionClient.active()&&m.player!=null&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);
|
saved=world.getWorldSave();c.waitFor(m->ProgressionClient.active()&&m.player!=null&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);
|
||||||
var source=c.computeOnClient(m->SculptureMaterialSampler.sample(new ItemStack(Items.CLAY)).apply(originalSource));
|
var source=originalSource;
|
||||||
var server=world.getServer();
|
var server=world.getServer();
|
||||||
server.runOnServer(s->{var p=player(s);p.teleportTo(.5,1,.5);p.getInventory().clearContent();p.setGameMode(GameType.SURVIVAL);for(int x=-7;x<=9;x++)for(int z=-5;z<=9;z++)p.level().setBlockAndUpdate(new BlockPos(x,0,z),Blocks.STONE.defaultBlockState());p.level().setBlockAndUpdate(WORKSHOP,Statuary.WORKSHOP.defaultBlockState());
|
server.runOnServer(s->{var p=player(s);p.teleportTo(.5,1,.5);p.getInventory().clearContent();p.setGameMode(GameType.SURVIVAL);for(int x=-7;x<=9;x++)for(int z=-5;z<=9;z++)p.level().setBlockAndUpdate(new BlockPos(x,0,z),Blocks.STONE.defaultBlockState());p.level().setBlockAndUpdate(WORKSHOP,Statuary.WORKSHOP.defaultBlockState());
|
||||||
var legacy=net.minecraft.nbt.NbtUtils.writeBlockState(Statuary.BLOCK.defaultBlockState());legacy.remove("properties");
|
var legacy=net.minecraft.nbt.NbtUtils.writeBlockState(Statuary.BLOCK.defaultBlockState());legacy.remove("properties");
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public final class SanctuaryMod implements ModInitializer {
|
|||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
fr.koka.sanctuary.building.ColoredBricks.register();
|
fr.koka.sanctuary.building.ColoredBricks.register();
|
||||||
|
fr.koka.sanctuary.gems.Gems.register();
|
||||||
fr.koka.sanctuary.sky.VortexClouds.register();
|
fr.koka.sanctuary.sky.VortexClouds.register();
|
||||||
fr.koka.sanctuary.loading.PreloadProgress.register();
|
fr.koka.sanctuary.loading.PreloadProgress.register();
|
||||||
fr.koka.sanctuary.progression.SanctuaryGameplay.register();
|
fr.koka.sanctuary.progression.SanctuaryGameplay.register();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import net.minecraft.network.chat.Component;
|
|||||||
import net.minecraft.resources.Identifier;
|
import net.minecraft.resources.Identifier;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.Items;
|
||||||
|
|
||||||
/** File selection is local; the two ordinary inventory slots and every crafted item are server-owned. */
|
/** File selection is local; the two ordinary inventory slots and every crafted item are server-owned. */
|
||||||
public final class ClayWorkshopScreen extends AbstractContainerScreen<ClayWorkshopMenu> {
|
public final class ClayWorkshopScreen extends AbstractContainerScreen<ClayWorkshopMenu> {
|
||||||
@@ -91,9 +92,9 @@ public final class ClayWorkshopScreen extends AbstractContainerScreen<ClayWorksh
|
|||||||
if(previewSource!=source||changedMaterial){
|
if(previewSource!=source||changedMaterial){
|
||||||
previewSource=source;lastMaterial=material.copy();sampledResources=resources;
|
previewSource=source;lastMaterial=material.copy();sampledResources=resources;
|
||||||
if(changedMaterial)palette=SculptureMaterialSampler.sample(material);
|
if(changedMaterial)palette=SculptureMaterialSampler.sample(material);
|
||||||
var colored=source==null?null:palette==null?source:palette.apply(source);
|
var colored=source==null?null:palette==null?source:ClayWorkshopMenu.appearance(source,material.getItem(),palette);
|
||||||
preview=colored==null?ItemStack.EMPTY:StatuaryEntity.item(colored);pending=true;
|
preview=colored==null?ItemStack.EMPTY:StatuaryEntity.item(colored);pending=true;
|
||||||
if(source!=null&&!busy)status=palette==null?text("choose_material"):text("material_ready",material.getHoverName());
|
if(source!=null&&!busy)status=palette==null?text("choose_material"):material.is(Items.CLAY)?text("clay_ready"):text("material_ready",material.getHoverName());
|
||||||
}
|
}
|
||||||
// Retry a selection that raced the input packet or server rate limit. The result is the
|
// Retry a selection that raced the input packet or server rate limit. The result is the
|
||||||
// acknowledgement; never predict consumption or manufacture an item on the client.
|
// acknowledgement; never predict consumption or manufacture an item on the client.
|
||||||
|
|||||||
@@ -34,13 +34,17 @@ public final class CompanionQueries {
|
|||||||
&&familiar.egg().getItem() instanceof net.minecraft.world.item.SpawnEggItem?net.minecraft.world.item.SpawnEggItem.getType(familiar.egg()):entity.getType();
|
&&familiar.egg().getItem() instanceof net.minecraft.world.item.SpawnEggItem?net.minecraft.world.item.SpawnEggItem.getType(familiar.egg()):entity.getType();
|
||||||
var id=BuiltInRegistries.ENTITY_TYPE.getKey(type).getPath();
|
var id=BuiltInRegistries.ENTITY_TYPE.getKey(type).getPath();
|
||||||
if(CompanionType.find(id)==null)return;
|
if(CompanionType.find(id)==null)return;
|
||||||
var seen=new TreeSet<>(Arrays.asList(record.notes().getOrDefault("seen_mobs","").split(",")));seen.remove("");
|
record=discoverSpecies(record,List.of(id));
|
||||||
if(seen.add(id))record=record.note("seen_mobs",String.join(",",seen));
|
|
||||||
if(entity instanceof net.minecraft.world.entity.animal.fish.TropicalFish fish){
|
if(entity instanceof net.minecraft.world.entity.animal.fish.TropicalFish fish){
|
||||||
var variants=fishVariants(record);variants.set(fishVariant(fish));record=record.note("seen_fish_v1",Base64.getEncoder().encodeToString(variants.toByteArray()));
|
var variants=fishVariants(record);variants.set(fishVariant(fish));record=record.note("seen_fish_v1",Base64.getEncoder().encodeToString(variants.toByteArray()));
|
||||||
}
|
}
|
||||||
if(!record.equals(CompanionService.record(p)))CompanionService.save(p,record);
|
if(!record.equals(CompanionService.record(p)))CompanionService.save(p,record);
|
||||||
}
|
}
|
||||||
|
/** The same encounter record is used by natural observation and administrative discoveries. */
|
||||||
|
public static CompanionRecord discoverSpecies(CompanionRecord record,Collection<String> ids){
|
||||||
|
var seen=new TreeSet<>(Arrays.asList(record.notes().getOrDefault("seen_mobs","").split(",")));seen.remove("");
|
||||||
|
return seen.addAll(ids)?record.note("seen_mobs",String.join(",",seen)):record;
|
||||||
|
}
|
||||||
private static int fishVariant(net.minecraft.world.entity.animal.fish.TropicalFish fish){
|
private static int fishVariant(net.minecraft.world.entity.animal.fish.TropicalFish fish){
|
||||||
int packed=fish.getPattern().getPackedId();return ((packed>>>8)*2+(packed&1))*256+fish.getBaseColor().getId()*16+fish.getPatternColor().getId();
|
int packed=fish.getPattern().getPackedId();return ((packed>>>8)*2+(packed&1))*256+fish.getBaseColor().getId()*16+fish.getPatternColor().getId();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package fr.koka.sanctuary.gems;
|
||||||
|
|
||||||
|
import com.mojang.serialization.MapCodec;
|
||||||
|
import fr.koka.sanctuary.worldgen.SanctuaryChunkGenerator;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.util.RandomSource;
|
||||||
|
import net.minecraft.world.level.WorldGenLevel;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||||
|
import net.minecraft.world.level.levelgen.feature.*;
|
||||||
|
|
||||||
|
/** Native seeded ore veins, scoped to Sanctuary even when its biomes are shared with vanilla. */
|
||||||
|
public record GemOreFeature(OreFeature ore) implements Feature {
|
||||||
|
public static final MapCodec<GemOreFeature> CODEC=OreFeature.CODEC.xmap(GemOreFeature::new,GemOreFeature::ore);
|
||||||
|
@Override public MapCodec<GemOreFeature> codec(){return CODEC;}
|
||||||
|
@Override public boolean place(WorldGenLevel level,ChunkGenerator generator,RandomSource random,BlockPos origin){
|
||||||
|
if(!(level.getLevel().getChunkSource().getGenerator() instanceof SanctuaryChunkGenerator))return false;
|
||||||
|
return ore.place(level,generator,random,origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package fr.koka.sanctuary.gems;
|
||||||
|
|
||||||
|
import fr.koka.sanctuary.SanctuaryMod;
|
||||||
|
import java.util.List;
|
||||||
|
import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
|
||||||
|
import net.fabricmc.fabric.api.creativetab.v1.CreativeModeTabEvents;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.registries.*;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.tags.TagKey;
|
||||||
|
import net.minecraft.util.valueproviders.UniformInt;
|
||||||
|
import net.minecraft.world.item.*;
|
||||||
|
import net.minecraft.world.level.block.*;
|
||||||
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||||
|
|
||||||
|
/** Ordinary gem items and blocks; their distribution lives in data, not this registry. */
|
||||||
|
public final class Gems {
|
||||||
|
public record Family(String id,Item gem,Block block,Block ore,Block deepslateOre){}
|
||||||
|
public static final Family RUBY=family("ruby",DyeColor.RED);
|
||||||
|
public static final Family SAPPHIRE=family("sapphire",DyeColor.BLUE);
|
||||||
|
public static final List<Family> FAMILIES=List.of(RUBY,SAPPHIRE);
|
||||||
|
public static void register(){
|
||||||
|
Registry.register(BuiltInRegistries.FEATURE_TYPE,SanctuaryMod.id("gem_ore"),GemOreFeature.CODEC);
|
||||||
|
for(var f:FAMILIES){
|
||||||
|
var biomes=TagKey.create(Registries.BIOME,SanctuaryMod.id("has_ore/"+f.id));
|
||||||
|
BiomeModifications.addFeature(c->c.hasTag(biomes),GenerationStep.Decoration.UNDERGROUND_ORES,
|
||||||
|
ResourceKey.create(Registries.PLACED_FEATURE,SanctuaryMod.id("ore_"+f.id)));
|
||||||
|
}
|
||||||
|
CreativeModeTabEvents.modifyOutputEvent(CreativeModeTabs.INGREDIENTS).register(out->out.insertAfter(Items.EMERALD,RUBY.gem,SAPPHIRE.gem));
|
||||||
|
CreativeModeTabEvents.modifyOutputEvent(CreativeModeTabs.BUILDING_BLOCKS).register(out->out.insertAfter(Blocks.EMERALD_BLOCK,RUBY.block,SAPPHIRE.block));
|
||||||
|
CreativeModeTabEvents.modifyOutputEvent(CreativeModeTabs.NATURAL_BLOCKS).register(out->{
|
||||||
|
out.insertAfter(Blocks.EMERALD_ORE,RUBY.ore,SAPPHIRE.ore);
|
||||||
|
out.insertAfter(Blocks.DEEPSLATE_EMERALD_ORE,RUBY.deepslateOre,SAPPHIRE.deepslateOre);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private static Family family(String id,DyeColor color){
|
||||||
|
var key=SanctuaryMod.id(id);var item=Registry.register(BuiltInRegistries.ITEM,key,new Item(new Item.Properties().setId(ResourceKey.create(Registries.ITEM,key))));
|
||||||
|
var block=block(id+"_block",new Block(properties(id+"_block",Blocks.EMERALD_BLOCK).mapColor(color.getMapColor())));
|
||||||
|
var ore=block(id+"_ore",new DropExperienceBlock(UniformInt.of(3,7),properties(id+"_ore",Blocks.EMERALD_ORE)));
|
||||||
|
var deep=block("deepslate_"+id+"_ore",new DropExperienceBlock(UniformInt.of(3,7),properties("deepslate_"+id+"_ore",Blocks.DEEPSLATE_EMERALD_ORE)));
|
||||||
|
return new Family(id,item,block,ore,deep);
|
||||||
|
}
|
||||||
|
private static BlockBehaviour.Properties properties(String id,Block source){return BlockBehaviour.Properties.ofFullCopy(source).setId(ResourceKey.create(Registries.BLOCK,SanctuaryMod.id(id)));}
|
||||||
|
private static Block block(String id,Block block){
|
||||||
|
var key=SanctuaryMod.id(id);Registry.register(BuiltInRegistries.BLOCK,key,block);
|
||||||
|
Registry.register(BuiltInRegistries.ITEM,key,new BlockItem(block,new Item.Properties().setId(ResourceKey.create(Registries.ITEM,key)).useBlockDescriptionPrefix()));return block;
|
||||||
|
}
|
||||||
|
private Gems(){}
|
||||||
|
}
|
||||||
@@ -47,6 +47,7 @@ public final class BlockKnowledgeService {
|
|||||||
private BlockKnowledgeService() {}
|
private BlockKnowledgeService() {}
|
||||||
|
|
||||||
public static void register() {
|
public static void register() {
|
||||||
|
DiscoveryCommands.register();
|
||||||
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> observeInventory(handler.player));
|
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> observeInventory(handler.player));
|
||||||
// Fabric also reaches DISCONNECT through Netty's channelInactive callback.
|
// Fabric also reaches DISCONNECT through Netty's channelInactive callback.
|
||||||
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> server.execute(() -> {
|
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> server.execute(() -> {
|
||||||
@@ -97,6 +98,12 @@ public final class BlockKnowledgeService {
|
|||||||
|
|
||||||
public static void recordSeen(ServerPlayer player, Block block) {
|
public static void recordSeen(ServerPlayer player, Block block) {
|
||||||
if (block.defaultBlockState().isAir() || player.isSpectator()) return;
|
if (block.defaultBlockState().isAir() || player.isSpectator()) return;
|
||||||
|
discover(player,block);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Explicit discoveries use the same record and events, including for a spectator command target. */
|
||||||
|
public static void discover(ServerPlayer player,Block block) {
|
||||||
|
if(block.defaultBlockState().isAir())return;
|
||||||
Session session = session(player);
|
Session session = session(player);
|
||||||
if (session.failed || !session.memory.recordSeen(id(block))) return;
|
if (session.failed || !session.memory.recordSeen(id(block))) return;
|
||||||
session.dirty = true;
|
session.dirty = true;
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package fr.koka.sanctuary.knowledge;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||||
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import fr.koka.sanctuary.companion.*;
|
||||||
|
import fr.koka.sanctuary.operator.OperatorService;
|
||||||
|
import fr.koka.sanctuary.progression.ProgressEffects;
|
||||||
|
import fr.koka.sanctuary.recipes.*;
|
||||||
|
import java.util.*;
|
||||||
|
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||||
|
import net.minecraft.commands.*;
|
||||||
|
import net.minecraft.commands.arguments.*;
|
||||||
|
import net.minecraft.core.registries.*;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import net.minecraft.world.item.*;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
|
||||||
|
/** One command routes through the existing discovery services and their dependent recipe/catalogue rules. */
|
||||||
|
public final class DiscoveryCommands {
|
||||||
|
private record Selection(List<Block> blocks,List<Item> items,List<EntityType<?>> mobs){}
|
||||||
|
@FunctionalInterface private interface SelectionArgument {Selection get(CommandContext<CommandSourceStack> context)throws CommandSyntaxException;}
|
||||||
|
public static void register(){
|
||||||
|
CommandRegistrationCallback.EVENT.register((dispatcher,registries,environment)->{
|
||||||
|
var root=Commands.literal("discovery").requires(OperatorService::canCommand);
|
||||||
|
root.then(targets(Commands.literal("all"),c->all(true,true,true)));
|
||||||
|
root.then(Commands.literal("blocks").then(targets(Commands.literal("all"),c->all(true,false,false))));
|
||||||
|
root.then(Commands.literal("items").then(targets(Commands.literal("all"),c->all(false,true,false))));
|
||||||
|
root.then(Commands.literal("mobs").then(targets(Commands.literal("all"),c->all(false,false,true))));
|
||||||
|
root.then(Commands.literal("block").then(targets(Commands.argument("block",ResourceArgument.resource(registries,Registries.BLOCK)),c->new Selection(List.of(ResourceArgument.getResource(c,"block",Registries.BLOCK).value()),List.of(),List.of()))));
|
||||||
|
root.then(Commands.literal("item").then(targets(Commands.argument("item",ResourceArgument.resource(registries,Registries.ITEM)),c->new Selection(List.of(),List.of(ResourceArgument.getResource(c,"item",Registries.ITEM).value()),List.of()))));
|
||||||
|
root.then(Commands.literal("mob").then(targets(Commands.argument("mob",ResourceArgument.resource(registries,Registries.ENTITY_TYPE)),c->new Selection(List.of(),List.of(),List.of(ResourceArgument.getResource(c,"mob",Registries.ENTITY_TYPE).value())))));
|
||||||
|
dispatcher.register(Commands.literal("sanctuary").then(root));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private static <T extends ArgumentBuilder<CommandSourceStack,T>> T targets(T node,SelectionArgument selection){
|
||||||
|
return node.executes(c->discover(c,selection.get(c),List.of(c.getSource().getPlayerOrException())))
|
||||||
|
.then(Commands.argument("players",EntityArgument.players()).executes(c->discover(c,selection.get(c),EntityArgument.getPlayers(c,"players"))));
|
||||||
|
}
|
||||||
|
private static Selection all(boolean blocks,boolean items,boolean mobs){
|
||||||
|
return new Selection(blocks?BuiltInRegistries.BLOCK.stream().filter(b->!b.defaultBlockState().isAir()).toList():List.of(),
|
||||||
|
items?BuiltInRegistries.ITEM.stream().filter(i->i!=Items.AIR).toList():List.of(),
|
||||||
|
mobs?BuiltInRegistries.ENTITY_TYPE.stream().filter(t->SpawnEggItem.byId(t).isPresent()).toList():List.of());
|
||||||
|
}
|
||||||
|
private static int discover(CommandContext<CommandSourceStack> context,Selection selection,Collection<ServerPlayer> players){
|
||||||
|
var source=context.getSource();
|
||||||
|
if(selection.blocks.stream().anyMatch(b->b.defaultBlockState().isAir())||selection.items.contains(Items.AIR)||selection.mobs.stream().anyMatch(t->SpawnEggItem.byId(t).isEmpty())){
|
||||||
|
source.sendFailure(Component.translatable("sanctuary.discovery.invalid"));return 0;
|
||||||
|
}
|
||||||
|
var species=selection.mobs.stream().map(BuiltInRegistries.ENTITY_TYPE::getKey).map(id->id.getNamespace().equals("minecraft")?id.getPath():id.toString()).toList();
|
||||||
|
int changed=0;
|
||||||
|
for(var player:players){
|
||||||
|
var record=CompanionService.record(player);
|
||||||
|
if(ProgressEffects.get(player)==null||record==null||!BlockKnowledgeService.healthy(player)||!RecipeService.healthy(player)){
|
||||||
|
source.sendFailure(Component.translatable("sanctuary.discovery.unavailable",player.getDisplayName()));continue;
|
||||||
|
}
|
||||||
|
// Validate bounded records before any write; no new save schema or unlock flag.
|
||||||
|
CompanionRecord next;
|
||||||
|
var discoveredItems=new HashSet<String>(RecipeService.knowledge(player).items);
|
||||||
|
for(var item:selection.items)discoveredItems.add(BuiltInRegistries.ITEM.getKey(item).toString());
|
||||||
|
for(var block:selection.blocks)if(block.asItem()!=Items.AIR)discoveredItems.add(BuiltInRegistries.ITEM.getKey(block.asItem()).toString());
|
||||||
|
try{next=CompanionQueries.discoverSpecies(record,species);if(discoveredItems.size()>RecipeKnowledge.MAX_IDS)throw new IllegalArgumentException("Discovery capacity");}
|
||||||
|
catch(IllegalArgumentException invalid){source.sendFailure(Component.translatable("sanctuary.discovery.unavailable",player.getDisplayName()));continue;}
|
||||||
|
CompanionService.save(player,next);
|
||||||
|
for(var block:selection.blocks)BlockKnowledgeService.discover(player,block);
|
||||||
|
for(var item:selection.items){if(item instanceof BlockItem b)BlockKnowledgeService.discover(player,b.getBlock());RecipeService.discover(player,item);}
|
||||||
|
RecipeService.refresh(player);
|
||||||
|
source.sendSuccess(()->Component.translatable("sanctuary.discovery.granted",player.getDisplayName(),selection.blocks.size(),selection.items.size(),selection.mobs.size()),true);changed++;
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
private DiscoveryCommands(){}
|
||||||
|
}
|
||||||
@@ -71,14 +71,18 @@ public final class ClayWorkshopMenu extends AbstractContainerMenu {
|
|||||||
var contents=item.get(DataComponents.CONTAINER);
|
var contents=item.get(DataComponents.CONTAINER);
|
||||||
return contents==null||!contents.nonEmptyItems().iterator().hasNext();
|
return contents==null||!contents.nonEmptyItems().iterator().hasNext();
|
||||||
}
|
}
|
||||||
|
/** Plain clay retains the model's sampled colors; other blocks transfer their texture palette. */
|
||||||
|
public static VoxelSculpture appearance(VoxelSculpture source,Item material,SculpturePalette palette){
|
||||||
|
return material==Items.CLAY?source:palette.apply(source);
|
||||||
|
}
|
||||||
public void select(ServerPlayer p,VoxelSculpture sculpture,Item material,SculpturePalette palette){
|
public void select(ServerPlayer p,VoxelSculpture sculpture,Item material,SculpturePalette palette){
|
||||||
if(level==null||!stillValid(p)||level.getGameTime()-lastSelection<4)return;
|
if(level==null||!stillValid(p)||level.getGameTime()-lastSelection<4)return;
|
||||||
var hit=new BlockHitResult(Vec3.atCenterOf(view.pos),Direction.UP,view.pos,false);
|
var hit=new BlockHitResult(Vec3.atCenterOf(view.pos),Direction.UP,view.pos,false);
|
||||||
if(UseBlockCallback.EVENT.invoker().interact(p,level,InteractionHand.MAIN_HAND,hit)!=InteractionResult.PASS||!stillValid(p))return;
|
if(UseBlockCallback.EVENT.invoker().interact(p,level,InteractionHand.MAIN_HAND,hit)!=InteractionResult.PASS||!stillValid(p))return;
|
||||||
lastSelection=level.getGameTime();
|
lastSelection=level.getGameTime();
|
||||||
if(sculpture!=null&&(!StatueDiscoveries.allowedModel(sculpture.modelId(),view.statues())||!material(input.getItem(0))||!input.getItem(0).is(material)||palette==null))return;
|
if(sculpture!=null&&(!StatueDiscoveries.allowedModel(sculpture.modelId(),view.statues())||!material(input.getItem(0))||!input.getItem(0).is(material)||palette==null))return;
|
||||||
if(!Objects.equals(selected,sculpture)||!Objects.equals(selectedPalette,palette))
|
if(!Objects.equals(selected,sculpture)||selectedMaterial!=material||!Objects.equals(selectedPalette,palette))
|
||||||
selectedResult=sculpture==null||palette==null?null:palette.apply(sculpture);
|
selectedResult=sculpture==null||palette==null?null:appearance(sculpture,material,palette);
|
||||||
selected=sculpture;selectedMaterial=material;selectedPalette=palette;refresh();broadcastChanges();
|
selected=sculpture;selectedMaterial=material;selectedPalette=palette;refresh();broadcastChanges();
|
||||||
}
|
}
|
||||||
private void refresh(){
|
private void refresh(){
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public final class RecipeService {
|
|||||||
}
|
}
|
||||||
private static Session session(ServerPlayer p){return SESSIONS.computeIfAbsent(p,Session::new);}
|
private static Session session(ServerPlayer p){return SESSIONS.computeIfAbsent(p,Session::new);}
|
||||||
public static RecipeKnowledge knowledge(ServerPlayer p){return session(p).data;}
|
public static RecipeKnowledge knowledge(ServerPlayer p){return session(p).data;}
|
||||||
|
public static boolean healthy(ServerPlayer p){return !session(p).invalid;}
|
||||||
public static void discover(ServerPlayer p,Item item){
|
public static void discover(ServerPlayer p,Item item){
|
||||||
if(ProgressEffects.get(p)==null||item==Items.AIR)return;var s=session(p);if(s.invalid)return;
|
if(ProgressEffects.get(p)==null||item==Items.AIR)return;var s=session(p);if(s.invalid)return;
|
||||||
s.dirty|=RecipeKnowledge.add(s.data.items,BuiltInRegistries.ITEM.getKey(item).toString());
|
s.dirty|=RecipeKnowledge.add(s.data.items,BuiltInRegistries.ITEM.getKey(item).toString());
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"": {
|
||||||
|
"model": "sanctuary:block/deepslate_ruby_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"": {
|
||||||
|
"model": "sanctuary:block/deepslate_sapphire_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"": {
|
||||||
|
"model": "sanctuary:block/ruby_block"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"": {
|
||||||
|
"model": "sanctuary:block/ruby_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"": {
|
||||||
|
"model": "sanctuary:block/sapphire_block"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"": {
|
||||||
|
"model": "sanctuary:block/sapphire_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"model": {
|
||||||
|
"type": "minecraft:model",
|
||||||
|
"model": "sanctuary:block/deepslate_ruby_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"model": {
|
||||||
|
"type": "minecraft:model",
|
||||||
|
"model": "sanctuary:block/deepslate_sapphire_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"model": {
|
||||||
|
"type": "minecraft:model",
|
||||||
|
"model": "sanctuary:item/ruby"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"model": {
|
||||||
|
"type": "minecraft:model",
|
||||||
|
"model": "sanctuary:block/ruby_block"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"model": {
|
||||||
|
"type": "minecraft:model",
|
||||||
|
"model": "sanctuary:block/ruby_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"model": {
|
||||||
|
"type": "minecraft:model",
|
||||||
|
"model": "sanctuary:item/sapphire"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"model": {
|
||||||
|
"type": "minecraft:model",
|
||||||
|
"model": "sanctuary:block/sapphire_block"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"model": {
|
||||||
|
"type": "minecraft:model",
|
||||||
|
"model": "sanctuary:block/sapphire_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"sanctuary.statuary.clay_ready": "Clay: original model colors.",
|
||||||
"sanctuary.weather.selected": "Real Time weather: %s for %s (%s). The daily forecast resumes at midnight.",
|
"sanctuary.weather.selected": "Real Time weather: %s for %s (%s). The daily forecast resumes at midnight.",
|
||||||
"sanctuary.weather.overcast": "Overcast",
|
"sanctuary.weather.overcast": "Overcast",
|
||||||
"sanctuary.weather.fog": "Fog",
|
"sanctuary.weather.fog": "Fog",
|
||||||
@@ -1972,7 +1973,7 @@
|
|||||||
"sanctuary.discovery.item_filter": "Show carried items or every item already discovered.",
|
"sanctuary.discovery.item_filter": "Show carried items or every item already discovered.",
|
||||||
"sanctuary.discovery.catalogue_required": "The Catalogue ability lets you browse discovered items you no longer carry.",
|
"sanctuary.discovery.catalogue_required": "The Catalogue ability lets you browse discovered items you no longer carry.",
|
||||||
"sanctuary.discovery.loading": "Loading statistics…",
|
"sanctuary.discovery.loading": "Loading statistics…",
|
||||||
"sanctuary.discovery.unavailable": "Statistics unavailable. Refresh to try again.",
|
"sanctuary.discovery.unavailable": "Unable to update discoveries for %s; no changes made.",
|
||||||
"sanctuary.discovery.mobs_hint": "Mobs you have killed or that have killed you. Sightings alone are not recorded yet.",
|
"sanctuary.discovery.mobs_hint": "Mobs you have killed or that have killed you. Sightings alone are not recorded yet.",
|
||||||
"sanctuary.discovery.count": "%s entries",
|
"sanctuary.discovery.count": "%s entries",
|
||||||
"sanctuary.discovery.empty": "No entries match this filter.",
|
"sanctuary.discovery.empty": "No entries match this filter.",
|
||||||
@@ -2245,5 +2246,15 @@
|
|||||||
"sanctuary.statuary.animals": "Animals",
|
"sanctuary.statuary.animals": "Animals",
|
||||||
"sanctuary.statuary.imports": "Imports",
|
"sanctuary.statuary.imports": "Imports",
|
||||||
"sanctuary.statuary.no_animals": "Encounter creatures to unlock their sculptures.",
|
"sanctuary.statuary.no_animals": "Encounter creatures to unlock their sculptures.",
|
||||||
"sanctuary.statuary.animal_error": "This creature’s model could not be sculpted."
|
"sanctuary.statuary.animal_error": "This creature’s model could not be sculpted.",
|
||||||
|
"item.sanctuary.ruby": "Ruby",
|
||||||
|
"block.sanctuary.ruby_block": "Block of Ruby",
|
||||||
|
"block.sanctuary.ruby_ore": "Ruby Ore",
|
||||||
|
"block.sanctuary.deepslate_ruby_ore": "Deepslate Ruby Ore",
|
||||||
|
"item.sanctuary.sapphire": "Sapphire",
|
||||||
|
"block.sanctuary.sapphire_block": "Block of Sapphire",
|
||||||
|
"block.sanctuary.sapphire_ore": "Sapphire Ore",
|
||||||
|
"block.sanctuary.deepslate_sapphire_ore": "Deepslate Sapphire Ore",
|
||||||
|
"sanctuary.discovery.granted": "Discoveries added for %s: %s blocks, %s items, %s species. Linked recipes updated; reopen catalogues.",
|
||||||
|
"sanctuary.discovery.invalid": "Choose a discoverable block, item or species."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"sanctuary.statuary.clay_ready": "Argile : couleurs originales du modèle.",
|
||||||
"sanctuary.weather.selected": "Météo Real Time : %s pour la journée du %s (%s). Le tirage quotidien reprend à minuit.",
|
"sanctuary.weather.selected": "Météo Real Time : %s pour la journée du %s (%s). Le tirage quotidien reprend à minuit.",
|
||||||
"sanctuary.weather.overcast": "Ciel gris",
|
"sanctuary.weather.overcast": "Ciel gris",
|
||||||
"sanctuary.weather.fog": "Brouillard",
|
"sanctuary.weather.fog": "Brouillard",
|
||||||
@@ -1972,7 +1973,7 @@
|
|||||||
"sanctuary.discovery.item_filter": "Afficher les objets portés ou tous les objets déjà découverts.",
|
"sanctuary.discovery.item_filter": "Afficher les objets portés ou tous les objets déjà découverts.",
|
||||||
"sanctuary.discovery.catalogue_required": "L’aptitude Catalogue permet de consulter les objets découverts que tu ne portes plus.",
|
"sanctuary.discovery.catalogue_required": "L’aptitude Catalogue permet de consulter les objets découverts que tu ne portes plus.",
|
||||||
"sanctuary.discovery.loading": "Chargement des statistiques…",
|
"sanctuary.discovery.loading": "Chargement des statistiques…",
|
||||||
"sanctuary.discovery.unavailable": "Statistiques indisponibles. Actualise pour réessayer.",
|
"sanctuary.discovery.unavailable": "Impossible de mettre à jour les découvertes de %s ; aucune modification effectuée.",
|
||||||
"sanctuary.discovery.mobs_hint": "Créatures déjà tuées ou responsables de ta mort. Les simples observations ne sont pas encore enregistrées.",
|
"sanctuary.discovery.mobs_hint": "Créatures déjà tuées ou responsables de ta mort. Les simples observations ne sont pas encore enregistrées.",
|
||||||
"sanctuary.discovery.count": "%s entrées",
|
"sanctuary.discovery.count": "%s entrées",
|
||||||
"sanctuary.discovery.empty": "Aucune entrée pour ce filtre.",
|
"sanctuary.discovery.empty": "Aucune entrée pour ce filtre.",
|
||||||
@@ -2245,5 +2246,15 @@
|
|||||||
"sanctuary.statuary.animals": "Animaux",
|
"sanctuary.statuary.animals": "Animaux",
|
||||||
"sanctuary.statuary.imports": "Imports",
|
"sanctuary.statuary.imports": "Imports",
|
||||||
"sanctuary.statuary.no_animals": "Rencontrez des créatures pour débloquer leurs sculptures.",
|
"sanctuary.statuary.no_animals": "Rencontrez des créatures pour débloquer leurs sculptures.",
|
||||||
"sanctuary.statuary.animal_error": "Impossible de sculpter le modèle de cette créature."
|
"sanctuary.statuary.animal_error": "Impossible de sculpter le modèle de cette créature.",
|
||||||
|
"item.sanctuary.ruby": "Rubis",
|
||||||
|
"block.sanctuary.ruby_block": "Bloc de rubis",
|
||||||
|
"block.sanctuary.ruby_ore": "Minerai de rubis",
|
||||||
|
"block.sanctuary.deepslate_ruby_ore": "Minerai de rubis des abîmes",
|
||||||
|
"item.sanctuary.sapphire": "Saphir",
|
||||||
|
"block.sanctuary.sapphire_block": "Bloc de saphir",
|
||||||
|
"block.sanctuary.sapphire_ore": "Minerai de saphir",
|
||||||
|
"block.sanctuary.deepslate_sapphire_ore": "Minerai de saphir des abîmes",
|
||||||
|
"sanctuary.discovery.granted": "Découvertes ajoutées pour %s : %s blocs, %s objets, %s espèces. Recettes liées actualisées ; rouvre les catalogues.",
|
||||||
|
"sanctuary.discovery.invalid": "Choisis un bloc, un objet ou une espèce découvrable."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:block/cube_all",
|
||||||
|
"textures": {
|
||||||
|
"all": "sanctuary:block/deepslate_ruby_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:block/cube_all",
|
||||||
|
"textures": {
|
||||||
|
"all": "sanctuary:block/deepslate_sapphire_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:block/cube_all",
|
||||||
|
"textures": {
|
||||||
|
"all": "sanctuary:block/ruby_block"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:block/cube_all",
|
||||||
|
"textures": {
|
||||||
|
"all": "sanctuary:block/ruby_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:block/cube_all",
|
||||||
|
"textures": {
|
||||||
|
"all": "sanctuary:block/sapphire_block"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:block/cube_all",
|
||||||
|
"textures": {
|
||||||
|
"all": "sanctuary:block/sapphire_ore"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "sanctuary:item/ruby"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "sanctuary:item/sapphire"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 596 B |
|
After Width: | Height: | Size: 624 B |
|
After Width: | Height: | Size: 202 B |
|
After Width: | Height: | Size: 573 B |
|
After Width: | Height: | Size: 391 B |
|
After Width: | Height: | Size: 605 B |
|
After Width: | Height: | Size: 211 B |
|
After Width: | Height: | Size: 208 B |
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"#c:ores/ruby",
|
||||||
|
"#c:ores/sapphire"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"#sanctuary:ruby_ores"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"#sanctuary:sapphire_ores"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:deepslate_ruby_ore",
|
||||||
|
"sanctuary:deepslate_sapphire_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:ruby_ore",
|
||||||
|
"sanctuary:sapphire_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"#c:storage_blocks/ruby",
|
||||||
|
"#c:storage_blocks/sapphire"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:ruby_block"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:sapphire_block"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"#c:gems/ruby",
|
||||||
|
"#c:gems/sapphire"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:ruby"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:sapphire"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"#c:ores/ruby",
|
||||||
|
"#c:ores/sapphire"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"#sanctuary:ruby_ores"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"#sanctuary:sapphire_ores"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:deepslate_ruby_ore",
|
||||||
|
"sanctuary:deepslate_sapphire_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:ruby_ore",
|
||||||
|
"sanctuary:sapphire_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"#c:storage_blocks/ruby",
|
||||||
|
"#c:storage_blocks/sapphire"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:ruby_block"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:sapphire_block"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:ruby_block",
|
||||||
|
"sanctuary:sapphire_block"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -64,6 +64,12 @@
|
|||||||
"sanctuary:brown_brick_wall",
|
"sanctuary:brown_brick_wall",
|
||||||
"sanctuary:green_brick_wall",
|
"sanctuary:green_brick_wall",
|
||||||
"sanctuary:red_brick_wall",
|
"sanctuary:red_brick_wall",
|
||||||
"sanctuary:black_brick_wall"
|
"sanctuary:black_brick_wall",
|
||||||
|
"sanctuary:ruby_block",
|
||||||
|
"sanctuary:ruby_ore",
|
||||||
|
"sanctuary:deepslate_ruby_ore",
|
||||||
|
"sanctuary:sapphire_block",
|
||||||
|
"sanctuary:sapphire_ore",
|
||||||
|
"sanctuary:deepslate_sapphire_ore"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:ruby_block",
|
||||||
|
"sanctuary:ruby_ore",
|
||||||
|
"sanctuary:deepslate_ruby_ore",
|
||||||
|
"sanctuary:sapphire_block",
|
||||||
|
"sanctuary:sapphire_ore",
|
||||||
|
"sanctuary:deepslate_sapphire_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
"sanctuary:ruby",
|
||||||
|
"sanctuary:sapphire"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_emerald": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:ruby"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:ruby_block"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_emerald"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:ruby_block"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_emerald": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:sapphire"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:sapphire_block"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_emerald"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:sapphire_block"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_emerald_block": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:ruby_block"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:ruby"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_emerald_block"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:ruby"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_deepslate_ruby_ore": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:deepslate_ruby_ore"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:ruby_from_blasting_deepslate_ruby_ore"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_deepslate_ruby_ore"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:ruby_from_blasting_deepslate_ruby_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_ruby_ore": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:ruby_ore"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:ruby_from_blasting_ruby_ore"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_ruby_ore"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:ruby_from_blasting_ruby_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_deepslate_ruby_ore": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:deepslate_ruby_ore"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:ruby_from_smelting_deepslate_ruby_ore"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_deepslate_ruby_ore"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:ruby_from_smelting_deepslate_ruby_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_ruby_ore": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:ruby_ore"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:ruby_from_smelting_ruby_ore"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_ruby_ore"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:ruby_from_smelting_ruby_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_emerald_block": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:sapphire_block"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:sapphire"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_emerald_block"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:sapphire"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_deepslate_sapphire_ore": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:deepslate_sapphire_ore"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:sapphire_from_blasting_deepslate_sapphire_ore"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_deepslate_sapphire_ore"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:sapphire_from_blasting_deepslate_sapphire_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_sapphire_ore": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:sapphire_ore"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:sapphire_from_blasting_sapphire_ore"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_sapphire_ore"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:sapphire_from_blasting_sapphire_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_deepslate_sapphire_ore": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:deepslate_sapphire_ore"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:sapphire_from_smelting_deepslate_sapphire_ore"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_deepslate_sapphire_ore"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:sapphire_from_smelting_deepslate_sapphire_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:recipes/root",
|
||||||
|
"criteria": {
|
||||||
|
"has_sapphire_ore": {
|
||||||
|
"conditions": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"items": "sanctuary:sapphire_ore"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:inventory_changed"
|
||||||
|
},
|
||||||
|
"has_the_recipe": {
|
||||||
|
"conditions": {
|
||||||
|
"recipes": "sanctuary:sapphire_from_smelting_sapphire_ore"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:recipe_unlocked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirements": [
|
||||||
|
[
|
||||||
|
"has_the_recipe",
|
||||||
|
"has_sapphire_ore"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"recipes": [
|
||||||
|
"sanctuary:sapphire_from_smelting_sapphire_ore"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:block",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:alternatives",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"condition": "minecraft:tool/can_silk_touch",
|
||||||
|
"name": "sanctuary:deepslate_ruby_ore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"modifier": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:apply_bonus",
|
||||||
|
"enchantment": "minecraft:fortune",
|
||||||
|
"formula": "minecraft:ore_drops"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:explosion_decay"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "sanctuary:ruby"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rolls": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"random_sequence": "sanctuary:blocks/deepslate_ruby_ore"
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:block",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:alternatives",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"condition": "minecraft:tool/can_silk_touch",
|
||||||
|
"name": "sanctuary:deepslate_sapphire_ore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"modifier": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:apply_bonus",
|
||||||
|
"enchantment": "minecraft:fortune",
|
||||||
|
"formula": "minecraft:ore_drops"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:explosion_decay"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "sanctuary:sapphire"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rolls": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"random_sequence": "sanctuary:blocks/deepslate_sapphire_ore"
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:block",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"condition": {
|
||||||
|
"type": "minecraft:survives_explosion"
|
||||||
|
},
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "sanctuary:ruby_block"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rolls": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"random_sequence": "sanctuary:blocks/ruby_block"
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:block",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:alternatives",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"condition": "minecraft:tool/can_silk_touch",
|
||||||
|
"name": "sanctuary:ruby_ore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"modifier": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:apply_bonus",
|
||||||
|
"enchantment": "minecraft:fortune",
|
||||||
|
"formula": "minecraft:ore_drops"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:explosion_decay"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "sanctuary:ruby"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rolls": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"random_sequence": "sanctuary:blocks/ruby_ore"
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:block",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"condition": {
|
||||||
|
"type": "minecraft:survives_explosion"
|
||||||
|
},
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "sanctuary:sapphire_block"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rolls": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"random_sequence": "sanctuary:blocks/sapphire_block"
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:block",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:alternatives",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"condition": "minecraft:tool/can_silk_touch",
|
||||||
|
"name": "sanctuary:sapphire_ore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"modifier": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:apply_bonus",
|
||||||
|
"enchantment": "minecraft:fortune",
|
||||||
|
"formula": "minecraft:ore_drops"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:explosion_decay"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "sanctuary:sapphire"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rolls": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"random_sequence": "sanctuary:blocks/sapphire_ore"
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shapeless",
|
||||||
|
"ingredients": [
|
||||||
|
"sanctuary:ruby_block"
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"count": 9,
|
||||||
|
"id": "sanctuary:ruby"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shaped",
|
||||||
|
"category": "building",
|
||||||
|
"key": {
|
||||||
|
"#": "sanctuary:ruby"
|
||||||
|
},
|
||||||
|
"pattern": [
|
||||||
|
"###",
|
||||||
|
"###",
|
||||||
|
"###"
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"id": "sanctuary:ruby_block"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:blasting",
|
||||||
|
"cookingtime": 200,
|
||||||
|
"experience": 1.0,
|
||||||
|
"group": "ruby",
|
||||||
|
"ingredient": "sanctuary:deepslate_ruby_ore",
|
||||||
|
"result": {
|
||||||
|
"id": "sanctuary:ruby"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:blasting",
|
||||||
|
"cookingtime": 200,
|
||||||
|
"experience": 1.0,
|
||||||
|
"group": "ruby",
|
||||||
|
"ingredient": "sanctuary:ruby_ore",
|
||||||
|
"result": {
|
||||||
|
"id": "sanctuary:ruby"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:smelting",
|
||||||
|
"cookingtime": 200,
|
||||||
|
"experience": 1.0,
|
||||||
|
"group": "ruby",
|
||||||
|
"ingredient": "sanctuary:deepslate_ruby_ore",
|
||||||
|
"result": {
|
||||||
|
"id": "sanctuary:ruby"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:smelting",
|
||||||
|
"cookingtime": 200,
|
||||||
|
"experience": 1.0,
|
||||||
|
"group": "ruby",
|
||||||
|
"ingredient": "sanctuary:ruby_ore",
|
||||||
|
"result": {
|
||||||
|
"id": "sanctuary:ruby"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shapeless",
|
||||||
|
"ingredients": [
|
||||||
|
"sanctuary:sapphire_block"
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"count": 9,
|
||||||
|
"id": "sanctuary:sapphire"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:crafting_shaped",
|
||||||
|
"category": "building",
|
||||||
|
"key": {
|
||||||
|
"#": "sanctuary:sapphire"
|
||||||
|
},
|
||||||
|
"pattern": [
|
||||||
|
"###",
|
||||||
|
"###",
|
||||||
|
"###"
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"id": "sanctuary:sapphire_block"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:blasting",
|
||||||
|
"cookingtime": 200,
|
||||||
|
"experience": 1.0,
|
||||||
|
"group": "sapphire",
|
||||||
|
"ingredient": "sanctuary:deepslate_sapphire_ore",
|
||||||
|
"result": {
|
||||||
|
"id": "sanctuary:sapphire"
|
||||||
|
}
|
||||||
|
}
|
||||||